Decorating class member functions

Discussion in 'Python' started by Andy Terrel, May 4, 2007.

  1. Andy Terrel

    Andy Terrel Guest

    Okay does anyone know how to decorate class member functions?

    The following code gives me an error:

    Traceback (most recent call last):
    File "decorators2.py", line 33, in <module>
    s.update()
    File "decorators2.py", line 13, in __call__
    retval = self.fn.__call__(*args,**kws)
    TypeError: update() takes exactly 1 argument (0 given)

    ------------------


    #! /usr/bin/env python

    class Bugger (object):
    def __init__ (self, module, fn):
    self.module = module
    self.fn = fn

    def __call__ (self,*args, **kws):
    ret_val = self.fn(*args,**kws)
    return ret_val

    def instrument (module_name):
    ret_val = lambda x: Bugger(module_name, x)
    return ret_val

    class Stupid:
    def __init__(self):
    self.val = 1

    @instrument("xpd.spam")
    def update(self):
    self.val += 1


    s = Stupid()
    s.update()
    Andy Terrel, May 4, 2007
    #1
    1. Advertising

  2. Andy Terrel

    Andy Terrel Guest

    Oh I should mention the decorator needs to have some notion of state
    (such as with the above class)
    Andy Terrel, May 4, 2007
    #2
    1. Advertising

  3. On May 3, 9:21 pm, Andy Terrel <> wrote:
    > Okay does anyone know how to decorate class member functions?
    >
    > The following code gives me an error:
    >
    > Traceback (most recent call last):
    > File "decorators2.py", line 33, in <module>
    > s.update()
    > File "decorators2.py", line 13, in __call__
    > retval = self.fn.__call__(*args,**kws)
    > TypeError: update() takes exactly 1 argument (0 given)
    >
    > ------------------
    >
    > #! /usr/bin/env python
    >
    > class Bugger (object):
    > def __init__ (self, module, fn):
    > self.module = module
    > self.fn = fn
    >
    > def __call__ (self,*args, **kws):
    > ret_val = self.fn(*args,**kws)
    > return ret_val
    >
    > def instrument (module_name):
    > ret_val = lambda x: Bugger(module_name, x)
    > return ret_val
    >
    > class Stupid:
    > def __init__(self):
    > self.val = 1
    >
    > @instrument("xpd.spam")
    > def update(self):
    > self.val += 1
    >
    > s = Stupid()
    > s.update()


    A decorator is a function that takes one single parameter: a function.
    "instrument" must return a decorator.
    Virgil Dupras, May 4, 2007
    #3
  4. On May 3, 9:33 pm, Virgil Dupras <> wrote:
    > On May 3, 9:21 pm, Andy Terrel <> wrote:
    >
    >
    >
    > > Okay does anyone know how to decorate class member functions?

    >
    > > The following code gives me an error:

    >
    > > Traceback (most recent call last):
    > > File "decorators2.py", line 33, in <module>
    > > s.update()
    > > File "decorators2.py", line 13, in __call__
    > > retval = self.fn.__call__(*args,**kws)
    > > TypeError: update() takes exactly 1 argument (0 given)

    >
    > > ------------------

    >
    > > #! /usr/bin/env python

    >
    > > class Bugger (object):
    > > def __init__ (self, module, fn):
    > > self.module = module
    > > self.fn = fn

    >
    > > def __call__ (self,*args, **kws):
    > > ret_val = self.fn(*args,**kws)
    > > return ret_val

    >
    > > def instrument (module_name):
    > > ret_val = lambda x: Bugger(module_name, x)
    > > return ret_val

    >
    > > class Stupid:
    > > def __init__(self):
    > > self.val = 1

    >
    > > @instrument("xpd.spam")
    > > def update(self):
    > > self.val += 1

    >
    > > s = Stupid()
    > > s.update()

    >
    > A decorator is a function that takes one single parameter: a function.
    > "instrument" must return a decorator.


    Oh wait, I just embarrassed myself. Nevermind my last post.
    Virgil Dupras, May 4, 2007
    #4
  5. On May 3, 9:21 pm, Andy Terrel <> wrote:
    > Okay does anyone know how to decorate class member functions?
    >
    > The following code gives me an error:
    >
    > Traceback (most recent call last):
    > File "decorators2.py", line 33, in <module>
    > s.update()
    > File "decorators2.py", line 13, in __call__
    > retval = self.fn.__call__(*args,**kws)
    > TypeError: update() takes exactly 1 argument (0 given)
    >
    > ------------------
    >
    > #! /usr/bin/env python
    >
    > class Bugger (object):
    > def __init__ (self, module, fn):
    > self.module = module
    > self.fn = fn
    >
    > def __call__ (self,*args, **kws):
    > ret_val = self.fn(*args,**kws)
    > return ret_val
    >
    > def instrument (module_name):
    > ret_val = lambda x: Bugger(module_name, x)
    > return ret_val
    >
    > class Stupid:
    > def __init__(self):
    > self.val = 1
    >
    > @instrument("xpd.spam")
    > def update(self):
    > self.val += 1
    >
    > s = Stupid()
    > s.update()


    Second attempt. After some fuss around, I think it's just not possible
    to have a class instance as a decorator. the self gets lost in
    translation.

    #! /usr/bin/env python
    class Caller(object):
    def __init__ (self, fn):
    self.fn = fn

    def __call__(self, *args, **kwargs):
    print 'Caller calling!', repr(args)
    return self.fn(*args, **kwargs)

    def mydecorator(f):
    return Caller(f)

    class Stupid:
    def __init__(self):
    self.val = 1

    @mydecorator
    def update(self):
    self.val += 1

    s = Stupid()
    s.update()

    Caller calling! ()
    Traceback (most recent call last):
    File "/Users/hsoft/Desktop/foo.py", line 22, in ?
    s.update()
    File "/Users/hsoft/Desktop/foo.py", line 8, in __call__
    return self.fn(*args, **kwargs)
    TypeError: update() takes exactly 1 argument (0 given)

    But why do you want to use a class? If you want to have a decorator
    with argument, you only need to have something like:

    def instrument(module):
    def decorator(f):
    def wrapper(*args, **kwargs):
    print module
    return f(*args, **kwargs)
    return wrapper
    return decorator
    Virgil Dupras, May 4, 2007
    #5
  6. Andy Terrel

    Andy Terrel Guest

    I just need to keep the state around. I make a call to some function
    that is pretty expensive so I want to save it as a member during the
    __init__ of the decorator.

    Yeah I'm afraid it can't be done either, that's why I asked the group.
    Andy Terrel, May 4, 2007
    #6
  7. Andy Terrel

    Jorge Godoy Guest

    Andy Terrel <> writes:

    > I just need to keep the state around. I make a call to some function
    > that is pretty expensive so I want to save it as a member during the
    > __init__ of the decorator.
    >
    > Yeah I'm afraid it can't be done either, that's why I asked the group.


    Have you looked at memoize decorators? They seem to do what you want.
    There are examples at the Python website (some link in there, I'm
    sorry...).

    This will give you lots of resources, including full recipes and
    comments from the Python cookbook:
    http://www.google.com.br/search?q=python decorator memoize


    --
    Jorge Godoy <>
    Jorge Godoy, May 4, 2007
    #7
  8. Andy Terrel

    Andy Terrel Guest

    not quite as elegant but here is a workaround... Thanks Virgil for
    taking some time to think about it.

    ---

    class Bugger (object):
    def __init__ (self, module):
    print "Entering __init__"
    self.module = module
    self.verb = 0

    def instrument (module_name):
    def wrapper(f):
    def _wrap(*args,**kws):
    ret_val = f(*args,**kws)
    return ret_val
    return _wrap
    b = Bugger(module_name)
    if b.verb == 0:
    ret_val = wrapper
    else:
    ret_val = lambda x:x
    return ret_val

    class Stupid:
    def __init__(self):
    self.val = 1

    @instrument("spam")
    def update(self):
    self.val += 1


    s = Stupid()
    s.update()
    s.update()
    s.update()
    s.update()
    print s.val
    Andy Terrel, May 4, 2007
    #8
  9. On Thu, 03 May 2007 19:28:52 -0700, Andy Terrel wrote:

    > I just need to keep the state around. I make a call to some function
    > that is pretty expensive so I want to save it as a member during the
    > __init__ of the decorator.
    >
    > Yeah I'm afraid it can't be done either, that's why I asked the group.


    You can do it if you give up on using the decorator syntax.

    (Now that I've said that, some clever bunny will show me how to do it.)

    def make_decorator(n):
    def addspam(fn):
    def new(*args):
    print "spam " * n
    return fn(*args)
    return new
    return addspam


    class Parrot(object):
    def __init__(self, count=3):
    from new import instancemethod as im
    self.describe = im(make_decorator(count)(self.__class__.describe), self)
    def describe(self):
    return "It has beautiful plummage."


    >>> bird = Parrot()
    >>> bird.describe()

    spam spam spam
    'It has beautiful plummage.'
    >>>
    >>>
    >>> bird = Parrot(5)
    >>> bird.describe()

    spam spam spam spam spam
    'It has beautiful plummage.'



    --
    Steven D'Aprano
    Steven D'Aprano, May 4, 2007
    #9
  10. Andy Terrel

    Peter Otten Guest

    Andy Terrel wrote:

    > Okay does anyone know how to decorate class member functions?
    >
    > The following code gives me an error:
    >
    > Traceback (most recent call last):
    > File "decorators2.py", line 33, in <module>
    > s.update()
    > File "decorators2.py", line 13, in __call__
    > retval = self.fn.__call__(*args,**kws)
    > TypeError: update() takes exactly 1 argument (0 given)
    >
    > ------------------
    >
    >
    > #! /usr/bin/env python
    >
    > class Bugger (object):
    > def __init__ (self, module, fn):
    > self.module = module
    > self.fn = fn
    >
    > def __call__ (self,*args, **kws):
    > ret_val = self.fn(*args,**kws)
    > return ret_val
    >
    > def instrument (module_name):
    > ret_val = lambda x: Bugger(module_name, x)
    > return ret_val
    >
    > class Stupid:
    > def __init__(self):
    > self.val = 1
    >
    > @instrument("xpd.spam")
    > def update(self):
    > self.val += 1
    >
    >
    > s = Stupid()
    > s.update()


    The problem is not that you are decorating a method but that you are trying
    to use a callable class instance as a method. For that to work the class
    has to implement the descriptor protocol, see

    http://users.rcn.com/python/download/Descriptor.htm

    class Bugger (object):
    def __init__ (self, module, fn, instance=None):
    self.module = module
    self.fn = fn
    self.instance = instance

    def __call__ (self, *args, **kws):
    print "calling %s.%s()" % (self.module, self.fn.__name__)
    if self.instance is not None:
    args = (self.instance,) + args
    ret_val = self.fn(*args, **kws)
    return ret_val

    def __get__(self, instance, class_):
    if instance is None:
    return self
    return Bugger(self.module, self.fn, instance)

    def instrument (module_name):
    ret_val = lambda x: Bugger(module_name, x)
    return ret_val

    class Stupid(object):
    @instrument("xpd.spam")
    def update(self):
    print "update()"

    s = Stupid()
    s.update()
    Stupid.update(s)

    Peter
    Peter Otten, May 4, 2007
    #10
  11. Andy Terrel

    7stud Guest

    On May 3, 7:21 pm, Andy Terrel <> wrote:
    > Okay does anyone know how to decorate class member functions?
    >
    > The following code gives me an error:
    >
    > Traceback (most recent call last):
    > File "decorators2.py", line 33, in <module>
    > s.update()
    > File "decorators2.py", line 13, in __call__
    > retval = self.fn.__call__(*args,**kws)
    > TypeError: update() takes exactly 1 argument (0 given)
    >
    > ------------------
    >
    > #! /usr/bin/env python
    >
    > class Bugger (object):
    > def __init__ (self, module, fn):
    > self.module = module
    > self.fn = fn
    >
    > def __call__ (self,*args, **kws):
    > ret_val = self.fn(*args,**kws)
    > return ret_val
    >
    > def instrument (module_name):
    > ret_val = lambda x: Bugger(module_name, x)
    > return ret_val
    >
    > class Stupid:
    > def __init__(self):
    > self.val = 1
    >
    > @instrument("xpd.spam")
    > def update(self):
    > self.val += 1
    >
    > s = Stupid()
    > s.update()


    As far as I can tell, the problem is that the decorator executes when
    the class is parsed, and at that time there is no self(the instance
    object). The decorator produces a callable Bugger object, but the
    callable object has no way to get self when s.update() is called.
    Normally when you call a class function, like s.update(), the
    __get__() method in the 'update' function object is called (all
    function objects have a __get__() method and therefore are
    descriptors). Then __get__() creates a method object out of the
    function object(update), and python automatically passes the instance
    object to the method object. However, the Bugger object does not have
    a __get__() method, so no method object is created when the Bugger
    object is called, and therefore self is not automatically passed to
    the Bugger object.

    The following solution adds a __get__() method to the Bugger object.
    Python automatically passes the instance object to the __get__()
    method, and the solution stores the instance in the Bugger object.
    Then __call__ is defined to send the instance object to update().

    class Bugger (object):
    def __init__ (self, module, fn):
    self.module = module
    self.fn = fn

    def __call__ (self,*args, **kws):
    ret_val = self.fn(self.obj, *args,**kws)
    return ret_val

    def __get__(descr, inst, instCls=None):
    descr.obj = inst
    return descr

    def instrument (module_name):
    ret_val = lambda func: Bugger(module_name, func)
    return ret_val

    class Stupid(object):
    def __init__(self):
    self.val = 1

    @instrument("xpd.spam")
    def update(self):
    self.val += 1

    s = Stupid()
    s.update()
    s.update()
    s.update()
    print s.val

    --output:--
    4
    7stud, May 4, 2007
    #11
  12. Andy Terrel

    Andy Terrel Guest

    Thanks Peter and 7stud. That is the solution that really works for
    me.
    Andy Terrel, May 4, 2007
    #12
  13. On May 3, 10:34 pm, Peter Otten <> wrote:
    > The problem is not that you are decorating a method but that you are trying
    > to use a callable class instance as a method. For that to work the class
    > has to implement the descriptor protocol, see
    >
    > http://users.rcn.com/python/download/Descriptor.htm
    >
    > class Bugger (object):
    > def __init__ (self, module, fn, instance=None):
    > self.module = module
    > self.fn = fn
    > self.instance = instance
    >
    > def __call__ (self, *args, **kws):
    > print "calling %s.%s()" % (self.module, self.fn.__name__)
    > if self.instance is not None:
    > args = (self.instance,) + args
    > ret_val = self.fn(*args, **kws)
    > return ret_val
    >
    > def __get__(self, instance, class_):
    > if instance is None:
    > return self
    > return Bugger(self.module, self.fn, instance)
    >
    > def instrument (module_name):
    > ret_val = lambda x: Bugger(module_name, x)
    > return ret_val
    >
    > class Stupid(object):
    > @instrument("xpd.spam")
    > def update(self):
    > print "update()"
    >
    > s = Stupid()
    > s.update()
    > Stupid.update(s)


    I've been bitten by the "class instances as decorators won't get
    bound" bug many times. I had no idea there was a direct solution.
    Thanks!

    --
    Adam Olsen, aka Rhamphoryncus
    Rhamphoryncus, May 4, 2007
    #13
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Michele Simionato
    Replies:
    4
    Views:
    424
    darkbeethoven
    Jun 5, 2012
  2. Hicham Mouline
    Replies:
    0
    Views:
    420
    Hicham Mouline
    Apr 23, 2009
  3. Hicham Mouline
    Replies:
    1
    Views:
    404
    Michael DOUBEZ
    Apr 24, 2009
  4. Rotwang
    Replies:
    6
    Views:
    116
    Rotwang
    Apr 4, 2013
  5. Dan Stromberg
    Replies:
    1
    Views:
    75
    Steven D'Aprano
    Jun 7, 2014
Loading...

Share This Page