【问题标题】:How to detect is decorator has been applied to method or function?如何检测装饰器是否已应用于方法或函数?
【发布时间】:2013-10-19 07:31:13
【问题描述】:

我的目标是我不想拥有一个可以同时使用函数和实例方法的装饰器,并且我想在方法或函数上应用装饰器时在包装函数中检索 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

这对我有用,除了一个小异常,当我在多个进程中并行运行测试时它会引发异常。

【问题讨论】:

    标签: python


    【解决方案1】:

    您可以solve this problem using descriptor protocol。通过从装饰器返回非数据描述符,您可以实现__get__,您可以在其中保存方法的实例/类。

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

    我们必须解决的问题是我们不能挂钩到method binding之前或之前:

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

    换句话说:当我们的包装器运行时,它的描述符协议,即__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
    

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

    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: {}        
    

    【讨论】:

    • 这是一个有趣的解决方案,但是有一个问题。如果静态函数的第一个参数是具有同名函数的类实例怎么办? class C: def staticmethod(self): passClass.staticmethod(C()) # AttributeError: 'function' object has no attribute 'original'
    • 我完全错过了这个案子!我猜身份检查wrap.original is fun 错误地假设如果某物是第一个arg,具有原始乐趣__name__ 的属性并且具有__func__ attr,那么它绝对是具有wrap 属性的本地wrap 函数。不一定是 - 让我们检查 original 是否存在:getattr(wrap, 'original', None) is fun
    • 如果两次应用相同的装饰器,另一个答案将失败。外部装饰器可以正常工作。但是内部装饰器将无法识别它是否装饰了函数或类的方法。
    【解决方案2】:

    你可以使用inspect.getargspec:

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

    示例用法:

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

    【讨论】:

    • 谢谢,看起来这可以解决我的问题,但我认为使用 inspect 根本不是“优雅”的方法,也许还有其他方法可以做到这一点?
    • 只是想注意,arg[0] 并不总是“自我”,尤其是当您使用嵌套类并且必须避免名称隐藏导致其他问题时。我也倾向于使用“inst”或“this”
    【解决方案3】:

    感谢这个 SO 回答:Using the same decorator (with arguments) with functions and methods

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

    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__ 返回一个可调用对象,该对象的作用域与实例相关

    【讨论】:

    • 感谢您的回答!绑定到实例的可调用对象会是什么样子?
    • 认为这个答案很完美,遗憾的是不适用于 classmethodstaticmethod
    【解决方案4】:

    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
    

    【讨论】:

    • arg[0] 可能并不总是“self”
    【解决方案5】:
    import functools
    import inspect
    
    
    def mydec():
        def decorator(func):
            @functools.wraps(func)
            def pickled_func(*args, **kwargs):
                is_method = False
                if len(args) > 0:
                    method = getattr(args[0], func.__name__, False)
                    if method:
                        wrapped = getattr(method, "__wrapped__", False)
                        if wrapped and wrapped == func:
                            print("Used mydec to a method")
                            is_method = True
    
                if is_method is False:
                    print("Used mydec not to a method.")
                result = func(*args, **kwargs)
                return result
        return decorator
    
    

    检查__wrapped__变量是否与decorated函数相同。

    【讨论】:

    • 如果两次使用同一个装饰器会失败。
    猜你喜欢
    • 1970-01-01
    • 2018-01-12
    • 2017-08-27
    • 2011-07-26
    • 2012-04-05
    • 2020-11-30
    • 2019-02-11
    • 2016-01-03
    • 1970-01-01
    相关资源
    最近更新 更多