关于python:如何检测decorator是否已应用于方法或函数?

How to detect is decorator has been applied to method or function?

目标是我不希望有一个同时使用函数和实例方法的修饰器,并且我希望在方法上应用了修饰器时,在包装函数中检索self对象,或者在应用于函数时检索函数对象本身。

以下是我发现的几乎有效的功能,这只是我用来检测已应用的装饰器的功能:

1
2
3
4
5
6
7
def _is_method(func):
    for stack_frame in inspect.stack():
        # if the code_context of the stack frame starts with 'class' this
        # function is defined within a class and so a method.
        if inspect.getframeinfo(stack_frame[0]).code_context[0].strip().startswith('class'):
            return True
    return False

这对我确实有效,有一个小的异常,当我在多个进程中并行运行测试时,它抛出异常。


您可以使用描述符协议来解决这个问题。通过从decorator返回非数据描述符,可以实现__get__,在这里可以保存方法的实例/类。

另一种更简单的方法是延迟检测实例/类,在装饰器制作的包装中,它可能将selfcls作为*args的第一个。这提高了修饰函数的"可检查性",因为它仍然是一个普通函数,而不是一个自定义的非数据描述器/函数对象。

我们必须解决的问题是,我们不能在方法绑定之前或之前钩住:

Note that the transformation from function object to (unbound or bound)
method object happens each time the attribute is retrieved from the class
or instance.

换句话说:当我们的包装器运行时,它的描述符协议,即函数的__get__方法包装器,已经用类/实例绑定了函数,并且产生的方法已经被执行。我们只剩下args/kwargs,在当前堆栈帧中没有直接可访问的类相关信息。

让我们从解决类/静态方法特殊情况开始,并将包装器实现为简单的打印机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def decorated(fun):
    desc = next((desc for desc in (staticmethod, classmethod)
                 if isinstance(fun, desc)), None)
    if desc:
        fun = fun.__func__

    @wraps(fun)
    def wrap(*args, **kwargs):
        cls, nonselfargs = _declassify(fun, args)
        clsname = cls.__name__ if cls else None
        print('class: %-10s func: %-15s args: %-10s kwargs: %-10s' %
              (clsname, fun.__name__, nonselfargs, kwargs))

    wrap.original = fun

    if desc:
        wrap = desc(wrap)
    return wrap

这是一个棘手的部分——如果这是一个方法/类方法调用,那么第一个参数必须分别是实例/类。如果是这样,我们就可以从这个参数中得到我们执行的方法。如果是这样,我们上面实现的包装器将作为__func__在内部。如果是这样,original会员将在我们的包装中。如果它与关闭时的fun相同,那么我们就在家中,可以安全地从剩余的参数中切片实例/类。

1
2
3
4
5
6
7
8
9
10
def _declassify(fun, args):
    if len(args):
        met = getattr(args[0], fun.__name__, None)
        if met:
            wrap = getattr(met, '__func__', None)
            if getattr(wrap, 'original', None) is fun:
                maybe_cls = args[0]
                cls = maybe_cls if isclass(maybe_cls) else maybe_cls.__class__
                return cls, args[1:]
    return None, args

让我们看看这是否适用于不同类型的函数/方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@decorated
def simplefun():
    pass

class Class(object):
    @decorated
    def __init__(self):
        pass

    @decorated
    def method(self, a, b):
        pass

    @decorated
    @staticmethod
    def staticmethod(a1, a2=None):
        pass

    @decorated
    @classmethod
    def classmethod(cls):
        pass

让我们看看这是否真的运行:

1
2
3
4
5
6
7
simplefun()
instance = Class()
instance.method(1, 2)
instance.staticmethod(a1=3)
instance.classmethod()
Class.staticmethod(a1=3)
Class.classmethod()

输出:

1
2
3
4
5
6
7
8
$ python Example5.py
class: None       func: simplefun       args: ()         kwargs: {}        
class: Class      func: __init__        args: ()         kwargs: {}        
class: Class      func: method          args: (1, 2)     kwargs: {}        
class: None       func: staticmethod    args: ()         kwargs: {'a1': 3}
class: Class      func: classmethod     args: ()         kwargs: {}        
class: None       func: staticmethod    args: ()         kwargs: {'a1': 3}
class: Class      func: classmethod     args: ()         kwargs: {}


正因为如此,答案是:对函数和方法使用相同的修饰器(带参数)

我来到这个解决方案,巫婆完美地为我工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def proofOfConcept():
    def wrapper(func):

        class MethodDecoratorAdapter(object):
            def __init__(self, func):
                self.func = func
                self.is_method = False

            def __get__(self, instance, owner):
                if not self.is_method:
                    self.is_method = True
                self.instance = instance

                return self

            def __call__(self, *args, **kwargs):
                # Decorator real logic goes here
                if self.is_method:
                    return self.func(self.instance, *args, **kwargs)
                else:
                    return self.func(*args, **kwargs)

        return wraps(func)(MethodDecoratorAdapter(func))

    return wrapper

注意:这不是线程安全的,要使用线程安全方法,必须从__get__返回一个可调用对象,该对象的作用域将绑定到实例。


您可以使用inspect.getargspec

1
2
3
4
5
import inspect

def _is_method(func):
    spec = inspect.getargspec(func)
    return spec.args and spec.args[0] == 'self'

示例用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> def dummy_deco(f):
...     print('{} is method? {}'.format(f.__name__, _is_method(f)))
...     return f
...
>>> @dummy_deco
... def add(a, b):
...     return a + b
...
add is method? False
>>> class A:
...     @dummy_deco
...     def meth(self, a, b):
...         return a + b
...
meth is method? True

注意,此代码取决于第一个参数的名称。如果名称不是self,它会将其视为非实例方法,即使它是。


python3溶液:

1
2
3
4
5
6
7
8
import inspect

def _is_method(func):
    spec = inspect.signature(func)
    if len(spec.parameters) > 0:
        if list(spec.parameters.keys())[0] == 'self':
            return True
    return False