[英]Python — dynamic multiple inheritance
我正在尋求有關設計代碼的建議。
我有幾個類,每個類代表一種文件類型,例如:MediaImageFile,MediaAudioFile和generic(以及基類)MediaGenericFile。
每個文件都有兩個變體:Master和Version,因此我創建了這些類來定義它們的特定行為。 編輯:版本表示主文件的調整大小/裁剪/修剪/等變體。 它主要用於預覽。
編輯: 為什么我想動態地這樣做的原因是這個應用程序應該是可重用的(它是Django-app),因此它應該很容易實現其他MediaGenericFile子類而不修改原始代碼。
首先,用戶應該能夠注冊自己的MediaGenericFile子類而不會影響原始代碼。
文件是版本還是主文件很容易(一個正則表達式)可以從文件名識別。
/path/to/master.jpg -- master /path/to/.versions/master_version.jpg -- version
Master / Version類使用MediaGenericFile的一些方法/屬性,如filename(您需要知道filename才能生成新版本)。
MediaGenericFile擴展了LazyFile,它只是懶惰的File對象。
現在我需要把它放在一起......
在我開始編寫“版本”功能之前,我有工廠類MediaFile,它根據擴展名返回適當的文件類型類:
>>> MediaFile('path/to/image.jpg')
<<< <MediaImageFile 'path/to/image.jpg'>
類Master和Version定義了使用MediaGenericFile等方法和屬性的新方法。
一種方法是創建動態新類型,它繼承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)
第二種方法是在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的方法仍然存在問題。
實現這種多重繼承的好方法是什么?
這個問題怎么稱呼? :)我試圖找到一些解決方案,但我根本不知道如何命名這個問題。
提前致謝!
對我的情況進行比較和實例檢查不會有問題,因為:
無論如何都要重新定義比較
class MediaGenericFile(object): def __eq__(self, other): return self.name == other.name
我永遠不需要檢查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__
子類要么必須:
import
ed然后讓父級或工廠通過遞歸搜索cls.__subclasses
來找到它們(每次創建可能必須發生一次,但這可能不是文件處理的問題) entry_points
類型工具找到,但這需要用戶更多的努力和協調
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.