Python - 动态多重继承

时间:2012-07-21 11:03:22

标签: python oop 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(你需要知道文件名来生成新版本)。

  4. MediaGenericFile扩展了LazyFile,它只是懒惰的File对象。

  5. 现在我需要把它放在一起......

    二手设计

    在开始编写'版本'功能之前,我有工厂类MediaFile,它根据扩展名返回适当的文件类型类:

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

    Classes 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的方法仍然存在问题。

    问题

    实现这种多重继承的好方法是什么?

    这个问题怎么称呼? :)我试图找到一些解决方案,但我根本不知道如何命名这个问题。

    提前致谢!

    答案注释

    ad larsmans

    对我的情况进行比较和实例检查不会有问题,因为:

    1. 无论如何都要重新定义比较

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

    3. 然而,每个实例创建新类型听起来像是一个相当大的缺陷。

      好吧,我可以在元类中动态创建两个变体,然后使用它们,如:

      >>> 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 提到的危险。

      感谢大家的建议。最后我理解了元类概念。好吧,至少我认为我理解它。推送原始主...

5 个答案:

答案 0 :(得分:4)

我不确定你希望它是如何动态,但使用“工厂模式”(这里使用类工厂),是相当可读和可理解的,可以做你想要的。这可以作为基础... 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), {})

答案 1 :(得分:3)

我当然建议不要在__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中设置新构造的类。)

答案 2 :(得分:2)

你怎么没有使用继承但是正在玩__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"""

...

目前尚不清楚为什么要这样做。我觉得这里有一种奇怪的代码味道。我建议使用一致的接口(duck-typing)而不是十几个类和isinstance检查整个代码中的更少的类,以使它全部工作。

也许您可以使用您希望在代码中执行的操作更新您的问题,人们可以帮助确定真实模式或建议更具惯用性的解决方案。

答案 3 :(得分:0)

你应该问的OOD问题是“我提议的继承的各个类别共享所有属性吗?”

继承的目的是共享实例自然具有的共同数据或方法。除了两个文件之外,图像文件和音频文件有什么共同之处?如果你真的想要扩展你的隐喻,你可以想象AudioFile.view()可以呈现 - 例如 - 音频数据的功率谱的可视化,但是ImageFile.listen()更没意义。

我认为你的问题是支持这种语言独立的概念问题,而不是对象工厂的Python依赖机制。我认为你没有适当的继承案例,或者你没有解释你的媒体对象需要分享的常见功能。

答案 4 :(得分:0)

您不必为每个实例创建新类。请勿在{{1​​}}中创建新类,并在__new__中创建新类。在base或base_module中定义元类。这两个“变体”子类很容易保存为其genaric父类的类属性,然后__metaclass__只根据它自己的规则查看文件名并决定返回哪个子类。

注意__new__在构造函数调用期间返回除“指定”之外的类。 You may have to take steps to invoke __init__ from withing __new__

子类要么必须:

  1. “注册”自己与要找到的工厂或家长
  2. __new__编辑,然后让父母或工厂通过import的递归搜索找到它们(每次创建可能必须发生一次,但这可能不是文件处理的问题)
  3. 通过使用"setuptools" entry_points类型工具找到,但需要用户付出更多努力和协调