How to detect is decorator has been applied to method or function?
目标是我不希望有一个同时使用函数和实例方法的修饰器,并且我希望在方法上应用了修饰器时,在包装函数中检索
以下是我发现的几乎有效的功能,这只是我用来检测已应用的装饰器的功能:
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返回非数据描述符,可以实现
另一种更简单的方法是延迟检测实例/类,在装饰器制作的包装中,它可能将
我们必须解决的问题是,我们不能在方法绑定之前或之前钩住:
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.
换句话说:当我们的包装器运行时,它的描述符协议,即函数的
让我们从解决类/静态方法特殊情况开始,并将包装器实现为简单的打印机:
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 |
这是一个棘手的部分——如果这是一个方法/类方法调用,那么第一个参数必须分别是实例/类。如果是这样,我们就可以从这个参数中得到我们执行的方法。如果是这样,我们上面实现的包装器将作为
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 |
注意:这不是线程安全的,要使用线程安全方法,必须从
您可以使用
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 |
注意,此代码取决于第一个参数的名称。如果名称不是
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 |