Python中的” “named tuples””是什么?

What are “named tuples” in Python?

在阅读python 3.1中的更改时,我发现了一些……意想不到的:

The sys.version_info tuple is now a named tuple:

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

因此,我的问题是:

  • 什么是命名的元组?
  • 如何使用它们?
  • 为什么/什么时候应该使用命名元组而不是普通元组?
  • 为什么/什么时候应该使用普通元组而不是命名元组?
  • 是否有任何类型的"命名列表"(命名元组的可变版本)?

命名元组基本上易于创建,轻量级对象类型。命名元组实例可以使用类似对象的变量取消引用或标准元组语法来引用。它们可以类似于struct或其他常见记录类型,但它们是不可变的。它们是在python 2.6和python 3.0中添加的,尽管有一个在python 2.4中实现的方法。

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

1
2
3
4
5
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)

使用命名的tuple,它变得更可读:

1
2
3
4
5
6
7
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)

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

1
2
3
4
5
6
7
8
9
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式和可读性的地方使用命名的元组,而不是元组。我个人已经开始使用它们来表示非常简单的值类型,特别是当它们作为参数传递给函数时。它使函数更具可读性,而不必看到元组打包的上下文。

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

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

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

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

如果希望能够更改值,则需要另一种类型。对于可变记录类型有一个方便的方法,它允许您为属性设置新的值。

1
2
3
4
5
6
7
>>> 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

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

如前所述,您应该检查文档,以获取构建这些示例的更多信息。


NamedDuple是用于生成元组类的工厂函数。使用这个类,我们还可以创建按名称可调用的元组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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?

命名的元组是一个元组。

它能做任何事情。

但它不仅仅是一个元组。

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

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

1
2
3
4
>>> from collections import namedtuple
>>> class_name = 'ANamedTuple'
>>> fields = 'foo bar baz'
>>> ANamedTuple = namedtuple(class_name, fields)

这将实例化它:

1
>>> ant = ANamedTuple(1, 'bar', [])

我们可以检查它并使用它的属性:

1
2
3
4
5
6
7
8
9
>>> ant
ANamedTuple(foo=1, bar='bar', baz=[])
>>> ant.foo
1
>>> ant.bar
'bar'
>>> ant.baz.append('anything')
>>> ant.baz
['anything']

更深层次的解释

要理解命名元组,首先需要知道什么是元组。元组本质上是一个不可变(不能在内存中就地更改)的列表。

以下是使用常规元组的方法:

1
2
3
4
5
6
7
8
9
>>> student_tuple = 'Lisa', 'Simpson', 'A'
>>> student_tuple
('Lisa', 'Simpson', 'A')
>>> student_tuple[0]
'Lisa'
>>> student_tuple[1]
'Simpson'
>>> student_tuple[2]
'A'

您可以使用ITerable解包扩展元组:

1
2
3
4
5
6
7
>>> first, last, grade = student_tuple
>>> first
'Lisa'
>>> last
'Simpson'
>>> grade
'A'

命名元组是允许通过名称而不是索引访问其元素的元组!

您可以这样创建一个名称副本:

1
2
>>> from collections import namedtuple
>>> Student = namedtuple('Student', ['first', 'last', 'grade'])

您还可以使用一个名称由空格分隔的字符串,这是对API的一种更易读的用法:

1
>>> Student = namedtuple('Student', 'first last grade')

How to use them?

您可以做tuples可以做的所有事情(见上文),也可以做以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> 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和其他用户可以找到它们。

全局模块级的工作示例:

1
2
3
4
5
6
>>> 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')

这说明查找定义失败:

1
2
3
4
5
6
7
8
>>> 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?

当它改进了代码,使代码中表达了元组元素的语义时,可以使用它们。如果要使用具有不变数据属性且没有功能的对象,则可以使用它们而不是对象。您还可以对它们进行子类化以添加功能,例如:

1
2
3
4
5
6
7
8
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)?

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

一个现在扩展的,甚至可能是李斯科夫可替换的,第一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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

命名副本是一个很好的功能,它们是数据的完美容器。当您必须"存储"数据时,您将使用元组或字典,例如:

1
user = dict(name="John", age=20)

或:

1
user = ("John", 20)

字典方法是压倒性的,因为dict是可变的,比元组慢。另一方面,元组是不可变的、轻量级的,但是对于数据字段中的大量条目缺乏可读性。

命名双重是两种方法的完美折衷,它们具有很好的可读性、轻量级和不可变性(加上它们是多态的!).


命名元组允许与检查类似版本的代码向后兼容

1
2
>>> sys.version_info[0:2]
(3, 1)

同时允许将来的代码使用此语法更加明确

1
2
3
4
>>> sys.version_info.major
3
>>> sys.version_info.minor
1

命名元组

是清理代码并使其更具可读性的最简单方法之一。它自己记录元组中发生的事情。namedtuples实例与普通元组的内存效率相同,因为它们没有每个实例的字典,这使得它们比字典更快。

1
2
3
4
5
6
7
8
9
from collections import namedtuple

Color = namedtuple('Color', ['hue', 'saturation', 'luminosity'])

 p = Color(170, 0.1, 0.6)
 if p.saturation >= 0.5:
     print"Whew, that is bright!"
 if p.luminosity >= 0.5:
     print"Wow, that is light"

如果不命名元组中的每个元素,它将如下所示:

1
2
3
4
5
p = (170, 0.1, 0.6)
if p[1] >= 0.5:
    print"Whew, that is bright!"
if p[2]>= 0.5:
   print"Wow, that is light"

很难理解第一个例子中发生了什么。对于名称为双的字段,每个字段都有一个名称。你可以通过名字而不是位置或索引来访问它。我们可以称之为p.饱和,而不是p[1]。这很容易理解。看起来更干净。

创建NamedDuple的实例比创建字典容易。

1
2
3
4
5
6
7
8
9
10
11
# dictionary
>>>p = dict(hue = 170, saturation = 0.1, luminosity = 0.6)
>>>p['hue']
170

#nametuple
>>>from collections import namedtuple
>>>Color = namedtuple('Color', ['hue', 'saturation', 'luminosity'])
>>>p = Color(170, 0.1, 0.6)
>>>p.hue
170

你什么时候可以用这个名字?

  • 如前所述,name-duple使我们对tuples有了更多的了解更容易的。因此,如果需要引用元组中的项,那么将它们创建为名称的双重形式是有意义的。
  • 除了比字典更轻外,namedtuple也与字典不同的是保持顺序。
  • 与上面的示例一样,创建名称大于字典。并引用命名中的项tuple看起来比字典更干净。p.hue而不是p['hue']
  • 句法

    1
    collections.namedtuple(typename, field_names[, verbose=False][, rename=False])
    • NamedDuple在集合库中。
    • typename:这是新tuple子类的名称。
    • 字段名称:每个字段的名称序列。它可以是一个序列如清单['x', 'y', 'z']或字符串x y z所示(不带逗号,仅空白)或x, y, z
    • 重命名:如果重命名为True,则无效的字段名将自动替换为位置名称。例如,['abc', 'def', 'ghi','abc']转换为['abc', '_1', 'ghi', '_3'],消除了关键字'def'(因为这是定义函数的保留字)和重复的字段名'abc'
    • verbose:如果verbose是True,则类定义只打印出来。在建造之前。

    如果您这样选择,您仍然可以按其位置访问名称副本。p[1] == p.saturation。它仍然像普通的元组一样解包。

    方法

    支持所有常规元组方法。例如:min()、max()、len()、in、not in、concatenation(+)、index、slice等,对于namedDuple还有一些附加的。注意:这些都以下划线开头。_replace_make_asdict

    _replace返回命名元组的新实例,用新值替换指定字段。

    句法

    1
    somenamedtuple._replace(kwargs)

    例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >>>from collections import namedtuple

    >>>Color = namedtuple('Color', ['hue', 'saturation', 'luminosity'])
    >>>p = Color(170, 0.1, 0.6)

    >>>p._replace(hue=87)
    Color(87, 0.1, 0.6)

    >>>p._replace(hue=87, saturation=0.2)
    Color(87, 0.2, 0.6)

    注意:字段名不在引号中;它们是此处的关键字。记住:元组是不可变的-即使它们是命名的两个元组并且具有_replace方法。_replace生成new实例;不修改原始值或替换旧值。当然,您可以将新结果保存到变量中。p = p._replace(hue=169)

    _make

    从现有序列或ITerable生成新实例。

    句法

    1
    somenamedtuple._make(iterable)

    例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     >>>data = (170, 0.1, 0.6)
     >>>Color._make(data)
    Color(hue=170, saturation=0.1, luminosity=0.6)

    >>>Color._make([170, 0.1, 0.6])  #the list is an iterable
    Color(hue=170, saturation=0.1, luminosity=0.6)

    >>>Color._make((170, 0.1, 0.6))  #the tuple is an iterable
    Color(hue=170, saturation=0.1, luminosity=0.6)

    >>>Color._make(170, 0.1, 0.6)
    Traceback (most recent call last):
        File"<stdin>", line 1, in <module>
        File"<string>", line 15, in _make
    TypeError: 'float' object is not callable

    最后一个怎么了?括号内的项应该是iterable。因此,圆括号内的列表或元组有效,但不作为iterable封闭的值序列返回错误。

    _asdict

    返回一个新的ordereddict,它将字段名映射到相应的值。

    句法

    1
    somenamedtuple._asdict()

    例子

    1
    2
     >>>p._asdict()
    OrderedDict([('hue', 169), ('saturation', 0.1), ('luminosity', 0.6)])

    参考:https://www.reddit.com/r/python/comments/38ee9d/intro_to_namedtuple/

    还有一个命名列表,类似于命名的tuple,但是是可变的。https://pypi.python.org/pypi/name列表


    什么是name-duple?

    顾名思义,namedtuple是一个名为的元组。在标准tuple中,我们使用索引访问元素,而namedtuple允许用户定义元素的名称。这非常方便,尤其是处理csv(逗号分隔值)文件和处理复杂和大型的数据集,在这些数据集中,代码使用索引变得混乱(而不是pythonic)。

    如何使用它们?

    1
    2
    3
    4
    5
    6
    7
    >>>from collections import namedtuple
    >>>saleRecord = namedtuple('saleRecord','shopId saleDate salesAmout totalCustomers')
    >>>
    >>>
    >>>#Assign values to a named tuple
    >>>shop11=saleRecord(11,'2015-01-01',2300,150)
    >>>shop12=saleRecord(shopId=22,saleDate="2015-01-01",saleAmout=1512,totalCustomers=125)

    阅读

    1
    2
    3
    4
    5
    6
    7
    8
    9
    >>>#Reading as a namedtuple
    >>>print("Shop Id =",shop12.shopId)
    12
    >>>print("Sale Date=",shop12.saleDate)
    2015-01-01
    >>>print("Sales Amount =",shop12.salesAmount)
    1512
    >>>print("Total Customers =",shop12.totalCustomers)
    125

    csv处理中有趣的场景:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from csv import reader
    from collections import namedtuple

    saleRecord = namedtuple('saleRecord','shopId saleDate totalSales totalCustomers')
    fileHandle = open("salesRecord.csv","r")
    csvFieldsList=csv.reader(fileHandle)
    for fieldsList in csvFieldsList:
        shopRec = saleRecord._make(fieldsList)
        overAllSales += shopRec.totalSales;

    print("Total Sales of The Retail Chain =",overAllSales)

    在python内部有一个名为tuple的容器,它可以用来创建类的定义,并具有原始tuple的所有特性。

    使用命名元组将直接应用于默认类模板来生成一个简单的类,这种方法允许大量的代码来提高可读性,并且在定义类时也非常方便。


    试试这个:

    1
    collections.namedtuple()

    基本上,namedtuples是易于创建的轻量级对象类型。它们将元组转换为简单任务的方便容器。使用namedtuples时,不必使用整数索引来访问元组的成员。

    实例:

    代码1:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    >>> from collections import namedtuple

    >>> Point = namedtuple('Point','x,y')

    >>> pt1 = Point(1,2)

    >>> pt2 = Point(3,4)

    >>> dot_product = ( pt1.x * pt2.x ) +( pt1.y * pt2.y )

    >>> print dot_product
    11

    代码2:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    >>> from collections import namedtuple

    >>> Car = namedtuple('Car','Price Mileage Colour Class')

    >>> xyz = Car(Price = 100000, Mileage = 30, Colour = 'Cyan', Class = 'Y')

    >>> print xyz

    Car(Price=100000, Mileage=30, Colour='Cyan', Class='Y')
    >>> print xyz.Class
    Y

    另一种使用命名元组的方法(新方法)是使用键入package中的named tuple:named tuple中的类型提示

    让我们使用本文中的热门答案示例来了解如何使用它。

    (1)在使用命名元组之前,代码如下:

    1
    2
    3
    4
    5
    6
    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)
    print(line_length)

    (2)现在我们使用命名的元组

    1
    from typing import NamedTuple, Number

    继承NamedDuple类并在新类中定义变量名。test是类的名称。

    1
    2
    3
    class test(NamedTuple):
    x: Number
    y: Number

    从类中创建实例并为其赋值

    1
    2
    pt1 = test(1.0, 5.0)   # x is 1.0, and y is 5.0. The order matters
    pt2 = test(2.5, 1.5)

    使用实例中的变量进行计算

    1
    2
    line_length = sqrt((pt1.x-pt2.x)**2 + (pt1.y-pt2.y)**2)
    print(line_length)

    其他人都已经回答过了,但我想我还有别的事要补充。

    可以直观地将NamedDuple视为定义类的快捷方式。

    请看一个定义class的繁琐而传统的方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Duck:
        def __init__(self, color, weight):
            self.color = color
            self.weight = weight
    red_duck = Duck('red', '10')

        In [50]: red_duck
        Out[50]: <__main__.Duck at 0x1068e4e10>
        In [51]: red_duck.color
        Out[51]: 'red'

    至于namedtuple

    1
    2
    3
    4
    5
    6
    7
    8
    from collections import namedtuple
    Duck = namedtuple('Duck', ['color', 'weight'])
    red_duck = Duck('red', '10')

    In [54]: red_duck
    Out[54]: Duck(color='red', weight='10')
    In [55]: red_duck.color
    Out[55]: 'red'