如何创建一个能够包装实例,类和静态方法的Python类装饰器?

时间:2011-11-18 16:10:07

标签: python reflection decorator instrumentation

我想创建一个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)

(*):如果你是decorating the methods directly,同样的问题会更简单。

2 个答案:

答案 0 :(得分:4)

因为方法是函数的包装器,所以在构造类之后将装饰器应用于类的方法时,您必须:

  1. 使用其im_func属性从方法中提取基础函数。
  2. 装饰功能。
  3. 重新应用包装。
  4. 使用包装,修饰的函数覆盖该属性。
  5. 一旦应用了classmethod装饰器,很难将@classmethod与常规方法区分开来;两种方法都是instancemethod类型。但是,您可以检查im_self属性,看看它是None。如果是这样,它是一个常规实例方法;否则它是classmethod

    静态方法是简单的函数(@staticmethod装饰器只是阻止应用通常的方法包装器)。所以你不必为这些做任何特别的事,看起来像。

    所以基本上你的算法看起来像这样:

    1. 获取属性。
    2. 可以赎罪吗?如果没有,请继续执行下一个属性。
    3. 类型为types.MethodType吗?如果是,则它是类方法或实例方法。
      • 如果im_selfNone,则为实例方法。通过im_func属性提取底层函数,修饰它,然后重新应用实例方法:meth = types.MethodType(func, None, cls)
      • 如果im_self不是None,则它是一种类方法。通过im_func提取基础函数并进行修饰。现在你必须重新应用classmethod装饰器,但你不能,因为classmethod()不接受一个类,所以没有办法指定它将附加到哪个类。相反,您必须使用实例方法装饰器:meth = types.MethodType(func, cls, type)。请注意,这里的type是实际内置的type
    4. 如果它的类型不是types.MethodType,那么它是静态方法或其他非绑定可调用的,所以只需装饰它。
    5. 将新属性设置回类。
    6. 这些在Python 3中有所改变 - 未绑定的方法是那里的功能,IIRC。无论如何,这可能需要在那里彻底重新考虑。

答案 1 :(得分:3)

有一个未记录的函数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)