当前位置:  开发笔记 > 编程语言 > 正文

Python装饰器让函数忘记它属于一个类

如何解决《Python装饰器让函数忘记它属于一个类》经验,为你挑选了6个好方法。

我正在尝试编写一个装饰器来做日志记录:

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'的范围有关,但我不知道是什么.



1> Eli Courtwri..:

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的建议.


顺便说一句,functools.wraps不保留im_*属性.你认为这个省略可以被认为是一个错误吗?

2> ianb..:

函数仅在运行时成为方法.也就是说,当你得到C.f一个绑定函数(和C.f.im_class is C).在定义函数时,它只是一个普通函数,它不受任何类的约束.这种未绑定和解除关联的功能是由记录器装饰的.

self.__class__.__name__将为您提供类的名称,但您也可以使用描述符以更一般的方式完成此操作.这个模式在装饰器和描述符的博客文章中有描述,特别是你的记录器装饰器的实现看起来像:

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 >

显然,可以改进输出(例如,通过使用getattr(self.func, 'im_class', None)),但是这种通用模式将适用于方法和函数.但它不适用于旧式类(但不要使用那些;)



3> Denis Ryzhko..:

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

    inspect.getouterframesargs[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):
    # ...

见全单元测试的使用细节.

这里只是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__()



4> Claudiu..:

似乎在创建类时,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)



5> Asa Ayers..:

类函数应始终将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__获取类的名称.



6> user398139..:

我找到了使用该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库文档中保留对框架的引用.


这正是我想要的 - 关于它将被绑定的方法和类的信息_before_它将在第一次被调用.
推荐阅读
linjiabin43
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有