Python dynamic inheritance: How to choose base class upon instance creation? Python dynamic inheritance: How to choose base class upon instance creation? python python

Python dynamic inheritance: How to choose base class upon instance creation?


I would favor composition over inheritance here. I think your current inheritance hierarchy seems wrong. Some things, like opening the file with or gzip have little to do with the actual image format and can be easily handled in one place while you want to separate the details of working with a specific format own classes. I think using composition you can delegate implementation specific details and have a simple common Image class without requiring metaclasses or multiple inheritance.

import gzipimport structclass ImageFormat(object):    def __init__(self, fileobj):        self._fileobj = fileobj    @property    def name(self):        raise NotImplementedError    @property    def magic_bytes(self):        raise NotImplementedError    @property    def magic_bytes_format(self):        raise NotImplementedError    def check_format(self):        peek = self._fileobj.read(len(self.magic_bytes_format))        self._fileobj.seek(0)        bytes = struct.unpack_from(self.magic_bytes_format, peek)        if (bytes == self.magic_bytes):            return True        return False    def get_pixel(self, n):        # ...        passclass JpegFormat(ImageFormat):    name = "JPEG"    magic_bytes = (255, 216, 255, 224, 0, 16, 'J', 'F', 'I', 'F')    magic_bytes_format = "BBBBBBcccc"class PngFormat(ImageFormat):    name = "PNG"    magic_bytes = (137, 80, 78, 71, 13, 10, 26, 10)    magic_bytes_format = "BBBBBBBB"class Image(object):    supported_formats = (JpegFormat, PngFormat)    def __init__(self, path):        self.path = path        self._file = self._open()        self._format = self._identify_format()    @property    def format(self):        return self._format.name    def get_pixel(self, n):        return self._format.get_pixel(n)    def _open(self):        opener = open        if self.path.endswith(".gz"):            opener = gzip.open        return opener(self.path, "rb")    def _identify_format(self):        for format in self.supported_formats:            f = format(self._file)            if f.check_format():                return f        else:            raise ValueError("Unsupported file format!")if __name__=="__main__":    jpeg = Image("images/a.jpg")    png = Image("images/b.png.gz")

I only tested this on a few local png and jpeg files but hopefully it illustrates another way of thinking about this problem.


What about defining the ImageZIP class on function-level ?
This will enable your dynamic inheritance.

def image_factory(path):    # ...    if format == ".gz":        image = unpack_gz(path)        format = os.path.splitext(image)[1][1:]        if format == "jpg":            return MakeImageZip(ImageJPG, image)        elif format == "png":            return MakeImageZip(ImagePNG, image)        else: raise Exception('The format "' + format + '" is not supported.')def MakeImageZIP(base, path):    '''`base` either ImageJPG or ImagePNG.'''    class ImageZIP(base):        # ...    return  ImageZIP(path)

Edit: Without need to change image_factory

def ImageZIP(path):    path = unpack_gz(path)    format = os.path.splitext(image)[1][1:]    if format == "jpg": base = ImageJPG    elif format == "png": base = ImagePNG    else: raise_unsupported_format_error()    class ImageZIP(base): # would it be better to use   ImageZip_.__name__ = "ImageZIP" ?        # ...    return ImageZIP(path)


If you ever need “black magic”, first try to think about a solution that doesn't require it. You're likely to find something that works better and results in needs clearer code.

It may be better for the image class constructors to take an already opened file instead of a path.Then, you're not limited to files on the disk, but you can use file-like objects from urllib, gzip, and the like.

Also, since you can tell JPG from PNG by looking at the contents of the file, and for gzip file you need this detection anyway, I recommend not looking at the file extension at all.

class Image(object):    def __init__(self, fileobj):        self.fileobj = fileobjdef image_factory(path):    return(image_from_file(open(path, 'rb')))def image_from_file(fileobj):    if looks_like_png(fileobj):        return ImagePNG(fileobj)    elif looks_like_jpg(fileobj):        return ImageJPG(fileobj)    elif looks_like_gzip(fileobj):        return image_from_file(gzip.GzipFile(fileobj=fileobj))    else:        raise Exception('The format "' + format + '" is not supported.')def looks_like_png(fileobj):    fileobj.seek(0)    return fileobj.read(4) == '\x89PNG' # or, better, use a library# etc.

For black magic, go to What is a metaclass in Python?, but think twice before using that, especially at work.