杰瑞科技汇

strunct python 整数

在 Python 中,并没有一个叫做 struct 的内置数据类型(比如像 C 语言里的 struct),有几种非常主流和强大的方法来实现类似 struct 的功能,即把多个相关的整数(或其他数据)打包成一个单一的结构化对象。

strunct python 整数-图1
(图片来源网络,侵删)

最常用和最 Pythonic 的方法是使用 dataclassesNamedTuple,对于需要高性能的场景,可以使用 ctypesstruct 模块(注意:这里的 struct 模块和 struct 数据类型是两回事)。

下面我将逐一介绍这些方法,并用一个统一的例子来对比它们。

示例场景

假设我们要表示一个“点”的坐标,它由 xy 两个整数组成。


使用 dataclasses (推荐,现代 Python 的首选)

dataclasses 是 Python 3.7+ 引入的标准库,它用简洁的语法为类添加了数据存储、类型提示和常用方法(如 __repr__, __eq__),是创建结构化数据的首选方式。

代码示例

from dataclasses import dataclass
@dataclass
class Point:
    """使用 dataclass 定义一个结构化的点坐标"""
    x: int
    y: int
# 创建一个点的实例
p1 = Point(x=10, y=20)
# 访问属性
print(f"p1 的 x 坐标是: {p1.x}")  # 输出: p1 的 x 坐标是: 10
print(f"p1 的 y 坐标是: {p1.y}")  # 输出: p1 的 y 坐标是: 20
# 修改属性
p1.x = 100
print(f"修改后的 p1: {p1}")      # 输出: 修改后的 p1: Point(x=100, y=20)
# 比较两个实例 (自动生成)
p2 = Point(x=10, y=20)
print(f"p1 和 p2 是否相等: {p1 == p2}") # 输出: p1 和 p2 是否相等: False (因为 p1.x 被改成了100)
p3 = Point(x=10, y=20)
print(f"p2 和 p3 是否相等: {p2 == p3}") # 输出: p2 和 p3 是否相等: True

优点

  • 代码简洁:只需一个装饰器 @dataclass 和类型注解。
  • 可读性强:代码清晰明了,易于理解。
  • 功能完整:自动生成 __init__, __repr__, __eq__ 等方法,减少了样板代码。
  • 类型提示:支持静态类型检查,有助于 IDE 的代码补全和错误检测。

使用 typing.NamedTuple

NamedTuplecollections 模块的一部分(Python 3.3+ 后移至 typing),它创建的是元组的子类,既有元组的不可变性特点,又有类似类的可读性。

代码示例

from typing import NamedTuple
class Point(NamedTuple):
    """使用 NamedTuple 定义一个结构化的点坐标"""
    x: int
    y: int
# 创建一个实例 (方式像调用函数)
p1 = Point(x=10, y=20)
# 访问属性
print(f"p1 的 x 坐标是: {p1.x}")  # 输出: p1 的 x 坐标是: 10
print(f"p1 的 y 坐标是: {p1.y}")  # 输出: p1 的 y 坐标是: 20
# NamedTuple 是不可变的,不能修改属性
# p1.x = 100  # 这行代码会抛出 AttributeError: can't set attribute
# 比较两个实例 (基于值的比较)
p2 = Point(x=10, y=20)
print(f"p1 和 p2 是否相等: {p1 == p2}") # 输出: p1 和 p2 是否相等: True

优点

  • 轻量级:比 dataclass 更轻量,本质上是优化的元组。
  • 不可变性:一旦创建,实例内容不能改变,这在某些场景下是优点(如作为字典的键)。
  • 性能稍好:在属性访问和内存占用上可能比 dataclass 稍有优势。

缺点

  • 不可变:如果你需要修改实例的属性,NamedTuple 就不合适了。

使用 ctypes (与 C 语言交互)

ctypes 模块用于和 C 语言的数据类型进行交互,它可以直接创建内存中的 C 风格结构体,非常适合需要与 C 库交互或进行底层内存操作的场景。

代码示例

from ctypes import Structure, c_int
class Point(Structure):
    """使用 ctypes 定义一个与 C 兼容的结构体"""
    _fields_ = [
        ("x", c_int),
        ("y", c_int)
    ]
# 创建实例
p1 = Point(x=10, y=20)
# 访问属性 (通过点号)
print(f"p1 的 x 坐标是: {p1.x}")  # 输出: p1 的 x 坐标是: 10
print(f"p1 的 y 坐标是: {p1.y}")  # 输出: p1 的 y 坐标是: 20
# 修改属性
p1.x = 100
print(f"修改后的 p1: {p1.x}, {p1.y}") # 输出: 修改后的 p1: 100, 20
# 获取内存地址
print(f"p1 在内存中的地址: {ctypes.addressof(p1)}")

优点

  • C 语言兼容:可以直接传递给 C 函数,或用于解析二进制文件。
  • 内存布局可控:可以精确控制结构体在内存中的对齐方式。

缺点

  • 语法繁琐:需要定义 _fields_ 列表,不如前两种方法直观。
  • 非 Pythonic:主要用于特定场景,不适合一般的应用程序开发。

使用 struct 模块 (打包/解包二进制数据)

struct 模块本身不是用来定义数据类型的,而是用来将 Python 的值(如整数)打包成二进制格式(bytes),或者从二进制数据中解包成 Python 值,它通常与文件 I/O 或网络通信结合使用。

虽然它不直接创建一个“结构体对象”,但它是实现结构化数据持久化和传输的核心工具。

代码示例

import struct
# 假设我们要打包一个点 (x=10, y=20)
# 'ii' 格式字符串表示两个 'int' 类型,每个 int 通常是 4 字节
# 字节顺序使用 '<' 表示小端法 (little-endian)
packed_data = struct.pack('<ii', 10, 20)
print(f"打包后的二进制数据: {packed_data}")
# 输出 (在 x86/x64 小端机器上): b'\n\x00\x00\x00\x14\x00\x00\x00'
# 解释: \n (10) + 3个字节0, \x14 (20) + 3个字节0
# 将二进制数据写入文件
with open('point.bin', 'wb') as f:
    f.write(packed_data)
# 从文件中读取并解包
with open('point.bin', 'rb') as f:
    # 读取 8 个字节 (因为两个 int 是 4*2=8 字节)
    data_from_file = f.read(8)
    x, y = struct.unpack('<ii', data_from_file)
print(f"从文件解包出的坐标: x={x}, y={y}") # 输出: 从文件解包出的坐标: x=10, y=20

优点

  • 高效二进制处理:是处理二进制数据(如文件、网络包)的标准工具。
  • 跨平台:可以指定字节顺序和对齐方式,确保数据在不同系统间正确传输。

缺点

  • 不是数据类型:它是一个转换工具,不会创建一个可以在代码中直接使用的 Point 对象。
  • 不安全:如果二进制数据格式不匹配(如文件损坏),unpack 会抛出异常。

总结与对比

特性 dataclasses typing.NamedTuple ctypes.Structure struct 模块
主要用途 创建 Python 内部的结构化对象 创建不可变的、类似元组的结构化对象 与 C 语言交互,底层内存操作 打包/解包二进制数据
可变性 可变 不可变 可变 N/A (工具)
语法 @dataclass + 类型注解 class(NamedTuple) + 类型注解 class(Structure) + _fields_ struct.pack/unpack
Pythonic 程度 非常高 中 (作为工具)
性能 良好 优秀 (轻量) 接近 C 非常快 (二进制操作)
推荐场景 绝大多数 Python 应用 需要不可变数据时 (如字典键) 调用 C 库、解析二进制文件 网络通信、文件 I/O

最终建议

  • 如果你想在 Python 代码中组织整数等数据首选 dataclasses,它功能强大、语法简洁、易于维护,是现代 Python 开发的标准实践。
  • 如果你的数据是不可变的,并且希望它像元组一样高效:使用 typing.NamedTuple
  • 如果你需要将数据写入文件、通过网络发送,或者与 C 语言库交互:使用 dataclassesNamedTuple 来定义你的数据模型,然后使用 struct 模块将其打包成二进制格式进行传输或存储。
分享:
扫描分享到社交APP
上一篇
下一篇