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

execution order of trait observers #874

Open
jgunstone opened this issue Oct 3, 2023 · 2 comments
Open

execution order of trait observers #874

jgunstone opened this issue Oct 3, 2023 · 2 comments

Comments

@jgunstone
Copy link

its not clear to me in what order the trait observers get executed and why?
can anyone shed light on this?

class Test(tr.HasTraits):
    a = tr.Bool(default_value=True)
    b = tr.Bool(default_value=True)
    c = tr.Bool(default_value=True)

    @tr.observe("a")
    def _a(self, on_change):
        print(f"a = {self.a}")

    @tr.observe("b")
    def _b(self, on_change):
        print(f"b = {self.b}")

    @tr.observe("c")
    def _c(self, on_change):
        print(f"c = {self.c}")

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


t1 = Test(a=False, b=False, c=False)
print("---")
t2 = Test(c=False, b=False, a=False)

# b = False
# c = False
# a = False
# ---
# b = False
# c = False
# a = False

many thanks

@jgunstone
Copy link
Author

jgunstone commented Oct 3, 2023

based on this:
#148

I implemented something like this for my use case:

class Test(tr.HasTraits):
    c = tr.Bool(default_value=True)
    b = tr.Bool(default_value=True)
    a = tr.Bool(default_value=True)

    @tr.observe("a")
    def _a(self, on_change):
        print(f"a = {self.a}")

    @tr.observe("b")
    def _b(self, on_change):
        print(f"b = {self.b}")

    @tr.observe("c")
    def _c(self, on_change):
        print(f"c = {self.c}")

    @classmethod
    def trait_order(cls):
        return [k for k, v in cls.__dict__.items() if isinstance(v, tr.TraitType)]

    def __init__(self, **kwargs):
        super().__init__()
        kwargs = self.get_ordered_kwargs(kwargs)
        {setattr(self, k, v) for k, v in kwargs.items()}

    def get_ordered_kwargs(self, kwargs):
        in_order = list(kwargs.keys())
        tr_order = self.trait_order()
        out_order = tr_order + [i for i in in_order if i not in tr_order]
        return {o: kwargs[o] for o in out_order}


t1 = Test(a=False, b=False, c=False)
print("---")
t2 = Test(c=False, b=False, a=False)

# c = False
# b = False
# a = False
# ---
# c = False
# b = False
# a = False

I'll leave the isse open for a while in case someone can suggest a "more traitlets native" way to approach this

@Paul-Aime
Copy link

Paul-Aime commented Feb 15, 2024

Might not be directly related, but as a note, if multiple callbacks created with @observe listen on the same trait, then they will be fired in alphabetical order.

Would be better to follow the definition order though.

class Test(tr.HasTraits):
    trait = tr.Bool(default_value=True)

    @tr.observe("trait")
    def _b(self, on_change):
        print("From _b")

    @tr.observe("trait")
    def _c(self, on_change):
        print("From _c")

    @tr.observe("trait")
    def _a(self, on_change):
        print("From _a")

t = Test()

t.trait = False

# From _a
# From _b
# From _c

This is due to for _, v in getmembers(cls) in traitlets.py#L985 MetaHasDescriptors.setup_class(...) where getmembers uses python dir, which in turns returns a sorted dictionary.

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

No branches or pull requests

2 participants