目标是我不会有一个可以同时使用函数和实例方法的装饰器,并且我想在装饰器上应用装饰器时在包装函数中检索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
这对我有用,有一个小例外,当我在多个进程中并行运行测试时抛出异常。
答案 0 :(得分:3)
您可以使用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
,它将把它视为非实例方法,即使它是。
答案 1 :(得分:3)
感谢您的回答: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__
返回一个可调范围的对象,该对象的范围与实例
答案 2 :(得分:3)
你可以solve this problem using descriptor protocol。通过从装饰器返回非数据描述符,您可以实现__get__
,您可以在其中保存方法的实例/类。
另一种(更简单的)方法是在装饰器制作的包装器中检测实例/类,这可能self
或cls
作为*args
的第一个。这提高了检查能力。装饰函数,因为它仍然是一个普通函数而不是自定义非数据解析器/函数对象。
我们必须解决的问题是我们无法挂钩method binding:
请注意从函数对象到(未绑定或绑定)的转换 每次从类中检索属性时都会发生方法对象 或者实例。
换句话说:当我们的包装器运行时,它的描述符协议,即函数的__get__
方法包装器,已经与类/实例绑定了函数,并且结果方法已经被执行。我们留下args / kwargs,并且在当前堆栈框架中没有直接可访问的类相关信息。
让我们从解决class / 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
这里有一个棘手的部分 - 如果这是一个方法/ classmethod调用,args中的第一个必须分别是实例/类。如果是这样,我们可以从这个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: {}
答案 3 :(得分:2)
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
答案 4 :(得分:0)
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__
变量是否与修饰的函数相同。