python-nametuple-命名元祖

1. 简介

官方文档:https://docs.python.org/3/library/collections.html#collections.namedtuple
命名元组为元祖中每个字段命名,使其能够像访问字典一样访问元祖中的值,使其更具有可读性,它具有普通元祖的方法和属性,也具有普通元祖不具有的独特方法和属性。
普通元祖和命名元祖最大区别之一在于:普通元祖的值定义完成后无法修改,但是命名元祖里面的值都是可以修改的

2. 参数介绍

先来看下 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 的时候有五个给出的有定义的参数,下面分别看下每个参数的含义

2.1 typename

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)

2.2 field_names

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))

2.3 rename

如果重命名为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)

2.4 defaults

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__.pydefaults 字段的定义

...
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)))))

2.5 module

命名元祖如果定义了 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

3. 属性和方法

3.1 方法

访问命名元祖中的值,可以使用索引值和字段名来访问,也可以将实例中的值赋值给其他。

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()

index() 方法也适用于命名元祖,作用是获取相应值的索引。

count()

count() 返回该值在命名元祖中出现的次数

_make()

_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()

_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()

_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文件的内容赋值到命名元祖中。

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

3.2 属性

_fields

_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

_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

__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

以上是命名元祖常用的方法和属性,查看更多请移步官方文档和源码。

你可能感兴趣的:(python,python)