Python中的“命名元组”是什么?

2024-05-17 15:53:08 发布

您现在位置:Python中文网/ 问答频道 /正文

在阅读changes in Python 3.1时,我发现了一些。。。意外:

The sys.version_info tuple is now a named tuple:

我以前从未听说过命名元组,我认为元素可以通过数字(如元组和列表中的)或键(如dict中的)进行索引。我从来没想到它们会同时被索引。

因此,我的问题是:

  • 什么叫元组?
  • 如何使用它们?
  • 为什么/何时应该使用命名元组而不是普通元组?
  • 为什么/何时应该使用普通元组而不是命名元组?
  • 有什么“命名列表”(命名元组的可变版本)吗?

Tags: theininfo元素列表isversionsys
3条回答

namedtuple是用于生成元组类的工厂函数。有了这个类,我们可以创建也可以按名称调用的元组。

import collections

#Create a namedtuple class with names "a" "b" "c"
Row = collections.namedtuple("Row", ["a", "b", "c"], verbose=False, rename=False)   

row = Row(a=1,b=2,c=3) #Make a namedtuple from the Row class we created

print row    #Prints: Row(a=1, b=2, c=3)
print row.a  #Prints: 1
print row[0] #Prints: 1

row = Row._make([2, 3, 4]) #Make a namedtuple from a list of values

print row   #Prints: Row(a=2, b=3, c=4)

What are named tuples?

命名元组是元组。

它能做任何事情。

但不仅仅是一个元组。

它是一个元组的特定子类,是根据您的规范以编程方式创建的,具有命名字段和固定长度。

例如,这将创建tuple的一个子类,除了具有固定长度(在本例中为3)之外,它还可以在使用tuple的任何地方使用,而不会中断。这被称为Liskov可替代性。

New in Python 3.6,我们可以使用带有typing.NamedTuple的类定义来创建namedtuple:

from typing import NamedTuple

class ANamedTuple(NamedTuple):
    """a docstring"""
    foo: int
    bar: str
    baz: list

上面的与下面的相同,只是上面另外有类型注释和docstring。以下是Python2+中提供的:

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

可以使用iterable解包来展开元组:

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

How to use them?

您可以执行元组可以执行的所有操作(参见上文)以及以下操作:

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

一位评论者问道:

In a large script or programme, where does one usually define a named tuple?

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

Why/when should I use named tuples instead of normal tuples?

在改进代码以使元组元素的语义在代码中表达时使用它们。

如果要使用具有不变数据属性且没有功能的对象,则可以使用它们而不是对象。

您还可以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)

Why/when should I use normal tuples instead of named tuples?

从使用命名元组切换到元组可能是一种回归。前期设计决策的核心是,当使用元组时,所涉及的额外代码的成本是否值得提高可读性。

命名元组与元组之间没有使用额外的内存。

Is there any kind of "named list" (a mutable version of the named tuple)?

您要寻找的是一个时隙对象,它实现了静态大小的列表的所有功能,或者是一个工作方式类似于命名元组的子类列表(它以某种方式阻止了列表的大小变化)

一个现在已经扩大的,甚至可能是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

命名元组基本上是易于创建的轻量级对象类型。命名元组实例可以使用类似对象的变量解引用或标准元组语法来引用。它们可以类似于struct或其他常见的记录类型,只是它们是不可变的。它们是在Python2.6和Python3.0中添加的,尽管有一个recipe for implementation in Python 2.4

例如,通常将点表示为元组(x, y)。这将导致如下代码:

pt1 = (1.0, 5.0)
pt2 = (2.5, 1.5)

from math import sqrt
line_length = sqrt((pt1[0]-pt2[0])**2 + (pt1[1]-pt2[1])**2)

使用命名元组,它变得更可读:

from collections import namedtuple
Point = namedtuple('Point', 'x y')
pt1 = Point(1.0, 5.0)
pt2 = Point(2.5, 1.5)

from math import sqrt
line_length = sqrt((pt1.x-pt2.x)**2 + (pt1.y-pt2.y)**2)

但是,命名元组仍然与普通元组向后兼容,因此以下操作仍然有效:

Point = namedtuple('Point', 'x y')
pt1 = Point(1.0, 5.0)
pt2 = Point(2.5, 1.5)

from math import sqrt
# use index referencing
line_length = sqrt((pt1[0]-pt2[0])**2 + (pt1[1]-pt2[1])**2)
 # use tuple unpacking
x1, y1 = pt1

因此,您应该使用命名元组而不是元组,只要您认为对象表示法将使您的代码更具python性,并且更易于阅读。我个人已经开始使用它们来表示非常简单的值类型,特别是当将它们作为参数传递给函数时。它使函数更具可读性,而不必查看元组打包的上下文。

此外,您还可以替换普通的不可变的类,这些类没有函数,只有字段。甚至可以将命名元组类型用作基类:

class Point(namedtuple('Point', 'x y')):
    [...]

但是,与元组一样,命名元组中的属性是不可变的:

>>> Point = namedtuple('Point', 'x y')
>>> pt1 = Point(1.0, 5.0)
>>> pt1.x = 2.0
AttributeError: can't set attribute

如果希望能够更改值,则需要其他类型。有一个方便的mutable recordtypes配方,允许您为属性设置新值。

>>> from rcdtype import *
>>> Point = recordtype('Point', 'x y')
>>> pt1 = Point(1.0, 5.0)
>>> pt1 = Point(1.0, 5.0)
>>> pt1.x = 2.0
>>> print(pt1[0])
    2.0

但是,我不知道任何形式的“命名列表”允许您添加新字段。你可能只想在这种情况下用字典。命名元组可以使用返回{'x': 1.0, 'y': 5.0}pt1._asdict()转换为字典,并且可以使用所有常用的字典函数进行操作。

如前所述,您应该check the documentation获取构造这些示例的更多信息。

相关问题 更多 >