Python 高级编程 026:内置数据结构之骈文纵论

Python 高级编程 026:内置数据结构之骈文纵论

❖ 序言 ❖


✦ 盖闻编程之道,贵在择器 ✦

✦ 数据之构,关乎效能 ✦

✦ 世人皆好列表之便捷,鲜察数组之精深 ✦

✦ 今作此文,以骈俪之体,论Python内置结构之优劣 ✦


Bilibili 同步视频

Python 高级编程 026:内置数据结构之骈文纵论


❖ 第一章 ❖

✦ 列表之弊,人所共知 ✦

━━━━━━━━━━━━━━━━━━━━━━━━

「列表者,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之妙 ✧


✦ 本文完 ✦

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

✧ 原创不易,若觉有用,还望点赞收藏 ✧

✧ 您的支持,是我持续创作的动力 ✧

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

相关推荐
程序员老舅7 小时前
啃透 I2C 驱动开发,才算入门嵌入式 Linux 内核驱动
数据结构·驱动开发·b树·内核·嵌入式·嵌入式开发·i2c
DLYSB_8 小时前
存储运维实战:基于 Ceph Event 监听与 Python 适配器的分布式存储健康度物理声光响应架构
运维·ceph·python·报警灯
阿童木写作8 小时前
跨境图片翻译工具多合一,批量图片视频字幕翻译加智能抠图
人工智能·python·音视频·语音识别
隐积9 小时前
3.1.7.Qt容器大家族(3):QVariant——万能盒子
开发语言·qt
阿童木写作10 小时前
Python实现Temu图片批量翻译自动化教程
运维·人工智能·python·自动化
就是一只白11 小时前
复制地理信息python版本
python
看浪的路人11 小时前
第1讲:Agent 到底是什么?和 Chatbot 有什么区别
python·ai
名字还没想好☜11 小时前
Next.js ‘use client‘ 到底加在哪:Server/Client Components 边界与常见报错
开发语言·前端·javascript·react·next.js
瓦学妹12 小时前
什么是网络索引?它是如何工作的?
大数据·网络·python·网络爬虫