Python - 动态多重继承

Tom*_*ich 8 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
    
    Run Code Online (Sandbox Code Playgroud)
  3. Master/Version类使用MediaGenericFile的一些方法/属性,如filename(您需要知道filename才能生成新版本).

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

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

二手设计

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

>>> MediaFile('path/to/image.jpg')
<<< <MediaImageFile 'path/to/image.jpg'>
Run Code Online (Sandbox Code Playgroud)

类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)
Run Code Online (Sandbox Code Playgroud)

方法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
Run Code Online (Sandbox Code Playgroud)

但是,这不起作用.调用Master/Version的方法仍然存在问题.

问题

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

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

提前致谢!

请注意答案

广告larsmans

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

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

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

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

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

>>> MediaGenericFile.version_class
<<< <class MediaGenericFileVersion>
>>> MediaGenericFile.master_class
<<< <class MediaGenericFileMaster>
Run Code Online (Sandbox Code Playgroud)

然后:

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)
Run Code Online (Sandbox Code Playgroud)

最终解决方案

最后,设计模式是工厂级的.MediaGenericFile子类是静态类型的,用户可以实现和注册自己的子类.主/版本变体是在元类中动态创建的(从多个mixin粘合在一起)并存储在'cache'中以避免larsmans提到的 风险.

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

Phi*_*per 1

您不必为每个实例创建一个新类。不要在__new__create 它们 in中创建新类__metaclass__。在基类或 base_module 中定义元类。这两个“变体”子类很容易保存为其通用父类的类属性,然后__new__只需根据它自己的规则查看文件名并决定返回哪个子类。

请注意,__new__返回的类不是构造函数调用期间“指定”的类。 您可能需要采取措施__init__从内部调用__new__

子类必须:

  1. 向工厂或母公司“注册”自己以便被发现
  2. import编辑,然后让父级或工厂通过递归搜索找到它们cls.__subclasses(每次创建可能必须发生一次,但这对于文件处理来说可能不是问题)
  3. 通过使用“setuptools”entry_points类型的工具找到,但这需要用户付出更多的努力和协调