Python 如何检测装饰器是否已应用于方法或功能?

Python 如何检测装饰器是否已应用于方法或功能?,python,Python,目标是我不希望有一个decorator同时处理函数和实例方法,我希望在包装函数中检索decorator应用于方法时的self对象,或者在应用于函数时检索函数对象本身 以下是我发现几乎有效的功能,这只是我用来检测应用了什么decorator的函数: def _is_method(func): for stack_frame in inspect.stack(): # if the code_context of the stack frame starts with 'cl

目标是我不希望有一个decorator同时处理函数和实例方法,我希望在包装函数中检索decorator应用于方法时的
self
对象,或者在应用于函数时检索函数对象本身

以下是我发现几乎有效的功能,这只是我用来检测应用了什么decorator的函数:

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
这对我来说确实有效,但有一个小异常,当我在多个进程中并行运行测试时,它会引发异常。

您可以使用:

用法示例:

>>> 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
,即使它是非实例方法,也会将其视为非实例方法。

多亏了这个答案:

我找到了一个完美的解决方案:

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
注意这不是线程安全的,要使用线程安全的方法,必须从
\uuuu get\uuuu
返回一个可调用对象,该对象的作用域将绑定到实例

。通过从decorator返回非数据描述符,您可以实现
\uuuu get\uuuu
,在这里您可以保存方法的实例/类

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

我们必须解决的问题是,我们无法在以下情况发生之前:

请注意,从函数对象到(未绑定或绑定)的转换 每次从类检索属性时都会发生方法对象 或举例

换句话说:当我们的包装器运行时,它的描述符协议,即函数的
\uuuuu get\uuuu
方法包装器,已经将函数与类/实例绑定,并且结果方法已经在执行。我们只剩下args/kwargs,在当前堆栈框架中没有直接可访问的类相关信息

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

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
这里是棘手的部分-如果这是一个方法/类方法调用,那么第一个参数必须分别是实例/类。如果是这样,我们就可以从这个参数中得到我们执行的方法。如果是这样的话,我们上面实现的包装器将作为
\uuu func\uu
放在里面。如果是这样,
原始
成员将在我们的包装中。如果它与闭包中的
fun
相同,那么我们就到家了,可以从剩余的参数中安全地分割实例/类

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
让我们看看这是否适用于不同的函数/方法变体:

@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
让我们看看它是否真的运行:

simplefun()
instance = Class()
instance.method(1, 2)
instance.staticmethod(a1=3)
instance.classmethod()
Class.staticmethod(a1=3)
Class.classmethod()
输出:

$ 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: {}        
python3的解决方案:

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
导入工具
进口检验
def mydec():
def装饰器(func):
@functools.wrapps(func)
def酸洗功能(*args,**kwargs):
方法是否为假
如果len(args)>0:
方法=getattr(参数[0],函数名,False)
如果方法:
wrapped=getattr(方法“\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
如果已包装且已包装==func:
打印(“用于方法的mydec”)
_method=True吗
如果is_方法为False:
打印(“使用mydec而不是方法”)
结果=函数(*args,**kwargs)
返回结果
返回装饰器

检查
\uuuuu wrapped\uuuuuu
变量是否与装饰的函数相同。

谢谢,看起来这可以解决我的问题,但我认为使用
inspect
根本不是“优雅”的方法,也许还有其他方法可以做到这一点?只是想注意,arg[0]并不总是“自我”,尤其是当您使用嵌套类并且必须避免名称阴影导致其他问题时。我也倾向于使用“inst”或“this”,这是一个有趣的解决方案,但有一个问题。如果静态函数的第一个参数是具有相同名称的函数的类实例,该怎么办<代码>C类:def staticmethod(self):pass
class.staticmethod(C())#AttributeError:“function”对象没有“original”属性
我完全错过了这个例子!我猜身份检查
wrap.original是有趣的
错误地认为,如果某个东西是第一个参数,它的属性是original fun
\uuuu name\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。不必如此-让我们检查是否存在
original
getattr(wrap,'original',None)很有趣
。如果应用同一个装饰程序两次,还有一个答案会失败。外部装饰师会很好用的。但是内部装饰程序不会识别它是否装饰了函数或类的方法。谢谢你的回答!绑定到实例的可调用对象是什么样子的?虽然这个答案很完美,但遗憾的是,它不适用于
classmethod
staticmethod
arg[0]如果两次使用同一个装饰器,可能并不总是“self”失败。
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