如何检测装饰器已应用于方法或函数?

can*_*nni 5 python

我的目标是我不会有一个可以同时使用函数和实例方法的装饰器,并且self当装饰器应用于方法时,我想在包装函数中检索对象;当应用于函数时,我想检索函数对象本身。 。

这是我发现几乎可以正常使用的功能,这只是我用来检测已应用哪种装饰器的功能:

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
Run Code Online (Sandbox Code Playgroud)

这确实对我有用,只有一个小例外,当我在多个进程中并行运行测试时,它将引发例外。

aur*_*igl 9

您可以使用描述符协议解决此问题。通过从装饰器返回非数据描述符,您可以实现__get__可以保存方法实例/类的位置。

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

我们要解决的问题是我们不能在方法绑定中或之前挂钩:

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

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

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

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
Run Code Online (Sandbox Code Playgroud)

棘手的部分来了——如果这是一个方法/类方法调用,第一个参数必须分别是实例/类。如果是这样,我们可以从这个 arg 中获得我们执行的方法。如果是这样,我们上面实现的包装器将在内部作为__func__. 如果是这样,original成员将在我们的包装器中。如果它与funfrom 闭包相同,我们就回家了,可以安全地从剩余的参数中切出实例/类。

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
Run Code Online (Sandbox Code Playgroud)

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

@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
Run Code Online (Sandbox Code Playgroud)

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

simplefun()
instance = Class()
instance.method(1, 2)
instance.staticmethod(a1=3)
instance.classmethod()
Class.staticmethod(a1=3)
Class.classmethod()
Run Code Online (Sandbox Code Playgroud)

输出:

$ 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: {}        
Run Code Online (Sandbox Code Playgroud)

  • 我完全错过了这个案例!我猜身份检查 `wrap.original is fun` 错误地假设,如果某物是第一个参数,属性为 original fun `__name__` 并且有 `__func__` 属性,那么它肯定是带有 `original` 属性的本地 `wrap` 函数。它不一定是 - 让我们也检查一下 `original` 是否存在:`getattr(wrap, 'original', None) is fun`。 (2认同)

can*_*nni 5

感谢这个答案:Using the samedecorator(witharguments)withfunctionsandmethods

我找到了这个对我来说完美无缺的解决方案:

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
Run Code Online (Sandbox Code Playgroud)

注意这不是线程安全的,要拥有线程安全的方法,必须返回一个可调用对象,__get__该对象的范围将与实例绑定