什么是命名元组?
命名元组是一个元组。
它完成了元组所能做的一切。
但它不仅仅是一个元组。
它是元组的一个特定子类,它是按照您的规范以编程方式创建的,具有命名字段和固定长度。
例如,这创建了一个元组的子类,除了具有固定长度(在本例中为三个)之外,它可以在任何使用元组的地方使用而不会中断。这称为 Liskov 可替代性。
New in Python 3.6,我们可以使用带有typing.NamedTuple 的类定义来创建命名元组:
from typing import NamedTuple
class ANamedTuple(NamedTuple):
"""a docstring"""
foo: int
bar: str
baz: list
上面和下面一样,只是上面多了一个类型注释和一个文档字符串。以下内容在 Python 2+ 中可用:
>>> from collections import namedtuple
>>> class_name = 'ANamedTuple'
>>> fields = 'foo bar baz'
>>> ANamedTuple = namedtuple(class_name, fields)
这会实例化它:
>>> ant = ANamedTuple(1, 'bar', [])
我们可以检查它并使用它的属性:
>>> ant
ANamedTuple(foo=1, bar='bar', baz=[])
>>> ant.foo
1
>>> ant.bar
'bar'
>>> ant.baz.append('anything')
>>> ant.baz
['anything']
更深层次的解释
要了解命名元组,您首先需要了解元组是什么。元组本质上是一个不可变(不能在内存中就地更改)列表。
以下是使用常规元组的方法:
>>> student_tuple = 'Lisa', 'Simpson', 'A'
>>> student_tuple
('Lisa', 'Simpson', 'A')
>>> student_tuple[0]
'Lisa'
>>> student_tuple[1]
'Simpson'
>>> student_tuple[2]
'A'
您可以使用可迭代解包来扩展元组:
>>> first, last, grade = student_tuple
>>> first
'Lisa'
>>> last
'Simpson'
>>> grade
'A'
命名元组是允许通过名称而不是索引访问其元素的元组!
你像这样创建一个命名元组:
>>> from collections import namedtuple
>>> Student = namedtuple('Student', ['first', 'last', 'grade'])
您还可以使用名称由空格分隔的单个字符串,这样 API 的可读性会更高:
>>> Student = namedtuple('Student', 'first last grade')
如何使用它们?
你可以做所有元组可以做的事情(见上文),也可以做以下事情:
>>> named_student_tuple = Student('Lisa', 'Simpson', 'A')
>>> named_student_tuple.first
'Lisa'
>>> named_student_tuple.last
'Simpson'
>>> named_student_tuple.grade
'A'
>>> named_student_tuple._asdict()
OrderedDict([('first', 'Lisa'), ('last', 'Simpson'), ('grade', 'A')])
>>> vars(named_student_tuple)
OrderedDict([('first', 'Lisa'), ('last', 'Simpson'), ('grade', 'A')])
>>> new_named_student_tuple = named_student_tuple._replace(first='Bart', grade='C')
>>> new_named_student_tuple
Student(first='Bart', last='Simpson', grade='C')
一位评论者问:
在大型脚本或程序中,通常在哪里定义命名元组?
您使用namedtuple 创建的类型基本上是您可以用简单的速记方式创建的类。像对待班级一样对待他们。在模块级别定义它们,以便 pickle 和其他用户可以找到它们。
工作示例,在全局模块级别:
>>> from collections import namedtuple
>>> NT = namedtuple('NT', 'foo bar')
>>> nt = NT('foo', 'bar')
>>> import pickle
>>> pickle.loads(pickle.dumps(nt))
NT(foo='foo', bar='bar')
这表明查找定义失败:
>>> def foo():
... LocalNT = namedtuple('LocalNT', 'foo bar')
... return LocalNT('foo', 'bar')
...
>>> pickle.loads(pickle.dumps(foo()))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <class '__main__.LocalNT'>: attribute lookup LocalNT on __main__ failed
为什么/何时应该使用命名元组而不是普通元组?
当它改进您的代码以在您的代码中表达元组元素的语义时使用它们。
如果您要使用具有不变数据属性且没有功能的对象,则可以使用它们代替对象。
你也可以subclass them to add functionality, for example:
class Point(namedtuple('Point', 'x y')):
"""adding functionality to a named tuple"""
__slots__ = ()
@property
def hypot(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __str__(self):
return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot)
为什么/何时应该使用普通元组而不是命名元组?
从使用命名元组切换到元组可能是一种回归。前期设计决策的中心是所涉及的额外代码的成本是否值得在使用元组时提高可读性。
命名元组与元组相比没有使用额外的内存。
是否有任何类型的“命名列表”(命名元组的可变版本)?
您正在寻找实现静态大小列表的所有功能的开槽对象或像命名元组一样工作的子类列表(并且以某种方式阻止列表大小变化。)
一个现在扩展的,甚至可能是 Liskov 可替代的第一个例子:
from collections import Sequence
class MutableTuple(Sequence):
"""Abstract Base Class for objects that work like mutable
namedtuples. Subclass and define your named fields with
__slots__ and away you go.
"""
__slots__ = ()
def __init__(self, *args):
for slot, arg in zip(self.__slots__, args):
setattr(self, slot, arg)
def __repr__(self):
return type(self).__name__ + repr(tuple(self))
# more direct __iter__ than Sequence's
def __iter__(self):
for name in self.__slots__:
yield getattr(self, name)
# Sequence requires __getitem__ & __len__:
def __getitem__(self, index):
return getattr(self, self.__slots__[index])
def __len__(self):
return len(self.__slots__)
要使用,只需子类化并定义__slots__:
class Student(MutableTuple):
__slots__ = 'first', 'last', 'grade' # customize
>>> student = Student('Lisa', 'Simpson', 'A')
>>> student
Student('Lisa', 'Simpson', 'A')
>>> first, last, grade = student
>>> first
'Lisa'
>>> last
'Simpson'
>>> grade
'A'
>>> student[0]
'Lisa'
>>> student[2]
'A'
>>> len(student)
3
>>> 'Lisa' in student
True
>>> 'Bart' in student
False
>>> student.first = 'Bart'
>>> for i in student: print(i)
...
Bart
Simpson
A