2011-11-18 57 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) 
相關問題