Python动态继承:如何在创建实例时选择基类? [英] Python dynamic inheritance: How to choose base class upon instance creation?

查看:2151
本文介绍了Python动态继承:如何在创建实例时选择基类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简介



我在编程工作中遇到一个有趣的情况,需要我在python中实现动态类继承的机制。当使用术语动态继承时,我的意思是一个不会从任何基类继承的类,而是根据一些参数选择在几个基类中继承几个基类。



我的问题如下:在我将介绍的情况下,通过动态继承实现所需的额外功能的最佳,最标准和pythonic方式将是什么。 >

要简单的总结一下这个例子,我将给出一个使用两个表示两种不同图像格式的类的例子:'jpg''png'图像。然后,我将尝试添加支持第三种格式的功能:'gz' image。我意识到我的问题不是那么简单,但我希望你准备好再和我一起再多一点。






两个图像示例案例



此脚本包含两个类: ImageJPG ImagePNG ,都从 Image 基类继承
。要创建一个图像对象的实例,请求用户调用 image_factory 函数,文件路径作为唯一的参数。



然后,该函数从路径中猜出文件格式( jpg png ),
返回相应类的实例。



两个具体的图像类( ImageJPG ImagePNG )能够通过他们的数据属性解码
文件。两者都以不同的方式进行。但是,
都要求文件对象的 Image 基类。为此,

  import os 

#------------------------------------ ------------------------------------------#
def image_factory(路径):
'''从文件扩展名
中猜出文件格式并返回相应的图像实例'''
format = os.path.splitext(path)[1] [ 1:]
如果格式=='jpg':return ImageJPG(path)
如果format =='png':return ImagePNG(path)
else:raise异常('格式' '+ format +'不支持'')

#----------------------------- -------------------------------------------------#
class Image(object):
'''由12个像素组成的假1D图像对象'''
def __init __(self,path):
self.path = path

def get _pixel(self,x):
assert x< 12
return self.data [x]

@property
def file_obj(self):return open(self.path,'r')

#------------------------------------------------ ------------------------------#
class ImageJPG(Image):
'''假JPG图像类以给定的方式解析文件'''

@property
def format(self):return'Joint Photographic Experts Group'

@属性
def data(self):
with self.file_obj as f:
f.seek(-50)
return f.read(12)

#----------------------------------------------- -------------------------------#
class ImagePNG(Image):
'''假PNG图像类以不同的方式解析文件'''

@property
def format(self):return'Portable Network Graphics'

@属性
def data(self):
with self.file_obj as f:
f.seek(10)
return f.read(12)

################################# #############################################
i = image_factory('images / lena.png')
打印i.format
打印i.get_pixel(5)




压缩图像示例案例



图像示例案例,一个想要
添加以下功能:



应该支持额外的文件格式, gz 格式。而不是
是一种新的图像文件格式,它只是一个压缩层,
一旦解压缩,就会显示一个 jpg 图像或code> png
image。



image_factory 功能保持其工作机制和将
简单地尝试创建一个具体图像类 ImageZIP
的实例,当它被赋予一个 gz 文件。完全一样,当
创建一个 ImageJPG 的实例,当给出一个 jpg 文件。 p>

ImageZIP 类只是想重新定义 file_obj 属性。
在任何情况下都不需要重新定义数据属性。问题的关键是
,根据什么文件格式在zip存档中隐藏
ImageZIP 类需要继承
从动态的 ImageJPG 或从 ImagePNG
继承的正确类只能在路径
参数被解析时才能在类创建时确定。



因此,这里是与$ code> ImageZIP class
相同的脚本,并且添加了 image_factory 函数。



在这个例子中, ImageZIP 类是无效的。
此代码需要Python 2.7。



  import os,gzip 

#--------- -------------------------------------------------- -------------------#
def image_factory(path):
'''从文件扩展名
中猜出文件格式,返回相应的图像实例'''
format = os.path.splitext(path)[1] [1:]
如果format =='jpg':return ImageJPG(path)
如果format =='png':return ImagePNG(path)
如果format =='gz':return ImageZIP(path)
else:raise异常('格式''+格式+'不支持。')

#----------------------------------- -------------------------------------------#
class Image (对象):
'''由12个像素组成的假1D图像对象'''
def __init __(self,path):
self.path = path

定义t_pixel(self,x):
assert x< 12
return self.data [x]

@property
def file_obj(self):return open(self.path,'r')

#------------------------------------------------ ------------------------------#
class ImageJPG(Image):
'''假JPG图像类以给定的方式解析文件'''

@property
def format(self):return'Joint Photographic Experts Group'

@属性
def data(self):
with self.file_obj as f:
f.seek(-50)
return f.read(12)

#----------------------------------------------- -------------------------------#
class ImagePNG(Image):
'''假PNG图像类以不同的方式解析文件'''

@property
def format(self):return'Portable Network Graphics'

@属性
def data(self):
with self.file_obj as f:
f.seek(10)
return f.read(12)

#---------------------------------- --------------------------------------------#
class ImageZIP(### ImageJPG或ImagePNG? ###):
'''表示压缩文件的类。有时继承自
ImageJPG,在其他时候继承自ImagePNG'''

@property
def format(self):return'Compressed'+ super(ImageZIP,self)。格式

@property
def file_obj(self):return gzip.open(self.path,'r')

######## ################################################## ######################
i = image_factory('images / lena.png.gz')
打印i.format
print i.get_pixel(5)




可能的解决方案



我已经找到一种通过拦截 __ new __ 调用 ImageZIP 类并使用类型函数。但是感觉很笨拙,我怀疑可能会有更好的方式使用一些Python技术或者我还不了解的设计模式。

  import re 

class ImageZIP(object):
'''表示压缩文件的类。有时继承自
ImageJPG,其他时候继承自ImagePNG'''

def __new __(cls,path):
如果cls是ImageZIP:
format = re .findall('(...)\.gz',path)[ - 1]
如果format =='jpg':返回类型(CompressedJPG,(ImageZIP,ImageJPG),{})路径)
如果格式=='png':返回类型(CompressedPNG,(ImageZIP,ImagePNG),{})(路径)
else:
返回对象.__新__(cls)

@property
def format(self):return'Compressed'+ super(ImageZIP,self).format

@property
def file_obj self):return gzip.open(self.path,'r')




结论



请记住,如果你想提出一个解决方案,目标是不要改变行为 image_factory 函数。该功能应保持不变。理想的目的是建立一个动态的 ImageZIP 类。



我只是不知道什么最好的办法是。但是,这是一个完美的场合,让我更多地了解一些Python的黑魔法。也许我的答案在于创建之后修改 self .__ cls __ 属性的策略,或者可能使用 __ metaclass __ 类属性?或者可能与特殊的 abc 抽象基类可能有助于在这里?或者其他未开发的Python领域?

解决方案

如何定义 ImageZIP 在功能级别?

这将启用您的动态继承

  def image_factory(path):
#...

如果format ==.gz:
image = unpack_gz(path)
格式= os.path.splitext(image)[1] [1:]
如果格式==jpg:
返回MakeImageZip(ImageJPG,image)
elif format == png
return MakeImageZip(ImagePNG,image)
其他:raise异常('格式''+格式+'不支持')

def MakeImageZIP基础,路径):
'''base` ImageJPG或ImagePNG'''

class ImageZIP(base):

#...

返回ImageZIP(路径)

编辑不需要更改 image_factory

  def ImageZIP(path):

path = unpack_gz(path)
format = os.path .splitext(image)[1] [1:]

如果format ==jpg:base = ImageJPG
elif format ==png:base = ImagePNG
否则:raise_unsupported_format_error()

class ImageZIP(base):#最好使用ImageZip _.__ name__ =ImageZIP?
#...

返回ImageZIP(路径)


Introduction

I have encountered an interesting case in my programming job that requires me to implement a mechanism of dynamic class inheritance in python. What I mean when using the term "dynamic inheritance" is a class that doesn't inherit from any base class in particular, but rather chooses to inherit from one of several base classes at instantiation, depending on some parameter.

My question is thus the following: in the case I will present, what would be the best, most standard and "pythonic" way of implementing the needed extra functionality via dynamic inheritance.

To summarize the case in point in a simple manner, I will give an example using two classes that represent two different image formats: 'jpg' and 'png' images. I will then try to add the ability to support a third format: the 'gz' image. I realize my question isn't that simple, but I hope you are ready to bare with me for a few more lines.


The two images example case

This script contains two classes: ImageJPG and ImagePNG, both inheriting from the Image base class. To create an instance of an image object, the user is asked to call the image_factory function with a file path as the only parameter.

This function then guesses the file format (jpg or png) from the path and returns an instance of the corresponding class.

Both concrete image classes (ImageJPGand ImagePNG) are able to decode files via their data property. Both do this in a different way. However, both ask the Image base class for a file object in order to do this.

import os

#------------------------------------------------------------------------------#
def image_factory(path):
    '''Guesses the file format from the file extension
       and returns a corresponding image instance.'''
    format = os.path.splitext(path)[1][1:]
    if format == 'jpg': return ImageJPG(path)
    if format == 'png': return ImagePNG(path)
    else: raise Exception('The format "' + format + '" is not supported.')

#------------------------------------------------------------------------------#
class Image(object):
    '''Fake 1D image object consisting of twelve pixels.'''
    def __init__(self, path):
        self.path = path

    def get_pixel(self, x):
        assert x < 12
        return self.data[x]

    @property
    def file_obj(self): return open(self.path, 'r')

#------------------------------------------------------------------------------#
class ImageJPG(Image):
    '''Fake JPG image class that parses a file in a given way.'''

    @property
    def format(self): return 'Joint Photographic Experts Group'

    @property
    def data(self):
        with self.file_obj as f:
            f.seek(-50)
            return f.read(12)

#------------------------------------------------------------------------------#
class ImagePNG(Image):
    '''Fake PNG image class that parses a file in a different way.'''

    @property
    def format(self): return 'Portable Network Graphics'

    @property
    def data(self):
        with self.file_obj as f:
            f.seek(10)
            return f.read(12)

################################################################################
i = image_factory('images/lena.png')
print i.format
print i.get_pixel(5)


The compressed image example case

Building on the first image example case, one would like to add the following functionality:

An extra file format should be supported, the gz format. Instead of being a new image file format, it is simply a compression layer that, once decompressed, reveals either a jpg image or a png image.

The image_factory function keeps its working mechanism and will simply try to create an instance of the concrete image class ImageZIP when it is given a gz file. Exactly in the same way it would create an instance of ImageJPG when given a jpg file.

The ImageZIP class just wants to redefine the file_obj property. In no case does it want to redefine the data property. The crux of the problem is that, depending on what file format is hiding inside the zip archive, the ImageZIP classes needs to inherit either from ImageJPG or from ImagePNG dynamically. The correct class to inherit from can only be determined upon class creation when the path parameter is parsed.

Hence, here is the same script with the extra ImageZIP class and a single added line to the image_factory function.

Obviously, the ImageZIP class is non-functional in this example. This code requires Python 2.7.

import os, gzip

#------------------------------------------------------------------------------#
def image_factory(path):
    '''Guesses the file format from the file extension
       and returns a corresponding image instance.'''
    format = os.path.splitext(path)[1][1:]
    if format == 'jpg': return ImageJPG(path)
    if format == 'png': return ImagePNG(path)
    if format == 'gz':  return ImageZIP(path)
    else: raise Exception('The format "' + format + '" is not supported.')

#------------------------------------------------------------------------------#
class Image(object):
    '''Fake 1D image object consisting of twelve pixels.'''
    def __init__(self, path):
        self.path = path

    def get_pixel(self, x):
        assert x < 12
        return self.data[x]

    @property
    def file_obj(self): return open(self.path, 'r')

#------------------------------------------------------------------------------#
class ImageJPG(Image):
    '''Fake JPG image class that parses a file in a given way.'''

    @property
    def format(self): return 'Joint Photographic Experts Group'

    @property
    def data(self):
        with self.file_obj as f:
            f.seek(-50)
            return f.read(12)

#------------------------------------------------------------------------------#
class ImagePNG(Image):
    '''Fake PNG image class that parses a file in a different way.'''

    @property
    def format(self): return 'Portable Network Graphics'

    @property
    def data(self):
        with self.file_obj as f:
            f.seek(10)
            return f.read(12)

#------------------------------------------------------------------------------#
class ImageZIP(### ImageJPG OR ImagePNG ? ###):
    '''Class representing a compressed file. Sometimes inherits from
       ImageJPG and at other times inherits from ImagePNG'''

    @property
    def format(self): return 'Compressed ' + super(ImageZIP, self).format

    @property
    def file_obj(self): return gzip.open(self.path, 'r')

################################################################################
i = image_factory('images/lena.png.gz')
print i.format
print i.get_pixel(5)


A possible solution

I have found a way of getting the wanted behavior by intercepting the __new__ call in the ImageZIP class and using the type function. But it feels clumsy and I suspect there might be a better way using some Python techniques or design patterns I don't yet know about.

import re

class ImageZIP(object):
    '''Class representing a compressed file. Sometimes inherits from
       ImageJPG and at other times inherits from ImagePNG'''

    def __new__(cls, path):
        if cls is ImageZIP:
            format = re.findall('(...)\.gz', path)[-1]
            if format == 'jpg': return type("CompressedJPG", (ImageZIP,ImageJPG), {})(path)
            if format == 'png': return type("CompressedPNG", (ImageZIP,ImagePNG), {})(path)
        else:
            return object.__new__(cls)

    @property
    def format(self): return 'Compressed ' + super(ImageZIP, self).format

    @property
    def file_obj(self): return gzip.open(self.path, 'r')


Conclusion

Bear in mind if you want to propose a solution that the goal is not to change the behavior of the image_factory function. That function should remain untouched. The goal, ideally, is to build a dynamic ImageZIP class.

I just don't really know what the best way to do this is. But this is a perfect occasion for me to learn more about some of Python's "black magic". Maybe my answer lies with strategies like modifying the self.__cls__ attribute after creation or maybe using the __metaclass__ class attribute? Or maybe something to do with the special abc abstract base classes could help here? Or other unexplored Python territory?

解决方案

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)

这篇关于Python动态继承:如何在创建实例时选择基类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆