Python decorator makes function forget that it belongs to a class
我正试图写一个装饰器来做日志记录:
1 2 3 4 5 6 7 8 9 10 11 12 13 | def logger(myFunc): def new(*args, **keyargs): print 'Entering %s.%s' % (myFunc.im_class.__name__, myFunc.__name__) return myFunc(*args, **keyargs) return new class C(object): @logger def f(): pass C().f() |
我想打印:
1 | Entering C.f |
但是,我得到了这个错误消息:
1 | AttributeError: 'function' object has no attribute 'im_class' |
大概这和"logger"中"myfunc"的范围有关,但我不知道是什么。
克劳迪乌的回答是正确的,但是你也可以通过从
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from functools import wraps # use this to preserve function signatures and docstrings def logger(func): @wraps(func) def with_logging(*args, **kwargs): print"Entering %s.%s" % (args[0].__class__.__name__, func.__name__) return func(*args, **kwargs) return with_logging class C(object): @logger def f(self): pass C().f() |
正如我所说,如果您从父类继承了一个函数,那么这将无法正常工作;在这种情况下,您可能会说
1 2 3 4 5 | class B(C): pass b = B() b.f() |
得到消息
函数只在运行时成为方法。也就是说,当你得到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class logger(object): def __init__(self, func): self.func = func def __get__(self, obj, type=None): return self.__class__(self.func.__get__(obj, type)) def __call__(self, *args, **kw): print 'Entering %s' % self.func return self.func(*args, **kw) class C(object): @logger def f(self, x, y): return x+y C().f(1, 2) # => Entering <bound method C.f of <__main__.C object at 0x...>> |
显然,可以改进输出(例如,使用
这里提出的想法很好,但也有一些缺点:
所以,我结合了这个页面的一些想法,链接,文档和我自己的头,最后找到了一个解决方案,它没有上述三个缺点。
因此,
- 知道修饰方法绑定到的类。
- 通过比
functools.wraps() 更正确地回答系统属性隐藏装饰器跟踪。 - 包含绑定未绑定实例方法、类方法、静态方法和普通函数的单元测试。
用途:
1 2 3 4 5 | pip install method_decorator from method_decorator import method_decorator class my_decorator(method_decorator): # ... |
有关用法的详细信息,请参阅完整的单元测试。
下面是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | class method_decorator(object): def __init__(self, func, obj=None, cls=None, method_type='function'): # These defaults are OK for plain functions # and will be changed by __get__() for methods once a method is dot-referenced. self.func, self.obj, self.cls, self.method_type = func, obj, cls, method_type def __get__(self, obj=None, cls=None): # It is executed when decorated func is referenced as a method: cls.func or obj.func. if self.obj == obj and self.cls == cls: return self # Use the same instance that is already processed by previous call to this __get__(). method_type = ( 'staticmethod' if isinstance(self.func, staticmethod) else 'classmethod' if isinstance(self.func, classmethod) else 'instancemethod' # No branch for plain function - correct method_type for it is already set in __init__() defaults. ) return object.__getattribute__(self, '__class__')( # Use specialized method_decorator (or descendant) instance, don't change current instance attributes - it leads to conflicts. self.func.__get__(obj, cls), obj, cls, method_type) # Use bound or unbound method with this underlying func. def __call__(self, *args, **kwargs): return self.func(*args, **kwargs) def __getattribute__(self, attr_name): # Hiding traces of decoration. if attr_name in ('__init__', '__get__', '__call__', '__getattribute__', 'func', 'obj', 'cls', 'method_type'): # Our known names. '__class__' is not included because is used only with explicit object.__getattribute__(). return object.__getattribute__(self, attr_name) # Stopping recursion. # All other attr_names, including auto-defined by system in self, are searched in decorated self.func, e.g.: __module__, __class__, __name__, __doc__, im_*, func_*, etc. return getattr(self.func, attr_name) # Raises correct AttributeError if name is not found in decorated self.func. def __repr__(self): # Special case: __repr__ ignores __getattribute__. return self.func.__repr__() |
似乎在创建类的同时,python创建常规的函数对象。之后它们只会变成未绑定的方法对象。知道了这一点,这是我唯一能找到做你想做的事情的方法:
1 2 3 4 5 6 7 8 9 10 11 12 | def logger(myFunc): def new(*args, **keyargs): print 'Entering %s.%s' % (myFunc.im_class.__name__, myFunc.__name__) return myFunc(*args, **keyargs) return new class C(object): def f(self): pass C.f = logger(C.f) C().f() |
这将输出所需的结果。
如果您希望将所有方法包装在一个类中,那么您可能希望创建一个wrapclass函数,然后可以这样使用:
1 | C = wrapClass(C) |
我使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import inspect def logger(myFunc): classname = inspect.getouterframes(inspect.currentframe())[1][3] def new(*args, **keyargs): print 'Entering %s.%s' % (classname, myFunc.__name__) return myFunc(*args, **keyargs) return new class C(object): @logger def f(self): pass C().f() |
虽然这并不一定比其他方法好,但这是我在调用decorator期间发现未来方法的类名的唯一方法。注意不要在
类函数应始终将self作为第一个参数,因此可以使用它而不是im_类。
1 2 3 4 5 6 7 8 9 10 11 12 | def logger(myFunc): def new(self, *args, **keyargs): print 'Entering %s.%s' % (self.__class__.__name__, myFunc.__name__) return myFunc(self, *args, **keyargs) return new class C(object): @logger def f(self): pass C().f() |
起初我想使用
如asaayers的答案所示,您不需要访问类对象。可能值得注意的是,由于python 3.3,您还可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | >>> def logger(myFunc): ... def new(*args, **keyargs): ... print('Entering %s' % myFunc.__qualname__) ... return myFunc(*args, **keyargs) ... ... return new ... >>> class C(object): ... @logger ... def f(self): ... pass ... >>> C().f() Entering C.f |
这还有一个额外的优点,即在嵌套类的情况下也可以工作,如本例所示,摘自PEP 3155:
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> class C: ... def f(): pass ... class D: ... def g(): pass ... >>> C.__qualname__ 'C' >>> C.f.__qualname__ 'C.f' >>> C.D.__qualname__ 'C.D' >>> C.D.g.__qualname__ 'C.D.g' |
另外请注意,在python3中,
当函数不知道它的类时,不要在定义时插入修饰代码,而是延迟运行此代码,直到访问/调用函数为止。描述符对象有助于在访问/调用时延迟插入自己的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class decorated(object): def __init__(self, func, type_=None): self.func = func self.type = type_ def __get__(self, obj, type_=None): return self.__class__(self.func.__get__(obj, type_), type_) def __call__(self, *args, **kwargs): name = '%s.%s' % (self.type.__name__, self.func.__name__) print('called %s with args=%s kwargs=%s' % (name, args, kwargs)) return self.func(*args, **kwargs) class Foo(object): @decorated def foo(self, a, b): pass |
现在我们可以在访问时(
1 2 | >>> Foo().foo(1, b=2) called Foo.foo with args=(1,) kwargs={'b': 2} |
完整示例:https://github.com/aurzenligl/study/blob/master/python-robotwrap/example4.py
还可以使用