n611x007 n611x007 - 1 month ago 10
Python Question

How do I decorate an instance method with another class in Python 2.7?

In Python 2.7 I'd like to decorate an instance method

test
in class
Foo
with a decorator that is also a class called
FooTestDecorator
. From user Chirstop's question and the Python 2 docs' Descriptor HowTo guide I created this example.

There seems to be an issue however, when I print my decorated method object, it's (inspected?) name is wrong because it is noted as a question mark like
Foo.?
.

import types
class FooTestDecorator(object):
def __init__(self,func):
self.func=func
self.count=0
# tried self.func_name = func.func_name, but seemed to have no effect

def __get__(self,obj,objtype=None):
return types.MethodType(self,obj,objtype)
def __call__(self,*args,**kwargs):
self.count+=1
return self.func(*args,**kwargs)

class Foo:
@FooTestDecorator
def test(self,a):
print a
def bar(self,b):
print b


if you test it:

f=Foo()
print Foo.__dict__['test']
print Foo.test
print f.test
print Foo.__dict__['bar']
print Foo.bar
print f.bar


you get

<__main__.FooTestDecorator ...object...>
<unbound method Foo.?>
<bound method Foo.? of ...instance...>
<function bar at 0x...>
<unbound method Foo.bar>
<bound method Foo.bar of ...instance...>


You can see the replacement method is shown as
Foo.?
. This seems wrong.

How do I get my class-decorated instance method right?

note: My reason is that I want to use variables from the
FooDecorator
instance's
self
which I would set at init. I didn't put this in the example to keep it simpler.

Answer

Your decorator instance has no __name__ attribute, so Python has to do with a question mark instead.

Use functools.update_wrapper() to copy over the function name, plus a few other interesting special attributes (such as the docstring, the function module name and any custom attributes the function may have):

import types
from functools import update_wrapper

class FooTestDecorator(object):
    def __init__(self,func):
        self.func=func
        self.count=0
        update_wrapper(self, func)

    def __get__(self,obj,objtype=None):
        return types.MethodType(self,obj,objtype)
    def __call__(self,*args,**kwargs):
        self.count+=1
        return self.func(*args,**kwargs)

Demo:

>>> f=Foo()
>>> print Foo.__dict__['test']
<__main__.FooTestDecorator object at 0x11077e210>
>>> print Foo.test
<unbound method Foo.test>
>>> print f.test
<bound method Foo.test of <__main__.Foo instance at 0x11077a830>>