2011-11-18 55 views
7

我想创建一个Python 类装饰器(*),它将能够无缝地包装类可能具有的所有方法类型:实例,类和静态。如何创建一个能够包装实例,类和静态方法的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

+0

看到不同的开发人员如何处理这个相同的,复杂的问题将会很有趣。 – wberry

+0

@wberry:是的,我已经阅读了当前的答案,并且发现很难选择“正确”的答案。 – Chuim

回答

4

因为方法是函数的包装,对类已建成之后,装饰应用到方法上的一类,你必须:使用其im_func属性

  1. 从中提取法的基本功能。
  2. 装饰功能。
  3. 重新应用包装。
  4. 用包装的装饰功能覆盖该属性。

一旦应用了@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. 将新属性设回到类上。

这些在Python 3中有所变化 - 未绑定的方法是那里的功能,IIRC。无论如何,这可能需要在那里完全重新考虑。

+0

选择这个作为答案,因为它清楚地解释了根本问题。谢谢你,很抱歉,很长的延迟! – Chuim

3

有一个未公开的功能,inspect.classify_class_attrs,它可以告诉你哪些属性是类方法或静态方法。在引擎盖下,它使用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) 
相关问题