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) ),但是这个通用模式对于方法和函数都是有效的。 然而,它不适用于旧式的类(但不要使用这些;)

这里提出的想法很好,但有一些缺点:

  1. inspect.getouterframesargs[0].__class__.__name__不适合普通函数和静态方法。
  2. __get__必须在一个类中,被@wraps拒绝。
  3. @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实例方法(绑定或非绑定)。