2012-01-29 64 views
25

我試圖攔截Python的雙下劃線的魔術方法在新的風格類的呼叫。這是一個簡單的例子,但它表明的意圖:我如何能攔截到新式類Python的「神奇」的方法的調用?

class ShowMeList(object): 
    def __init__(self, it): 
     self._data = list(it) 

    def __getattr__(self, name): 
     attr = object.__getattribute__(self._data, name) 
     if callable(attr): 
      def wrapper(*a, **kw): 
       print "before the call" 
       result = attr(*a, **kw) 
       print "after the call" 
       return result 
      return wrapper 
     return attr 

如果我周圍使用清單代理對象,我得到了非魔術方法預期的行爲,但我的包裝功能絕不會爲魔術方法。

>>> l = ShowMeList(range(8)) 

>>> l #call to __repr__ 
<__main__.ShowMeList object at 0x9640eac> 

>>> l.append(9) 
before the call 
after the call 

>> len(l._data) 
9 

如果我不從對象(第一行class ShowMeList:)繼承一切正常:

>>> l = ShowMeList(range(8)) 

>>> l #call to __repr__ 
before the call 
after the call 
[0, 1, 2, 3, 4, 5, 6, 7] 

>>> l.append(9) 
before the call 
after the call 

>> len(l._data) 
9 

如何做到這一點攔截與新式類?

+1

你到底想通過截獲雙下劃線的方法呢?還是僅僅因爲好奇? – thesamet 2012-01-29 23:31:28

+0

我總是在這裏列出所有魔術方法:https://github.com/niccokunzmann/wwp/blob/6df6d7f60893a23dc84a32ba244b31120b1241a9/magicMethods。py(生成的,所以它適用於python 2和3) – User 2013-02-18 22:13:28

+0

實際上,你想要做的就是攔截對新樣式類的_instances_的魔術方法的調用 - 但這仍然是一個很好的問題。 – martineau 2013-11-13 02:24:39

回答

24

由於性能方面的原因,Python始終在類(和父類'__dict__)中查找魔術方法,並且不使用常規屬性查找機制。解決方法是在類創建時使用元類自動添加魔術方法的代理;例如,我已經使用這種技術來避免爲封裝類編寫樣板調用方法。

class Wrapper(object): 
    """Wrapper class that provides proxy access to an instance of some 
     internal instance.""" 

    __wraps__ = None 
    __ignore__ = "class mro new init setattr getattr getattribute" 

    def __init__(self, obj): 
     if self.__wraps__ is None: 
      raise TypeError("base class Wrapper may not be instantiated") 
     elif isinstance(obj, self.__wraps__): 
      self._obj = obj 
     else: 
      raise ValueError("wrapped object must be of %s" % self.__wraps__) 

    # provide proxy access to regular attributes of wrapped object 
    def __getattr__(self, name): 
     return getattr(self._obj, name) 

    # create proxies for wrapped object's double-underscore attributes 
    class __metaclass__(type): 
     def __init__(cls, name, bases, dct): 

      def make_proxy(name): 
       def proxy(self, *args): 
        return getattr(self._obj, name) 
       return proxy 

      type.__init__(cls, name, bases, dct) 
      if cls.__wraps__: 
       ignore = set("__%s__" % n for n in cls.__ignore__.split()) 
       for name in dir(cls.__wraps__): 
        if name.startswith("__"): 
         if name not in ignore and name not in dct: 
          setattr(cls, name, property(make_proxy(name))) 

用法:

class DictWrapper(Wrapper): 
    __wraps__ = dict 

wrapped_dict = DictWrapper(dict(a=1, b=2, c=3)) 

# make sure it worked.... 
assert "b" in wrapped_dict      # __contains__ 
assert wrapped_dict == dict(a=1, b=2, c=3)  # __eq__ 
assert "'a': 1" in str(wrapped_dict)    # __str__ 
assert wrapped_dict.__doc__.startswith("dict()") # __doc__ 
+0

@kindall我刪除了看起來像一個孤立的代碼行,但是你可能要仔細檢查你的示例是否沒有失去預期的功能。 – Air 2013-10-30 23:26:43

+0

在通過'dir()'循環之前執行'ignore.update(dct)'是否有任何問題,並將最後兩個'if'語句合併爲一個?對我來說感覺更清潔一點,但也許會出現意想不到的後果,我以我的經驗沒有預料到。 – Air 2013-10-31 00:00:19

6

使用__getattr____getattribute__是一類的最後的資源來獲得一個屬性做出迴應。

考慮以下幾點:

>>> class C: 
    x = 1 
    def __init__(self): 
     self.y = 2 
    def __getattr__(self, attr): 
     print(attr) 

>>> c = C() 
>>> c.x 
1 
>>> c.y 
2 
>>> c.z 
z 

__getattr__方法時,沒有別的辦法只能稱爲(它不會在運營商工作,你可以閱讀有關here)。

在您例如,__repr__和許多其他魔術方法在object類中已定義。

有一兩件事可以做,想,這是定義那些神奇的方法,使然後調用__getattr__方法。選中此的其他問題由我和它的答案(link)看到一些代碼這樣做。

-1

剪切和從文檔複製:

對於老式類,特殊的方法總是以完全相同的方式與任何其他方法或屬性擡頭。

對於新樣式類,只有在對象類型定義的情況下,而不是在對象的實例字典中定義的特殊方法的隱式調用才能保證正常工作。

+5

這個答案100%正確,而沒有解釋和完全無益。抱歉。 – 2012-01-30 07:01:11

+2

一個老貝爾實驗室的笑話:一個在濃霧中迷失的飛行員看到一幢辦公大樓,喊道:「我在哪裏?」,在「飛機上」得到答案,並觸及......艾倫鎮。他是怎麼知道的? 「答案是100%正確的......」 – denis 2012-04-20 16:57:43