You need to enable JavaScript to run this app. 基于《Fluent Python》的序列定义,列表与数组存储值的方式有何差异?
优惠活动
大模型
产品
解决方案
定价
更多

基于《Fluent Python》的序列定义,列表与数组存储值的方式有何差异?

容器序列与扁平序列的存储逻辑差异

容器序列(以list为例)的存储本质

普通list的内部内存结构,本质是一块连续的指针数组,每个槽位仅存储指向堆内存中独立Python对象的8字节引用(64位系统下),本身不存任何元素的实际值。
举个实际存储场景:
当你创建demo_list = [1, 2, 3, 4]时,CPython运行时会完成这些操作:

  • 单独在堆上为每个整数1、2、3、4创建独立的Python int对象(每个对象约占28字节内存,包含对象头、类型标记、实际数值)
  • 给list分配一块连续内存,依次存入这四个int对象的内存地址,也就是指针
  • 整个存储过程中,list和各个int对象的内存是完全分离的,list必须通过指针才能找到对应元素的实际值。

这种结构的优势是灵活性极强:你可以把任意类型的对象塞进list的槽位里,因为槽位存的只是内存地址,不管指向什么类型的对象都能兼容。这也是为什么list可以混合存储不同类型元素的底层原因。

扁平序列(以array.array为例)的存储方式

array这类扁平序列的存储逻辑,完全跳过了“独立堆对象+指针引用”的中间层:

  1. 初始化时必须显式指定元素的固定类型(比如array.array('f', ...)指定存单精度浮点数、array.array('u', ...)存Unicode字符),运行时会根据这个类型确定每个元素的固定内存宽度。
  2. 序列本身的内存块除了极少量的元数据(比如长度、类型编码),后续的连续内存空间直接按照定长宽度,物理写入每个元素的原始二进制值。

同样存4个32位整数的场景下,创建demo_array = array.array('i', [1,2,3,4])时:

  • 不会为1、2、3、4创建任何独立的Python int对象
  • 直接在array的连续内存块里,按4字节一个的宽度,依次写入这四个整数的二进制补码值:第一个4字节存1的二进制,第二个存2,第三个存3,第四个存4
  • 整个值存储过程完全在array自己的内存空间内完成,没有任何跨内存块的指针跳转。

这也是为什么同样存一组整数,array的内存占用会比list低一个数量级的核心原因——list的近一半内存都花在了维护独立对象和指针对象上。

“物理存储实际值”的具体含义

书中的表述可以拆解为两个和容器序列的核心区别:

  • 无独立元素对象的长期持有:array本身不会长期维护一堆独立Python对象的引用,只有当你主动从array中读取某个元素时,运行时才会临时根据内存块里存的原始二进制值,生成一个对应的Python对象(比如读取到的1,才会创建一个临时的Python int实例),这个临时对象和array本身没有长期的绑定关系,读取完成后就可以被正常回收。
  • 直接的内存定位逻辑:因为元素类型固定、宽度固定,定位元素时不需要顺着指针跳转,只需要通过起始内存地址 + 偏移量 * 固定元素宽度的公式,就能直接在array自己的内存块里读取到元素的原始值,全程不需要访问其他区域的内存。

这种结构的代价是灵活性极低:因为元素宽度从初始化时就定死了,无法存入变长的字符串、自定义实例这类非定长的内容,更不能在同一块内存里混存不同宽度的类型。

其他扁平序列的共性

书中提到的str、bytes、bytearray、memoryview都遵循这个存储逻辑:

  • str和bytes的内存块直接连续存储字符、字节的编码值,不会为每个单独的字符创建独立的小对象
  • bytearray是可修改的版本,存储逻辑和bytes完全一致
  • memoryview本身不持有独立数据,只是对其他扁平序列的原始值内存块做的可操作视图,自然也没有引用层的结构。

内容的提问来源于stack exchange,提问作者pangna

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.09 08:45:04