【PYTHON】解決isinstance時包裝物件以擴充套件/新增功能

2020-12-02 PYTHON

在python中,我看到了使用holding或wrapping來擴充套件物件或類的功能而不是繼承的建議。特別是,我認為亞歷克斯·馬泰利在他的演講中談到了這一點。我在庫中見過這種模式用於依賴注入,比如Python Design Patterns
我遇到的一個問題是,當我必須與使用
pycontainer,此模式失敗,因為保持/包裝物件未通過isinstance測試。如何設定holding/wrapping物件以繞過不必要的型別檢查?這可以一般地做嗎?在某種意義上,我需要一些類似於簽名保持函式裝飾器的類例項(例如,isinstance anti-pattern或michele siminonato的simple_decorator)。
一個條件:我並不是說所有的isinstance用法都是不合適的;有幾個答案說明了這一點。也就是說,應該認識到isinstance的使用對物件互動造成了很大的限制——它迫使繼承成為多型性的來源,而不是行為。
對於這究竟是如何/為什麼是一個問題,似乎有一些困惑,所以讓我提供一個簡單的例子(從decorator中廣泛提出來)。假設我們有一個類foo,以及一個foofactory。為了這個例子,假設我們希望能夠例項化記錄每個函式呼叫的foo物件,或者不需要——想想aop。此外,我們希望在不以任何方式修改foo類/原始碼的情況下實現這一點(例如,我們實際上可能正在實現一個通用工廠,它可以動態地向任何類例項新增日誌記錄功能)。第一步可能是:

class Foo(object):
    def bar():
        print 'We\'re out of Red Leicester.'

class LogWrapped(object):
    def __init__(self, wrapped):
        self.wrapped = wrapped
    def __getattr__(self, name):
        attr = getattr(self.wrapped, name)
        if not callable(attr):
            return attr
        else:
            def fun(*args, **kwargs):
                print 'Calling ', name
                attr(*args, **kwargs)
                print 'Called ', name
            return fun

class FooFactory(object):
    def get_foo(with_logging = False):
        if not with_logging:
            return Foo()
        else:
            return LogWrapped(Foo())

foo_fact = FooFactory()
my_foo = foo_fact.get_foo(True)
isinstance(my_foo, Foo) # False!

可能有一些原因使您可能希望以這種方式執行任務(改用decorators等),但請記住:
我們不想碰足球課。假設我們正在編寫框架程式碼,這些程式碼可能會被我們還不知道的客戶使用。
關鍵是返回一個本質上是foo的物件,但具有附加的功能。對於任何其他期望foo的客戶機程式碼來說,它應該是一個foo——儘可能多。因此,我們希望圍繞isinstance展開工作。
是的,我知道我不需要工廠類(在這裡先為自己辯護)。

解決辦法

如果所依賴的庫程式碼使用isinstance並依賴於繼承,為什麼不遵循此路徑?如果你不能改變圖書館,那麼最好保持與它的一致性。
我也認為isinstance有合法的用途,隨著abstract base classes在2.6中的引入,這已經得到了官方的認可。有些情況下,isinstance確實是正確的解決方案,而不是使用hasattr或使用異常進行duck鍵入。
如果出於某種原因您真的不想使用繼承,則可以使用一些髒選項:
使用例項方法只能修改類例項。使用new.instancemethod為例項建立包裝器方法,然後呼叫在原始類中定義的原始方法。這似乎是唯一既不修改原始類也不定義新類的選項。
如果可以在執行時修改類,則有許多選項:
使用執行時mixin,即只需將一個類新增到類的__base__屬性中。但這更多地用於新增特定功能,而不是不知道需要包裝什麼的不加區分的包裝。
dave答案中的選項(python中的類裝飾器>=2.6或元類)。
編輯:對於你的具體例子,我想只有第一個選項有效。但我仍然會考慮建立一個LogFoo或者為日誌之類的特定內容選擇完全不同的解決方案。

出處

Have any Question?

Let us answer it!