Comments (3)
I was surprised to see the second example working, I thought it worked only when y
was set in the constructor. Indeed, there are some untold bugs, like a new watcher is mounted on the widget value every time a new instance is created.
Note that the example below actually raises an error currently, I believe due to #777. It would work if you set y
before calling super().__init__
in the constructor. @philippjfr we need to make sure we're okay with this for Param 2?
import param, panel as pn
class P(param.Parameterized):
x = param.Integer(5)
def __init__(self, **params):
super().__init__(**params)
self.y = pn.widgets.IntSlider(value=7)
@param.depends("y.value", watch=True)
def fn(self):
self.x = self.y.value
p = P()
p.y.value=21
p.x
# 5 # Why is this not 32?
An alternative to the above and that is somewhat less verbose is to leverage ClassSelector
. It works well because the ClassSelector
Parameter has instantiate=True
by default so Param makes a deepcopy of the widget. Parameter(widget_instance, instantiate=True)
would also work well, but that would require the users making the class variable mistake to understand instantiate
, I wouldn't be on that, it's never been really understood well.
import param, panel as pn
class P(param.Parameterized):
x = param.Integer(5)
y = param.ClassSelector(default=pn.widgets.IntSlider(value=7), class_=pn.widgets.IntSlider)
@param.depends("y.value", watch=True)
def fn(self):
print('callback')
self.x = self.y.value
p = P()
p.y.value = 21
p.x
Among the suggestions, I would be more in favor of 1. Even though I think having a Parameterized instance as a class variable can be a valid thing to do, after all it's just a mutable data structure and there are valid use cases for such an object as a class variable. So if that were to be implemented, I'd make warning the default behavior but would allow disabling it altogether (import param; param.warn_parameterized_classvar = False
). Or, the default behavior could be not to warn, but Panel would revert that.
This issue focused on Panel widgets, but I've seen users put all sorts of stuff as class variables while I assume that wasn't really their intention. Let's not forget that many Panel users aren't software developers. I remember clearly finding Param super magic when I started using it, how the hell can these Parameters that look like class variables be turned into instance variables?! That went against all the little Python knowledge I had at the time :)
So while there are ways we could improve Param's API to help our users, I also think there are documentation improvements we could make:
- I looked for
class variable
in the Param documentation, there's no occurrence of that. - Panel How-To guides don't explain how to create a class (Parameterized or not) whose attributes are widgets, they're jumping directly from function/
pn.bind
toParameterized
class/mapping to widgets. - There's a reference to this issue in the Explanation page Panel and Param, it's pretty new and I'm not sure it's easily discoverable
- Having both
param.depends
andpn.depends
being used somewhat interchangeably probably lead some users to
assume Panel objects and Parameters behave similarly.
I've put together a list of examples found on Discourse,
https://discourse.holoviz.org/t/why-do-some-panel-elements-dissappear-while-others-persist/1260: all sorts of objects set as class variables without any Parameter.
...
class Mapview(param.Parameterized):
tiles = gv.tile_sources.CartoEco()
aoi_polygons = gv.Polygons([], crs=crs.GOOGLE_MERCATOR)
aoi_colours = ['red','blue','green','orange','purple']
aoi_stream = hv.streams.PolyDraw(source=aoi_polygons, num_objects=5,styles={'fill_color': aoi_colours})
template_df = pd.DataFrame({'lng': [], 'lat': []}, columns=['lng', 'lat'])
dfstream = hv.streams.Buffer(template_df, index=False, length=10000, following=False)
points = hv.DynamicMap(gv.Points, streams=[dfstream])
map_layout = tiles * aoi_polygons * points
def show_map(self):
# set style options on map_layout
self.map_layout.opts(
# the ratio of WMTS height:width must be eqaul to the data_aspect value (0.5)
# or the map will stretch/skew
gv.opts.WMTS(global_extent=True,width=1200,height=600,show_grid=False,xaxis=None,yaxis=None),
gv.opts.Polygons(fill_alpha=0.1),
gv.opts.Points(size=5, color='green', fill_alpha=0.3, line_alpha=0.4)
)
return self.map_layout.opts(data_aspect=0.5)
https://discourse.holoviz.org/t/how-to-access-param-values-of-widgets-inside-a-dynamically-defined-widgetbox/2024/5: WidgetBox
layout as a class variable
...
class DynamicWidgetBox(param.Parameterized):
options = param.ListSelector(default=[], objects=['a', 'b', 'c'])
wBox = pn.WidgetBox(horizontal=True)
@param.depends('options')
def widgets(self):
selects = []
for v in self.options:
selects.append(pn.widgets.MultiSelect(name=v, options=[1,2,3,4,5]))
self.wBox[:] = [*selects]
return self.wBox
...
https://discourse.holoviz.org/t/multiselect-widget-with-multiple-options-for-each-value/1118/3: data and a widget as class variable, with @pn.depends('widget.value')
used to register a callback.
import panel as pn
import pandas as pd
import param
import holoviews as hv
import hvplot.pandas
class AppTest(param.Parameterized):
test_dict = {"key1" : ['A']*20,
"key2" : [1]*20,
"key3": [1,2]*10,
"value" : [100]*20}
test_dtf = pd.DataFrame(test_dict)
test_list = ['key1','key2','key3']
default_value=test_dtf[test_list].drop_duplicates().values.tolist()[0]
multi_select = pn.widgets.MultiSelect(name='Test', value=[default_value] ,options= test_dtf[test_list].drop_duplicates().values.tolist())
@pn.depends('multi_select.value')
def view(self):
if len(self.multi_select.value) ==0:
return hv.Curve([])
else:
df=pd.DataFrame(self.multi_select.value,columns=['val','x','y'])
return df.hvplot.scatter('x','y')
pn.extension()
viewer = AppTest()
pn.Row(viewer.multi_select,viewer.view)
https://discourse.holoviz.org/t/panel-tabs-updated-widget-values/1500/2: @pn.depends('widget.value')
used to register a callback, watching multiple widgets.
import panel as pn
import param
pn.extension()
class Tabs_example(param.Parameterized):
intinput = pn.widgets.IntInput(value=0)
txtinput = pn.widgets.TextInput(value='a')
intslide = pn.widgets.IntRangeSlider(start=0,end=10)
@pn.depends('intinput.value','txtinput.value','intslide.value')
def summary(self):
return pn.pane.Markdown(f'int : {self.intinput.value} <br> txt : {self.txtinput.value} <br> slide start :{self.intslide.value[0]} <br> slide end : {self.intslide.value[1]}')
t=Tabs_example()
pn.Tabs(('int',t.intinput),('text',t.txtinput),('slider',t.intslide),('summary',t.summary))
https://discourse.holoviz.org/t/simple-form-using-panel/5428/2: super().__init__
not called.
...
class Navbar(param.Parameterized):
def __init__(self):
self.left_nav_bar = pn.widgets.Select(
name="Choose",
options=[
"Employees",
"Customers",
],
)
self.customer_tabs = Customer()
self.employee_tabs = Employee()
@pn.depends("left_nav_bar.value")
def display(self):
if self.left_nav_bar.value == "Employees":
return self.employee_tabs.display
else:
return self.customer_tabs.display
...
from param.
Yikes! Thanks for those examples "from the wild". That code will have all sorts of issues with some things being instance attributes and some things being shared across all instances; fun to watch what happens as long as you don't need it to work! Much of it does provide support for option 2: If people repeatedly and predictably expect Parameterized classes to handle Parameterized class attributes like Parameters, should we just do that? pd.DataFrame
used as above would suggest an even more extreme Option 4 of treating all class attributes as Parameters. Supporting Option 4 seems likely to be a major limitation on how people can use a Parameterized class in a larger codebase, and in this case sharing a single df across instances is usually ok and actually desirable, so hopefully we don't need to consider 4.
An alternative to the above and that is somewhat less verbose is to leverage ClassSelector
Not sure which one you mean by "the above"; the code I showed as the way we expected it to be done already does use ClassSelector?
y = param.ClassSelector(default=pn.widgets.IntSlider(value=7), class_=pn.widgets.IntSlider)
To encourage this usage, should we make the class_
argument for ClassSelector optional and make it default to the type()
of the default
? That way people would just have to type y = param.ClassSelector(default=pn.widgets.IntSlider(value=7))
.
I looked for "class variable" in the Param documentation, there's no occurrence of that.
I assume that's because in Python it's a class "attribute", not a class variable. We should maybe throw in "variable" in some places alongside "attribute" in case that's what people are searching for based on terminology from other languages.
from param.
Capturing discussion between @philippjfr and myself today, the ways we could think to address this issue are:
- Special Panel class for Panel users to use instead of Parameterized (e.g. adapting Viewable)
- Pros: Can be very specific to Panel use cases without affecting any other use of Parameterized. Backwards compatibility issues likely to be very limited.
- Cons: Won't address issues with people who do choose param.Parameterized (which we'd steer them away from if they are using widgets directly)
- Special behavior for param.Parameterized
a. Duplicate Parameterizeds (or Panel objects, or Parameterized that have a "value") the same as we do Parameters, but don't make them Parameters
- Pros: Can provide per-object instantiation without changing how a widget is accessed at the class level
- Cons: Hard to reason about, accessing the "value" of the widget from within the class is wordy and nonobvious, unable to set any options for the Parameter that gets created (e.g.per_instance
).
b. Promote a Panel widget (or any Parameterized? or Parameterized that have a "value"?) to a ClassSelector(class=..widget, default=the widget) when found at the Parameterized class level
- Pros: Simple syntax, ensures instantiation of widgets per instance.
- Cons: Accessing the "value" of the widget from within the class is wordy and nonobvious, unable to set any options for the Parameter that gets created (e.g.per_instance
).
c. Create a Parameter for the value, then store the widget somewhere for recreation around the instantiated parameter.
- Pros: value of the widget becomes accessible simply, as the Python attribute of the instance, with the same name as the Parameter
- Cons: Hard to reason about; Probably breaks a whole lot of code.
Our general impression was that option 1 was the most appropriate, making this be a Panel issue rather than a Param one, but other input would be welcome.
from param.
Related Issues (20)
- `allow_None` not enforced when set to `False` and the default value is `None` HOT 1
- Suggestions to improve the reactive expression guide
- 2.0.0rc6 - param.ListSelector Issue HOT 7
- In Windows, `import param` pops up a command line window HOT 3
- A “doc” directory is installed directly in site-packages
- Speed up load with 2% by improving _register_watcher HOT 3
- Speed up derived applications like Panel by speeding up Path Parameter
- Make it easy to use Traitlets including Ipywidgets/ AnyWidgets with Param HOT 1
- param.A + param.B = Param.C, how to express this relationship beween parameters ? HOT 2
- 'import param' fails: ImportError: cannot import name '__version__' from 'param._version' (/usr/local/lib/python3.9/site-packages/param/_version.py) HOT 13
- Add a `metadata` slot
- Cannot change instance attributes with a Callable without passing in parameterized class HOT 3
- Define Comparator equality functions for more types HOT 1
- DeprecationWarning in test_reactive_logic_unary_ops HOT 1
- Default range of param.Range fails to update using self.param.<parameter>.default assignment HOT 4
- Ability to apply @param.depends to all (nested) Parameters of a class HOT 1
- Param with bounds should default to lower bound HOT 7
- Add .is_active parameter to reactive expressions HOT 3
- Let reactive expressions .watch method support async functions
- Context manager .param.update should restore references
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from param.