Search code examples
pythonenthoughttraits

Get list of traits listeners -- who's listening to my traits?


I have some external objects listening/handling another object's traits. How can I get a list of the listeners/handlers to that objects traits? I have multiple objects listening to another's traits and I'd like to be able to query somehow and determine which ones are still connected.

Thanks!

Here's an example using the Enthought Traits module:

 from traits.api import  HasTraits,Str,Int,Float

 class GenerateEvents ( HasTraits ):
     name   = Str
     age    = Int
     weight = Float

 class ListenEvents ( HasTraits ):
     def _name_changed ( self, object, name, old, new ):
         print "_name_changed:", object, name, old, new

     def _age_changed ( self, object, name, old, new ):
         print "_age_changed:", object, name, old, new

     def _weight_changed ( self, object, name, old, new ):
         print "_weight_changed:", object, name, old, new

 class AnotherListenEvents ( HasTraits ):
     def _name_changed ( self, object, name, old, new ):
         print "Another _name_changed:", object, name, old, new

     def _age_changed ( self, object, name, old, new ):
         print "another _age_changed:", object, name, old, new

     def _weight_changed ( self, object, name, old, new ):
         print "another _weight_changed:", object, name, old, new

 ge = GenerateEvents()
 le = ListenEvents()
 ale = AnotherListenEvents()
 ge.set( name = 'Joe', age = 22, weight = 152.0 )
 ge.add_trait_listener( le )
 ge.add_trait_listener( ale )
 ge.set( name = 'Mike', age = 34, weight = 178.0 )

Note that ge has two listeners, le and ale. However, given ge how could I find out what the listeners are? Note that listeners can added/removed dynamically in the code so they are not fixed.

I hope that clarifies a bit.


Solution

  • Take a look at Robert Kern's answer in this thread from the enthought-dev mailing list: http://enthought-dev.117412.n3.nabble.com/How-do-I-find-the-listeners-for-a-trait-td1716192.html

    Here's a modified version of your code, with a couple functions added for retrieving and displaying the listeners. I added a static listener _age_changed, a listener created with the on_trait_change decorator, and a listener created with ge.on_trait_change(...).

    from traits.api import (HasTraits, Str, Int, Float, on_trait_change,
                            TraitChangeNotifyWrapper)
    from traits.trait_notifiers import StaticTraitChangeNotifyWrapper
    
    
    def get_listeners(h):
        """
        h must be a HasTraits instance.
    
        Returns a dictionary whose keys are trait names and whose values
        are lists of notifiers.
        """
        listeners = {}
        for name in h.traits():
            notifiers = h.trait(name)._notifiers(0)
            if notifiers is not None:
                # Filter out the static listeners.  Comment this out
                # if you want to keep those.
                notifiers = [notifier for notifier in notifiers
                                if not isinstance(notifier, StaticTraitChangeNotifyWrapper)]
                listeners[name] = notifiers
        return listeners
    
    
    def print_listeners(listeners):
        """
        Print the dictionary of listeners returned by `get_listeners(h)`.
        """
        for name, notifiers in listeners.items():
            print "trait '%s' has the following listeners:" % (name,)
            for notifier in notifiers:
                if notifier.name is None:
                    handler = notifier.handler
                    print "    '%s' %r" % (handler.__name__, type(handler))
                else:
                    print "    '%s' on object %s" % (notifier.name, notifier.object)
    
    
    class GenerateEvents ( HasTraits ):
        name   = Str
        age    = Int
        weight = Float
    
        def _age_changed(self, old):
            print "age changed from ", old, "to", self.age
    
        @on_trait_change('weight')
        def do_something(self, obj, name, old, new):
            print "do_something: name =", name
    
    
    class ListenEvents ( HasTraits ):
        def _name_changed ( self, object, name, old, new ):
            print "_name_changed:", object, name, old, new
    
        def _age_changed ( self, object, name, old, new ):
            print "_age_changed:", object, name, old, new
    
        def _weight_changed ( self, object, name, old, new ):
            print "_weight_changed:", object, name, old, new
    
    
    class AnotherListenEvents ( HasTraits ):
        def _name_changed ( self, object, name, old, new ):
            print "Another _name_changed:", object, name, old, new
    
        def _age_changed ( self, object, name, old, new ):
            print "another _age_changed:", object, name, old, new
    
        def _weight_changed ( self, object, name, old, new ):
            print "another _weight_changed:", object, name, old, new
    
    
    def printit(foo):
        print foo
    
    
    ge = GenerateEvents()
    le = ListenEvents()
    ale = AnotherListenEvents()
    ge.set( name = 'Joe', age = 22, weight = 152.0 )
    ge.add_trait_listener( le )
    ge.add_trait_listener( ale )
    ge.set( name = 'Mike', age = 34, weight = 178.0 )
    
    # Make the function `printit` a listener to ge.name.
    ge.on_trait_change(printit, name='name')
    

    Here's what you get when you run it (in ipython):

    In [103]: run trait_listeners_question
    age changed from  22 to 22
    do_something: name = weight
    age changed from  34 to 34
    _age_changed: <__main__.GenerateEvents object at 0x2680950> age 22 34
    another _age_changed: <__main__.GenerateEvents object at 0x2680950> age 22 34
    _name_changed: <__main__.GenerateEvents object at 0x2680950> name Joe Mike
    Another _name_changed: <__main__.GenerateEvents object at 0x2680950> name Joe Mike
    do_something: name = weight
    _weight_changed: <__main__.GenerateEvents object at 0x2680950> weight 152.0 178.0
    another _weight_changed: <__main__.GenerateEvents object at 0x2680950> weight 152.0 178.0
    
    In [104]: listeners = get_listeners(ge)
    
    In [105]: print_listeners(listeners)
    trait 'trait_added' has the following listeners:
        '_trait_added_changed' on object <weakref at 0x2656d08; to 'ListenEvents' at 0x2680d70>
        '_trait_added_changed' on object <weakref at 0x2656e68; to 'AnotherListenEvents' at 0x26808f0>
    trait 'age' has the following listeners:
        '_age_changed' on object <weakref at 0x2656c58; to 'ListenEvents' at 0x2680d70>
        '_age_changed' on object <weakref at 0x2656db8; to 'AnotherListenEvents' at 0x26808f0>
    trait 'ev' has the following listeners:
        '_ev_changed' on object <weakref at 0x2656c00; to 'ListenEvents' at 0x2680d70>
    trait 'name' has the following listeners:
        '_name_changed' on object <weakref at 0x2656cb0; to 'ListenEvents' at 0x2680d70>
        '_name_changed' on object <weakref at 0x2656e10; to 'AnotherListenEvents' at 0x26808f0>
        'printit' <type 'function'>
    trait 'weight' has the following listeners:
        'do_something' on object <weakref at 0x2671368; to 'GenerateEvents' at 0x2680950>
        '_weight_changed' on object <weakref at 0x2656ba8; to 'ListenEvents' at 0x2680d70>
        '_weight_changed' on object <weakref at 0x2656d60; to 'AnotherListenEvents' at 0x26808f0>