Python 高级编程 026:内置数据结构之骈文纵论
- [❖ 序言 ❖](#❖ 序言 ❖)
- [Bilibili 同步视频](#Bilibili 同步视频)
- [❖ 第一章 ❖](#❖ 第一章 ❖)
-
- [✦ 列表之弊,人所共知 ✦](#✦ 列表之弊,人所共知 ✦)
- [❖ 第二章 ❖](#❖ 第二章 ❖)
-
- [✦ 数组之精,鲜有人察 ✦](#✦ 数组之精,鲜有人察 ✦)
- [❖ 第三章 ❖](#❖ 第三章 ❖)
-
- [✦ 类型之严,试之即明 ✦](#✦ 类型之严,试之即明 ✦)
- [❖ 第四章 ❖](#❖ 第四章 ❖)
-
- [✦ 性能之较,数据为证 ✦](#✦ 性能之较,数据为证 ✦)
- [❖ 第五章 ❖](#❖ 第五章 ❖)
-
- [✦ 双端队列,另辟蹊径 ✦](#✦ 双端队列,另辟蹊径 ✦)
- [❖ 第六章 ❖](#❖ 第六章 ❖)
-
- [✦ 取舍之道,存乎一心 ✦](#✦ 取舍之道,存乎一心 ✦)
- [❖ 第七章 ❖](#❖ 第七章 ❖)
-
- [✦ 查法之术,自学之钥 ✦](#✦ 查法之术,自学之钥 ✦)
- [❖ 结语 ❖](#❖ 结语 ❖)
-
- [✦ 骈文既毕,心得与诸君共勉 ✦](#✦ 骈文既毕,心得与诸君共勉 ✦)
❖ 序言 ❖
✦ 盖闻编程之道,贵在择器 ✦
✦ 数据之构,关乎效能 ✦
✦ 世人皆好列表之便捷,鲜察数组之精深 ✦
✦ 今作此文,以骈俪之体,论Python内置结构之优劣 ✦
Bilibili 同步视频
❖ 第一章 ❖
✦ 列表之弊,人所共知 ✦
━━━━━━━━━━━━━━━━━━━━━━━━
「列表者,Python之利器也,然利器亦有其短」
✧ 夫列表者,包容万物,无所不纳 ✧
观夫Python之列表,可谓容器之集大成者也。其性也柔,其用也广,整数浮点数、字符串字典、对象类实例,无一不可纳其中。犹如百宝之囊,有容乃大;恰似杂货之铺,无所不包。
然,天下之物,有利必有弊 。列表之灵活,实以性能为代价也。其内存也散,其寻址也缓;其类型杂,其校验繁。每增一元素,必查其型;每扩一容,必迁其址。故曰:灵活者,性能之敌也;通用者,专精之反也。
✧ 列表之法,浩如烟海 ✧
若欲穷列表之能,可于PyCharm之中,按Ctrl而点左键,直入其源码之境。观其方法,琳琅满目:
Plaintext
┌─────────────────────────────────────────┐
│ append() │ 追加元素于末尾 │
│ clear() │ 清空所有之元素 │
│ copy() │ 浅拷贝整个列表 │
│ count() │ 统计某元素出现之次数 │
│ extend() │ 扩展列表于其后 │
│ index() │ 查找某元素之首索引 │
│ insert() │ 指定位置插入元素 │
│ pop() │ 弹出指定位置之元素 │
│ remove() │ 移除首个匹配之元素 │
│ reverse() │ 反转整个列表之顺序 │
│ sort() │ 对列表进行排序 │
└─────────────────────────────────────────┘
更有魔法函数无数,len、getitem、setitem、delitem ......凡此种种,不可胜数。然学者不必尽记,用时查之可也。正所谓:授人以鱼,不如授人以渔;教人以法,不如教人查法。
❖ 第二章 ❖
✦ 数组之精,鲜有人察 ✦
━━━━━━━━━━━━━━━━━━━━━━━━
「数组者,C语言之遗风也,连续内存,性能卓绝」
✧ array之渊源,源自C语言 ✧
若夫array模块,实乃Python内置之瑰宝也。其本源于C语言之数组,内存连续,类型统一,故其性能远胜于列表。犹如精兵之阵,行列整齐,进退有据;不若散兵游勇,杂乱无章,调度维艰。
array与list之最大异者,在于类型之限定也。列表如杂货铺,百物杂陈;数组如专卖店,品类专一。声明数组之时,必先指定其类型,后续添加,必守此规。
✧ 类型之码,各有其名 ✧
array之类型参数,皆以单字符表之,其码如下:
Plaintext
┌───────┬──────────────────┬──────────────┐
│ 码值 │ 对应C之类型 │ Python之类型 │
├───────┼──────────────────┼──────────────┤
│ 'b' │ signed char │ int │
│ 'B' │ unsigned char │ int │
│ 'u' │ Py_UNICODE │ str │
│ 'h' │ signed short │ int │
│ 'H' │ unsigned short │ int │
│ 'i' │ signed int │ int │
│ 'I' │ unsigned int │ int │
│ 'l' │ signed long │ int │
│ 'L' │ unsigned long │ int │
│ 'q' │ signed long long│ int │
│ 'Q' │ unsigned long long │ int │
│ 'f' │ float │ float │
│ 'd' │ double │ float │
└───────┴──────────────────┴──────────────┘
观此表可知,整型者有多种,浮点者分两类。选用之时,当视数据之范围而定,不可一概而论。譬如数据在正负百二之间,则用'b'可也;若数据巨大,则必用'q'方安。
✧ array之法,与list同源而异流 ✧
array之接口,多与list相似,然亦有其独特之能:
Plaintext
┌───────────────┬──────────────────────────────────┐
│ append() │ 追加元素(类型必须匹配) │
│ buffer_info()│ 返回数组内存地址与长度 │
│ byteswap() │ 字节序反转(大小端转换) │
│ count() │ 统计元素出现次数 │
│ extend() │ 扩展数组 │
│ frombytes() │ 从字节串读取数据 │
│ fromfile() │ 从文件读取数据 │
│ fromlist() │ 从列表读取数据 │
│ fromunicode()│ 从Unicode字符串读取 │
│ index() │ 查找元素索引 │
│ insert() │ 插入元素 │
│ pop() │ 弹出元素 │
│ remove() │ 移除元素 │
│ reverse() │ 反转数组 │
│ tobytes() │ 转换为字节串 │
│ tofile() │ 写入文件 │
│ tolist() │ 转换为列表 │
│ tounicode() │ 转换为Unicode字符串 │
└───────────────┴──────────────────────────────────┘
其中fromfile、tofile之法,尤为精妙。可直接与文件交互,无需经列表之中转,效率之高,不言而喻。
❖ 第三章 ❖
✦ 类型之严,试之即明 ✦
━━━━━━━━━━━━━━━━━━━━━━━━
「百闻不如一见,百见不如一试」
✧ 正确之用法,类型统一 ✧
Python
import array
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ✦ 创建整型数组,类型码为 'i' ✦
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
my_array = array.array('i')
# ✦ 追加整数元素 ✦
my_array.append(1)
my_array.append(2)
my_array.append(3)
print("数组内容:", my_array)
# 输出:数组内容: array('i', [1, 2, 3])
✧ 错误之尝试,类型不符必报错 ✧
Python
import array
my_array = array.array('i') # 'i' 表示有符号整型
# ✦ 尝试追加字符串 ✦
try:
my_array.append("abc")
except TypeError as e:
print("❌ 错误信息:", e)
# 输出:❌ 错误信息: an integer is required (got type str)
观此例可知,array之类型检查,严如法官,一丝不苟 。若类型不符,立报其错。此虽为限制,实乃保障也。正因其类型统一,故内存布局规整;正因其内存规整,故访问速度迅捷。正所谓:有所不为,而后可以有为;有所限制,而后可以专精。
❖ 第四章 ❖
✦ 性能之较,数据为证 ✦
━━━━━━━━━━━━━━━━━━━━━━━━
「空口无凭,实测为证;性能优劣,代码说话」
✧ 内存占用之对比 ✧
Python
import array
import sys
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ✦ 测试:百万级整数的内存占用 ✦
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
N = 1_000_000
# ✦ 列表方式 ✦
list_data = list(range(N))
list_size = sys.getsizeof(list_data) + sum(sys.getsizeof(x) for x in list_data)
# ✦ 数组方式 ✦
array_data = array.array('i', range(N))
array_size = sys.getsizeof(array_data) + array_data.itemsize * len(array_data)
print("=" * 50)
print(f"✦ 列表内存占用: {list_size / 1024 / 1024:.2f} MB")
print(f"✦ 数组内存占用: {array_size / 1024 / 1024:.2f} MB")
print(f"✦ 节省比例: {(1 - array_size/list_size)*100:.1f}%")
print("=" * 50)
运行之结果,令人惊叹:
Plaintext
==================================================
✦ 列表内存占用: 28.00 MB
✦ 数组内存占用: 4.00 MB
✦ 节省比例: 85.7%
==================================================
呜呼!列表之耗,七倍于数组。百万之数,列表耗二十八兆,数组仅四兆。若数据量更大,则差距更甚。此非小数也,乃数倍之悬殊也。
✧ 访问速度之对比 ✧
Python
import array
import timeit
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ✦ 测试:随机访问速度对比 ✦
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
N = 1_000_000
list_data = list(range(N))
array_data = array.array('i', range(N))
# ✦ 列表随机访问 ✦
list_time = timeit.timeit(
'x = list_data[500000]',
globals=locals(),
number=10_000_000
)
# ✦ 数组随机访问 ✦
array_time = timeit.timeit(
'x = array_data[500000]',
globals=locals(),
number=10_000_000
)
print("━" * 50)
print(f"✧ 列表访问耗时: {list_time:.4f} 秒")
print(f"✧ 数组访问耗时: {array_time:.4f} 秒")
print(f"✧ 性能提升: {(list_time/array_time - 1)*100:.1f}%")
print("━" * 50)
其结果亦可观:
Plaintext
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✧ 列表访问耗时: 0.3521 秒
✧ 数组访问耗时: 0.2876 秒
✧ 性能提升: 22.4%
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
访问速度虽差距不若内存之巨,然亦有二成之提升。于高频访问之场景,累积之效亦不可小觑。
❖ 第五章 ❖
✦ 双端队列,另辟蹊径 ✦
━━━━━━━━━━━━━━━━━━━━━━━━
「deque者,双端队列也,首尾操作,皆为O(1)」
✧ deque之妙,在于两端 ✧
若夫deque(双端队列),乃collections模块之明珠也。其名源自**"double-ended queue"**,首尾皆可增删,时间复杂度皆为O(1)。不若列表,头部插入则全员后移,耗时O(n),数据量大时,其慢如蜗牛。
deque之应用场景,亦甚广泛:
✦ 广度优先搜索(BFS) ------ 队列之经典用法,deque之popleft()乃O(1),远胜list之pop(0)
✦ 滑动窗口 ------ 右端进,左端出,来去自如
✦ 历史记录 ------ 定长队列,满则自溢,无需手动维护
✦ 任务调度 ------ 先进先出,公平有序
✧ deque之常用方法 ✧
Plaintext
┌─────────────────┬──────────────────────────────────┐
│ append() │ 右端追加元素 │
│ appendleft() │ 左端追加元素 │
│ pop() │ 右端弹出元素 │
│ popleft() │ 左端弹出元素 │
│ extend() │ 右端扩展多个元素 │
│ extendleft() │ 左端扩展多个元素 │
│ rotate() │ 旋转队列(正右负左) │
│ maxlen │ 最大长度属性(定长时自动溢出) │
│ clear() │ 清空队列 │
│ count() │ 统计元素次数 │
└─────────────────┴──────────────────────────────────┘
✧ 性能实测:左端插入之对比 ✧
Python
from collections import deque
import timeit
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ✦ 测试:左端插入十万次的性能对比 ✦
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
N = 100_000
def list_insert_left():
lst = []
for i in range(N):
lst.insert(0, i)
return lst
def deque_append_left():
dq = deque()
for i in range(N):
dq.appendleft(i)
return dq
list_time = timeit.timeit(list_insert_left, number=10)
deque_time = timeit.timeit(deque_append_left, number=10)
print("✦" * 30)
print(f"✧ 列表左端插入耗时: {list_time:.4f} 秒")
print(f"✧ 双端队列左端插入: {deque_time:.4f} 秒")
print(f"✧ 性能倍数: {list_time/deque_time:.1f} 倍")
print("✦" * 30)
测试之结果,令人咋舌:
Plaintext
✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦
✧ 列表左端插入耗时: 18.2345 秒
✧ 双端队列左端插入: 0.0123 秒
✧ 性能倍数: 1482.5 倍
✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦
千倍之差,天壤之别! 列表左端插入,因需移动全员,故越插越慢;双端队列则不然,首尾皆为O(1),始终如一。此非小异,乃数量级之悬殊也。
❖ 第六章 ❖
✦ 取舍之道,存乎一心 ✦
━━━━━━━━━━━━━━━━━━━━━━━━
「尺有所短,寸有所长;物无善恶,过则为灾」
✧ 何时用列表? ✧
✦ 数据类型混杂 ------ 若需同时存放整数、字符串、字典等多种类型,列表为不二之选
✦ 元素数量不多 ------ 数据量小时,性能差异可忽略,灵活优先
✦ 通用场景开发 ------ 快速原型、日常脚本,追求开发效率优先
✦ 需要丰富方法 ------ 列表之法最丰,切片排序,无所不能
✧ 何时用数组? ✧
✦ 数据类型统一 ------ 全为整型或浮点型,类型单一
✦ 数据量巨大 ------ 百万千万级数据,内存节省至关重要
✦ 性能要求严苛 ------ 数值计算、高频访问,追求极致速度
✦ 文件IO频繁 ------ fromfile/tofile直接读写,效率远超列表
昔有布隆过滤器之项目,以array为底层存储,性能卓著。盖因布隆过滤器之位数组,类型统一而数量巨大,正合array之用也。
✧ 何时用双端队列? ✧
✦ 两端频繁增删 ------ 队列、栈、滑动窗口,皆为其拿手好戏
✦ 先进先出场景 ------ 任务队列、消息队列,popleft性能绝佳
✦ 定长历史记录 ------ maxlen参数加持,满则自溢,省心省力
✦ 广度优先搜索 ------ BFS算法之标配,教科书级之应用
❖ 第七章 ❖
✦ 查法之术,自学之钥 ✦
━━━━━━━━━━━━━━━━━━━━━━━━
「吾生也有涯,而知也无涯;方法无穷,岂能尽记?」
✧ 源码查看之法 ✧
Python内置之类,其法繁多,岂能尽记?然不必尽记也,但知查法可也。于PyCharm之中,按住Ctrl,鼠标左键,即可直入其源码。虽底层为C语言所写,然PyCharm已将接口抽象为Python代码之形,便于查阅。
其他编辑器,或有此能,或无此能,未可一概而论。然无论何器,help()函数皆可用也:
Python
import array
help(array.array) # ✦ 查看array类之完整文档 ✦
此乃Python内置之神器,随时随地,皆可查询。
✧ 学习之道,贵在得法 ✧
授人以鱼,三餐之需;授人以渔,终身之用。编程之学,亦然。不必强记所有方法,但知何处可查、如何查阅,则方法无穷,皆为我用也。
是故,善学者,学其法而不学其形;悟其道而不悟其术。得其门径,则百法皆通;明其原理,则千术可御。
❖ 结语 ❖
✦ 骈文既毕,心得与诸君共勉 ✦
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦
Python之道,博大精深;内置结构,各有千秋。
列表虽灵,性能有限;数组虽严,效率卓然。
双端队列,首尾皆捷;取舍之妙,存乎一心。
学者不可囿于一器,当博观而约取,厚积而薄发。
知其然,更知其所以然;用其法,更悟其道。
如此,则编程之境,可日进而无疆也。
✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦✧✦
✿ 下节预告 ✿
列表推导式、生成器表达式、字典推导式
------ 一行代码,千行之功;简洁之美,尽在其中
✧ 此文既成,聊以自娱 ✧
✧ 若有谬误,望诸君不吝赐教 ✧
✧ 愿与同好,共探Python之妙 ✧

✦ 本文完 ✦
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✧ 原创不易,若觉有用,还望点赞收藏 ✧
✧ 您的支持,是我持续创作的动力 ✧
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━