xAp*_*ple 37 python oop inheritance design-patterns
我在编程工作中遇到了一个有趣的案例,要求我在python中实现动态类继承机制.我在使用术语"动态继承"时的意思是一个特别是不从任何基类继承的类,而是选择在实例化时从几个基类之一继承,具体取决于某些参数.
因此,我的问题如下:在我将介绍的情况下,通过动态继承实现所需额外功能的最佳,最标准和"pythonic"方式是什么.
为了以简单的方式总结这个案例,我将举例说明使用两个代表两种不同图像格式的类:'jpg'
和'png'
图像.然后我将尝试添加支持第三种格式的功能:'gz'
图像.我意识到我的问题并不那么简单,但我希望你已经准备好与我一起承担更多的问题了.
此脚本包含两个类:ImageJPG
和ImagePNG
,都继Image
承自基类.要创建图像对象的实例,系统会要求用户image_factory
使用文件路径作为唯一参数来调用该函数.
然后,此函数从路径中猜出文件格式(jpg
或png
),并返回相应类的实例.
两个具体的图像类(ImageJPG
和ImagePNG
)都能够通过其data
属性解码文件.两者都以不同的方式做到这一点.但是,Image
为了执行此操作,都要求基类提供文件对象.
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)
Run Code Online (Sandbox Code Playgroud)
在第一个图像示例案例的基础上,想要添加以下功能:
应支持额外的文件格式,即gz
格式.它不仅仅是一种新的图像文件格式,它只是一个压缩层,一旦解压缩,就会显示jpg
图像或png
图像.
该image_factory
函数保持其工作机制,并且只是ImageZIP
在给定gz
文件时尝试创建具体图像类的实例.完全以同样的方式创建一个ImageJPG
给定jpg
文件的实例.
该ImageZIP
班只想重新定义该file_obj
物业.在任何情况下,它都不想重新定义该data
属性.问题的关键在于,根据zip存档中隐藏的文件格式,ImageZIP
类需要动态地继承ImageJPG
或从中继承ImagePNG
.只有在path
解析参数时才能在创建类时确定要继承的正确类.
因此,这里是与该额外ImageZIP
类相同的脚本和一个添加到该image_factory
函数的行.
显然,ImageZIP
在这个例子中,该类是无功能的.此代码需要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)
Run Code Online (Sandbox Code Playgroud)
我找到了一种通过拦截类中的__new__
调用ImageZIP
并使用该type
函数来获得所需行为的方法.但它感觉笨拙,我怀疑使用一些我还不知道的Python技术或设计模式可能有更好的方法.
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')
Run Code Online (Sandbox Code Playgroud)
请记住,如果您想提出一个目标不是改变image_factory
函数行为的解决方案.该功能应该保持不变.理想情况下,目标是建立一个动态ImageZIP
类.
我真的不知道最好的办法是什么.但这对我来说是一个完美的场合,可以让我更多地了解一些Python的"黑魔法".也许我的答案在于self.__cls__
创建后修改属性或者使用__metaclass__
class属性等策略?或者也许与特殊的abc
抽象基类有关可以帮助吗?还是其他未开发的Python领域?
std*_*err 18
我赞成在这里继承作文.我认为你当前的继承层次似乎是错误的.有些东西,比如用gzip或gzip打开文件,与实际的图像格式没什么关系,可以在一个地方轻松处理,同时你想要分开使用特定格式自己的类的细节.我认为使用组合可以委派实现特定的细节,并且有一个简单的公共Image类,而不需要元类或多重继承.
import gzip
import struct
class 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):
# ...
pass
class 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")
Run Code Online (Sandbox Code Playgroud)
我只在一些本地的png和jpeg文件上测试了这个,但希望它说明了另一种思考这个问题的方法.
Nik*_*s R 12
如何ImageZIP
在功能级别定义类?
这将使你的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)
Run Code Online (Sandbox Code Playgroud)
编辑:无需更改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)
Run Code Online (Sandbox Code Playgroud)
如果您需要 \xe2\x80\x9cblack magic\xe2\x80\x9d,请首先尝试考虑不需要它的解决方案。您可能会发现一些效果更好并且需要更清晰的代码的东西。
\n\n对于图像类构造函数来说,采用已打开的文件而不是路径可能会更好。\n然后,您就不再局限于磁盘上的文件,还可以使用 urllib、gzip 和喜欢。
\n\n另外,由于您可以通过查看文件内容来区分 JPG 和 PNG,并且对于 gzip 文件,无论如何您都需要进行此检测,因此我建议根本不要查看文件扩展名。
\n\nclass Image(object):\n def __init__(self, fileobj):\n self.fileobj = fileobj\n\ndef image_factory(path):\n return(image_from_file(open(path, \'rb\')))\n\ndef image_from_file(fileobj):\n if looks_like_png(fileobj):\n return ImagePNG(fileobj)\n elif looks_like_jpg(fileobj):\n return ImageJPG(fileobj)\n elif looks_like_gzip(fileobj):\n return image_from_file(gzip.GzipFile(fileobj=fileobj))\n else:\n raise Exception(\'The format "\' + format + \'" is not supported.\')\n\ndef looks_like_png(fileobj):\n fileobj.seek(0)\n return fileobj.read(4) == \'\\x89PNG\' # or, better, use a library\n\n# etc.\n
Run Code Online (Sandbox Code Playgroud)\n\n对于黑魔法,请转到Python 中的元类是什么?,但在使用它之前要三思而后行,尤其是在工作中。
\n 归档时间: |
|
查看次数: |
15188 次 |
最近记录: |