Controlling the sequence of events triggering for combinations of param.depends and param.watch

Hello everyone,

could please somebody help shedding some light for me on the inner workings of the sequence of events and the best practices of controlling those, when combining @param.depends() decorators and self.param.watch() calls?

I understand that the order of the functions, e.g. inside a class, is used by default as the order of precedence. What I don’t understand is, how those decorated functions intertwine with those that have watchers created by param.watch.
It appears like the decorated functions get called first (as suggested by the reserved precedence=-1 for the decorator) and in the default order, but param.watched functions get called in-between when triggered. That may not be desirable, when a queued-like behaviour is required, like in the example below, when the external class delivers a value

Is there a way of more fine grained control over this behaviour, such as “first call all decorated, then the watched”? Or how would one use the batch_call_watchers or the discard_events methods inside a class?

Similar question would be how to accumulate the triggering events and consume them all at once, when there is more than one parameter set as trigger.

Part of the problem arises for me from being unable to set a param.watch with a Parameterized class parameter as outlined in this bug report.

For instance, in the toy example below, I would like to…

  • monitorBa should only execute once after addAtoB is finished
  • monitor2BAa should only execute once last in sequence
  • bonus: updateAinBA should execute after addAtoB without any param.watch functions being called beforehand

…and now the toy example (sorry if it is a bit convoluted):

import param

class Aclass(param.Parameterized):

    Aa = param.Integer(12)
    Ab = param.String("qwe")

    def __init__(self, **params):
        super().__init__(**params)

    def incrAa(self):
        self.Aa += 1

    def moreAb(self):
        self.Ab += self.Ab

class Bclass(param.Parameterized):

    theA = param.Parameterized()
    Ba   = param.Integer(3)
    Bb   = param.String("asd")
    BAa  = param.Integer(4)
    BAa2 = param.Integer(5)

    def __init__(self, otherclass: Aclass, **params):
        self.theA = otherclass
        super().__init__(**params)
        self.param.watch(self.monitorBa, ['Ba'], queued=True, precedence=1)
        self.param.watch(self.monitorBAa, ['BAa'], queued=True, precedence=2)
        self.param.watch(self.monitorBAa2, ['BAa2'], queued=True, precedence=3)
        self.param.watch(self.monitor2BAa, ['BAa', 'BAa2'], queued=True, precedence=4)

    @param.depends('theA.Aa', watch=True)
    def addAtoB(self):
        self.Ba += self.theA.Aa
        self.Ba += self.theA.Aa
        print("\nBclass - addAtoB - self.theA.Aa: ", self.theA.Aa)
        
    def monitorBa(self, *events):
        print("\nBclass - monitorBa - self.Ba: ", self.Ba)

    def monitorBAa2(self, *events):
        print(f"\nBclass - monitorBAa2 - self.BAa: {self.BAa},  self.BAa2:  {self.BAa2}")

    def monitor2BAa(self, *events):
        self.Bb += self.Bb
        print(f"\nBclass - monitor2BAa - self.Bb: {self.Bb}")

    def monitorBAa(self, *events):
        self.BAa2 += self.BAa
        print(f"\nBclass - monitorBAa - self.BAa: {self.BAa},  self.BAa2:  {self.BAa2}")

    @param.depends('theA.Aa', watch=True)
    def updateAinBA(self):
        self.BAa = self.theA.Aa
        print("\nBclass - updateAinBA - self.BAa: ", self.BAa)

anA = Aclass()
anB = Bclass(otherclass=anA)
anA.incrAa()

which produces:

Bclass - monitorBa - self.Ba: 16
Bclass - monitorBa - self.Ba: 29
Bclass - addAtoB - self.theA.Aa: 13
Bclass - monitorBAa - self.BAa: 13, self.BAa2: 18
Bclass - monitor2BAa - self.Bb: asdasd
Bclass - monitorBAa2 - self.BAa: 13, self.BAa2: 18
Bclass - monitor2BAa - self.Bb: asdasdasdasd
Bclass - updateAinBA - self.BAa: 13

thanks a lot for your help

Param - Dependencies and Watchers