Skip to content

Commit

Permalink
Ensure class watchers are not inherited by instance parameter (#833)
Browse files Browse the repository at this point in the history
Co-authored-by: maximlt <mliquet@anaconda.com
  • Loading branch information
philippjfr authored Sep 16, 2023
1 parent 779f734 commit c087fb9
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 15 deletions.
23 changes: 8 additions & 15 deletions param/parameterized.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,21 +403,14 @@ def iscoroutinefunction(function):
def _instantiate_param_obj(paramobj, owner=None):
"""Return a Parameter object suitable for instantiation given the class's Parameter object"""

# Shallow-copy Parameter object, with special handling for watchers
# (from try/except/finally in Parameters.__getitem__ in https://github.com/holoviz/param/pull/306)
p = paramobj
try:
# Do not copy watchers on class parameter
watchers = p.watchers
p.watchers = {}
p = copy.copy(p)
except:
raise
finally:
p.watchers = {k: list(v) for k, v in watchers.items()}

# Shallow-copy Parameter object
p = copy.copy(paramobj)
p.owner = owner

# Reset watchers since class parameter watcher should not execute
# on instance parameters
p.watchers = {}

# shallow-copy any mutable slot values other than the actual default
for s in p.__class__.__slots__:
v = getattr(p, s)
Expand Down Expand Up @@ -3895,10 +3888,10 @@ def __init__(self, **params):
self.param._setup_params(**params)
object_count += 1

self.param._update_deps(init=True)

self._param__private.initialized = True

self.param._update_deps(init=True)

@property
def param(self):
return Parameters(self.__class__, self=self)
Expand Down
88 changes: 88 additions & 0 deletions tests/testwatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ def setUp(self):
self.accumulator = 0
self.list_accumulator = []

def tearDown(self):
SimpleWatchExample.param.d.bounds = None

def test_triggered_when_changed(self):
def accumulator(change):
self.accumulator += change.new
Expand Down Expand Up @@ -513,6 +516,91 @@ def test_nested_batched_watch_not_onlychanged(self):
self.assertEqual(args[1].new, 0)
self.assertEqual(args[1].type, 'set')

def test_watch_param_slot(self):
obj = SimpleWatchExample()

cls_events = []
SimpleWatchExample.param.watch(cls_events.append, 'd', what='bounds')

obj.param.objects('existing')['d'].bounds = (1, 2)

assert len(cls_events) == 1
assert cls_events[0].name == 'd'
assert cls_events[0].what == 'bounds'
assert cls_events[0].new == (1, 2)

inst_events = []
obj.param.watch(inst_events.append, 'd', what='bounds')

obj.param.objects('existing')['d'].bounds = (3, 4)

assert len(cls_events) == 1
assert len(inst_events) == 1
assert inst_events[0].name == 'd'
assert inst_events[0].what == 'bounds'
assert inst_events[0].new == (3, 4)

def test_param_watch_no_side_effect(self):
# Example 1 of https://github.com/holoviz/param/issues/829

class P(param.Parameterized):
x = param.Parameter()

store = []
P.param.watch(store.append, 'x')

P.x = 10

assert len(store) == 1
assert 'value' in P.param.x.watchers

p = P()

assert 'value' in P.param.x.watchers

# Checking this does not have bad side-effects
p.param.x

# Watcher still on the class Parameter
assert 'value' in P.param.x.watchers
# Watcher not on the instance
assert p.param.x.watchers == {}

P.x = 20
# Watcher still triggered
assert len(store) == 2

p.x = 30
# Watcher not triggerd on instance update
assert len(store) == 2

def test_param_watch_multiple_instances(self):
# Example 4 of https://github.com/holoviz/param/issues/829

class P(param.Parameterized):
x = param.Parameter()
l = param.List([])

@param.depends('x:constant', watch=True)
def cb(self):
self.l.append(self.param.x.constant)

assert P.param.x.watchers == {}

p = P()

# Creating the instance ???
assert P.param.x.watchers == {}

p2 = P()

# Modify constant on p2.param.x
p2.param.x.constant = True

# The event should only trigger on p2, not p
assert p2.l == [True]
assert p.l == []

def test_watch_deepcopy(self):
obj = SimpleWatchExample()

Expand Down

0 comments on commit c087fb9

Please sign in to comment.