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.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
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论