热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

python定义集合数据对象_Python中常见的数据结构:记录、结构体和纯数据对象

与数组相比,记录数据结构中的字段数目固定,每个都有一个名称,类型也可以不同。本文将介绍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] = 12

TypeError:

"'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.mileage

40231.0

# 类是可变的:

car2.mileage = 12

car2.windshield = 'broken'

# 类的默认字符串形式没多大用处,必须手动编写一个 __repr__ 方法:

car1

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__ 方法:

>>> car1

Car(color='red', mileage=3812.4, automatic=True)

# 访问字段:

>>> car1.mileage

3812.4

# 字段是不可变的:

>>> car1.mileage = 12

AttributeError: "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__ 方法:

>>> car1

Car(color='red', mileage=3812.4, automatic=True)

# 访问字段:

>>> car1.mileage

3812.4

# 字段是不可变的:

>>> car1.mileage = 12

AttributeError: "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)

# 得到的是一团内存中的数据:

>>> data

b'\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__ 效果:

>>> car1

namespace(automatic=True, color='red', mileage=3812.4)

# 实例支持属性访问并且是可变的:

>>> car1.mileage = 12

>>> car1.windshield = 'broken'

>>> del car1.automatic

>>> car1

namespace(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。



推荐阅读
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • 提升Python编程效率的十点建议
    本文介绍了提升Python编程效率的十点建议,包括不使用分号、选择合适的代码编辑器、遵循Python代码规范等。这些建议可以帮助开发者节省时间,提高编程效率。同时,还提供了相关参考链接供读者深入学习。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文介绍了如何使用python从列表中删除所有的零,并将结果以列表形式输出,同时提供了示例格式。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
  • 花瓣|目标值_Compose 动画边学边做夏日彩虹
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Compose动画边学边做-夏日彩虹相关的知识,希望对你有一定的参考价值。引言Comp ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • importjava.util.ArrayList;publicclassPageIndex{privateintpageSize;每页要显示的行privateintpageNum ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
author-avatar
蓝雪帝国666
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有