holoviz / param

Param: Make your Python code clearer and more reliable by declaring Parameters
https://param.holoviz.org
BSD 3-Clause "New" or "Revised" License
412 stars 69 forks source link

Implement support for allowing Parameter references #843

Closed philippjfr closed 9 months ago

philippjfr commented 9 months ago

This PR brings an exceptionally useful feature that we have had in Panel since 1.2 to Param. Specifically it allows passing Parameter references (i.e. Parameter objects, reactive functions and expressions) as a parameter value and then automatically mirrors the value of the reference, e.g.:

class Parameters(param.Parameterized):

    string = param.String(default="string", allow_refs=True)

p = Parameters()
p2 = Parameters(string=p.param.string)

assert p.string == p2.string
p.string = 'new_string'
assert p2.string == 'new_string'

This is much, much easier than setting up callbacks and together with the addition of reactive functions and expressions makes it super easy to set up parameters that depend on some other dynamically computed values. The implementation here is fully backward compatible, since you must enable the support for resolving references like this explicitly with the new Parameter.allow_refs slot.

Note that references can only be used on instances, classes will not attempt to resolve references, however you may use a class parameter as a reference.

This implementation also implements updating of references, which could not reasonably be implemented in Panel and is a good reason to move this support to Param.

In addition to allowing simple references to be resolved support can also be enabled for nested references using the Parameter.nested_refs slot. This allows constructs such as this to work:

class Parameters(param.Parameterized):

    string = param.String(default="string", allow_refs=True)

    dictionary = param.Dict(default={}, allow_refs=True, nested_refs=True)

p = Parameters()
p2 = Parameters(dictionary={'key': p.param.string})
p3 = Parameters(string='foo')

assert p2.dictionary == {'key': 'string'}
p2.dictionary = {'new key': p3.param.string}
assert p2.dictionary == {'new key': 'foo'}
philippjfr commented 9 months ago

I'm a little wary about changing that this close to release. How would you feel about the idea that in 2.0 we begin warning people that they should explicitly set allow_refs=False when we detect something that would be treated as a reference once we explicitly enable it. Then we can give people some time to update their code and then flip the switch by default in 2.1.

jbednar commented 9 months ago

Ok, though I'd probably not announce 2.0 widely until 2.1 is in place, leaving the 2.0 as a helpful interim step for people to move their code into compatibility, rather than one we'd expect people to adopt widely. 2.0 already has compatibility implications and I'd like to guide people from where they are into the new way, rather than make them adapt twice.

philippjfr commented 9 months ago

I'm going to go ahead and merge. As I need this in a dev release asap the timelines here are tight and this was written with backward compatibility at the forefront. If you're coming at this later please still give it a thorough review.