Python decorator使函数忘记它属于一个类

2024-04-24 15:21:24 发布

您现在位置: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):
    @logger
    def f():
        pass

C().f()

我想打印:

Entering C.f

但是我得到了一条错误信息:

AttributeError: 'function' object has no attribute 'im_class'

可能这与logger中的myFunc的作用域有关,但我不知道是什么。


Tags: namenewreturnobjectdefargs装饰pass
3条回答

函数只在运行时成为方法。也就是说,当得到C.f时,得到一个绑定函数(和C.f.im_class is C)。在定义函数时,它只是一个普通函数,没有绑定到任何类。这个未绑定和解除关联的函数是由logger修饰的。

self.__class__.__name__将为您提供类的名称,但您也可以使用描述符以更通用的方式完成此任务。这个模式被描述为in a blog post on Decorators and Descriptors,特别是logger decorator的实现如下所示:

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

克劳迪乌的答案是正确的,但是您也可以通过从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,因为这是正确的类。另一方面,这可能是可以接受的,在这种情况下,我建议采用这种方法而不是克劳迪乌的建议。

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

  1. inspect.getouterframesargs[0].__class__.__name__不适用于纯函数和静态方法。
  2. __get__必须在被@wraps拒绝的类中。
  3. @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__()

相关问题 更多 >