官方文档:https://docs.python.org/3/library/collections.html#collections.namedtuple
命名元组为元祖中每个字段命名,使其能够像访问字典一样访问元祖中的值,使其更具有可读性,它具有普通元祖的方法和属性,也具有普通元祖不具有的独特方法和属性。
普通元祖和命名元祖最大区别之一在于:普通元祖的值定义完成后无法修改,但是命名元祖里面的值都是可以修改的
先来看下 namedtuple
的代码定义
# namedtuple.__init__.py
def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None):
"""Returns a new subclass of tuple with named fields.
在定义 namedtuple
的时候有五个给出的有定义的参数,下面分别看下每个参数的含义
typename
返回一个名为 typename 的新元组子类。
# namedtuple_test.py
from collections import namedtuple
Position = namedtuple('Position', 'x, y, z')
p1 = Position('1', 2, 3)
print(p1, type(p1))
# 输出
Position(x='1', y=2, z=3) <class '__main__.Position'>
这样可能不能很好的看出 typename
的作用,我们来修改下代码,就可以更容易的看出
from collections import namedtuple
Position = namedtuple('PositionName', 'x, y, z')
p1 = Position('1', 2, 3)
print(p1, type(p1))
# 输出
PositionName(x='1', y=2, z=3) <class '__main__.PositionName'>
可以看到为元祖真正命名的是 typename
这个参数。
我们可以使用 namedtuple.__repr__()
方法来查看 typename
的值,不过没有什么实用意义
Position = namedtuple('PositionName', 'x, y, z')
p = Position('1', 2, 3)
print(p.__repr__())
# 输出
PositionName(x='1', y=2, z=3)
field_names
元祖中每个字段的名称定义,是一个字符串序列。有三种方式可以定义
['x', 'y', 'z']
'x, y, z'
'x y z'
from collections import namedtuple
Position1 = namedtuple('PositionName1', 'x, y, z')
Position2 = namedtuple('PositionName2', ['x', 'y', 'z'])
Position3 = namedtuple('PositionName3', 'x y z')
p1 = Position1('1', 2, 3)
p2 = Position2('1', 2, 3)
p3 = Position3('1', 2, 3)
print(p1)
print(p2)
print(p3)
# 输出
PositionName1(x='1', y=2, z=3)
PositionName2(x='1', y=2, z=3)
PositionName3(x='1', y=2, z=3)
可以看到这三种定义 field_names
的方式最后的结果都是一样的
**值得注意的是这里 field_names
在定义的时候,这里的值同样遵守 python 命名规范,不能和 keyword 冲突,也不能以数字和关键字下划线开头。 **
# __init__.py
...
if isinstance(field_names, str):
field_names = field_names.replace(',', ' ').split()
field_names = list(map(str, field_names))
typename = _sys.intern(str(typename))
如果重命名为true,则无效的字段名称将自动替换为位置名称。无效的字段名包括 和关键字冲突、重复字段名等等…
from collections import namedtuple
Position = namedtuple('Position', ['aaa', 'def', 'class', '__name__', 'bbb'], rename=True)
print(Position.__doc__)
# 输出
Position(aaa, _1, _2, _3, bbb)
像 def class __name__
等关键字都被 ‘_’ + 索引位置 重新命名。
下面这段代码是 __init__.py
中关于 rename
的定义
...
if rename:
seen = set()
for index, name in enumerate(field_names):
if (not name.isidentifier()
or _iskeyword(name)
or name.startswith('_')
or name in seen):
field_names[index] = f'_{index}'
seen.add(name)
defaults
的默认值可以是 None
,也可以是可迭代的值。因为默认参数的缘故(位置参数必须位于关键字参数前面),所以默认值会从最右边开始迭代。下面用例子来说明
from collections import namedtuple
Position = namedtuple('PositionName', 'x y z p', defaults=(3, 5))
p1 = Position(1, 2)
p2 = Position(1, 2, 3)
p3 = Position(1, 2, 3, 4)
print(p1)
print(p2)
print(p3)
# 输出
PositionName(x=1, y=2, z=3, p=5)
PositionName(x=1, y=2, z=3, p=5)
PositionName(x=1, y=2, z=3, p=4)
值得注意的是,当定义一个默认值的时候,也需要使用 defaults=(1, )
这样的格式。因为默认值是可迭代类型的值。
以下为 __init__.py
中 defaults
字段的定义
...
field_defaults = {}
if defaults is not None:
defaults = tuple(defaults)
if len(defaults) > len(field_names):
raise TypeError('Got more default values than field names')
field_defaults = dict(reversed(list(zip(reversed(field_names),
reversed(defaults)))))
命名元祖如果定义了 module 的值,则将命名元组的__module__属性设置为该值。
也就是说可以将定义好的命名元祖赋值给其他模块,以便在后面需要的时候方便调用。
from collections import namedtuple
import time
Position1 = namedtuple('PositionName', 'x y z p', module=time)
Position2 = namedtuple('PositionName', 'x y z p', module='utils')
print(Position1.__module__)
print(Position2.__module__)
# 输出
<module 'time' (built-in)>
utils
这里的 time
因为是内置模块,所以被标记了 built-in
。这样我们其他文件中使用 utils
这个我们自己编写的模块的时候,就可以使用该命名元祖。
官方定义
# __init__.py
...
# For pickling to work, the __module__ variable needs to be set to the frame
# where the named tuple is created. Bypass this step in environments where
# sys._getframe is not defined (Jython for example) or sys._getframe is not
# defined for arguments greater than 0 (IronPython), or where the user has
# specified a particular module.
if module is None:
try:
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
if module is not None:
result.__module__ = module
访问命名元祖中的值,可以使用索引值和字段名来访问,也可以将实例中的值赋值给其他。
from collections import namedtuple
Position = namedtuple('PositionName', 'x y z')
p = Position(1, 2, 3)
a, b, c = p
print(p.x)
print(p[0])
print(a, b, c)
# 输出
1
1
1 2 3
值得注意的是也可以使用 python 的反射机制来获取命名元祖的值
from collections import namedtuple
Position = namedtuple('PositionName', 'x y z')
p = Position(1, 2, 3)
print(getattr(p, 'y'))
# 输出
2
同样 hasattr()
也适用,但是 setattr()
就不能作用于命名元祖。
index()
方法也适用于命名元祖,作用是获取相应值的索引。
count()
返回该值在命名元祖中出现的次数
_make()
用于创建新的实例(命名元祖),参数为可迭代的值
from collections import namedtuple
Position = namedtuple('PositionName', 'x y z')
p_list = [1, 2, 3]
p = Position._make(p_list)
print(p)
# 输出
PositionName(x=1, y=2, z=3)
_asdict()
用于将命名元祖的值创建为字典格式,元祖中的值映射到字典的键值对中。
from collections import namedtuple
Position = namedtuple('PositionName', 'x y z')
p = Position(1, 2, 3)
p_dict = p._asdict()
print(p_dict, type(p_dict))
# 输出
{'x': 1, 'y': 2, 'z': 3} <class 'dict'>
_replace()
替换命名元祖中的值并返回一个新的命名元祖
from collections import namedtuple
Position = namedtuple('PositionName', 'x y z')
p = Position(1, 2, 3)
p_new = p._replace(x=11, y=22)
print(p_new)
# 输出
PositionName(x=11, y=22, z=3)
命名元祖还可以和csv文件进行交互,读取到的csv文件的内容赋值到命名元祖中。
csv文件内容如下
# books.csv
name,author,price
python-test-with-pytest,Brian·Okken,70
python-cookbook,David·Beazley,120
from collections import namedtuple
import csv
BookInfo = namedtuple('Book', 'name a price')
for info in map(BookInfo._make, csv.reader(open('./books.csv', 'r'))):
print(info.name, info.a, info.price)
# 输出
name author price
python-test-with-pytest Brian·Okken 70
python-cookbook David·Beazley 120
_fields
列出字段名称的字符串元组。在基于现有命名元祖创建新的命名元祖的时候非常有用
from collections import namedtuple
Position = namedtuple('PositionName', 'x y z')
City = namedtuple('City', 'name country')
CityPosition = namedtuple('CityPosition', Position._fields + City._fields)
city_p = CityPosition(11, 22, 33, 'hangzhou', 'China')
print(City._fields)
print(CityPosition._fields)
print(city_p)
# 输出
('name', 'country')
('x', 'y', 'z', 'name', 'country')
CityPosition(x=11, y=22, z=33, name='hangzhou', country='China')
_fields_defaults
返回一个将字段名映射到默认值的字典。
from collections import namedtuple
Position1 = namedtuple('PositionName1', 'x y z', defaults=[11])
Position2 = namedtuple('PositionName2', 'x y z', defaults=[11, 22])
print(Position1._field_defaults)
print(Position2._field_defaults)
# 输出
{'z': 11}
{'y': 11, 'z': 22}
__doc__
是对命名元祖的描述,默认是返回这个命名元祖的 typename + fields
,可以手动修改
from collections import namedtuple
Position1 = namedtuple('PositionName1', 'x y z')
Position2 = namedtuple('PositionName2', 'x y z')
Position2.__doc__ = 'new documents'
print(Position1.__doc__)
print(Position2.__doc__)
# 输出
PositionName1(x, y, z)
new documents
以上是命名元祖常用的方法和属性,查看更多请移步官方文档和源码。