Python - dynamic multiple inheritance

I am looking for advice on the design of my code.

Introduction

I have several classes, each of which represents a single file type, for example: MediaImageFile, MediaAudioFile and the common (as well as the base class) MediaGenericFile.

Each file has two options: "Wizard" and "Version", so I created these classes to determine their specific behavior. EDIT: The version represents a resized / cropped / cropped / etc version of the main file. It was mainly used for preview.

EDIT: The reason I want to do this dynamically is because this application should be reusable (this is a Django application), and therefore it should be easy to implement another subclass of MediaGenericFile without changing the source code.

What i want to do

  • First of all, the user should be able to register their own subclasses of MediaGenericFile without affecting the original code.

  • Whether a version file or a wizard is available (one regular expression) recognized from the file name.

    /path/to/master.jpg -- master /path/to/.versions/master_version.jpg -- version 
  • The Master / Version classes use some MediaGenericFile methods / properties, such as the file name (you need to know the file name to create a new version).

  • MediaGenericFile extends LazyFile, which is just a lazy file object.

Now I need to connect it ...

Used design

Before starting the encoding of the “versions”, I had a factory class MediaFile, which returns a class of the corresponding file type according to the extension:

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

The Master and Version classes define new methods that use the methods and attributes of MediaGenericFile, etc.

Approach 1

One approach is to create a dynamically new type that inherits from Master (or Version) and MediaGenericFile (or a subclass).

 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) 

Approach 2

The second approach is to create the "contrib_to_instance" method in Master / Version and call it after creating new_class, but this is more complicated than I thought:

 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 

However, this does not work. There are still problems with calling Master / Version methods.

Questions

What would be a good way to implement this multiple inheritance?

What is the name of this problem? :) I tried to find some solutions, but I just don’t know what to name this problem.

Thanks in advance!

Answer Note

larsmans ad

Comparing and checking the instance will not be a problem for my case, because:

  • In any case, comparisons are overridden.

     class MediaGenericFile(object): def __eq__(self, other): return self.name == other.name 
  • I never need to check isinstance (MediaGenericFileVersion, instance). I use isinstance (MediaGenericFile, instance) and isinstance (Version, instance), and both work as expected.

However, creating a new type for an instance sounds to me like a significant defect.

Well, I could dynamically create both variants in a metaclass and then use them, for example:

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

And then:

 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) 

Final decision

Finally, a factory design template. Subclasses of MediaGenericFile are statically typed, users can implement and register their own. Wizard / version variants are dynamically created (glued from several mixins) in the metaclass and stored in the cache to avoid the dangers noted by larsmans.

Thanks to everyone for their suggestions. Finally, I understand the concept of a metaclass. Well, at least I think I understand that. Click the starter wizard ...

+8
python design oop multiple-inheritance
source share
5 answers

You do not need to create a new class for each instance. Do not create new classes in __new__ , create them in __metaclass__ . define a metaclass in the database or in base_module. The two “variant” subclasses are easily stored as class attributes of their parent, and then __new__ simply looks at the file name according to its own rules and decides which subclass should be returned.

Watch for __new__ , which returns a class other than the one that was "assigned" during the constructor call. You may need to take steps to call __init__ with __new__

Subclasses must either:

  • "register" yourself with the factory or parent to be found
  • be import ed, and then the parent or factory will find them through a recursive search for cls.__subclasses (maybe this will happen once for each creation, but this is probably not a problem for processing files)
  • found using the "setuptools" entry_points , but this requires more effort and coordination on the part of the user.
0
source share

I'm not sure how dynamic you want it to be, but using the factory template (here using the factory class) is fairly readable and straightforward and can do what you want. This can serve as a base ... MediaFactory can be smarter and you can register several other classes instead of hard coding MediaFactoryMaster , etc.

 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 

another possible 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), {}) 
+4
source share

I would advise against the first approach to building classes in __new__ . The problem is that you create a new type for each instance, which causes overhead and, even worse, leads to a failure in type comparison:

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

This violates the principle of least surprise, because classes may seem completely identical:

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

You can make approach 1 work correctly, though if you build classes at the module level, outside of MediaFile :

 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, {}) 

then in MediaFile.__new__ look at the required class by name in classes . (Alternatively, install new classes in the module instead of dict .)

+3
source share

Why don't you use inheritance but play with __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""" ... 

It is not clear why you are doing this. I think there is a strange smell of code here. I would recommend fewer duck-typing classes than a dozen classes and isinstance checked all the code to make this all work.

Perhaps you can update your question with what you would like to do in your code, and people can help either identify the real template or offer a more idiomatic solution.

+2
source share

The OOD question you should ask is: "Do the different classes of my proposed inheritance share any properties?"

The purpose of inheritance is to share common data or methods that naturally have instances. Besides both files, what is common with image files and audio files? If you really want to stretch your metaphors, you could have AudioFile.view() , which could represent, for example, a visualization of the power spectra of audio data, but ImageFile.listen() makes even less sense.

I think your question questions this independent language concept in favor of Python-dependent factory object mechanics. I don’t think you have a proper case of inheritance here, or you couldn’t explain what common functions your multimedia objects should use.

0
source share

All Articles