我想创建一个Python类装饰器(*(,它能够无缝地包装类可能具有的所有方法类型:实例、类和静态。
这是我现在拥有的代码,其中破坏它的部分评论道:
def wrapItUp(method):
def wrapped(*args, **kwargs):
print "This method call was wrapped!"
return method(*args, **kwargs)
return wrapped
dundersICareAbout = ["__init__", "__str__", "__repr__"]#, "__new__"]
def doICareAboutThisOne(cls, methodName):
return (callable(getattr(cls, methodName))
and (not (methodName.startswith("__") and methodName.endswith("__"))
or methodName in dundersICareAbout))
def classDeco(cls):
myCallables = ((aname, getattr(cls, aname)) for aname in dir(cls) if doICareAboutThisOne(cls, aname))
for name, call in myCallables:
print "*** Decorating: %s.%s(...)" % (cls.__name__, name)
setattr(cls, name, wrapItUp(call))
return cls
@classDeco
class SomeClass(object):
def instanceMethod(self, p):
print "instanceMethod: p =", p
@classmethod
def classMethod(cls, p):
print "classMethod: p =", p
@staticmethod
def staticMethod(p):
print "staticMethod: p =", p
instance = SomeClass()
instance.instanceMethod(1)
#SomeClass.classMethod(2)
#instance.classMethod(2)
#SomeClass.staticMethod(3)
#instance.staticMethod(3)
我有两个问题试图使这项工作:
- 当迭代所有可调用时,我如何确定它是实例、类还是静态类型
- 如何用正确的包装版本来覆盖该方法,该版本对于每种情况都是正确调用的
目前,此代码根据注释的代码段被取消注释而生成不同的TypeError
,如:
TypeError: unbound method wrapped() must be called with SomeClass instance as first argument (got int instance instead)
TypeError: classMethod() takes exactly 2 arguments (3 given)
(*(:如果你直接装饰这些方法,同样的问题会简单得多。
因为方法是函数的包装器,所以要在类构造完成后将decorator应用于类上的方法,必须:
- 使用方法的
im_func
属性从该方法中提取基础函数 - 装饰功能
- 重新涂上包装纸
- 使用包装的装饰函数覆盖属性
一旦应用了@classmethod
装饰器,就很难区分classmethod
和常规方法;这两种方法都属于CCD_ 7类型。但是,您可以检查im_self
属性并查看它是否为None
。如果是,那么它就是一个常规实例方法;否则它是CCD_ 10。
静态方法是简单的函数(@staticmethod
装饰器只是阻止应用通常的方法包装器(。所以你不必为这些做任何特别的事情,看起来就像。
所以基本上你的算法是这样的:
- 获取属性
- 它可以调用吗?如果没有,请继续下一个属性
- 它的类型是
types.MethodType
吗?如果是,则它要么是类方法,要么是实例方法。- 如果它的
im_self
是None
,那么它就是一个实例方法。通过im_func
属性提取底层函数,对其进行修饰,并重新应用实例方法:meth = types.MethodType(func, None, cls)
- 如果它的
im_self
不是None
,则它是一个类方法。通过CCD_ 19排泄并修饰其潜在功能。现在你必须重新应用classmethod
装饰器,但你不能,因为classmethod()
没有类,所以没有办法指定它将附加到什么类。相反,你必须使用实例方法装饰器:meth = types.MethodType(func, cls, type)
。请注意,这里的type
是实际内置的type
- 如果它的
- 如果它的类型不是
types.MethodType
,那么它是一个静态方法或其他非绑定可调用的方法,所以只需修饰它即可 - 将新属性重新设置到类上
这些在Python3中有所改变——未绑定的方法是IIRC中的函数。无论如何,这可能需要彻底反思。
有一个未记录的函数inspect.classify_class_attrs
,它可以告诉您哪些属性是classmethods或staticmethods。在引擎盖下,它使用isinstance(obj, staticmethod)
和isinstance(obj, classmethod)
对静态方法和类方法进行分类。按照这种模式,这在Python2和Python3:中都有效
def wrapItUp(method,kind='method'):
if kind=='static method':
@staticmethod
def wrapped(*args, **kwargs):
return _wrapped(*args,**kwargs)
elif kind=='class method':
@classmethod
def wrapped(cls,*args, **kwargs):
return _wrapped(*args,**kwargs)
else:
def wrapped(self,*args, **kwargs):
return _wrapped(self,*args,**kwargs)
def _wrapped(*args, **kwargs):
print("This method call was wrapped!")
return method(*args, **kwargs)
return wrapped
def classDeco(cls):
for name in (name
for name in dir(cls)
if (callable(getattr(cls,name))
and (not (name.startswith('__') and name.endswith('__'))
or name in '__init__ __str__ __repr__'.split()))
):
method = getattr(cls, name)
obj = cls.__dict__[name] if name in cls.__dict__ else method
if isinstance(obj, staticmethod):
kind = "static method"
elif isinstance(obj, classmethod):
kind = "class method"
else:
kind = "method"
print("*** Decorating: {t} {c}.{n}".format(
t=kind,c=cls.__name__,n=name))
setattr(cls, name, wrapItUp(method,kind))
return cls
@classDeco
class SomeClass(object):
def instanceMethod(self, p):
print("instanceMethod: p = {}".format(p))
@classmethod
def classMethod(cls, p):
print("classMethod: p = {}".format(p))
@staticmethod
def staticMethod(p):
print("staticMethod: p = {}".format(p))
instance = SomeClass()
instance.instanceMethod(1)
SomeClass.classMethod(2)
instance.classMethod(2)
SomeClass.staticMethod(3)
instance.staticMethod(3)