2012-03-30 87 views
23

我寫了一些包含另一個對象作爲屬性的包裝器。該包裝將所有屬性請求(__getattr____setattr__)代理(轉發)到作爲屬性存儲的對象。我還需要爲我的代理提供什麼,以便在通常情況下包裝看起來像包裝類?如何僞造/代理Python中的類

我想我需要修復諸如繼承的東西,也許__repr__,... 我還需要照顧什麼以及如何修復繼承以便instanceof()有效?

編輯:我嘗試做一個函數的代理,但我不完全瞭解的配方,它失敗:(

setattr_=object.__setattr__ 
getattr_=object.__getattribute__ 

class Proxy(object): 
    __slots__=["_func", "_params", "_kwargs", "_obj", "_loaded", "__weakref__"] 
    def __init__(self, func, *params, **kwargs): 
     setattr_(self, "_func", func) 
     setattr_(self, "_params", params) 
     setattr_(self, "_kwargs", kwargs) 

     setattr_(self, "_obj", None) 
     setattr_(self, "_loaded", False) 

    def _get_obj(self): 
     if getattr_(self, "_loaded")==False: 
      print("Loading") 
      setattr_(self, "_obj", getattr_(self, "_func")(*getattr_(self, "_params"), **getattr_(self, "_kwargs"))) 
      setattr_(self, "_loaded", True) 

     return getattr_(self, "_obj") 
    # 
    # proxying (special cases) 
    # 
    def __getattribute__(self, name): 
     return getattr(getattr_(self, "_get_obj")(), name) 
    def __delattr__(self, name): 
     delattr(getattr_(self, "_get_obj")(), name) 
    def __setattr__(self, name, value): 
     setattr(getattr_(self, "_get_obj")(), name, value) 

    def __nonzero__(self): 
     return bool(getattr_(self, "_get_obj")()) 
    def __str__(self): 
     return str(getattr_(self, "_get_obj")()) 
    def __repr__(self): 
     return repr(getattr_(self, "_get_obj")()) 

    # 
    # factories 
    # 
    _special_names=[ 
     '__abs__', '__add__', '__and__', '__call__', '__cmp__', '__coerce__', 
     '__contains__', '__delitem__', '__delslice__', '__div__', '__divmod__', 
     '__eq__', '__float__', '__floordiv__', '__ge__', '__getitem__', 
     '__getslice__', '__gt__', '__hash__', '__hex__', '__iadd__', '__iand__', 
     '__idiv__', '__idivmod__', '__ifloordiv__', '__ilshift__', '__imod__', 
     '__imul__', '__int__', '__invert__', '__ior__', '__ipow__', '__irshift__', 
     '__isub__', '__iter__', '__itruediv__', '__ixor__', '__le__', '__len__', 
     '__long__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', 
     '__neg__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', 
     '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', 
     '__repr__', '__reversed__', '__rfloorfiv__', '__rlshift__', '__rmod__', 
     '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', 
     '__rtruediv__', '__rxor__', '__setitem__', '__setslice__', '__sub__', 
     '__truediv__', '__xor__', 'next', 
    ] 

    @classmethod 
    def _create_class_proxy(cls, theclass): 
     """creates a proxy for the given class""" 

     def make_method(name): 
      def method(self, *args, **kw): 
       return getattr(getattr_(self, "_get_obj")(), name)(*args, **kw) 
      return method 

     namespace={} 
     for name in cls._special_names: 
      if hasattr(theclass, name): 
       namespace[name]=make_method(name) 
     return type("%s(%s)"%(cls.__name__, theclass.__name__), (cls,), namespace) 

    def __new__(cls, obj, *args, **kwargs): 
     """ 
     creates an proxy instance referencing `obj`. (obj, *args, **kwargs) are 
     passed to this class' __init__, so deriving classes can define an 
     __init__ method of their own. 
     note: _class_proxy_cache is unique per deriving class (each deriving 
     class must hold its own cache) 
     """ 
     try: 
      cache=cls.__dict__["_class_proxy_cache"] 
     except KeyError: 
      cls._class_proxy_cache=cache={} 
     try: 
      theclass=cache[obj.__class__] 
     except KeyError: 
      cache[obj.__class__]=theclass=cls._create_class_proxy(obj.__class__) 
     ins=object.__new__(theclass) 
     theclass.__init__(ins, obj, *args, **kwargs) 
     return ins 

if __name__=='__main__': 
    def t(x, y): 
     print("Running t") 
     return x+y 

    a=Proxy(t, "a", "b") 
    print("Go") 
    print(a+"c") 
+1

出於興趣,你爲什麼不繼承? – brice 2012-03-30 12:08:09

+1

因爲這個包裝應該包裝各種對象。它是一個通用代理。 – Gerenuk 2012-03-30 12:09:11

+0

你已經改變了一下食譜。它看起來像你真正想要的是'functools.partial',但可能不是;你真的想要解決什麼問題? – SingleNegationElimination 2012-04-02 18:29:40

回答

20

這個問題是相當不錯的這個偏方解決:

Object Proxying (Python recipe)

,你必須遵循的總體思路是,在大多數類方法是通過0一些組合訪問和__getattribute__要麼在類本身,要麼在其元類上,但是這不適用於python特殊方法,即以雙下劃線開頭和結尾的方法,對於那些被發現的方法,它們必須是實際類中的實際方法,沒有屬性代理是可能的。

您必須提供哪些方法顯然取決於代理類本身提供哪些方法。您需要爲isinstance()提供的特殊方法是__instancecheck__ and __subclasscheck__方法。要使repr()正常工作,您還必須在代理類本身上定義合適的__repr__()

+0

這似乎很有趣,但是我發現它很難理解所有細節:(我試着將它作爲函數調用的代理 - 請參閱編輯。但是,我想我需要在對象初始化後以某種方式執行特殊名稱生成可以幫助我理解我需要更改的內容嗎? – Gerenuk 2012-04-02 17:07:55

3

一般情況下,你可以使用wrapt庫(pypi),這確實繁重的你:

的纏住了模塊的重點非常的正確性。因此,它遠遠超出了現有機制,例如functools.wraps(),以確保裝飾器保留內省,簽名,類型檢查等功能。[012]

爲了確保開銷儘可能小,擴展模塊用於性能關鍵組件

supports creating custom wrapper classes。爲了添加您自己的屬性,您需要聲明它們以使wrapt不會嘗試將它們傳遞給包裝的實例。您可以:

  • 前綴與_self_屬性,並添加屬性訪問
  • 聲明在類級別的屬性,除了在__init__
  • 使用插槽,如果適合你的類(未提及在文檔),像這樣:

    class ExtendedMesh(ObjectProxy): 
        __slots__ = ('foo') 
    
        def __init__(self, subject): 
         super().__init__(subject) 
         self.foo = "bar" 
    

它還supports function wrappers,這migh不適合你的目的。