Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/363.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python装饰器使函数忘记它属于一个类_Python_Reflection_Metaprogramming - Fatal编程技术网

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

Python装饰器使函数忘记它属于一个类,python,reflection,metaprogramming,Python,Reflection,Metaprogramming,我正在尝试编写一个decorator来进行日志记录: 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

我正在尝试编写一个decorator来进行日志记录:

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”的作用域有关,但我不知道是什么。

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

类函数应该始终将self作为它们的第一个参数,所以您可以使用它而不是imu类

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.\uuuuu name\uuuu,但这不起作用,因为实例没有名称。您必须使用
self.\uuuuu class.\uuuu name.\uuuuu
来获取类名。

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()

并在您实际想要获取消息的位置获取消息
输入B.f
,因为这是正确的类。另一方面,这可能是可以接受的,在这种情况下,我建议使用这种方法而不是克劳迪乌的建议。

您也可以使用
new.instancemethod()
从函数创建实例方法(绑定或未绑定)。

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

self.\uuuuuu class\uuuu.\uuuuu name\uuuuu
将为您提供类的名称,但您也可以使用描述符以更一般的方式完成此操作。描述了此模式,您的记录器装饰器的具体实现如下所示:

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...>>
类记录器(对象):
定义初始化(self,func):
self.func=func
定义获取(self,obj,type=None):
返回self.\uuuuu类(self.func.\uuuuu获取(对象,类型))
定义呼叫(自,*args,**kw):
打印“输入%s”%self.func
返回自函数(*参数,**kw)
C类(对象):
@记录器
def f(自身、x、y):
返回x+y
C().f(1,2)
#=>进入

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

我使用
inspect
库找到了另一个解决类似问题的方法。调用decorator时,即使函数尚未绑定到类,也可以检查堆栈并发现哪个类正在调用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()

虽然这并不一定比其他方法更好,但这是我在调用decorator期间发现未来方法的类名的唯一方法。请注意不要在
检查
库文档中保留对框架的引用。

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

  • inspect.getouterframe
    args[0]。\uuuuuu class\uuuuuuu.\uuuuuu name\uuuuuu
    不适用于普通函数和静态方法
  • \uuuu get\uuuu
    必须位于类中,该类被
    @wrapps
    拒绝
  • @wrapps
    本身应该能够更好地隐藏跟踪
  • 因此,我结合了本页的一些想法、链接、文档和我自己的想法,
    最后找到了一个解决方案,没有上述三个缺点

    因此,
    方法\u decorator

    • 知道修饰方法绑定到的类
    • 通过比
      functools.wrapps()更正确地响应系统属性来隐藏装饰程序跟踪
    • 包括绑定的未绑定实例方法、类方法、静态方法和普通函数的单元测试
    用法:

    pip install method_decorator
    from method_decorator import method_decorator
    
    class my_decorator(method_decorator):
        # ...
    

    下面是
    方法\u 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__()
    

    当函数不知道它的类时,不要在定义时注入装饰代码,而是延迟运行此代码,直到函数被访问/调用。描述符对象有助于在访问/调用时延迟注入自己的代码:

    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
    
    现在我们可以在访问时(
    \uuuu get\uuu
    )和调用时(
    \uuu call\uu
    )检查类。此机制适用于普通方法和静态|类方法:

    >>> Foo().foo(1, b=2)
    called Foo.foo with args=(1,) kwargs={'b': 2}
    
    完整示例位于:

    如中所示,您不需要访问类对象。值得一提的是,由于Python 3.3,您还可以使用,这为您提供了完全限定的名称:

    def记录器(myFunc): ... def新(*参数,**键参数): ... 打印('输入%s'%myFunc.\uu质量名称\uuuu) ... 返回myFunc(*args,**keyargs) ... ... 还新 ... >>>C类(对象): ... @记录器 ... def f(自我): ... 通过 ... >>>C().f() 进入C.f
    这还有另外一个优点,即在嵌套类的情况下也可以工作,如本示例所示,该示例取自:

    >>C类:
    ...   def f():通过
    ...   D类:
    ...     def g():通过
    ...
    >>>C.(质量)