-
-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add bind function and associated reactive API #460
Conversation
@jlstevens suggested the hooks could take the form of: {class_to_check_isinstance:['param1', 'param2',...], ...} However that doesn’t seem sufficient, e.g. in panel I actually create synthetic parameters around ipywidgets: from .pane.ipywidget import IPyWidget
if IPyWidget.applies(arg) and hasattr(arg, 'value'):
name = type(arg).__name__
if name in ipywidget_classes:
ipy_param = ipywidget_classes[name]
else:
ipy_param = param.parameterized_class(name, {'value': param.Parameter()})
ipywidget_classes[name] = ipy_param
ipy_inst = ipy_param(value=arg.value)
arg.observe(lambda event: ipy_inst.param.set_param(value=event['new']), 'value')
return ipy_inst.param.value |
I think a dictionary is always appropriate as you have a match condition then a way to get the parameters. So maybe a bunch of things could be supported: {class1_to_check_isinstance:['param1', 'param2',...],
class2_to_check_isinstance:function_returning_parameter_list_given_instance,
predicate_function:function_returning_parameter_list_given_instance
... } Then you can use as complicated a format as necessary. |
Looks good to me but probably needs |
I think a |
@philippjfr do you need this in 2.0? |
@philippjfr , up to you to declare this ready for review. |
I've decided simply to support a list of hooks instead of the dictionary format as I don't much like the idea of mixing types and predicate functions in the dictionary suggestion, because it makes the ordering ambiguous, while the list of hooks simply iterates in order until it is either exhausted or returns a Parameter or function with dependencies. |
I fully agree, and strongly regret adding |
Please also consider supporting param.bind/param.depends to a Parameterized class will bind to its .value parameter if it exists. And maybe even to its .object or .objects parameter if it exists. This will solve many issues
|
This new implementation allows providing hooks that convert arguments to dependencies so you could do something like this: def transform_pane_obj_dep(obj):
return obj.param.object if isinstance(obj, PaneBase) else obj
param.parameterized.register_depends_transform(transform_pane_obj_dep) It definitely isn't param's job to register something like this though, so please file an issue on Panel instead. |
Hooks are an implementation detail. I really Think that there should be only one .bind function. Having param.bind and pn.bind with different behaviour is just so confusing to users in practice. And if .bind could be used as an annotation and .depends could be ditched or vice versa it would again make Panel and HoloViz easier to explain. |
That's the whole point of this PR, to have one version of depends and bind with the same behavior. I'm just saying that while param defines the ability to define hooks, the actual implementation will have to be in Panel so I'm asking you to open an issue there. |
That is not what I'm asking 😉. Im asking to just have param.bind to replace param.depends as well as pn.bind and pn.depends |
Got you, I was referring to this part of your post:
I will see what it would look like to allow binding string parameters to methods like depends. |
Don't really get this,
Not sure I like
Fixed
I put a poll up to decide between these and people didn't vote for
This isn't true, reactive is always truthy, your example simply happened to assume truthiness.
I'm quite wary of adding API that make it appear like two objects are the same when they really aren't. Either |
Co-authored-by: Maxime Liquet <[email protected]>
I don't use .watch, but I guess I have the same complaint about that too, then! Sounds like the semantics of "watch" is thus "register_watcher", i.e., the opposite of "watch". A verb method on an object normally means an action invoked on that object, not an action another object is doing on this one, but sounds like it's too late to change in any case.
So that means none of the logical operators work? Does that need to be documented specially?
I think they share enough similarities that this is a problem already, which is what I'm concerned about. Is making them be the same problematic? |
Don't see how
Yes, that seems like a good idea. Reactive doesn't support operator keywords (i.e.
Yes, unfortunately, we can't really resolve a reactive expression on |
I guess your point about |
I'd like to merge this so we can start testing it as part of a beta release. There's a few outstanding items that I promise to address:
|
Final changes we decided on:
We have also decided that to make the parallels between |
Thanks for all the review comments. I think everyone's input really helped tighten up the API and come up with a consistent story for bind, reactive and parameters. |
Yep! I'm really happy with how it has all come together. Great job, everyone! |
Adds
param.bind
function which acts likefunctools.partial
but evaluates the current value of a parameter.param.depends
andparam.bind
so that they can accept other objects and interpret them as parameters (making them behave likepn.depends
andpn.bind
).reactive
APIreactive acts as a proxy for the object it is wrapping, therefore the API of
reactive
itself should be minimal to avoid overriding the behavior of the wrapped object. Here is the currentreactive
public API. When reviewing please consider the naming and consider whether it is likely to clash with objects we are wrapping.resolve
: Evaluates and returns the current value of the reactive expressionset_input
: If the input to the expression was a scalar value (i.e. not a reference such as Parameter or bound function) then this allows updating the input to the expressionwatch
: Watches the expressionAdditionally we introduce a few additional methods to support certain operations that cannot easily be replicated using standard Python operations:
bool_
: Casts the object to bool (equivalent to.pipe(bool)
), needed because__bool__
must return a boolean value, not just something that will evaluate to a boolean value.is_
: Allows performing identity comparison on the underlying object, e.g.obj is None
len
: Computes thelen
of the object, since__len__
cannot return anything except anint
.pipe
: Allows chaining an arbitrary function on the reactive expression (modeled onpd.DataFrame.pipe
)when
: Construct a newreactive
object that only emits new events when the provided reference updates (required because performing actions based only on some external trigger, e.g. clicking a button, is hard to express otherwise).where
: Construct that allows returning either value A or value B (required becauseA if ... else B
is hard to express otherwise).