Python装饰器使function忘记它属于一个类
我想写一个装饰器来做日志logging:
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()
我想这打印:
Entering Cf
但是我却得到这个错误信息:
AttributeError: 'function' object has no attribute 'im_class'
大概这是与'logging器'内'myFunc'的范围有关,但我不知道是什么。
克劳狄的答案是正确的,但是你也可以通过从self
论证中得到类名来作弊。 这会在inheritance的情况下给出令人误解的日志语句,但是会告诉你正在调用其方法的对象的类。 例如:
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()
正如我所说,如果你从父类inheritance了一个函数,这将无法正常工作。 在这种情况下,你可能会说
class B(C): pass b = B() bf()
并得到消息Entering Bf
你实际上想要得到消息Entering Cf
因为这是正确的类。 另一方面,这可能是可以接受的,在这种情况下,我build议这种方法克劳迪乌的build议。
函数只在运行时变成方法。 也就是说,当你得到Cf
时,你会得到一个绑定的函数(而Cfim_class is C
)。 当你的函数被定义的时候,它只是一个普通的函数,它不会绑定到任何类。 logging器装饰了这个未绑定和解除关联的function。
self.__class__.__name__
会给你类的名字,但你也可以使用描述符来以更通用的方式来完成这个。 这个模式在装饰器和描述符的博客文章中有描述 ,特别是你的logging器装饰器的实现如下所示:
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 Cf of <__main__.C object at 0x...>>
很明显,输出可以被改进(例如,通过使用getattr(self.func, 'im_class', None)
),但是这个通用模式对于方法和函数都是有效的。 然而,它不适用于旧式的类(但不要使用这些;)
这里提出的想法很好,但有一些缺点:
-
inspect.getouterframes
和args[0].__class__.__name__
不适合普通函数和静态方法。 -
__get__
必须在一个类中,被@wraps
拒绝。 -
@wraps
本身应该是更好地隐藏痕迹。
所以,我已经结合了这个网页,链接,文档和我自己的头脑的一些想法,
最后find了一个解决scheme,缺乏上述三个缺点。
因此, method_decorator
:
- 知道装饰方法所绑定的类。
- 通过比
functools.wraps()
更正确地回答系统属性来隐藏装饰器跟踪。 - 用unit testing来覆盖未绑定的实例方法,类方法,静态方法和普通函数。
用法:
pip install method_decorator from method_decorator import method_decorator class my_decorator(method_decorator): # ...
查看完整的unit testing以获取使用细节 。
这里只是method_decorator
类的代码:
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, eg: __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__()
似乎在创build类时,Python会创build常规的函数对象。 之后它们只会变成未绑定的方法对象。 知道,这是我能find你想要的唯一方法:
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 Cf = logger(Cf) C().f()
这会输出所需的结果。
如果你想把所有的方法都包装在一个类中,那么你可能需要创build一个wrapClass函数,然后你可以像这样使用它:
C = wrapClass(C)
类函数应该始终把自己作为第一个参数,所以你可以使用它来代替im_class。
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()
起初我想用self.__name__
但是因为这个实例没有名字,所以不起作用。 您必须使用self.__class__.__name__
来获取类的名称。
我发现另一个解决scheme,使用inspect
库非常类似的问题。 当调用装饰器时,即使该函数尚未绑定到类,您可以检查堆栈并发现哪个类正在调用装饰器。 你至less可以得到类的string名称,如果这是你所需要的(可能由于它正在被创build,所以可能不能引用它)。 然后你不需要在课程创build完毕后再打电话。
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()
虽然这不一定比其他的更好 ,但是在调用装饰器的过程中, 唯一的办法就是找出未来方法的类名。 注意不要在inspect
库文档中保留对框架的引用。
您还可以使用new.instancemethod()
)从函数创build实例方法(绑定或非绑定)。