2011-07-23 79 views
6

我最近在Python中的某個ORM文檔對象周圍開發了一個名爲DocumentWrapper的類,以透明地向它添加一些功能,而無需以任何方式更改其界面。如何使用Python僞造類型

我只是有這個問題。假設我有一些User對象包裹在其中。調用isinstance(some_var, User)將返回False,因爲some_var確實是DocumentWrapper的一個實例。

有沒有什麼辦法來僞造Python中的對象類型,使其具有相同的調用返回值True

+0

多重繼承? – JBernardo

+1

'isinstance(some_var.user,User)'?你究竟在做什麼? –

+0

只是試圖有一個透明的包裝,其行爲*就像包裝的類。包括與isinstance。多重繼承不是解決方案,至少因爲用戶只是DocumentWrapper包裝的許多類中的一個。 (我無法控制這些類,我無法更改它們的繼承樹。) – Pierre

回答

7

測試對象的類型通常是python中的反模式。在某些情況下是有意義的測試「鴨式」的對象,是這樣的:

hasattr(some_var, "username") 

但即使這是不可取的,比如有爲什麼表達可能返回false,即使一個包裝的原因與__getattribute__使用一些魔法來正確代理屬性。

通常傾向於允許變量只採用一種抽象類型,可能還有None。通過將可選類型的數據傳遞給不同的變量,可以實現基於不同輸入的不同行爲。你想做這樣的事情:

def dosomething(some_user=None, some_otherthing=None): 
    if some_user is not None: 
     #do the "User" type action 
    elif some_otherthing is not None: 
     #etc... 
    else: 
     raise ValueError("not enough arguments") 

當然,這一切都假設你有一些控制類型檢查的代碼級別。假設它不是。對於「isinstance()」返回true,該類必須出現在實例的基地中,否則該類必須有__instancecheck__。既然你不控制這些課程中的任何一個,你就不得不在這個實例中使用一些詭計。做這樣的事情:

def wrap_user(instance): 
    class wrapped_user(type(instance)): 
     __metaclass__ = type 
     def __new__(cls): 
      pass 
     def __init__(self): 
      pass 
     def __getattribute__(self, attr): 
      self_dict = object.__getattribute__(type(self), '__dict__') 
      if attr in self_dict: 
       return self_dict[attr] 
      return getattr(instance, attr) 
     def extra_feature(self, foo): 
      return instance.username + foo # or whatever 
    return wrapped_user() 

我們正在做的是在時間動態地創建一個新的類,我們需要包裝的實例,從實際繼承被包裝對象的__class__。如果原始文件有一些我們實際上不想遇到的額外行爲(例如查找具有某個類名稱的數據庫表),我們也會花費額外的麻煩來覆蓋__metaclass__。這種風格的一個很好的方便之處在於,我們永遠不必在包裝類上創建任何實例屬性,因爲沒有self.wrapped_object,因爲該值存在於類創建時間

編輯:正如在評論中指出,上述只適用於一些簡單的類型,如果你需要代理的更詳細的屬性,在目標對象上,(比如說,方法),然後看下面的答案:Python - Faking Type Continued

+0

非常感謝您的寶貴幫助=) – Pierre

+0

爲什麼(看起來多餘)'__metaclass__ = type'必要? –

+0

您已經有效地取消了[描述符協議](https://docs.python.org/3/howto/descriptor.html),例如'wrap_user(obj).extra_feature()'返回一個未綁定的方法。您至少需要檢查從'self_dict'檢索到的對象上的'__get__'方法。查看[Python-Faking Type Continued](https://stackoverflow.com/q/31658171),查看某人提出的後續問題。 –

0

這聽起來像你想測試你的DocumentWrapper包裝對象的類型,而不是DocumentWrapper本身的類型。如果這是正確的,那麼接口DocumentWrapper需要公開該類型。例如,您可能會向返回包裝對象類型的類添加一個方法。但我不認爲通過使isinstance的呼叫模糊不清,當它不是時,它會返回True,這是解決此問題的正確方法。

0

最好的辦法是從用戶繼承DocumentWrapper本身,或混合式模式,從很多類做多inherintance

class DocumentWrapper(User, object) 

你也可以假isinstance()通過操縱obj.__class__結果,但是這是深級別的魔法,不應該這樣做。

+0

謝謝。用戶不是唯一包裝的文件類型,所以這不會不幸的工作。但是,謝謝,我甚至不知道多個繼承可能與Python =) – Pierre

11

可以使用__instancecheck__魔術方法覆蓋默認isinstance行爲:

@classmethod 
def __instancecheck__(cls, instance): 
    return isinstance(instance, User) 

這只是如果你想你的對象是一個透明包裝;也就是說,如果您希望DocumentWrapper的行爲類似User。否則,只需將包裝的類作爲屬性公開即可。

這是一個Python 3的補充;它帶有抽象基類。你不能這樣做在Python 2

+1

我聽到這個,謝謝......但我使用Python 2.x +( – Pierre

+2

這是在2.6 https://docs.python。 org/2/reference/datamodel.html#customizing-instance-and-subclass-checking – Anentropic

+4

需要注意的是,這個方法在**包裝類**的** meta **類中。 –

1

下面是使用元類的解決方案,但你需要修改包裝類:

>>> class DocumentWrapper: 
    def __init__(self, wrapped_obj): 
     self.wrapped_obj = wrapped_obj 

>>> class MetaWrapper(abc.ABCMeta): 
    def __instancecheck__(self, instance): 
     try: 
      return isinstance(instance.wrapped_obj, self) 
     except: 
      return isinstance(instance, self) 

>>> class User(metaclass=MetaWrapper): 
    pass 

>>> user=DocumentWrapper(User()) 
>>> isinstance(user,User) 
True 
>>> class User2: 
    pass 

>>> user2=DocumentWrapper(User2()) 
>>> isinstance(user2,User2) 
False 
+0

大燈泡,**你需要修改被包裝的類**所以你只能「僞造」你自己的類型,例如不是字符串或整數 –

4

在包裝類DocumentWrapper覆蓋__class__

class DocumentWrapper(object): 

    @property 
    def __class__(self): 
    return User 

>>> isinstance(DocumentWrapper(), User) 
True 

這種方式需要對包裝User類中沒有修改。

Python Mock也是這樣做的(請參閱mock-2.0.0中的mock.py:612,找不到源代碼鏈接到,抱歉)。