一、简介
记录中的字段数目固定,并且每一个都含有一个名称,类型可以不相同。
下面会介绍几种Python中的记录,结构体和纯数据对象(指没有任何方法和行为的对象,不同于类对象,含有方法或属性,纯数据对象只用作存储和传递数据),这些都是Python标准库中含有的内置数据类型和类。
二、数据类型
1、字典——简单数据对象
字典能存储任意数量的对象,每个对象都由一个键来标识,也称为映射或关联数组,能快速的按照指定的键进行查找、插入和删除关联的对象。
字典可以当做记录数据类型或数据对象来使用,创建字典很方便,Python中有较多的语法糖支持字典的使用。但字典对象可变,可以任意删除和添加字段,并未对字段名称做限制,所以有时候可能会产生bug。
car2 = {'color': 'blue', 'mileage': 40231, 'automatic': False}
# __repr__方法返回一个字符串,包含了字典的所有内容
print(car2)
# 获取mileage的值
print(car2['mileage'])
# 字典是可变的,可以添加新的键值对
car2['mileage'] = 12
car2['windshield'] = 'broken'
print(car2)
# 对于错误,缺失和额外的字段名称没有保护措施
car3 = {
'color': 'green',
'automatic': False,
'windshield': 'broken',
}
print(car3)
以上代码的输出:
{'color': 'blue', 'mileage': 40231, 'automatic': False}
40231
{'color': 'blue', 'mileage': 12, 'automatic': False, 'windshield': 'broken'}
{'color': 'green', 'automatic': False, 'windshield': 'broken'}
2、自定义类——手动精细控制
类可以用来为数据对象定义可重用的存储结构,确保每个对象都拥有相同的字段。普通的Python类可以作为记录数据类型,但需要完成一些其他的便利功能,比如实现构造函数__init__和字符串化函数__repr__。
在类上进行存储的字段是可以变化的,但是需要进行访问权限控制,使用@property可以创建只读字段。并且可以编写其他适合类的业务逻辑函数,但这就意味着这些对象不是纯数据对象了。
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
def __repr__(self):
return f'Car({self.color}, {self.mileage})'
if __name__ == '__main__':
car1 = Car('red', 37281)
car2 = Car('blue', 40231)
# 获取mileage的值
print(car2.mileage)
# 类属性可变
car2.mileage = 12
car2.color = 'red'
print(car2)
3、collections.namedtuple——方便的数据对象
namedtuple 是从Python2.6之后添加的数据类型。与自定义类相似,namedtuple 可以让记录重用,并确保每次都使用正确的字段名,与tuple一样,namedtuple不可变,在初始化之后不能再添加和修改现有字段。
除此之外,namedtuple 相当于具有名称的元组。存储在其中的每个对象都可以通过唯一标识符访问。namedtuple对象在内部是作为普通的Python类实现的,内存占用优于普通的类,和普通元组一样高效。
用namedtuple替换普通的元组和字典可以减轻同事的负担,因为使用namedtuple传递的数据在某种程度上能做到“自说明”
Car = namedtuple('Car', 'color mileage automatic')
car1 = Car('red', 3812.4, True)
# namedtuple内部实现了__repr__方法
print(car1)
# 访问字段
print(car1.color)
# 字段是只读的
# car1.mileage = 12
# 抛出异常 AttributeError: can't set attribute
# car1.windshield = 'broken'
# 抛出异常 AttributeError: 'Car' object has no attribute 'windshield'
以上代码运行结果:
Car(color='red', mileage=3812.4, automatic=True)
red
4、typing.NamedTuple——改进版namedtuple
这个类自Python3.6添加,是collections模块中namedtuple类的姊妹。它与namedtuple非常相似,主要的区别就是支持用新语法来定义记录类型并支持类型注解(type hint)
不过,只有像mypy这样的类型检查工具才会在意注解。不过即使没有使用工具支持,类型注解也可以帮助其他程序员更好的理解代码。
class Car(NamedTuple):
color: str
mileage: float
automatic: bool
car1 = Car('red', 3812.4, True)
# namedtuple内部实现了__repr__方法
print(car1)
# 访问字段
print(car1.color)
# 字段是只读的
# car1.mileage = 12
# 抛出异常 AttributeError: can't set attribute
# car1.windshield = 'broken'
# 抛出异常 AttributeError: 'Car' object has no attribute 'windshield'
# 不会抛出异常,只有mypy这样的类型检查工具才会落实类型注解
print(Car('red', 'NOT_A_FLOAT', 99))
以上代码运行结果
Car(color='red', mileage=3812.4, automatic=True)
red
Car(color='red', mileage='NOT_A_FLOAT', automatic=99)
5、struct.Struct——序列化C结构体
struct.Struct类用于在Python值和C结构体之间转换,并将其序列化为Python字节对象。例如可以用来处理存储在文件中或来自网络连接的二进制数据。
结构体使用与格式化字符串类似的语法来定义,能够定义并组织各种C数据类型(如char, int, long,以及对应的无符号的变体)。
序列化结构体一般不用来表示只在Python代码中处理的数据对象,而是主要用作数据交换格式。
在某些情况下,与其他类型相比,将原始数据类型打包到结构体中占的内存较少,但大多数情况下这都属于高级的优化。
MyStruct = Struct('i?f')
data = MyStruct.pack(23, False, 42.0)
# 得到的是一团内存中的数据
print(data)
# 数据可以再次解包
print(MyStruct.unpack(data))
以上代码运行结果:
b'\x17\x00\x00\x00\x00\x00\x00\x00\x00\x00(B'
(23, False, 42.0)
6、types.SimpleNamespace——多样的属性访问
types.SimpleNamespace类添加自Python3.3,可以使用属性访问的方式访问其名字空间。也就是说,SimpleNamespace实例所有的键都公开为类属性。访问时可以使用obj.key这样的点式语法,不需要用obj[‘key’]方括号索引语法。
正如其名,SimpleNamespace很简单,基本就是一个扩展版的字典,能够很好的访问属性并打印,还能自由的添加,删除和修改属性。
car1 = SimpleNamespace(color='red', mileage=3812.4, automatic=True)
# __repr__方法返回一个字符串,包含了实例的所有属性
print(car1)
# 实例支持修改,增加和删除属性
car1.mileage = 12
car1.windshield = 'broken'
del car1.automatic
print(car1)
以上代码运行结果
namespace(color='red', mileage=3812.4, automatic=True)
namespace(color='red', mileage=12, windshield='broken')
三、总结
以上介绍的数据对象类型,在不同场景有不同选择, 大致可以总结为以下几点:
- 如果只有两三个字段,字段顺序容易记忆并且无需使用字段名称,则使用简单元组对象,例如,二维坐标(x, y)。
- 如果想锁定字段名称避免输入错误,可以使用collections.namedtuple或者typing.NamedTuple这样的简单元组
- 如果希望保持简单,建议使用简单的字典对象,语法方便,易于和json对象转换
- 如果需要对数据结构完全掌控,可以使用含有getter和setter等权限控制方法的自定义类实现。
- 如果需要向对象添加行为(方法),则应该从头开始编写自定义类,或者通过扩展collections.namedtuple或typing.NamedTuple来编写自定义类
- 如果想严格打包数据以将其序列化到磁盘或者通过网络发送,建议使用struct.Struct。
一般情况下,如果想在Python中实现一个普通的记录、结构体或数据对象,建议在Python2.x中使用collections.namedtuple,在Python3中使用typing.NamedTuple。