使用namedtuple让Python代码更清晰


这篇文章主要介绍为什么使用namedtuple结果,以及对应的使用场景。目标是通过使用namedtuple,让代码更清晰。

为什么使用namedtuple

tuple结构有两个问题:

  1. tuple只能用index来获取元素,可读性差。比如a[0],我们并不知道位置0上的元素是什么,必须去看变量a的介绍才行。
  2. tuple是点对点的数据结构,粗心的时候很难保证两个tuple里的元素数量一致,或者 都有同样的属性。比如('Jack', 35, 166)('Pony', 29),后者没有身高这个属性。

因为这两个问题,使用tuple会让代码的可读性变差。而使用namedtuple可以解决上面两个问题,因为namedtuple是key:value键值对,可以通过key给每个值命名,让代码更清晰。

from collections import namedtuple

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

# 也可以写成下面的形式,只不过上面的写法更简洁
# Person = namedtuple('Person', ['name', 'age', 'height'])

jack = Person('Jack', 23, 166)

In [30]: jack
Out[30]: Person(name='Jack', age=23, height=166)

In [32]: jack.name
Out[32]: 'Jack'

In [33]: jack.age
Out[33]: 23

可以看到,jack作为一个namedtuple,我们可以清晰地看到它包含的属性的意义。这里大家可能会奇怪,感觉Person = namedtuple('Person', 'name age height') 中第一个参数传入的Person根本没用啊。其实这里第一个参数是class的真正名字,而等号左边的Person只是这个class的一个引用(reference)罢了。关于这一点,会在下面单独拿一个小节介绍。

namedtuple的使用场景

使用namedtuple一行创建immutable class

Person = namedtuple('Person', 'name age height') 这行其实相当于创建了一个名为Person的class:

# 写法1
Person = namedtuple('Person', 'name age height')

# 写法2
class Person:
  def __init__(self, name, age, height):
    self.name = name
    self.age = age
    self.height = height

这两种写法唯一的区别是:

  1. 使用namedtuple创建后,里面的属性是无法更改的
  2. 使用class语法创建后,里面的属性是可以更改的

所以namedtuple的适用场景是创建 immutable class(不可变类)

继承namedtuple

因为namedtuple相当于创建了一个immutable class,所以自然可以对这个class进行继承:

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

class PersonWithMethod(Person):
  def say_my_name(self):
    print(f'My name is {self.name}')

pony = PersonWithMethod('Pony', 29, 175)

In [35]: pony.say_my_name()
My name is Pony

namedtuple扩展知识

一些方便的内置函数


In [37]: jack._asdict()
Out[37]: OrderedDict([('name', 'Jack'), ('age', 23), ('height', 166)])

# ._asdict() 可以用来输出json格式
In [40]: json.dumps(jack._asdict())
Out[40]: '{"name": "Jack", "age": 23, "height": 166}'

# ._replace() 返回的是一个shallow copy,原先的jack是没有被更改的
In [41]: jack._replace(height=175)
Out[41]: Person(name='Jack', age=23, height=175)

# ._make() 可以用来新建一个实例,等同于 Person('Pony', 29, 175)
In [44]: jack._make(['Pony', 29, 175]) # 注意这里传入的必须是a sequence or iterable object
Out[44]: Person(name='Pony', age=29, height=175)

nametuple的第一个参数有什么用?

我们先用nametuple创建两个class

Person1 = namedtuple('Runner', 'name age height')
Person2 = namedtuple('Walker', 'name age height')

In [48]: Person1
Out[48]: __main__.Runner

In [50]: Person2
Out[50]: __main__.Walker

jack = Person1('Jack', 23, 166)
pony = Person2('Pony', 29, 175)

In [52]: jack
Out[52]: Runner(name='Jack', age=23, height=166)

In [53]: pony
Out[53]: Walker(name='Pony', age=29, height=175)

可以看到Person1Person2只是class的两个reference而已,RunnerWalker才是class真正的名字。

把上面的写法用正常构建class的写法来表示的话,会更容易理解:

class Runner:
  def __init__(self, name, age, height):
    self.name = name
    self.age = age
    self.height = height

class Walker:
  def __init__(self, name, age, height):
    self.name = name
    self.age = age
    self.height = height

jack = Runner('Jack', 23, 166)
pony = Walker('Pony', 29, 175)

In [55]: jack
Out[55]: <__main__.Runner at 0x1082bc5f8>

In [56]: pony
Out[56]: <__main__.Walker at 0x1082bc2e8>

在实际使用namedtuple的时候,建议直接把等号左边的reference和等号右边的class name写成一样的形式:

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

Python3.6之后,可以使用typing.NameTuple

from typing import NamedTuple

class PathContainer(NamedTuple):
    root: str
    document: str
    exe: str

path_container = PathContainer("root_path","document_path","exe_path")

欢迎订阅我的博客:RSS feed
知乎: 赤乐君
Blog: BrambleXu
GitHub: BrambleXu
Medium: BrambleXu

参考资料


文章作者: BrambleXu
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 BrambleXu !
评论
  目录