Skip to content
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

Class-level watchers and copying watchers behaviors #829

Closed
maximlt opened this issue Sep 4, 2023 · 1 comment · Fixed by #833
Closed

Class-level watchers and copying watchers behaviors #829

maximlt opened this issue Sep 4, 2023 · 1 comment · Fixed by #833
Assignees
Labels
type-bug Bug report
Milestone

Comments

@maximlt
Copy link
Member

maximlt commented Sep 4, 2023

In #826 a discussion started on how the watchers are specially handled when a Parameter is copied from class to an instance. I've come up with some examples that exhibit some bugs and interesting behavior (e.g. side effects of calling p.param.x).

  1. Class-level watcher on default
store = []
def cb(event): store.append(event)

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

P.param.watch(cb, 'x')

P.x = 10

assert len(store) == 1

print(P.param.x.watchers)
assert 'value' in P.param.x.watchers

p = P()

print(P.param.x.watchers)
assert 'value' in P.param.x.watchers

p.param.x  # side-effect

print(P.param.x.watchers)
assert P.param.x.watchers == {}  # :( Watchers no longer saved on the class Parameter

print(p.param.x.watchers)
assert 'value' in p.param.x.watchers  # :( Now saved on the instance Parameter

P.x = 20
p.x = 30

assert len(store) == 1  # Changes at the class or instance level no longer re-trigger the callback.
  1. Class-level watcher on an Parameter attribute (constant)
store = []
def cb(event): store.append(event)

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

P.param.watch(cb, 'x', 'constant')

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

P.param.x.constant = True

assert len(store) == 1

p = P()

assert 'constant' in P.param.x.watchers
print(P.param.x.watchers)

p.param.x  # side-effect

assert P.param.x.watchers == {}  # :( Watchers no longer saved on the class Parameter
print(P.param.x.watchers)

print(p.param.x.watchers)
assert 'constant' in p.param.x.watchers  # :( Now saved on the instance Parameter

p.param.x.constant = False

assert len(store) == 2  # Changing constant on the instance triggers the callback

P.param.x.constant is True  # just checking the value

P.param.x.constant = False

assert len(store) == 2  # Changes at the class level no longer re-trigger the callback.
  1. Instance-level watcher on value
class P(param.Parameterized):
    x = param.Parameter()
    l = param.Parameter([])
    
    @param.depends('x', watch=True)
    def cb(self):
        self.l.append(self.x)

print(P.param.x.watchers)
assert P.param.x.watchers == {} 

print(P.param._depends)
assert P.param._depends['watch'][0][3][0].what == 'value'

p = P()

print(P.param.x.watchers)
assert P.param.x.watchers == {}

print(p.param.watchers)
assert 'x' in p.param.watchers

print(P.param._depends)
assert P.param._depends['watch'][0][3][0].what == 'value'

p.x = 30

assert len(p.l) == 1
# Nothing wrong in this example, just checking how things work
  1. Instance-level watcher on an attribute, two instances
class P(param.Parameterized):
    x = param.Parameter()
    l = param.List([])
    
    @param.depends('x:constant', watch=True)
    def cb(self):
        print('cb', self.param.x.constant)
        self.l.append(self.param.x.constant)

print(P.param.x.watchers)
assert P.param.x.watchers == {}

print(P.param._depends)
assert P.param._depends['watch'][0][3][0].what == 'constant'

p = P()

print(P.param.x.watchers)
assert 'constant' in P.param.x.watchers

p2 = P()
p2.param.x.constant = True

assert p2.l == [True]  # ok

assert p.l == [False]  # :( Changing the attribute on another instance triggered the callback attached to instance `p` too
  1. Instance-level watcher on an attribute
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)

print(P.param.x.watchers)
assert P.param.x.watchers == {}

print(P.param._depends)
assert P.param._depends['watch'][0][3][0].what == 'constant'

p = P()

print(P.param.x.watchers)
assert 'constant' in P.param.x.watchers   # The watchers are temporarily set on the class Parameter?
P.param.x.constant = False

print(p.l, P.l)
assert p.l == P.l == []

p.param.x  # side-effect

print(P.param.x.watchers)
assert P.param.x.watchers  == {}

print(p.param.x.watchers)
assert 'constant' in p.param.x.watchers

p.param.x.constant is False  # just checking the value

p.param.x.constant = True

print(p.l)
assert p.l == [True]
@maximlt maximlt added the type-bug Bug report label Sep 4, 2023
@maximlt
Copy link
Member Author

maximlt commented Sep 4, 2023

Somewhat related to #773

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-bug Bug report
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants