Python namedtuple的优点

Python namedtuple的优点,Python有专门的namedtuple容器类型,但似乎没有得到应有的重视。这是Python中那些缺乏关注但又令人惊叹的特性之一。

利用namedtuple可以手动定义类(class),除此之外,本节还会介绍namedtuple中其他有趣的特性。

那么namedtuple是什么,有什么特别之处呢?理解namedtuple的一个好方法是将其视为内置元组数据类型的扩展。

Python的元组是用于对任意对象进行分组的简单数据结构。元组也是不可变的,创建后就不能修改。来看一个简单的例子:

>>> tup = ('hello', object(), 42)
>>> tup
('hello', <object object at 0x105e76b70>, 42)
>>> tup[2]
42
>>> tup[2] = 23
TypeError:
"'tuple' object does not support item assignment"

简单元组有一个缺点,那就是存储在其中的数据只能通过整数索引来访问。无法给存储在元组中的单个属性赋予名称,因而代码的可读性不高。

另外,元组是一种具有单例性质的数据结构,很难保证两个元组存有相同数量的字段和相同的属性,因此很容易因为不同元组之间的字段顺序不同而引入难以意识到的bug。

Python namedtuple的优点 namedtuple上场

namedtuple旨在解决两个问题。

首先,与普通元组一样,namedtuple是不可变容器。一旦将数据存储在namedtuple的顶层属性中,就不能更新属性了。namedtuple对象上的所有属性都遵循“一次写入,多次读取”的原则。

其次,namedtuple就是具有名称的元组。存储在其中的每个对象都可以通过唯一的(人类可读的)标识符来访问。因此不必记住整数索引,也无须采用其他变通方法,如将整数常量定义为索引的助记符。
下面来看看namedtuple:

>>> from collections import namedtuple
>>> Car = namedtuple('Car' , 'color mileage')

namedtuple在Python 2.6被首次添加到标准库中。使用时需要导入collections模块。上面的例子中定义了一个简单的Car数据类型,含有colormileage两个字段。

你可能想知道为什么本例中将字符串'Car'作为第一个参数传递给namedtuple工厂函数。

这个参数在Python文档中被称为typename,在调用namedtuple函数时作为新创建的类名称。

由于namedtuple并不知道创建的类最后会赋给哪个变量,因此需要明确告诉它需要使用的类名。namedtuple会自动生成文档字符串和__repr__,其中的实现中会用到类名。

在这个例子中还有另外一个奇特的语法:为什么将字段作为'color mileage'这样的字符串整体传递?

答案是namedtuple的工厂函数会对字段名称字符串调用split(),将其解析为字段名称列表。分开来就是下面这两步:

>>> 'color mileage'.split()
['color', 'mileage']
>>> Car = namedtuple('Car', ['color', 'mileage'])

当然,如果更倾向于分开写的话,也可以直接传入带有字符串字段名称的列表。使用列表的好处是,在需要拆分成多行时可以更轻松地重新格式化代码:

>>> Car = namedtuple('Car', [
...     'color',
...     'mileage',
... ])

无论以什么方式初始化,现在都可以使用Car工厂函数创建新的“汽车”对象,其效果和手动定义Car类并提供一个接受colormileage值的构造函数相同:

>>> my_car = Car('red', 3812.4)
>>> my_car.color
'red'
>>> my_car.mileage
3812.4

除了通过标识符来访问存储在namedtuple中的值之外,索引访问仍然可用。因此namedtuple可以用作普通元组的替代品:

>>> my_car[0]
'red'
>>> tuple(my_car)
('red', 3812.4)

元组解包和用于函数参数解包的*操作符也能正常工作:

>>> color, mileage = my_car
>>> print(color, mileage)
red 3812.4
>>> print(*my_car)
red 3812.4

自动得到的namedtuple对象字符串形式也挺不错的,不用自己编写相关函数了:

>>> my_car
Car(color='red' , mileage=3812.4)

与元组一样,namedtuple是不可变的。试图覆盖某个字段时会得到一个AttributeError异常:

>>> my_car.color = 'blue'
AttributeError: "can't set attribute"

namedtuple对象在内部是以普通的Python类实现的。当涉及内存使用时,namedtuple比普通类“更好”,它和普通元组的内存占用都比较少。

可以这么看:namedtuple适合在Python中以节省内存的方式快速手动定义一个不可变的类。

Python namedtuple的优点 子类化namedtuple

因为namedtuple建立在普通Python类之上,所以还可以向namedtuple对象添加方法。例如,可以像其他类一样扩展namedtuple定义的类,为其添加方法和新属性。来看一个例子:

Car = namedtuple('Car', 'color mileage')

class MyCarWithMethods(Car):
    def hexcolor(self):
        if self.color == 'red':
            return '#ff0000'
        else:
            return '#000000'

现在能够创建MyCarWithMethods对象并调用hexcolor()方法了,就像预期的那样:

>>> c = MyCarWithMethods('red', 1234)
>>> c.hexcolor()
'#ff0000'

这种方式可能有点笨拙,但适合构建具有不可变属性的类,不过也很容易带来其他问题。

例如,由于namedtuple内部的结构比较特殊,因此很难添加新的不可变字段。另外,创建namedtuple类层次的最简单方法是使用基类元组的_fields属性:

>>> Car = namedtuple('Car', 'color mileage')
>>> ElectricCar = namedtuple(
...     'ElectricCar', Car._fields + ('charge',))

结果符合预期:

>>> ElectricCar('red', 1234, 45.0)
ElectricCar(color='red', mileage=1234, charge=45.0)

Python namedtuple的优点 内置的辅助方法

除了_fields属性,每个namedtuple实例还提供了其他一些有用的辅助方法。这些方法都以单下划线(_)开头。单下划线通常表示方法或属性是“私有”的,不是类或模块的稳定公共接口的一部分。

然而在namedtuple中,下划线具有不同的含义。这些辅助方法和属性是namedtuple公共接口的一部分,以单下划线开头只是为了避免与用户定义的元组字段发生命名冲突。所以在需要时就尽管使用吧。

下面会介绍一些能用到这些namedtuple辅助方法的情形。我们从_asdict()辅助方法开始,该方法将namedtuple的内容以字典形式返回:

>>> my_car._asdict()
OrderedDict([('color', 'red'), ('mileage', 3812.4)])

这样在生成JSON输出时可以避免拼错字段名称:

>>> json.dumps(my_car._asdict())
'{"color": "red", "mileage": 3812.4}'

另一个有用的辅助函数是_replace()。该方法用于创建一个元组的浅副本,并能够选择替换其中的一些字段:

>>> my_car._replace(color='blue')
Car(color='blue', mileage=3812.4)

最后介绍的是_make()类方法,用来从序列或迭代对象中创建namedtuple的新实例:

>>> Car._make(['red', 999])
Car(color='red', mileage=999)

Python namedtuple的优点 何时使用namedtuple

namedtuple能够更好地组织数据的结构,让代码更整洁、更易读。

例如,将格式固定、针对特定场景的数据类型(比如字典)替换为namedtuple能更清楚地表达开发者的意图。通常,当我尝试以这种方式重构时,就会神奇地为眼前的问题想出一个更好的解决方案。

用namedtuple替换非结构化的元组和字典还可以减轻同事的负担,因为namedtuple让数据在传递时在某种程度上做到了自说明(self documenting)。

另一方面,如果namedtuple不能帮我编写更整洁、更易维护的代码,那么我会尽量避免使用。像本书介绍的许多其他技术一样,滥用namedtuple会带来负面影响。
但只要仔细使用,namedtuples无疑能让Python代码更好、更可读。

关键要点

  • collection.namedtuple能够方便地在Python中手动定义一个内存占用较少的不可变类。

  • 使用namedtuple能按照更易理解的结构组织数据,进而简化了代码。

  • namedtuple提供了一些有用的辅助方法,虽然这些方法以单下划线开头,但实际上是公共接口的一部分,可以正常使用。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程