您可以solve this problem using descriptor protocol。通过从装饰器返回非数据描述符,您可以实现__get__
,您可以在其中保存方法的实例/类。
另一个(更简单)的方法是在装饰器制作的包装器中检测实例/类,可能有self
或cls
作为*args
的第一个。这提高了修饰函数的“可检查性”,因为它仍然是一个普通函数,而不是一个自定义的非数据描述符/函数对象。
问题我们要解决的是,我们不能钩住或之前method binding:
注意,从功能对象(未结合的或结合的)的转变 方法对象发生每个属性从检索时间类 或实例。
换句话说:当封装器运行时,它的描述符协议,功能即__get__
方法的包装器,已绑定类/实例的功能和已经在执行产生的方法。我们留下了args/kwargs,并且在当前堆栈框架中没有直接可访问的与类相关的信息。
让我们先从解决类/ 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
这里谈到棘手的部分 - 如果这是一个方法/类方法调用,第一个参数的个数必须是实例/类分别。如果是这样,我们可以从这个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 wrap.original 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: {}
谢谢,看起来像这样可以解决我的问题,但我想使用'inspect'根本不是“优雅”的方法,也许还有其他方法呢? – canni