簡體   English   中英

Python - 動態多重繼承

[英]Python — dynamic multiple inheritance

我正在尋求有關設計代碼的建議。

介紹

我有幾個類,每個類代表一種文件類型,例如:MediaImageFile,MediaAudioFile和generic(以及基類)MediaGenericFile。

每個文件都有兩個變體:Master和Version,因此我創建了這些類來定義它們的特定行為。 編輯:版本表示主文件的調整大小/裁剪/修剪/等變體。 它主要用於預覽。

編輯: 為什么我想動態地這樣做的原因是這個應用程序應該是可重用的(它是Django-app),因此它應該很容易實現其他MediaGenericFile子類而不修改原始代碼。

我想做的事

  1. 首先,用戶應該能夠注冊自己的MediaGenericFile子類而不會影響原始代碼。

  2. 文件是版本還是主文件很容易(一個正則表達式)可以從文件名識別。

     /path/to/master.jpg -- master /path/to/.versions/master_version.jpg -- version 
  3. Master / Version類使用MediaGenericFile的一些方法/屬性,如filename(您需要知道filename才能生成新版本)。

  4. MediaGenericFile擴展了LazyFile,它只是懶惰的File對象。

現在我需要把它放在一起......

二手設計

在我開始編寫“版本”功能之前,我有工廠類MediaFile,它根據擴展名返回適當的文件類型類:

>>> MediaFile('path/to/image.jpg')
<<< <MediaImageFile 'path/to/image.jpg'>

類Master和Version定義了使用MediaGenericFile等方法和屬性的新方法。

方法1

一種方法是創建動態新類型,它繼承Master(或Version)和MediaGenericFile(或子類)。

class MediaFile(object):
    def __new__(cls, *args, **kwargs):
        ...  # decision about klass
        if version:
            bases = (Version, klass)
            class_name = '{0}Version'.format(klass.__name__)
        else:
            bases = (Master, klass)
            class_name = '{0}Master'.format(klass.__name__)

        new_class = type(class_name, bases, {})
        ...
        return new_class(*args, **kwargs)

方法2

第二種方法是在Master / Version中創建方法'contribution_to_instance'並在創建new_class后調用它,但這比我想象的更棘手:

classs Master(object):
    @classmethod
    def contribute_to_instance(cls, instance):
        methods = (...)
        for m in methods:
            setattr(instance, m, types.MethodType(getattr(cls, m), instance))

class MediaFile(object):
    def __new__(*args, **kwargs):
        ...  # decision about new_class
        obj = new_class(*args, **kwargs)
        if version:
            version_class = Version
        else:
            version_class = Master

        version_class.contribute_to_instance(obj)
        ...
        return obj

但是,這不起作用。 調用Master / Version的方法仍然存在問題。

問題

實現這種多重繼承的好方法是什么?

這個問題怎么稱呼? :)我試圖找到一些解決方案,但我根本不知道如何命名這個問題。

提前致謝!

請注意答案

廣告larsmans

對我的情況進行比較和實例檢查不會有問題,因為:

  1. 無論如何都要重新定義比較

     class MediaGenericFile(object): def __eq__(self, other): return self.name == other.name 
  2. 我永遠不需要檢查isinstance(MediaGenericFileVersion,instance)。 我正在使用isinstance(MediaGenericFile,instance)和isinstance(Version,instance),兩者都按預期工作。

然而,每個實例創建新類型聽起來像是一個相當大的缺陷。

好吧,我可以在元類中動態創建兩個變體然后使用它們,例如:

>>> MediaGenericFile.version_class
<<< <class MediaGenericFileVersion>
>>> MediaGenericFile.master_class
<<< <class MediaGenericFileMaster>

然后:

class MediaFile(object):
    def __new__(cls, *args, **kwargs):
        ...  # decision about klass
        if version:
            attr_name = 'version_class'
        else:
            attr_name = 'master_class'

    new_class = getattr(klass, attr_name)
    ...
    return new_class(*args, **kwargs)

最終解決方案

最后,設計模式是工廠級的。 MediaGenericFile子類是靜態類型的,用戶可以實現和注冊自己的子類。 主/版本變體是在元類中動態創建的(從幾個mixin粘合在一起)並存儲在'cache'中以避免larsmans提到的風險

謝謝大家的建議。 最后我理解了元類概念。 好吧,至少我認為我理解它。 推送原始主...

我不確定你想要它有多動態 ,但使用“工廠模式”(這里使用類工廠),是相當可讀和可理解的,可以做你想要的。 這可以作為基礎... MediaFactory可以更聰明,你可以注冊多個其他類,而不是硬編碼MediaFactoryMaster等...

class MediaFactory(object):

    __items = {}

    @classmethod
    def make(cls, item):
        return cls.__items[item]

    @classmethod
    def register(cls, item):
        def func(kls):
            cls.__items[item] = kls
            return kls
        return func

class MediaFactoryMaster(MediaFactory, Master): pass
class MediaFactoryVersion(MediaFactory, Version): pass

class MediaFile(object):
    pass

@MediaFactoryMaster.register('jpg') # adapt to take ['jpg', 'gif', 'png'] ?
class MediaFileImage(MediaFile):
    pass

@MediaFactoryVersion.register('mp3') # adapt to take ['mp3', 'ogg', 'm4a'] ?
class MediaFileAudio(MediaFile):
    pass

其他可能的MediaFactory.make

@classmethod
def make(cls, fname):
    name, ext = somefunc(fname)
    kls = cls.__items[ext]
    other = Version if Version else Master
    return type('{}{}'.format(kls.__name__,other.__name__), (kls, other), {})

我當然建議不要在__new__中構建類的第一種方法。 它的問題是你為每個實例創建一個新類型,這會導致開銷,更糟糕的是,導致類型比較失敗:

>>> Ham1 = type("Ham", (object,), {})
>>> Ham2 = type("Ham", (object,), {})
>>> Ham1 == Ham2
False
>>> isinstance(Ham1(), Ham2)
False
>>> isinstance(Ham2(), Ham1)
False

這違反了最少驚喜的原則,因為這些類似乎完全相同:

>>> Ham1
<class '__main__.Ham'>
>>> Ham2
<class '__main__.Ham'>

但是,如果您在MediaFile之外的模塊級別構建類,則可以使方法1正常工作:

classes = {}
for klass in [MediaImageFile, MediaAudioFile]:
    for variant in [Master, Version]:
        # I'd actually do this the other way around,
        # making Master and Version mixins
        bases = (variant, klass)
        name = klass.__name__ + variant.__name__
        classes[name] = type(name, bases, {})

然后,在MediaFile.__new__ ,按類名查找所需的classes (或者,在模塊上而不是在dict設置新構造的類。)

你怎么不使用繼承但是正在玩__new__

class GenericFile(File):
    """Base class"""

class Master(object):
    """Master Mixin"""

class Versioned(object):
    """Versioning mixin"""

class ImageFile(GenericFile):
    """Image Files"""

class MasterImage(ImageFile, Master):
    """Whatever"""

class VersionedImage(ImageFile, Versioned):
    """Blah blah blah"""

...

目前尚不清楚為什么你這樣做。 我覺得這里有一種奇怪的代碼味道。 我建議使用一致的接口( 鴨子類型 )而不是十幾個類來進行更少的類,並在整個代碼中進行isinstance檢查以使其全部工作。

也許您可以使用您希望在代碼中執行的操作更新您的問題,並且人們可以幫助確定真實模式或建議更慣用的解決方案。

你應該問的OOD的問題是“做各種類的我所提出的繼承份額的任何屬性呢?”

繼承的目的是共享實例自然具有的共同數據或方法。 除了兩個文件之外,圖像文件和音頻文件有什么共同之處? 如果你真的想要擴展你的隱喻,你可以想象有AudioFile.view()可以呈現 - 例如 - 音頻數據的功率譜的可視化,但ImageFile.listen()更沒意義。

我認為你的問題是支持這種語言獨立的概念問題,而不是對象工廠的Python依賴機制。 我認為你沒有適當的繼承案例,或者你無法解釋媒體對象需要共享的常見功能。

您不必為每個實例創建新類。 不要創建新類__new__在創建它們__metaclass__ 在base或base_module中定義元類。 這兩個“變體”子類很容易保存為其genaric父類的類屬性,然后__new__只根據它自己的規則查看文件名並決定返回哪個子類。

注意__new__在構造函數調用期間返回除“提名”之外的類。 您可能必須采取措施從__new__調用__init__

子類要么必須:

  1. “注冊”自己與要找到的工廠或父母
  2. import ed然后讓父級或工廠通過遞歸搜索cls.__subclasses來找到它們(每次創建可能必須發生一次,但這可能不是文件處理的問題)
  3. 通過使用“setuptools” entry_points類型工具找到,但這需要用戶更多的努力和協調

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM