免费下载案例集|20+数字化领先企业人才培养实践经验 了解详情
写点什么

Python 中常见的数据结构:记录、结构体和纯数据对象

  • 2019-09-30
  • 本文字数:4645 字

    阅读完需:约 15 分钟

Python中常见的数据结构:记录、结构体和纯数据对象

与数组相比,记录数据结构中的字段数目固定,每个都有一个名称,类型也可以不同。


本文将介绍 Python 中的记录、结构体,以及“纯数据对象”,但只介绍标准库中含有的内置数据类型和类。


顺便说一句,这里的“记录”定义很宽泛。例如,这里也会介绍像 Python 的内置元组这样的类型。由于元组中的字段没有名称,因此一般不认为它是严格意义上的记录。


Python 提供了几种可用于实现记录、结构体和数据传输对象的数据类型。本节将快速介绍每个实现及各自特性,最后进行总结并给出一个决策指南,用来帮你做出自己的选择。


好吧,让我们开始吧!

字典——简单数据对象

Python 字典能存储任意数量的对象,每个对象都由唯一的键来标识。字典也常常称为映射或关联数组,能高效地根据给定的键查找、插入和删除所关联的对象。


Python 的字典还可以作为记录数据类型(record data type)或数据对象来使用。在 Python 中创建字典很容易,因为语言内置了创建字典的语法糖,简洁又方便。


字典创建的数据对象是可变的,同时由于可以随意添加和删除字段,因此对字段名称几乎没有保护措施。这些特性综合起来可能会引入令人惊讶的 bug,毕竟要在便利性和避免错误之间做出取舍。


car1 = {    'color': 'red',    'mileage': 3812.4,    'automatic': True,}car2 = {    'color': 'blue',    'mileage': 40231,    'automatic': False,}
# 字典有不错的__repr__方法:>>> car2{'color': 'blue', 'automatic': False, 'mileage': 40231}
# 获取mileage:>>> car2['mileage']40231
# 字典是可变的:>>> car2['mileage'] = 12>>> car2['windshield'] = 'broken'>>> car2{'windshield': 'broken', 'color': 'blue', 'automatic': False, 'mileage': 12}
# 对于提供错误、缺失和额外的字段名称并没有保护措施:car3 = { 'colr': 'green', 'automatic': False, 'windshield': 'broken',}
复制代码

元组——不可变对象集合

Python 元组是简单的数据结构,用于对任意对象进行分组。元组是不可变的,创建后无法修改。


在性能方面,元组占用的内存略少于 CPython 中的列表,构建速度也更快。


从如下反汇编的字节码中可以看到,构造元组常量只需要一个 LOAD_CONST 操作码,而构造具有相同内容的列表对象则需要多个操作:


>>> import dis>>> dis.dis(compile("(23, 'a', 'b', 'c')", '', 'eval'))      0 LOAD_CONST            4 ((23, 'a', 'b', 'c'))      3 RETURN_VALUE
>>> dis.dis(compile("[23, 'a', 'b', 'c']", '', 'eval')) 0 LOAD_CONST 0 (23) 3 LOAD_CONST 1 ('a') 6 LOAD_CONST 2 ('b') 9 LOAD_CONST 3 ('c') 12 BUILD_LIST 4 15 RETURN_VALUE
复制代码


不过你无须过分关注这些差异。在实践中这些性能差异通常可以忽略不计,试图通过用元组替换列表来获得额外的性能提升一般都是入了歧途。


单纯的元组有一个潜在缺点,即存储在其中的数据只能通过整数索引来访问,无法为元组中存储的单个属性制定一个名称,从而影响了代码的可读性。


此外,元组总是一个单例模式的结构,很难确保两个元组存储了相同数量的字段和相同的属性。


这样很容易因疏忽而犯错,比如弄错字段顺序。因此,建议尽可能减少元组中存储的字段数量。


# 字段:color、mileage、automatic>>> car1 = ('red', 3812.4, True)>>> car2 = ('blue', 40231.0, False)
# 元组的实例有不错的__repr__方法:>>> car1('red', 3812.4, True)>>> car2('blue', 40231.0, False)
# 获取mileage:>>> car2[1]40231.0
# 元组是可变的:>>> car2[1] = 12TypeError:"'tuple' object does not support item assignment"
# 对于错误或额外的字段,以及提供错误的字段顺序,并没有报错措施:>>> car3 = (3431.5, 'green', True, 'silver')
复制代码

编写自定义类——手动精细控制

类可用来为数据对象定义可重用的“蓝图”(blueprint),以确保每个对象都提供相同的字段。


普通的 Python 类可作为记录数据类型,但需要手动完成一些其他实现中已有的便利功能。例如,向__init__构造函数添加新字段就很烦琐且耗时。


此外,对于从自定义类实例化得到的对象,其默认的字符串表示形式没什么用。解决这个问题需要添加自己的__repr__方法。这个方法通常很冗长,每次添加新字段时都必须更新。


存储在类上的字段是可变的,并且可以随意添加新字段。使用 @property 装饰器能创建只读字段,并获得更多的访问控制,但是这又需要编写更多的胶水代码。


编写自定义类适合将业务逻辑和行为添加到记录对象中,但这意味着这些对象在技术上不再是普通的纯数据对象。


class Car:    def __init__(self, color, mileage, automatic):        self.color = color        self.mileage = mileage        self.automatic = automatic
>>> car1 = Car('red', 3812.4, True)>>> car2 = Car('blue', 40231.0, False)
# 获取mileage:>>> car2.mileage40231.0
# 类是可变的:>>> car2.mileage = 12>>> car2.windshield = 'broken'
# 类的默认字符串形式没多大用处,必须手动编写一个__repr__方法:>>> car1<Car object at 0x1081e69e8>
复制代码

collections.namedtuple——方便的数据对象

自 Python 2.6 以来添加的 namedtuple 类扩展了内置元组数据类型。与自定义类相似,namedtuple 可以为记录定义可重用的“蓝图”,以确保每次都使用正确的字段名称。


与普通的元组一样,namedtuple 是不可变的。这意味着在创建 namedtuple 实例之后就不能再添加新字段或修改现有字段。


除此之外,namedtuple 就相当于具有名称的元组。存储在其中的每个对象都可以通过唯一标识符访问。因此无须整数索引,也无须使用变通方法,比如将整数常量定义为索引的助记符。


namedtuple 对象在内部是作为普通的 Python 类实现的,其内存占用优于普通的类,和普通元组一样高效:


>>> from collections import namedtuple>>> from sys import getsizeof
>>> p1 = namedtuple('Point', 'x y z')(1, 2, 3)>>> p2 = (1, 2, 3)
>>> getsizeof(p1)72>>> getsizeof(p2)72
复制代码


由于使用 namedtuple 就必须更好地组织数据,因此无意中清理了代码并让其更加易读。


我发现从专用的数据类型(例如固定格式的字典)切换到 namedtuple 有助于更清楚地表达代码的意图。通常,每当我在用 namedtuple 重构应用时,都神奇地为代码中的问题想出了更好的解决办法。


用 namedtuple 替换普通(非结构化的)元组和字典还可以减轻同事的负担,因为用 namedtuple 传递的数据在某种程度上能做到“自说明”。


>>> from collections import namedtuple>>> Car = namedtuple('Car' , 'color mileage automatic')>>> car1 = Car('red', 3812.4, True)
# 实例有不错的__repr__方法:>>> car1Car(color='red', mileage=3812.4, automatic=True)
# 访问字段:>>> car1.mileage3812.4
# 字段是不可变的:>>> car1.mileage = 12AttributeError: "can't set attribute">>> car1.windshield = 'broken'AttributeError:"'Car' object has no attribute 'windshield'"
复制代码

typing.NamedTuple——改进版 namedtuple

这个类添加自 Python 3.6,是 collections 模块中 namedtuple 类的姊妹。它与 namedtuple 非常相似,主要区别在于用新语法来定义记录类型并支持类型注解(type hint)。


注意,只有像 mypy 这样独立的类型检查工具才会在意类型注解。不过即使没有工具支持,类型注解也可帮助其他程序员更好地理解代码(如果类型注解没有随代码及时更新则会带来混乱)。


>>> from typing import NamedTuple
class Car(NamedTuple): color: str mileage: float automatic: bool
>>> car1 = Car('red', 3812.4, True)
# 实例有不错的__repr__方法:>>> car1Car(color='red', mileage=3812.4, automatic=True)
# 访问字段:>>> car1.mileage3812.4
# 字段是不可变的:>>> car1.mileage = 12AttributeError: "can't set attribute">>> car1.windshield = 'broken'AttributeError:"'Car' object has no attribute 'windshield'"
# 只有像mypy 这样的类型检查工具才会落实类型注解:>>> Car('red', 'NOT_A_FLOAT', 99)Car(color='red', mileage='NOT_A_FLOAT', automatic=99)
复制代码

struct.Struct——序列化 C 结构体

struct.Struct 类用于在 Python 值和 C 结构体之间转换,并将其序列化为 Python 字节对象。例如可以用来处理存储在文件中或来自网络连接的二进制数据。


结构体使用与格式化字符串类似的语法来定义,能够定义并组织各种 C 数据类型(如 char、int、long,以及对应的无符号的变体)。


序列化结构体一般不用来表示只在 Python 代码中处理的数据对象,而是主要用作数据交换格式。


在某些情况下,与其他数据类型相比,将原始数据类型打包到结构体中占用的内存较少。但大多数情况下这都属于高级(且可能不必要的)优化。


>>> from struct import Struct>>> MyStruct = Struct('i?f')>>> data = MyStruct.pack(23, False, 42.0)
# 得到的是一团内存中的数据:>>> datab'\x17\x00\x00\x00\x00\x00\x00\x00\x00\x00(B'
# 数据可以再次解包:>>> MyStruct.unpack(data)(23, False, 42.0)
复制代码

types.SimpleNamespace——花哨的属性访问

这里再介绍一种高深的方法来在 Python 中创建数据对象:types.SimpleNamespace。该类添加自 Python 3.3,可以用属性访问的方式访问其名称空间。


也就是说,SimpleNamespace 实例将其中的所有键都公开为类属性。因此访问属性时可以使用 obj.key 这样的点式语法,不需要用普通字典的 obj[‘key’]方括号索引语法。所有实例默认都包含一个不错的__repr__。


正如其名,SimpleNamespace 很简单,基本上就是扩展版的字典,能够很好地访问属性并以字符串打印出来,还能自由地添加、修改和删除属性。


>>> from types import SimpleNamespace>>> car1 = SimpleNamespace(color='red',...                        mileage=3812.4,...                        automatic=True)
# 默认的__repr__效果:>>> car1namespace(automatic=True, color='red', mileage=3812.4)
# 实例支持属性访问并且是可变的:>>> car1.mileage = 12>>> car1.windshield = 'broken'>>> del car1.automatic>>> car1namespace(color='red', mileage=12, windshield='broken')
复制代码

小结

那么在 Python 中应该使用哪种类型的数据对象呢?从上面可以看到,Python 中有许多不同的方法实现记录或数据对象,使用哪种方式通常取决于具体的情况。


如果只有两三个字段,字段顺序易于记忆或无须使用字段名称,则使用简单元组对象。例如三维空间中的(x, y, z)点。


如果需要实现含有不可变字段的数据对象,则使用 collections.namedtuple 或 typing.NamedTuple 这样的简单元组。


如果想锁定字段名称来避免输入错误,同样建议使用 collections.namedtuple 和 typing.NamedTuple。


如果希望保持简单,建议使用简单的字典对象,其语法方便,和 JSON 也类似。


如果需要对数据结构完全掌控,可以用 @property 加上设置方法和获取方法来编写自定义的类。


如果需要向对象添加行为(方法),则应该从头开始编写自定义类,或者通过扩展 collections.namedtuple 或 typing.NamedTuple 来编写自定义类。


如果想严格打包数据以将其序列化到磁盘上或通过网络发送,建议使用 struct.Struct。


一般情况下,如果想在 Python 中实现一个普通的记录、结构体或数据对象,我的建议是在{\rm Python}~2.x 中使用 collections.namedtuple,在 Python 3 中使用其姊妹 typing.NamedTuple。


本文内容来自作者图书作品《深入理解 Python 特性》,点击购买


2019-09-30 15:5712949

评论

发布
暂无评论
发现更多内容

mac触控板鼠标控制软件 Bettertouchtool 4.291中文破解下载

iMac小白

BetterTouchTool破解

百家国企走进云投集团,探索世界500强数智化转型之路

用友BIP

企业数智化

Util应用框架快速入门(四)- 集成测试 快速入门

何镇汐

C# 开源 后端 软件开发

为什么OpenAPI是未来企业数字化转型的决定性因素?

华为云开发者联盟

云计算 后端 华为云 华为云开发者联盟 华为云API Explorer

十个使用Spring Cloud和Java创建微服务的实践案例

树上有只程序猿

Java 微服务 SpringCloud

浅析KV存储之长尾时延解决办法

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟

Microsoft Remote Desktop for Mac(远程桌面连接工具)激活版

iMac小白

microsoft remote desktop

DBeaverUltimate for Mac(数据库管理软件) 终极版下载

iMac小白

DBeaver Ultimate软件 DBeaverUltimate下载 DBeaverUltimate破解版

国内首批!华为云云原生中间件DCS&DMS获软件可信“卓越级”认证

华为云PaaS服务小智

云计算 软件开发 华为云

SmartBear正式收购Stoplight,并计划在核心API设计、文档和门户产品中集成其功能

龙智—DevSecOps解决方案

SmartBear Stoplight

案例分享:某汽车企业通过龙智拓展Jira功能,实现高效项目管理

龙智—DevSecOps解决方案

瑞技伙伴 | WEKA® Data Platform,为AI等高性能工作负载提供澎湃动力

Bytebridge

AI HPC 存储方案 WEKA

Presentation Prompter for Mac(mac屏幕提词器) v5.4.2永久激活版

mac

苹果mac Windows软件 Presentation Prompter 屏幕提词器软件

SideFX Houdini for mac(3D物理模拟和视觉特效软件) v18.5.696永久激活版

mac

苹果mac Windows软件 SideFX Houdini 视觉特效软件

桌面云一体机有哪些厂家?价格怎么样?

青椒云云电脑

桌面云 云桌面

云桌面系统简介与深度解析

青椒云云电脑

桌面云 云桌面 云桌面系统

中国集成电路设计业2023年会演讲预告 | 龙智Perforce专家解析半导体设计中的数字资产管理

龙智—DevSecOps解决方案

芯片

七个优秀微服务跟踪工具

高端章鱼哥

微服务架构

KubeBlocks v0.7.0 发布!支持引用外部组件,解耦备份 API,还支持了 Pika!

小猿姐

数据库 云原生

昇腾CANN 7.0 黑科技:大模型推理部署技术解密

华为云开发者联盟

人工智能 华为云 昇腾CANN 华为云开发者联盟 LLM模型

KubeBlocks 完成阿里云 PolarDB 数据库产品生态集成认证啦!

小猿姐

数据库 云计算 云原生

ChatGPT上新,效果炸裂,知识平台才是大模型的最佳狩猎场

数新网络官方账号

openai ChatGPT

云电脑哪家好?主流云电脑平台详细测评

青椒云云电脑

云电脑

平安人寿基于 Apache Doris 统一 OLAP 技术栈实践

SelectDB

数据库 大数据 数据仓库 数据分析 apache doris

WebSocket魔法师:打造实时应用的无限可能

EquatorCoco

编程 Web Service

改良版芯片重回中国市场?GPT-5 明年降临?丨 RTE 开发者日报 Vol.82

声网

Navicat Premium 15 for Mac(数据库开发工具)v15.0.36中文激活版

mac

Navicat Premium 苹果mac Windows软件 数据库管理软件

Python中常见的数据结构:记录、结构体和纯数据对象_编程语言_Dan Bader_InfoQ精选文章