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
数据类型,含有color
和mileage
两个字段。
你可能想知道为什么本例中将字符串'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
类并提供一个接受color
和mileage
值的构造函数相同:
>>> 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提供了一些有用的辅助方法,虽然这些方法以单下划线开头,但实际上是公共接口的一部分,可以正常使用。