使用命名元组提升代码可读性

关于 collections 模块

python 自带的一些数据类型已经非常实用了,但在一些情况下,原生的数据结构并不能满足我们的需求。而在 python 中,想从底层定义一种数据类型又特别困难。因此, python 在它的 collections 模块中又为我们提供了几种新的数据类型,灵活的使用它们将会提升我们编写程序的效率。

1. 为什么要用命名元组

很多时候,我们需要用比较规范的形式将数据组织在一起,而对于简单的数据,使用结构体就有一些大材小用了。这个时候, namedtuple 这个类就可以很好地帮我们解决这一个问题。

2. 定义元组

命名元组的定义方式是: namedtuple(typename, field_names) ,其中 typename 是这一种命名元组的名字,而 field_names 是元素对应的名称,而且它可以为下面的任意一种形式:

介绍了申明的方法了之后,我们就拿一个最简单的例子来实战一下。在 python 程序中,我们经常用到“点”这样的类型,而这种时候命名元组的便捷性就体现出来了。

from collections import namedtuple
p = namedtuple('Point', ['x', 'y'])

这样,我们就定义了一种新的命名元组,接下来我们就可以使用它了。

3. 使用元组

接下来,我们用定义好的命名元组创建两个新的元组:

a = p(1, 2)
b = p(x=3, y=4)
print(a)

可以看到,初始化命名元组时,直接赋值与关键字赋值都是可以的,而这一段程序的输出结果是:

Point(x=1, y=2)

前面的 Point ,就是我们刚刚标记的命名元组名称。这样就说明 ab 这两个变量已经是 Point 类型的了。此时,我们试一下求一下这两个点之间的距离。

dis = lambda a, b : ((a.x - b.x) ** 2 + (a.y - b.y) ** 2) ** 0.5
print(dis(a, b))
2.8284271247461903

可以看到,这个时候我们就可以通过 a.x 这样的形式访问命名元组中的元素了。当然,我们还是可以像之前一样,使用 a[0] 来获取第一个元素。

4. 将一个列表导入到命名元组

有些时候,我们想将一个列表导入到命名元组中,这个时候我们就可以使用 _make() 方法来完成。

l = [1, 2]
a = p._make(l)
print(a)
Point(x=1, y=2)

有些时候,将列表转化为命名元组,会更有利于数据的处理,因此要灵活使用这一项功能。

5. 修改命名元组的值

标题看上去很令人疑惑–一个元组的值怎么能被修改呢。其实所谓的“修改”,就是新建一个修改后的元组,替换原有的元组,这样相当于间接地完成了元组的修改。而这一个功能可以通过内置方法 _replace() 来实现。

a = p(x=1, y=2)
a._replace(x=3)
print(a)
Point(x=3, y=2)

这样通过内置方法,我们就可以去“修改”命名元组的值了。

6. 命名元组的嵌套

比方说,我之前定义了一个命名元组叫 Point,而我现在想要定义一个正方形 Square ,它由 x, y, a 组成。那么这个时候,我们能否套用之前 Point 的定义,去组合出 Square 这个类型呢?

这当然是可以的,当我们使用命名元组的 _fields 这一个属性时,它将会返回这个元组组成的元素名称。

>>> p._fields
('x', 'y')

而将这个返回值用于定义新命名元组时的参数设定,我们也就实现了命名元组的嵌套:

sq = namedtuple('Square', p.fields + ('a',))
s = sq(1, 1, 4)
print(s)
Square(x=1, y=1, a=4)

这样我们就实现了一个正方形的定义。那我们能不能用另方法,去定义实现一种新的命名元组”矩形” Rectangle ,它由两组 xy 坐标组成呢?

rect = namedtuple('Rectangle', p._fields * 2)
Encountered duplicate field name: 'x'

可以看到,它提示我们有重复的属性 x ,因为命名元组要求参数唯一,这也是命名元组相较于结构体的一个缺陷。这时我们只能老老实实的用不同的变量名了。。。

rect = namedtuple('Rectangle', 'x1 y1 x2 y2')
r = rect(0, 0, 4, 4)
print(r)
Rectangle(x1=0, y1=0, x2=4, y2=4)

结语

正确地使用 namedtuple 可以使我们的代码变得更加直观,并且相较于结构体,它的定义更加的轻量化。不过当然,在某些情况下,我们还是需要使用结构体来完成更复杂的操作。因此我们在编程时应当作出合理的选择,这样才能发挥 python 最大的效率。