2011-07-15 100 views

回答

27

Python與屬性和方法沒有區別。方法只是一個屬性,其類型只是instancemethod,恰好可以調用(支持__call__)。

如果要實現這一點,您__getattr__方法應該返回一個函數(lambda或正def,無論您需求),也許檢查調用之後的東西。

+1

謝謝。我在Google上找到了[this](http://venodesigns.net/2010/11/02/emulating-rubys-method_missing-in-python/)。 – missingfaktor

+2

作爲參考,有問題的鏈接通過定義應該被視爲方法的屬性名稱列表來避開歧義(這聽起來像是有點違背了目的,因爲您可以將這些方法中的每一個都定義並委託給存根)。 –

4

Python沒有像Ruby那樣區分方法和屬性(又名「實例變量」)。在Python中查找方法和其他對象屬性的方式完全相同 - 甚至Python在查找階段都不知道其差異。直到找到屬性,它只是一個字符串。

所以,如果你問的方式,以確保__getattr__呼籲方法,我怕你可能不會找到一個完美的解決方案。但是簡單地從__getattr__返回一個函數(甚至是全新的dynamically bound method)是很容易的。

+1

出於同樣的原因您可以在Ruby中使用'method_missing'或在Smalltalk中使用'doesNotUnderstand'。 – missingfaktor

+0

我明白你爲什麼要使用'__getattr__'。我只是不明白你爲什麼「只想攔截方法調用」。 – senderle

+0

我的設計並不需要攔截字段訪問,但它似乎不是一個很大的絆腳石。我發現@Emil提供的解決方案足夠滿足我的要求。 – missingfaktor

1

雖然我不推薦它!!!!!!!!!!!!!!!!!!!!!

這種接近於實現調用特殊方法的行爲,爲每個不符合可調用屬性/方法的名稱調用特殊方法。當然,它們還沒有真正的單獨命名空間,所以它可能會感覺有點奇怪。它的工作原理是覆蓋__getattribute__,它在較低級別上工作,然後__getattr__它嘗試在屬性失敗時取回屬性,它返回一個curried特殊方法,用您調用的名稱進行調用,如果它成功則將其傳遞,否則它將被調用 用代理對象包裝結果,該代理對象之後的行爲幾乎完全相同,除了它使用特殊方法實現調用。

它不允許你訪問調用對象,因爲如果它已經是一個你可以存儲的非可調用屬性,我想不出一種不會泄漏內存(調用對象)的好方法。我能想到的唯一想法是啓動一個新的線程,在一分鐘後刪除它,那麼你可能已經調用了它,除非你在封閉中使用它,在這種情況下不會被支持)。

編輯:我忘了callable可能有一些誤報。

依賴於http://pypi.python.org/pypi/ProxyTypes

from peak.util.proxies import ObjectWrapper 
from functools import partial 

def m(name, *args, **kwargs): 
    print(name,repr(args),repr(kwargs)) 


class CallProxy(ObjectWrapper): 
    def __init__(self, obj, m, method_name): 
     ObjectWrapper.__init__(self, obj) 
     object.__setattr__(self, "_method_name", method_name) 
     object.__setattr__(self, "_m", m) 

    def __call__(self, *args, **kwargs): 
     return self._m(self._method_name, *args,**kwargs) 


class Y(object): 
    def __init__(self): 
     self.x = [3] 
    def __getattribute__(self, name): 
     try: 
      val = object.__getattribute__(self, name) 
      if not callable(val): 
       return CallProxy(val, m, name) 
      else: 
       return val 
     except AttributeError: 
      return partial(m, name) 

In [2]: y=Y() 

In [3]: y.x 
Out[3]: [3] 

In [4]: y.z 
Out[4]: <functools.partial at 0x2667890> 

In [5]: y.zz([12]) 
('zz', '([12],)', '{}') 

In [6]: y.x.append(5) 

In [7]: y.x 
Out[7]: [3, 5] 

In [8]: y.x(1,2,3,key="no") 
('x', '(2, 3)', "{'key': 'no'}") 
3

您可以實現在下面的方式類似特徵的missing_method:

https://gist.github.com/gterzian/6400170

import unittest 
from functools import partial 

class MethodMissing: 
    def method_missing(self, name, *args, **kwargs): 
     '''please implement''' 
     raise NotImplementedError('please implement a "method_missing" method') 

    def __getattr__(self, name): 
     return partial(self.method_missing, name) 


class Wrapper(object, MethodMissing): 
    def __init__(self, item): 
     self.item = item 

    def method_missing(self, name, *args, **kwargs): 
     if name in dir(self.item): 
      method = getattr(self.item, name) 
      if callable(method): 
       return method(*args, **kwargs) 
      else: 
       raise AttributeError(' %s has not method named "%s" ' % (self.item, name)) 


class Item(object): 
    def __init__(self, name): 
     self.name = name 

    def test(self, string): 
     return string + ' was passed on' 


class EmptyWrapper(object, MethodMissing): 
    '''not implementing a missing_method''' 
    pass 


class TestWrapper(unittest.TestCase): 
    def setUp(self): 
     self.item = Item('test') 
     self.wrapper = Wrapper(self.item) 
     self.empty_wrapper = EmptyWrapper() 

    def test_proxy_method_call(self): 
     string = self.wrapper.test('message') 
     self.assertEqual(string, 'message was passed on') 

    def test_normal_attribute_not_proxied(self,): 
     with self.assertRaises(AttributeError): 
      self.wrapper.name 
      self.wrapper.name() 

    def test_empty_wrapper_raises_error(self,): 
     with self.assertRaises(NotImplementedError): 
      self.empty_wrapper.test('message') 


if __name__ == '__main__': 
    unittest.main()