我正在尝试编写装饰器来进行记录:
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 C.f
但我收到此错误消息:
AttributeError: 'function' object has no attribute 'im_class'
据推测,这与'logger'中'myFunc'的范围有关,但我不知道是什么。
答案 0 :(得分:43)
Claudiu的回答是正确的,但您也可以通过从self
参数中获取类名来作弊。这将在继承的情况下给出误导性的日志语句,但会告诉您正在调用其方法的对象的类。例如:
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()
正如我所说,如果你从父类继承了一个函数,这将无法正常工作;在这种情况下,你可能会说
class B(C):
pass
b = B()
b.f()
并获取消息Entering B.f
,您实际上想要获取消息Entering C.f
,因为这是正确的类。另一方面,这可能是可以接受的,在这种情况下,我建议这种方法优于Claudiu的建议。
答案 1 :(得分:25)
函数仅在运行时成为方法。也就是说,当你得到C.f
时,你会得到一个绑定函数(和C.f.im_class is C
)。在定义函数时,它只是一个普通函数,它不受任何类的约束。这种未绑定和解除关联的功能是由记录器修饰的。
self.__class__.__name__
将为您提供类的名称,但您也可以使用描述符以更一般的方式完成此操作。此模式描述为in a blog post on Decorators and Descriptors,特别是您的记录器装饰器的实现如下所示:
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...>>
显然可以改进输出(例如,使用getattr(self.func, 'im_class', None)
),但这种通用模式对于方法和函数都有效。但是它不适用于旧式类(但不要使用那些;)
答案 2 :(得分:16)
这里提出的想法非常好,但有一些缺点:
inspect.getouterframes
和args[0].__class__.__name__
不适用于普通函数和静态方法。__get__
必须在班级中,但@wraps
拒绝。@wraps
本身应该更好地隐藏痕迹。所以,我结合了这个页面,链接,文档和我自己的头脑中的一些想法 最后找到了一个解决方案,它缺乏上述所有三个缺点。
因此,method_decorator
:
functools.wraps()
更准确地回答系统属性来隐藏装饰器跟踪。用法:
pip install method_decorator
from method_decorator import method_decorator
class my_decorator(method_decorator):
# ...
请参阅full unit-tests for usage details。
这里只是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, 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__()
答案 3 :(得分:7)
似乎在创建类时,Python会创建常规的函数对象。它们之后才会变成未绑定的方法对象。知道了,这是我能找到你想要的唯一方法:
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函数,然后可以像这样使用它:
C = wrapClass(C)
答案 4 :(得分:6)
类函数应始终将self作为其第一个参数,因此您可以使用它而不是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__
来获取班级的名称。
答案 5 :(得分:6)
我找到了使用inspect
库的非常类似问题的另一种解决方案。当调用decorator时,即使该函数尚未绑定到该类,您也可以检查堆栈并发现哪个类正在调用装饰器。您至少可以获取该类的字符串名称,如果这是您需要的全部内容(可能在创建之后仍无法引用它)。然后,在创建类之后,您无需调用任何内容。
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
库文档中保留对框架的引用。
答案 6 :(得分:0)
您还可以使用new.instancemethod()
从函数创建实例方法(绑定或未绑定)。
答案 7 :(得分:0)
而不是在定义时注入装饰代码,当函数不知道它的类时,延迟运行此代码直到访问/调用函数。描述符对象有助于在访问/调用时间延迟注入自己的代码:
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
现在我们可以在访问时间(__get__
)和通话时间(__call__
)检查班级。这种机制适用于普通方法和静态类方法:
>>> 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
答案 8 :(得分:0)
如Asa Ayers' answer所示,您不需要访问类对象。值得一提的是,从Python 3.3开始,您还可以使用__qualname__
,它为您提供了完全限定的名称:
>>> 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:
>>> 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'
还请注意,在Python 3中,im_class
属性已消失,因此,如果您确实希望在装饰器中访问该类,则需要其他方法。我目前使用的方法涉及object.__set_name__
,在my answer to "Can a Python decorator of an instance method access the class?"