《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.6 享元模式

简介:

本节书摘来自华章出版社《Python编程实战:运用设计模式、并发和程序库创建高质量程序》一 书中的第2章,第2.6节,作者:(美) Mark Summerfield,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.6 享元模式

如果有许多比较小的对象需要处理,而这些小对象很多又彼此相同,那么就可以使用“享元模式”(Flyweight Pattern)。该模式的实现方式为:只给每种对象创建一个实例,并在有需要时共享此实例。
由于Python使用对象引用,所以很自然地体现出了享元模式的思路。比方说,如果有个字符串列表很长,而且其中许多字符串都一样,那么,使用对象引用(也就是变量)来存储要比直接使用“字面量字符串”(literal string)存储节省许多内存。
screenshot

在上述代码片段中,x元组用8个对象引用来保存3个字符串,而y元组则用8个对象引用保存了8个字符串,因为这种简化的写法实际上与_anonymous_item0 = "red", ..., _anonymous_item7 = "green"; y = (_anonymous_item0, ... _anonymous_item7)等效。
要想在Python语言中利用享元模式,最简单的办法可能就是使用dict了,它可以把每个值都同独特的键关联起来。比方说,在创建大量HTML页面时,我们想根据CSS(Cascading Style Sheets,层叠样式表)来指定“字型”(font),这样就不用每次都创建新字型了,而是可以预先(或在首次使用时)把它们保存在dict里面。等需要使用某个字型时,再将其从dict里取出来。这样就能保证每种字型无论使用多少次,都只会创建一次。
有时我们需要处理大量对象,而其中绝大部分或所有对象都互不相同,并且这些对象未必很小。在这种情况下,有个简单的办法可以降低内存用量,这就是使用__slots__。
screenshot

上面这个简单的Point类可以存放点的三维坐标及颜色。由于用了__slots__,所以Point实例都没有自己的dict(也就是没有self.__dict__)。然而,这样做同时也意味着不能向单个对象中随意添加attribute。(该类代码节选自pointstore1.py。)
在某台电脑中测试时,用上述代码创建含有100万个点的元组(测试程序几乎没有做其他事情)需要2.5秒,并占用183MiB内存。若是不用__slots__,那么执行时间能少零点几秒,但内存占用量却高达312MiB。
默认情况下,Python总是会花更多内存来提升执行速度,但如果有必要的话,通常也可以反过来做,那就是通过降低执行速度来节省内存用量。
screenshot

上面列出了第二版Point类的前几行代码(该类节选自pointstore2.py)。它用DBM(键值对)数据库来保存数据,而数据库本身则存放在磁盘文件中。指向DBM的对象引用保存在静态的(也就是类级别的)Point.__dbm变量里。所有Point实例都使用同一份底层DBM文件。我们首先要打开DBM文件,以便后续使用。shelve模块的默认做法是:如果没发现相关的DBM文件,那就自动创建一份。(稍后我们将演示如何保证DBM文件能正常关闭。)
在存储值时,shelve模块会将其“序列化”(pickle),而在获取值时,则会将其“反序列化”(unpickle)。(由于在反序列化的过程中能够执行任意Python代码,所以Python的序列化格式是不安全的。因此,切勿使用由不可信的数据源所提供的序列化数据,也不要将无访问限制的数据序列化。如果想在这些情况下使用序列化数据,那么可通过“校验和”(checksum)及“加密”(encryption)等自制的安全措施来保证数据安全。)
screenshot

上述方法的代码与pointstore1.py中的完全相同,但这些值都会存储到底层的DBM文件里。
screenshot

上述方法可提供Point实例中x、y、z、color等attribute的“键字符串”(key string)。这个键由实例的十六进制ID(ID是由Python语言内置的id()函数所返回的独特数字)及attribute名构成。比方说,某个Point实例的ID是3?954?827,那么其x值所对应的键字符串就是“3C588B:x”,而y值则可由“3C588B:y”这个键查到,其余attribute亦是如此。
screenshot

上述方法会在访问Point对象的某个attribute时(比如执行x = point.x时)调用。
DBM数据库的键与值必须是bytes。好在Python的DBM模块可以接受str或bytes作键,遇到str时,会用默认的UTF-8编码将其转换为bytes。如果像本例一样使用shelve模块,那么凡是可以序列化的值就都能保存到数据库里,因为shelve模块会根据情况在其他类型与bytes之间相互转换。
上面两个方法使我们能够获得与attribute相关的键,并根据键来查出attribute值。另外,由于使用了shelve模块,所以获取到的值会从序列化的bytes自动转换成原本的类型(比方说,获取到的点颜色值会是int或None类型)。
screenshot

设置Point的attribute时(例如执行point.y = y时)会调用上述方法。在该方法中,我们会根据键查出相关的attribute值,并通过shelve模块把值序列化为bytes。
screenshot

在Point类最后,我们通过atexit模块的register()函数来注册DBM的close()方法,使得程序在终止时能够调用此方法。
在某台测试机上创建含有100万个点的数据库大约需要一分钟时间,但程序只占用29MiB内存(外加361MiB的磁盘文件),而第一版程序则要占用183MiB内存。尽管生成DBM文件确实需要一些时间,但只要生成好了,查询速度就会很快,因为大部分操作系统都会把频繁使用的磁盘文件缓存起来。

相关文章
|
13天前
|
设计模式 存储 Java
23种设计模式,享元模式的概念优缺点以及JAVA代码举例
【4月更文挑战第6天】享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享技术有效地支持大量细粒度对象的重用。这个模式在处理大量对象时非常有用,特别是当这些对象中的许多实例实际上可以共享相同的状态时,从而可以减少内存占用,提高程序效率
31 4
|
1月前
|
Python
如何使用Python编写一个简单的计算器程序
如何使用Python编写一个简单的计算器程序
37 0
|
1月前
|
算法 安全 调度
解决Python并发访问共享资源引起的竞态条件、死锁、饥饿问题的策略
解决Python并发访问共享资源引起的竞态条件、死锁、饥饿问题的策略
25 0
|
1月前
|
Linux 数据安全/隐私保护 iOS开发
python如何将程序编译成exe
python如何将程序编译成exe
32 0
|
30天前
|
存储 算法 数据处理
使用Python编写高效的数据处理程序
在当今信息爆炸的时代,数据处理变得越来越重要。本文将介绍如何使用Python语言编写高效的数据处理程序,包括利用Python内置的数据结构和函数、优化算法和并行处理等技术,帮助开发者更好地处理和分析大规模数据。
|
4天前
|
设计模式 存储 Java
小谈设计模式(27)—享元模式
小谈设计模式(27)—享元模式
|
17天前
|
分布式计算 算法 搜索推荐
优化 Python 程序的五大技巧
本文介绍了优化 Python 程序的五大技巧,涵盖了代码结构优化、算法选择、内置函数利用、库的使用以及并行处理等方面。通过对这些技巧的实践,可以提升 Python 程序的性能和效率,从而更好地满足各类应用的需求。
|
29天前
|
存储 数据库连接 数据处理
Python语言的程序框架
Python语言的程序框架
|
1月前
|
设计模式 缓存 Java
设计模式之享元模式
设计模式之享元模式
|
1月前
|
SQL 安全 测试技术
如何在 Python 中进行 Web 应用程序的安全性管理,例如防止 SQL 注入?
如何在 Python 中进行 Web 应用程序的安全性管理,例如防止 SQL 注入?
15 0