您可以使用描述符协议解决此问题。通过从装饰器返回非数据描述符,您可以实现__get__
可以保存方法的实例/类的位置。
另一种(更简单)的方法是在装饰器制造的包装器中检测实例/类,该包装器可能具有self
或cls
作为第一个*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
棘手的部分来了——如果这是一个方法/类方法调用,那么第一个参数必须分别是实例/类。如果是这样,我们可以从这个 arg 中获取我们执行的方法。如果是这样,我们上面实现的包装器将在内部作为__func__
. 如果是这样,original
成员将在我们的包装器中。如果它与fun
from 闭包相同,我们就可以安全地从剩余的 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: {}