用python做后端_javaee是前端还是后端

Python (68) 2023-03-24 16:06

本节是第五讲的第十四小节,本节参照上一节所讲的python面向对象的知识,以一个图形处理的类讲解如何创建聚集组合数据的类。

创建聚集组合数据的类(Creating Classes That Aggregate Collections)

用于表示2D颜色图像的一个简单方法是使用一个2维数组,每个数组元素代表一种颜色。因此,如果需要表示一个100x100的图像,我们就必须存储10000个颜色。 对Image类(在文件Image.py中),我们将釆取一种具有更加高效的潜力的做法。Image 存储一种单一的背景色,图像中各个点的颜色则不同于背景色。这是通过将字典用作稀疏数组实现的,字典中的每个键是一个(x,y)坐标,对应的值则为该点的颜色。如果我们有一个100x100的图像,并且其中半数的点都与背景色相同,那么只需要存储 5 000 + 1种颜色,从而大幅节省内存资源。

Image.py模块釆用了我们现在应该已经熟悉的模式:模块从一个shebang行开始,之后是注释中的版权信息。再之后是模块的docstring以及一些doctests/接下来是导入语句,本模块中需要导入os模块与pickle模块。在讲解图像的加载与保存时,我们将简要讲述Pickle模块的使用。在导入相应模块之后,我们创建一些自定义异常类:

class lmageError(Exception): pass

class CoordinateError(lmageError): pass

我们只是展示了头两个异常类,其他异常类(LoadError、SaveError、ExportError 与NoFilenameError)的创建方式是相同的,并且也都继承自ImageError。

Image类的使用者可以对任一种特定的异常进行测试,也可以只对基类的ImageError异常进行测试。

该模块的余下部分包括Image类的主体,最后则是用于运行模块的doctests的标准的三行代码。在具体讲解该类及其方法之前,我们先看一下该类如何使用:

border_color =“#FF0000" # red

square_color = "#0000FF” # blue

width, height = 240, 60

midx,midy = width // 2, height // 2

image = Image.Image(width, height, "square_eye.img")

for x in range(width):

for y in range(height):

if x < 5 or x >= width - 5 or y < 5 or y >= height - 5:

image[x, y] = border_color

elif midx - 20 < x < midx + 20 and midy - 20 < y < midy + 20:

image[x, y] = square_color

image.save()

image.export("square_eye.xpm")

注意,我们可以使用项存取操作符([])来设置图像中的颜色。方括号也可以用于对特定(x,y)坐标的颜色进行获取或删除,也可以有效地将其颜色设置为背景色。坐标是以单独的元组对象的形式进行传递的(借助于其中的逗号操作符),就像我们使用 image[(x, y)]这种表达方式一样。在Python中,实现这种无缝的语法整合是容易的 ——我们只需要实现适当的特殊方法,比如,对项存取操作符,对应的特殊方法包括 __getitem__()、__setitem__()与__delitem__()。

Image类使用HTML风格的十六进制字符串表示颜色。在创建图像时就必须设置背景色,否则就默认为白色。在存储与加载图像时,Image类使用其自定义格式,但也可以使用.xpm格式导出,该格式很多图像处理应用程序都可以理解。如图展示了上面的代码段生成的.xpm图像。

用python做后端_javaee是前端还是后端_https://bianchenghao6.com/blog_Python_第1张

现在我们来看Image类的方法,从class行与初始化程序开始。创建Image时,使用者(该类的使用者)必须指定宽度与高度,但文件名与背景色则是可选的,因为有默认值。字典self.__data的键是(x, y)坐标,值为颜色字符串。集合self.__colors使用背景色进行初始化,并用于追踪图像中使用的各种排他性的颜色。

除文件名外,所有数据属性都是私有属性,因此,必须提供一种途径,以便使用者可以对这些属性进行存取。通过使用特性,可以容易地做到这一点。

class Image:

def __init__(self, width, height, filename=“”,background=“#FFFFFF"):

self.filename = filename

self.__background = background

self.__data = {}

self.__width = width

self.__height = height

self.__colors = {self.__background}

@property

def background(self):

return self.__background

@property

def width(self):

return self.__width

@property

def height(self):

return self.__height

@property

def colors(self):

return set(self.__colors)

在返回对象的某个数据属性时,我们需要知道该属性是属于可变类型还是固定类型,返回固定类型总是安全的,因为这种类型的数据属性不能改变,但对于可变类型的数据属性,我们必须考虑某种折衷。返回对可变属性的引用是非常快速高效的,因为并没有实际的复制操作发生——但这也意味着调用者可以对对象的内部状态进行存取,并可能在对其改变时导致对象无效。一种解决方案是总是返回可变数据属性的副本,除非有证据表明这种做法会对性能产生很大的负面影响。(这种情况下,保证颜色集合有效的一种替代方法是在需要颜色集合时,总是返回set(self.__data.values()) | {self.__background}。)

#这一方法将返回给定坐标的颜色(使用项存取操作符[])

def__getitem__(self, coordinate):

assert len(coordinate) == 2, "coordinate should be a 2-tuple"

if (not (0 <= coordinate[0] < self.width) or not (0 <= coordinate[1] < self.height)):

raise CoordinateError(str(coordinate))

return self.__data.get(tuple(coordinate), self.__background)

下表列出了用于项存取操作符的特殊方法以及其他一些组合类型相关的特殊方法。

特殊方法 使用 描述

__contains__(self,x) x in y 如果x在序列y中或x是映射y中的键,就返回True

__delitem__(self,k) del y[k] 删除序列y中的第k项或映射y中键为k的项

__getitem__(self,k) y[k] 返回序列y中的第k项或映射y中键为k的项的值

__iter__(self) for x in y: pass 返回序列y中的项或映射y中键的迭代子

__len__(self) len(y) 返回y中项的个数

__reversed__(self) reversed(y) 返回序列y中的项或映射y中键的反向迭代子

__setitem__(self, k, v) y[k] = v 将序列y中的第k项(或映射y中键为k的项)设置为v

def__setitem__(self, coordinate, color):

assert len(coordinate) == 2, "coordinate should be a 2-tuple"

if (not (0 <= coordinate[0] < self.width) or not (0 <= coordinate[1] < self.height)):

raise CoordinateError(str(coordinate))

if color == self.__background:

self.__data.pop(tuple(coordinate), None)

else:

self.__data[tuple(coordinate)] = color

self.__colors.add(color)

我们对项存取釆用了两种策略。第一种策略的使用前提是,传递给项存取方法的坐标是长度为2的序列(通常是二元组),并使用断言来确保满足这一约束。第二种策略是接受任意的坐标值,如果超过取值范围,就产生自定义异常。

我们使用dict.get()方法来取回给定坐标的颜色(默认值为背景色),通过这一方法, 如果该坐标处的颜色尚未设置,就正确地返回背景色,而不是产生KeyError异常。

如果使用者将某坐标值设置为背景色,我们可以简单地将相应的字典项删除,因为字典中没有包含的坐标都假定使用背景色。为此,我们必须使用dict.pop()并给定另 一个单元参数,而不是使用del语句,因为这样可以保证在键(坐标)不存在于字典中的情况下,也可以避免产生KeyError异常。

如果颜色不同于背景色,我们就将给定的坐标指定为该颜色,并将该颜色添加到图像使用的颜色集中。

def __delitem__(self, coordinate):

assert len(coordinate) == 2, "coordinate should be a 2-tuple”

if (not (0 <= coordinate[0] < self.width) or not (0 <= coordinate!!] < self.height)):

raise CoordinateError(str(coordinate))

self.__data.pop(tuple(coordinate),None)

如果某坐标的颜色被删除,那么该坐标的颜色被设置为背景色。这里仍然需要使用dict.pop()来移除相应项,因为不管给定的坐标项是否存在于字典中,这一方法总是可以正确工作,而不会产生异常。

我们没有提供__len__()的实现,因为对二维对象而言,这一函数没有实际意义。 另外,我们也没有提供表象形式,因为Image不能仅通过调用Image()就完全实现,因 此,我们也没有提供__repr__()(或__str__())的实现。如果用户对Image对象调用repr() 或str(),那么object.__repr__()这一基类实现将返回适当的字符串,比如'<Image.Image object at 0x9c794ac>'。这是用于无法使用eval()评估的对象的标准格式,十六进制数字是对象ID—这是独一无二的(通常是对象在内存中所在地址),但是短暂的。

我们希望Image类的使用者可以保存与加载其图像数据,因此,我们提供了两个方法来完成这两个任务,分别为save()与load()。

为保存数据,我们采用的做法是对其进行pickling,在Python中,pickling是将 Python对象进行序列化(转换为字节序列,或转换为字符串)的一种方法。pickling 之所以强大,是因为进行pickling处理的对象可以是组合数据类型,比如列表或字典, 并且,即便要进行pickling处理的对象内部包含其他对象(包含其他组合类型,其中又可以嵌套地包含其他组合类型),仍然可以统一进行pickling处理——并且不会使得对象重复出现。

pickle可以直接地读入到Python变量中——我们并不需要进行任何分析或解释处理,因此,使用pickle是存取与加载数据的特别集合时的一种理想选择,尤其对小程序以及个人使用的程序。然而,pickle没有安全机制(没有加密,也没有数字签名),因此,加载来自不可信源的pickle可能是危险的。鉴于此,对那些不纯粹用于个人目的的程序,最好是创建一种专用于该程序的自定义文件格式。

def save(self, filename=None):

if filename is not None:

self.filename = filename

if not self.filename:

raise NoFilenameError()

fh = None

try:

data = [self.width, self.height, self.__background,self.__data]

fh = open(self.filename, "wb")

pickle.dump(data, fh, pickle.HIGHEST_PROTOCOL)

except (EnvironmentError, pickle.PicklingError) as err:

raise SaveError(str(err))

finally:

if fh is not None:

fh.close()

该函数的第一部分纯粹用于处理文件名。如果创建Image对象时没有指定文件名或没有设置文件名,就必须对save()方法给定显式的文件名(对这种情况,可以釆用 “另存为"的方式,并设置内部使用的文件名)。如果没有指定文件名,就使用当前的文件名;如果没有当前使用的文件名,也没有指定文件名,就会产生异常。我们创建一个列表(data)用于存放待存储的对象,包括self.__data字典(其中存放坐标-颜色项),但不包含颜色集,因为该数据集可能被重构。之后,我们以二进制写模式打开文件,并调用pickle.dump()函数将数据对象写入到文件中。这一部分功能实现完毕!pickle模块可以使用多种格式(在文档中称为protocols)对数据进行序列化,不同格式由pickle.dump()的第三个参数指定。Protocol 0表示的是ASCII,在调试时是有用的。我们使用了 protocol 3 (pickle.HIGHEST_PROTOCOL), 一种紧凑的二进制格式,这也是为什么要以二进制格式打开文件。

def load(self, filename=None):

if filename is not None:

self.filename = filename

if not self.filename:

raise NoFilenameError()

fh = None

try:

fh = open(self.filename,"rb")

data = pickle.load(fh)

(self.__width, self.__height, self.__background,self. __data) = data

self.__colors = (set(self.__data.values()) |{self.__background})

except (EnvironmentError, pickle.UnpicklingError) as err:

raise LoadError(str(err))

finally:

If fh is not None:

fh.dose()

读取pickle时,没有指定protocol——pickle.load()函数完全可以推断出用于自己的protocol。

这一函数开始时与save()函数类似,获取待加载文件的文件名。文件必须以二进制读模式打开,并使用语句data = pickle.load(fh)读入数据。data对象是对保存的数据的精确重建,因此,这里实际上是一个列表,包括宽度与长度整数值、背景色字符串以及包含坐标-颜色项的字典。我们使用元组拆分来将data列表的项赋值到适当的变量中,因此,此前存放的图像数据将丢失(可接受的)。

颜色集合的重建是通过提取字典(包含坐标-颜色项)中的所有颜色,之后添加背景色实现的。

def export(self, filename):

if filename.lower().endswith(".xpm"):

self.__export_xpm(filename)

else:

raise ExportError("unsupported export format:" +os.path.splitext(filename)[1])

我们提供了一个通用的导出方法,该方法使用文件扩展名确定调用哪一个私有方 法一对无法导出的文件格式,则产生异常。这里,我们仅支持保存到.xpm文件(仅对包含少于8930种颜色的图像)。我们没有给出__export_xpm()方法,因为该方法并不与本章的主题真正相关,但本书源代码中仍然包含了该方法。

以上内容部分摘自视频课程05后端编程Python14图形处理类,更多实操示例请参照视频讲解。跟着张员外讲编程,学习更轻松,不花钱还能学习真本领。

发表回复