2011-10-11 78 views
4

我的問題是,我正在使用一個元類來包裝某些類的方法在一個計時器記錄的目的。Python __metaclass__繼承問題

例如:

class MyMeta(type): 

    @staticmethod 
    def time_method(method): 
     def __wrapper(self, *args, **kwargs): 
      start = time.time() 
      result = method(self, *args, **kwargs) 
      finish = time.time() 
      sys.stdout.write('instancemethod %s took %0.3f s.\n' %(
       method.__name__, (finish - start))) 
      return result 
     return __wrapper 

    def __new__(cls, name, bases, attrs): 
     for attr in ['__init__', 'run']: 
      if not attr in attrs: 
       continue 

      attrs[attr] = cls.time_method(attrs[attr]) 

     return super(MetaBuilderModule, cls).__new__(cls, name, bases, attrs) 

我遇到的問題是,我的包裝,每運行「__init__」即使我真的只是想對當前模塊我實例化。任何想要時間的方法都是一樣的。我不想讓時間在任何繼承的方法上運行,除非它們沒有被覆蓋。

class MyClass0(object): 
    __metaclass__ = MyMeta 
    def __init__(self): 
     pass 

    def run(self): 
     sys.stdout.write('running') 
     return True 


class MyClass1(MyClass0): 
    def __init__(self): # I want this timed 
     MyClass0.__init__(self) # But not this. 
     pass 

    ''' I need the inherited 'run' to be timed. ''' 

我試過一些東西,但到目前爲止我沒有成功。

回答

4

用屬性保護計時代碼。這樣,只有對象上最外面的裝飾方法纔會實際得到時間。

@staticmethod 
def time_method(method): 
    def __wrapper(self, *args, **kwargs): 
     if hasattr(self, '_being_timed'): 
      # We're being timed already; just run the method 
      return method(self, *args, **kwargs) 
     else: 
      # Not timed yet; run the timing code 
      self._being_timed = True # remember we're being timed 
      try: 
       start = time.time() 
       result = method(self, *args, **kwargs) 
       finish = time.time() 
       sys.stdout.write('instancemethod %s took %0.3f s.\n' %(
        method.__name__, (finish - start))) 
       return result 
      finally: 
       # Done timing, reset to original state 
       del self._being_timed 
    return __wrapper 

計時只有最外層的方法比「除非他們沒有被覆蓋不定時繼承的方法」略有不同,但我相信它可以解決你的問題。

+0

謝謝佩特,這工作完美。我非常接近我能夠品嚐到的答案。有時你需要另一雙眼睛來理解我所猜測的瘋狂。再次感謝! CB – theceebee

1

我不確定這與多繼承有什麼關係。

麻煩的是,的MyClass0任何子類必須是相同的元類,這意味着MyClass1獲取與MyMeta.__new__創建的一個實例,因此它的方法得到處理和包裹在所述定時碼。

實際上,你需要的是一個MyClass0.__init__莫名其妙地返回的東西在下面兩種情況下的不同:

  1. 當直接調用(實例MyClass0直接,或當MyClass1不覆蓋它),它需要返回定時方法
  2. 當一個子類定義中調用,它需要返回原來的不計時方法

這是不可能的,因爲MyClass0.__init__不知道爲什麼它被稱爲。

我看到三個選項:

  1. 讓元類更復雜。它可以通過基類來檢查它們是否已經是元類的實例;如果是這樣,它可以創建它們的新副本,從正在構建的類中存在的方法中移除定時包裝器。您不希望直接對基類進行變異,因爲這會影響它們的使用(包括直接實例化或其他類覆蓋不同方法的子類時)。它的缺點是它真的擰緊instanceof關係;除非你通過創建它們的新子類(ugh!)並緩存所有的變體來構造基類的細微變化,所以你永遠不會構造重複項(ugh!),你完全無法自然假設兩個類共享一個基類(它們可能只共享兩個完全獨立的基類生成的模板)。
  2. 使時間碼更復雜。有一個start_timingstop_timing方法,並且如果start_timing在方法已被定時時調用,則只需增加一個計數器,而stop_timing只是遞減一個計數器,並且只在計數器達到零時停止計時。注意調用其他定時方法的定時方法;您需要爲每個方法名稱分配不同的計數器。
  3. 放棄元類,只是在你想要顯式定義的方法上使用裝飾器,以某種方式獲取undecorated方法,以便覆蓋的定義可以調用它。這將涉及幾行鍋爐板每次使用;那很可能會比其他兩個選項中的代碼行少加起來。
+0

感謝您的幫助,我將與第2號一起去。我希望儘可能讓代碼儘可能方便人們閱讀,但仍然強制時間/日誌記錄發生,所以我覺得元類路由是最好的方式。使用裝飾器依賴於繼承/抽象類來實際裝飾他們的方法的人,因爲我必須假定只有基本的Python技能,所以儘可能自動化是最好的方式。再次感謝! CB – theceebee