Python collections模块-namedtuple

概览

  • 为什么会有namedtuple? class vs dict vs list vs tuple vs namedtuple
  • namedtuple初始化方式
  • namedtuple的一些特性
  • 特殊方法

为什么会有namedtuple

namedtuple中文应该叫做命名元组, 我们可以先分析普通元组的相对其他数据结构的优势

  • 元组不可变, 初始化之后不能对里面的元素重新赋值, 也不能再新增或者删除元素. 可能一定程度上限制了元组,但是不可变在函数式编程, 以及多线程环境是有帮助的.
  • 因为元组不可变, 相对其他的数据结构来说可以节省很多的空间. 大致可以描述为, 你只要花现实生活中谈到的出厂价, 就可以存储对应量的数据. 而以下我们谈到的数据结构, 总会有一些服务费.
  • 列表每次扩容缩容, 以及初始化的时候, 都会预留一部分空间. 预留空间就是列表的服务费.
  • 定义一个类会有类属性字典, 而实例也有自己的属性字典. 这些开销比元组保存的数据大得多. 类和实例的这些额外开销也属于服务费.
  • 字典其实是散列表, 而对于任意一种散列表, 都存在一个装填因子, 代表散列表的装满程度. 所以正常情况下, 字典的占用空间肯定比你存储的在内的元素所需的空间大. 字典占用的这些额外空间也是服务费.

所以元组至少是一种空间上比较高效的数据结构.

而元组的劣势在于, 只能通过索引访问数据. 当你想保存一个结构化数据的时候, 用它写出来的代码可能不够清晰, 也不是很方便. 所以在面临代码的可读性与代码的性能的时候, 代码的可读性应该是更重要的. 同时在不同的应用场景下, 不可变性也是它的弱点.

命名元组解决的核心问题是: 你通过字段名初始化, 访问数组, 让你更清晰的管理结构化数据. 同时它也继承了元组所有的优点.

namedtuple的初始化方式

命名元组用来定义一个结构化的数据类型

from collections import namedtuple

Person  = namedtuple('Person', ('age', 'name', 'email'))

luvjoey1996 = Person(23, 'luvjoey1996', 'luvjoey1996@gmail.com')

然后你就可以通过字段名清晰明了的访问对应的属性

print(luvjoey1996.age)
# 23
print(luvjoey1996.name)
# luvjoey1996

上面是通过一个元组来指定它的属性, 我们还可以通过一个字符串, 加上分割符的形式

Person = namedtuple('Person', 'age name email')
Person = namedtuple('Person', 'age,name,email')

namedtuple不允许使用保留字或者不合法的变量名称来当作字段名, 默认行为是报错, 可以切换成另一种行为: 把不合法的属性名称变成 下划线 + 索引, _0 这种形式.

Person = namedtuple('Person', 'age name email class')
# ValueError: Type names and field names cannot be a keyword: 'class'

Person = namedtuple('Person', 'age name email hello%world')
# ValueError: Type names and field names must be valid identifiers: 'hello%world'

# 你可以指定rename参数为True, 让namedtuple自动帮你重命名, 但是不建议这种方式, 最好还是调整字段名字(下划线加上原始的索引)
Person = namedtuple('Person', 'age name email class', rename=True)
luvjoey1996 = Person(23, 'luvjoey1996', 'luvjoey1996@gmail.com','developer')
# 此时不会抛出异常, 但是后续访问不能使用 person.class 而应该使用 person._3, 即下划线加上原始的索引.

namedtuple也可以指定默认参数

Person = namedtuple('Person', ('age', 'name',        'email'), 
                    defaults= ( 23,   'luvjoey1996', 'luvjoey1996@gmail.com'))
person = Person()
print(person)
# Person(age=23, name='luvjoey1996', email='luvjoey1996@gmail.com')

# 默认参数列表的长度不一定与字段名列表的长度一致, 但是他们的对应关系是右对齐的, 像下面这样
# 只指定了两个默认参数, 分别为 name->luvjoey1996, email->luvjoey1996@gmail.com
Person = namedtuple('Person', ('age', 'name',        'email'), 
                    defaults= (       'luvjoey1996', 'luvjoey1996@gmail.com'))
person = Person(23)
print(person)
# Person(age=23, name='luvjoey1996', email='luvjoey1996@gmail.com')

namedtuple 的一些特性

namedtuple可以通过字段名访问属性, 也可以通过下标

Person = namedtuple('Person', 'age name email')
person = Person(23, 'luvjoey1996', 'luvjoey1996@gmail.com')

print(person.age, person[0])
# 23 23

namedtuple的属性不能修改, 因为元组本身就是不能修改的

person.age = 24
# AttributeError

namedtuple可以正常被json序列化成json的列表, 当然你不能通过json反序列化回来

import json

print(json.dumps(person))
# '[23, "luvjoey1996", "luvjoey1996@gmail.com"]'

namedtuple的特殊方法

通过namedtuple._asdict, 你可以很方便的将命名元组转化成字典

print(person._asdict())
# {'age': 23, 'name': 'luvjoey1996', 'email': 'luvjoey1996@gmail.com'}

通过namedtuple._replace方法, 你可以得到一个新的命名元组, 替换某些属性

new_person = person._replace(age=24, name='hello world')
print(new_person)
# Person(age=24, name='hello world', email='luvjoey1996@gmail.com')
# 新的person age->24, name-> hello world, email与原person一致

元组在实际应用中比较鸡肋的点是, 与列表的相似度太高, 并没有将它的不可变性充分发挥出来. 而命名元组一定程度上解决了这个问题, 包括提高代码的可读性, 也与列表有了较大的区分度.

当然我们写代码也就是很简单的, 在力所能及的范围内, 写好就行, 命名元组应用还是比较基础的, 属于信手拈来的小优化. 不属于过度优化的范畴.

展示评论