类的继承python事例,Python动态继承:如何在创建实例时选择基类?

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.

89b3813f74da1d9a82ad908bfb242aaf.png

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.

DQZ74.png

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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值