Combining on_trait_changed with own decorators

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

Combining on_trait_changed with own decorators

Martin Richter-2
Hi there,

I'm currently stumbling over the way the on_trait_change decorators do
the dispatching in the background.

What I want to do: Combine my own decorator with on_trait_changed, for
example:

#----------------------------------------
from functools import wraps

def foobar_deco(func):
    """encapsulate function call with 'foo' and 'bar'"""
    @wraps(func)
    def wrapped(self, *args, **kwargs):
        print("foo")
        try:
            return_vals = func(self, *args, **kwargs)
        except:
            # throw further preserving traceback
            raise
        finally:
            print("bar")
        return return_vals
    return wrapped

#----------------------------------------

However, when try to use this with on_trait_changed like this

#----------------------------------------
from traits.api import HasTraits, Int, on_trait_change

class C(HasTraits):
y = Int
@foobar_deco
@on_trait_change('y')
def baz(self, obj, name, old, new):
print("%s.%s: %s -> %s"%(str(obj), name, str(old), str(new)))

c = C()
c.y = 3

#----------------------------------------

I will get a `TypeError: baz() takes exactly 5 arguments (1 given)'
because (as far as I understand) the handler's signature is checked
against the possible ones mentioned in the traits docu starting with
`handler(self)' which is passed on to the method baz. There an exception
is raised but somehow not propagated back correctly such that the right
handler can be found. Is there a way to improve my own decorator
`foobar_deco' such that the dispatching done in `rebind_call_0' in
trait_notifiers.py:505 is done right (== propagated until
rebinf_call_4)? I was hoping that functool.wraps would do everything for
me ...

Thank you very much,
Martin



_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: Combining on_trait_changed with own decorators

Chris Colbert
The problem is that traits inspects the arg spec of the function to determine which rebinder to use. Since your decorated function has the spec *args, **kwargs, traits just uses rebind0.

This is my chief complaint about traits notification and why (if we ever get around to writing it) the future version of traits will pass a single rich event object as the argument to a trait change handler.

For your use case, this should work:


In [10]: from traits.api import *

In [11]: import functools

In [12]: def deco(func):
   ....:     @functools.wraps(func)
   ....:     def closure(self, name, old, new):
   ....:         print name, old, new
   ....:     return closure
   ....: 

In [13]: class Foo(HasTraits):
   ....:     a = Int
   ....:     @deco
   ....:     @on_trait_change('a')
   ....:     def handler(self):
   ....:         print 'im ignored'
   ....:         

In [14]: f = Foo()

In [15]: f.a
Out[15]: 0

In [16]: f.a = 12
<__main__.Foo object at 0x102c01770> a 12


On Thu, Aug 9, 2012 at 10:57 AM, Martin Richter <[hidden email]> wrote:
Hi there,

I'm currently stumbling over the way the on_trait_change decorators do
the dispatching in the background.

What I want to do: Combine my own decorator with on_trait_changed, for
example:

#----------------------------------------
from functools import wraps

def foobar_deco(func):
    """encapsulate function call with 'foo' and 'bar'"""
    @wraps(func)
    def wrapped(self, *args, **kwargs):
        print("foo")
        try:
            return_vals = func(self, *args, **kwargs)
        except:
            # throw further preserving traceback
            raise
        finally:
            print("bar")
        return return_vals
    return wrapped

#----------------------------------------

However, when try to use this with on_trait_changed like this

#----------------------------------------
from traits.api import HasTraits, Int, on_trait_change

class C(HasTraits):
y = Int
@foobar_deco
@on_trait_change('y')
def baz(self, obj, name, old, new):
print("%s.%s: %s -> %s"%(str(obj), name, str(old), str(new)))

c = C()
c.y = 3

#----------------------------------------

I will get a `TypeError: baz() takes exactly 5 arguments (1 given)'
because (as far as I understand) the handler's signature is checked
against the possible ones mentioned in the traits docu starting with
`handler(self)' which is passed on to the method baz. There an exception
is raised but somehow not propagated back correctly such that the right
handler can be found. Is there a way to improve my own decorator
`foobar_deco' such that the dispatching done in `rebind_call_0' in
trait_notifiers.py:505 is done right (== propagated until
rebinf_call_4)? I was hoping that functool.wraps would do everything for
me ...

Thank you very much,
Martin



_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev


_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev