MicroPython array 模块完全指南:数值型数组的类型码、缓冲协议与内存高效用法
2026/9/20 22:36:09 网站建设 项目流程
  • 嵌入式
  • 语言运行时
  • 编程语言
  • 解释器
  • 编译器
  • 物联网
  • 系统编程

【免费下载链接】micropython

MicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems

项目地址:https://gitcode.com/gh_mirrors/mi/micropython
点击查看免费下载

导读

array是 MicroPython 内置的数值型数组模块,它用 C 语言原生的连续内存块存储同类型元素,相比 Python 列表(list)在内存占用上显著更小、访问速度更快,非常适合微控制器这类内存受限(constrained systems)环境下的数据采集、传感器读数缓冲、二进制协议组包/解析等场景。本文以 docs/library/array.rst 为骨架,结合源码实现与官方测试用例,系统讲解array的类型码(typecode)、构造方式、读写方法、切片语义、缓冲协议互操作,以及运算符与比较规则,帮助你写出内存友好且行为正确的 MicroPython 数组代码。

模块概览:与 CPythonarray的对应关系

array模块在 MicroPython 中与 CPython 标准库同名模块相对应,__name__"array",对外仅暴露一个工厂类array。从源码看,模块本身非常精简:py/modarray.c 中模块全局表只注册了array一个符号,并通过MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_array, mp_module_array)注册为可扩展模块;而类型array的完整行为(构造、打印、迭代、下标、缓冲等)全部实现在 py/objarray.c 的mp_type_array类型对象中,并与bytearraymemoryview共享大量实现代码。

支持的格式类型码与官方文档一致,共 12 种:

类型码C 类型字节大小有符号
bsigned char1
Bunsigned char1
hsigned short2
Hunsigned short2
isigned int4
Iunsigned int4
lsigned long4
Lunsigned long4
qsigned long long8
Qunsigned long long8
ffloat(单精度)4依赖浮点支持
ddouble(双精度)8依赖浮点支持

上述字节大小依据 py/binary.c 中mp_binary_get_size()'@'(native 对齐)分支:b/B为 1 字节,h/Hsizeof(short)i/Isizeof(int)l/L为 4 字节,q/Q为 8 字节。其中fd两个浮点类型码取决于固件是否启用浮点支持(MicroPython 存在MICROPY_FLOAT_IMPL_NONE的无浮点构建);若编译配置未开启浮点,使用f/d会失败。q/Q是否可用还受目标平台整数宽度影响,在纯 32 位平台上按 8 字节布局存储。

注意:MicroPython 的l/L固定为 4 字节(而非某些平台上 Clong的 8 字节),这是与 CPython 的一个细微差异点。

构造array(typecode, [iterable])

array构造函数的原型为array(typecode, [iterable])

  • 第一个参数typecode必填,为上述类型码字符(字符串);
  • 第二个参数iterable可选,提供初始元素;省略时创建空数组。

从实现看,array_make_new()(py/objarray.c)在只传一个参数时直接分配一个长度为 0 的空数组;传入两个参数时调用array_construct()(py/objarray.c)完成构造。array_construct()有一条特殊的“原始拷贝”(raw copy)路径:当初始值是一个bytesbytearray对象且其缓冲区长度是元素大小的整数倍时,MicroPython 直接memcpy底层字节,不做逐元素转换——这意味着array('h', b'\x01\x00\x02\x00')这类写法是允许的,并且与字节序无关(原样复制)。官方测试 tests/basics/array_construct.py 明确验证了从bytes/bytearray的原始拷贝行为。

常见构造示例:

from array import array # 空数组 empty = array('h') print(len(empty)) # 0 # 从列表构造 a = array('B', [1, 2, 3]) print(a, len(a)) # array('B', [1, 2, 3]) 3 # 从元组构造 b = array('b', (1, 2)) # 从 bytes 原始拷贝(字节序中立) c = array('h', b'\x01\x00\x02\x00') # 2 个元素,原样复制 # 从其他 array 转换 d = array('H', array('b', [1, 2])) # 从 bytearray 构造 e = array('i', bytearray(4)) # 1 个 4 字节 int 元素,值为 0

array_construct()mp_obj_len_maybe(initializer)会先尝试取得初始值的长度以便一次性预分配底层缓冲区(py/objarray.c),对于已知长度的可迭代对象(如列表、元组、另一个数组)可以避免多次扩容重分配,这是写内存高效代码时值得利用的特性。

核心方法:appendextend

append(val)

在数组末尾追加一个新元素并自动扩容,返回None。底层实现在 py/objarray.c:当空闲容量(free)耗尽时,会以“每次额外多分配 8 个元素”的策略m_renew重新分配内存(即 amortized 扩容,避免频繁 realloc),然后通过mp_binary_set_val_array()把 Python 值按类型码编码写入。因此append会做隐式类型转换,array('B').append(300)之类的越界值会被按该类型的 C 语义截断或抛错。

extend(iterable)

把可迭代对象中的所有元素追加到数组末尾,返回None,等价于原地拼接。底层array_extend()(py/objarray.c)有一个与 CPython 不同的扩展行为:任何实现了缓冲协议的对象(如bytesbytearray、另一个array)都会走“整块拷贝”快速路径——直接把对方的原始缓冲区按自身元素大小分割后memcpy进来;不具备缓冲协议的对象(如列表、生成器)则退回逐元素迭代。官方测试 tests/basics/array_add.py 验证了extend接受 array、list 和生成器表达式三种输入。

a = array('I', [1]) a.extend(array('I', [5])) # 数组 a.extend([6, 7]) # 列表 a.extend(i for i in (8, 9)) # 生成器 print(a) # array('I', [1, 5, 6, 7, 8, 9])

下标访问与切片语义

__getitem__(index)——a[index]

  • index为整数时,返回该位置元素的 Python 标量值;负数索引从末尾倒数;越界抛出IndexError
  • index为切片时,返回一个新的array(类型码与源数组相同)。
  • 底层由array_subscr()(py/objarray.c)实现,切片读取会新建数组并用memcpy拷贝元素区域;切片步长仅支持 1(即None),其他步长会抛NotImplementedError

特别说明__getitem__等特殊方法不能以a.__getitem__(0)的形式直接调用(会失败),也不会出现在a.__dict__中,但a[0]语法正常可用。这是 MicroPython 为省内存而对特殊方法做特殊处理的普遍设计。

a = array('h', [10, 20, 30, 40]) print(a[0]) # 10 print(a[-1]) # 40(负数索引从末尾数) print(a[1:3]) # array('h', [20, 30])(切片返回新数组)

__setitem__(index, value)——a[index] = value

  • index为整数时,把value按类型码编码后写入该位置;负数索引同样支持,越界抛IndexError
  • index为切片时,value必须是一个array(或兼容对象),右侧元素的类型码必须与左侧兼容,否则抛ValueError(源码中的compat_error分支,见 py/objarray.c);切片的赋值/替换支持改变数组长度(MP_PY_ARRAY_SLICE_ASSIGN配置开启时)。
a = array('B', [1, 2, 3, 4]) a[0] = 9 print(a) # array('B', [9, 2, 3, 4]) a[1:3] = array('B', [7, 8, 9]) print(a) # array('B', [9, 7, 8, 9, 4])(切片赋值可改变长度)

__len__()——len(a)

返回数组的元素个数(不是字节数)。底层由array_unary_op()中的MP_UNARY_OP_LEN分支直接返回内部len字段(py/objarray.c),是一个 O(1) 操作。同样的特殊方法限制依然适用:a.__len__()直接调用会失败,len(a)正常工作。

拼接运算:++=

  • a + other__add__):返回一个新的array,内容为两数组按顺序拼接。底层MP_BINARY_OP_ADD分支(py/objarray.c)在分配新缓冲区后调用mp_seq_cat拼接;一个 MicroPython 对 CPython 的扩展是:右侧操作数可以是任何有缓冲协议的对象(如bytes),而不限定必须是array
  • a += other__iadd__):原地拼接,语义完全等价于extend(other)(py/objarray.c),返回原对象本身。
a1 = array('I', [1]) a2 = array('I', [2]) print(a1 + a2) # array('I', [1, 2])(新对象) a1 += array('I', [3, 4]) print(a1) # array('I', [1, 3, 4])(原地修改)

表示形式:__repr__

str(a)repr(a)返回形如"array(<type>, [<elements>])"的字符串,其中<type>是类型码字母,<elements>是逗号分隔的元素列表。底层array_print()(py/objarray.c)逐元素调用mp_binary_get_val_array解码并打印。空数组的表示为array('h')(无方括号部分)。

print(repr(array('b', [1, 2, 3]))) # array('b', [1, 2, 3]) print(array('h')) # array('h')

同样地,__repr__也不能以a.__repr__()直接调用,但str(a)/repr(a)均可。

缓冲协议:与memoryviewbytes的零拷贝互操作

文档明确说明:array对象实现了缓冲协议(buffer protocol),整个数组的内容可以作为原始字节直接访问,例如通过memoryview或任何使用该协议的接口。这是array在嵌入式场景最有价值的特性之一:

  • 底层由array_get_buffer()(py/objarray.c)暴露buf(指向元素内存的指针)与len(字节长度 = 元素个数 × 元素大小),并报告typecode
  • 因此你可以把array直接传给bytes()memoryview()struct.unpack()、串口/UART 写入、网络socket.send()等接受缓冲协议对象的 API,实现零拷贝传输。
  • memoryview包装后,可以按任意切片查看同一块内存而无需复制;memoryview对象内部持有指向原数组缓冲的指针(见 py/objarray.c 对 memoryview 复用 array 实现的设计注释),只要原数组仍被引用就不会被 GC 回收。
a = array('H', [0x1234, 0x5678]) mv = memoryview(a) # 零拷贝视图 print(mv[0]) # 0x1234(按元素访问) raw = bytes(a) # 转成 4 个原始字节 print(len(raw)) # 4

官方测试 tests/basics/array1.py 也验证了bytes(array('b', ...)) == b'abc'以及array('b', [...]) == b'abc'这类基于缓冲内容的比较行为。

比较运算规则

array支持相等与大小比较,规则如下(对应 py/objarray.c 的typecode_for_comparison()array_binary_op()):

  • 相等比较(==/!=):只要两侧的类型码“兼容”(大小写形式相同,如bBhH),就按底层字节内容比较;与bytes等有缓冲协议的对象也可以比较。浮点类型码f/d不参与按字节比较(NaN 永不等于自身),此时会抛NotImplementedError
  • 大小比较(<<=>>=):仅限无符号整数类型码(B/H/I/L/Q),且按字节序列做字典序比较;有符号类型码或不兼容类型码的大小比较抛NotImplementedError
  • 官方的 tests/basics/array1.py 对b/h/i/l/q及其大写形式的两两相等性、以及 tests/basics/array1.py 对无符号类型码的大小比较都有覆盖。
print(array('b', [1, 2]) == array('B', [1, 2])) # True(有符号/无符号兼容) print(array('B', [1, 1]) < array('B', [1, 2])) # True(无符号字典序比较)

迭代与成员测试

  • array是可直接迭代的对象(MP_TYPE_FLAG_ITER_IS_GETITER),迭代器实现在 py/objarray.c,按元素逐个返回 Python 标量,可用于for循环、sum()、列表推导等。
  • 成员测试in仅对bytearray支持子串搜索;对一般array若右侧是整数或浮点数会抛NotImplementedError,其他情况返回False(py/objarray.c)。
  • bool(array)依据元素个数判断真假:空数组为False,非空为TrueMP_UNARY_OP_BOOL,见 tests/basics/array1.py)。
a = array('B', [1, 2, 3]) for x in a: print(x) # 1 2 3 print(bool(array('i'))) # False print(bool(array('i', [1]))) # True

使用建议与注意事项

  • 内存优先用array而非list:以array('B')存储 1000 个传感器读数仅占约 1 KB 连续内存,而 Pythonlist因每个元素都是独立对象(对象头 + 指针 + 数据)通常要多消耗数倍内存,在 RAM 只有几十 KB 的 MCU 上差异巨大。
  • 优先用扩展性好的extend整块拷贝:向数组追加大量数据时,extend(bytes/bytearray/array)走底层memcpy快路径,比逐元素append更高效;预知元素总数时可先用array(typecode, [0]*n)或从已知长度对象构造,避免中途反复扩容。
  • 用好缓冲协议实现零拷贝 I/O:采集完一批数据后直接用memoryview(a)或直接传入bytes(a),即可把array内容交给UART.write()socket.send()struct.unpack()等接口,无需手工逐元素转换。
  • 注意类型码差异:MicroPython 的l/L固定 4 字节;f/d依赖固件浮点支持;q/Q受平台位宽影响。跨固件移植时以当前固件的实际行为为准。
  • 特殊方法不可直接调用__getitem__/__setitem__/__len__/__add__/__iadd__/__repr__均不能以a.__xxx__()形式直接调用(会失败且不出现在__dict__中),必须使用对应的运算符或内建函数语法。

延伸阅读

  • 模块入口与注册:py/modarray.c
  • array/bytearray/memoryview共享的核心实现:py/objarray.c
  • 类型码字节大小与二进制编解码:py/binary.c
  • 官方行为测试:tests/basics/array1.py、tests/basics/array_construct.py、tests/basics/array_construct_endian.py、tests/basics/array_add.py
  • 官方 API 文档:docs/library/array.rst
  • 嵌入式
  • 语言运行时
  • 编程语言
  • 解释器
  • 编译器
  • 物联网
  • 系统编程

【免费下载链接】micropython

MicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems

项目地址:https://gitcode.com/gh_mirrors/mi/micropython
点击查看免费下载

相关推荐

上一篇:告别兼容性困扰:macOS-VirtualBox全版本适配测试(6.1+至最新版)
下一篇:Android-PickerView 终极解析:WheelView滚轮核心机制深度揭秘

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询