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

标签 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

这确实对我有用,但有一个小异常(exception),当我在多个进程中并行运行测试时它会抛出异常。

最佳答案

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

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

我们要解决的问题是我们不能 Hook 到method binding之前或之前。 :

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.

换句话说:当我们的包装器运行时,它的描述符协议(protocol),即__get__函数的方法包装器,已经将函数与类/实例绑定(bind)在一起,并且结果方法已经被执行。我们只剩下 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 相同,我们就可以从剩余的参数中安全地切分实例/类。

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

关于python - 如何检测装饰器是否已应用于方法或函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19314405/

相关文章:

python - Django 同步数据库错误 : One or more models did not validate

python - 在 Python 中选择两个时间

python - 为什么 re.escape 逃脱空间

python - 使用字符值在 Pandas 中创建新行

python - 从列表中生成字典按键列表

Python3套接字客户端发送和接收十六进制字符串

python - 返回模型 Django Rest Framework 的键/属性 JSON 对象而不是 JSON 数组

python - matplotlib 中的条件函数绘图

python - 在 Python 中读取具有不同列数的大文件的最快方法

python - 如何为 Python matplotlib.animation 添加随时间变化的标题?