目录

具名元祖

前言

本文将简介 Python 的具名元祖namedtuple,以及它的使用方式和使用场合。

注意

本文中的命令行示范,都是基于以下环境:

  • 操作系统: CentOS 7
  • Python:3.7.9

 

什么是具名元祖

namedtuple,称为具名元祖,有些书上也把它叫做有名元祖。在 Python 内置的序列类型 一文中,本人介绍过元祖(tuple),它是 Python 提供的一种基础数据类型,像列表一样可以按序列存放数据,区别在于元祖的元素不可修改,所以不具有可变性。元祖存放的数据,由于没有名称,所以往往没有意义(meaningless),比如:

1
t = ('John', 26, 173, 'USA')

虽然从它元素的内容,我们可以大致猜到这些元素的含义,但这些数值本身是没有名称的,整个元祖对象也没有名称。

namedtuple本质上是一个工厂函数,它被用来构建一个类对象。比起普通元祖,这个类创建出来的实例对象,每个元素都对应着一个字段名,所以整个实例对象往往是有含义的。而它构造的实例对象,占用的内存和普通元祖一样多,因为它的字段名被存在了类对象里面。这个实例对象和普通的对象实例比起来要小一些,不会用实例对象的__dict__来存放属性。

 

具名元祖的特点

用 namedtuple 创建的具名元祖,有以下几个特点:

  • 具有普通元祖的所有功能;
  • 可以从可迭代对象构造实例对象(_make 方法);
  • 可以从字典构造实例对象;
  • 可以用字段名访问元素;
  • 可以转换成有序字典(OrderedDict);
  • 方便调试(因为每个元素都有名字)

 

基本用法

用 namedtuple 创建一个具名元祖的基本语法如下:

1
2
import collections
class_obj = collections.namedtuple(typename, field_names, rename=False, defaults=None) 

参数的含义是:

  • typename:元组名称
  • field_names:元组中元素的名称
  • rename:如果设为 True,不合法的名称(Python 关键字)会被替换成以下划线开头的数字
  • defaults:None 或者可迭代对象,可迭代对象中的元素值,作为参数的默认值

后面两个参数不常用,这里不展开介绍了。

函数返回一个类对象,用户用这个类对象去创建实例。

下面来看个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import collections

Student = collections.namedtuple('Student', ['name', 'age', 'sex', 'country'])	# 1.
# Student = collections.namedtuple('Student', 'name age sex country')
s1 = Student('John', 21, 'male', country='USA')		# 2.

print(s1)			# 3.
print(s1[0])		# 4.
print(s1.name)		# 5.

for info in s1:		# 6.
  print(f'{info}', end=' ')
print()

_, *age_and_sex, _ = s1		# 7.
print(age_and_sex)

s1.name = 'Jack'		# 8.
  1. 调用 namedtuple 创建了一个类对象 Student,它类似于普通元祖,区别在于它有名称:‘Student’,各个元素也有对应的字段名称:”name“,”age“,”sex“,”country“。创建 namedtuple 的时候,可以用一个可迭代对象(例子中用了列表)来创建这些字段,也可以用一个由空格分隔开的字段名组成的字符串来定义。
  2. 创建一个新实例对象的时候,元祖的元素值必须按照对应字段的顺序传入到构造函数中。可以像普通函数调用一样,用关键字来给参数赋值。
  3. namedtuple 创建的类,重构了__repr__方法,所以用 print() 打印能显示对象具体的信息。
  4. 可以用位置索引来访问元素。
  5. 也可以用名称来访问元素。
  6. 支持迭代。
  7. 也可以像普通元祖那样拆包。
  8. 具名元祖和元祖一样,具有不可变性(immutable),不能修改它的元素。

执行结果如下:

1
2
3
4
5
6
7
8
Student(name='John', age=21, sex='male', country='USA')
John
John
John 21 male USA 
[21, 'male']
Traceback (most recent call last):
...
AttributeError: can't set attribute

 

高级用法

获取字段名称

可以用_fields属性获取具名元祖中所有字段的名称:

1
2
s1 = Student(name='John', age=21, sex='male', country='USA')
print(s1._fields)

执行结果:

1
('name', 'age', 'sex', 'country')

用可迭代对象构造

_make方法帮助我们用可迭代对象构造具名元祖的对象:

1
2
student_info = ['John', 21, 'male', 'USA']
new_student = Student._make(student_info)

这里 student_info 可以是任何可迭代对象,只要长度和具名元祖 Student 的字段个数一致即可。其实,我们也可以直接用 Python 拆包与装包的详解(一) 中介绍过的单星号来做同样的事:

1
2
student_info = ['John', 21, 'male', 'USA']
new_student = Student(*student_info)

转换成 OrderedDict 对象

具名元祖对象的每一个元素都有对应的字段名称,所以它很像是一个 dict。_asdict方法可以帮助我们根据具名元祖对象的内容生成一个新的 OrderedDict 对象。OrderedDict 是 dict 的子类,和 dict 相比,它是一个有序字典,新增或删除元素不会改变原有元素的顺序:

1
2
s1 = Student(name='John', age=21, sex='male', country='USA')
print(s1._asdict())

执行结果:

1
OrderedDict([('name', 'John'), ('age', 21), ('sex', 'male'), ('country', 'USA')])

用字典对象构造

上一节我们看到,可以把具名元祖对象转换成新的 OrderedDict 对象,相反,我们也可以用一个 dict 对象来构造具名元祖对象,这里只要用双星号拆包字典对象即可:

1
2
3
student_info = {'name': 'John', 'age': 21, 'sex': 'male', 'country':'USA'}
s1 = Student(**student_info)
print(s1)

执行结果:

1
Student(name='John', age=21, sex='male', country='USA')

替换字段的值

我们可以用_replace方法生成新的具名元祖对象,新对象替换原对象中的某个字段:

1
2
3
4
5
s1 = Student(name='John', age=21, sex='male', country='USA')
print(s1)
s2 = s1._replace(name='Jack')
print(s1)
print(s2)

执行结果:

1
2
3
Student(name='John', age=21, sex='male', country='USA')
Student(name='John', age=21, sex='male', country='USA')
Student(name='Jack', age=21, sex='male', country='USA')

可以看到,_replace函数并不会改变原来对象中的值,事实上它也不可能改变,因为具名元祖对象本身是不可变的。它复制了一个新的对象,并把相关的字段的值修改掉。

具名元祖还有一些其它方法和使用细节,具体可参考 Python 标准库文档

 

参考

https://docs.python.org/3.7/library/collections.html#collections.namedtuple

https://www.runoob.com/note/25726


- 全文完 -

相关文章

「 您的赞赏是激励我创作和分享的最大动力! 」