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

时间:2010-06-03 23:50:17

标签: python tuples namedtuple

阅读changes in Python 3.1,我发现了一些......意外的事情:

  

sys.version_info元组现在是命名元组

之前我从未听说过命名元组,我认为元素可以用数字(如元组和列表)或键(如dicts)索引。我从没想过他们可以双向索引。

因此,我的问题是:

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

12 个答案:

答案 0 :(得分:1037)

命名元组基本上是易于创建的轻量级对象类型。可以使用类似对象的变量解引用或标准元组语法来引用命名的元组实例。它们可以与struct或其他常见记录类型类似地使用,除了它们是不可变的。它们是在Python 2.6和Python 3.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

因此,你应该使用命名元组而不是元组,你认为对象符号会使你的代码更加pythonic,更容易阅读。我个人已经开始使用它们来表示非常简单的值类型,特别是在将它们作为参数传递给函数时。它使函数更具可读性,而不会看到元组打包的上下​​文。

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

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

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

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

答案 1 :(得分:88)

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)

答案 2 :(得分:65)

  

什么是元组?

一个命名元组是一个元组。

它完成了元组所能做的一切。

但它不仅仅是一个元组。

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

例如,这创建了一个元组的子类,除了具有固定长度(在这种情况下为三个)之外,它可以在任何使用元组的地方使用而不会破坏。这被称为Liskov可替代性:

>>> 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 unpacking扩展元组:

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

为什么/何时应该使用普通元组而不是命名元组?

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

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

  

是否有任何类型的&#34;命名列表&#34; (命名元组的可变版本)?

您正在寻找实现静态大小列表的所有功能的插槽对象或者像命名元组一样工作的子类列表(并且以某种方式阻止列表更改大小。)

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

答案 3 :(得分:38)

namedtuples是一个很棒的功能,它们是数据的完美容器。当您必须“存储”数据时,您将使用元组或词典,例如:

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

或:

user = ("John", 20)

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

namedtuples是两种方法的完美折衷方案,具有极佳的可读性,轻量级和不变性(加上它们具有多态性!)。

答案 4 :(得分:28)

命名元组允许向后兼容检查类似

的版本的代码
>>> sys.version_info[0:2]
(3, 1)

使用此语法

允许将来的代码更明确
>>> sys.version_info.major
3
>>> sys.version_info.minor
1

答案 5 :(得分:9)

namedtuple

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

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"

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

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"

要理解第一个例子中发生的事情要困难得多。使用namedtuple,每个字段都有一个名称。您可以通过名称而不是位置或索引来访问它。而不是p[1],我们可以称之为p.saturation。它更容易理解。它看起来更干净。

创建namedtuple的实例比创建字典更容易。

# 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

您何时可以使用namedtuple

  1. 如上所述,namedtuple使得元组理解得更多 更轻松。因此,如果您需要引用元组中的项目,那么 将它们创建为命名元组只是有意义。
  2. 除了比字典更轻量级之外,还有一个名字 保持顺序不像字典。
  3. 如上例所示,创建实例更简单 namedtuple比字典。并引用命名中的项目 元组看起来比字典更清晰。 p.hue而非。{ p['hue']
  4. 语法

    collections.namedtuple(typename, field_names[, verbose=False][, rename=False])
    
    • namedtuple位于集合库中。
    • typename:这是新元组子类的名称。
    • field_names:每个字段的名称序列。它可以是一个序列 如列表['x', 'y', 'z']或字符串x y z(没有逗号,只是 空白)或x, y, z
    • 重命名:如果重命名为True,则会自动生成无效的字段名称 替换为位置名称。例如,['abc', 'def', 'ghi','abc']将转换为['abc', '_1', 'ghi', '_3'],从而消除了'def' 关键字'abc'(因为这是用于定义函数的保留字) 和重复的字段名True
    • 详细:如果详细信息为p[1] == p.saturation,则仅打印类定义 在建造之前。

    如果您愿意,您仍然可以按位置访问namedtuples。 _replace。它仍然像常规元组一样解压缩。

    方法

    支持所有regular tuple methods。例如:min(),max(),len(),in,not in,concatenation(+),index,slice等。还有一些额外的用于namedtuple。注意:这些都以下划线开头。 _make_asdict_replace

    <强> somenamedtuple._replace(kwargs) 返回指定元组的新实例,用新值替换指定字段。

    语法

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

    <强> somenamedtuple._make(iterable)

    从现有序列或可迭代构建新实例。

    语法

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

    实施例

    _asdict

    最后一个发生了什么?括号内的项应该是可迭代的。因此括号内的列表或元组可以正常工作,但是没有作为迭代包含的值序列会返回错误。

    <强> somenamedtuple._asdict()

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

    语法

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

    实施例

    {{1}}

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

    还有一个命名列表,类似于命名元组但可变 https://pypi.python.org/pypi/namedlist

答案 6 :(得分:8)

whattuple是什么意思?

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

如何使用它们?

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

阅读

>>>#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处理中的有趣场景:

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)

答案 7 :(得分:5)

在Python里面,可以很好地使用名为tuple的容器,它可以用来创建类的定义,并具有原始元组的所有功能。

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

答案 8 :(得分:1)

试试这个:

collections.namedtuple()

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

示例:

代码1:

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

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

答案 9 :(得分:1)

我认为值得使用类型提示添加有关 NamedTuples 的信息:

# dependencies
from typing import NamedTuple, Optional

# definition
class MyNamedTuple(NamedTuple):
    an_attribute: str
    my_attribute: Optional[str] = None
    next_attribute: int = 1

# instantiation
my_named_tuple = MyNamedTuple("abc", "def")
# or more explicitly:
other_tuple = MyNamedTuple(an_attribute="abc", my_attribute="def")

# access
assert "abc" == my_named_tuple.an_attribute
assert 1 == other_tuple.next_attribute

答案 10 :(得分:0)

使用命名元组的另一种方法(新方法)是通过键入以下软件包来使用NamedTuple:Type hints in namedtuple

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

(1)在使用命名元组之前,代码是这样的:

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)现在我们使用命名的元组

from typing import NamedTuple, Number

继承NamedTuple类,并在新类中定义变量名称。测试是类的名称。

class test(NamedTuple):
x: Number
y: Number

从类中创建实例并为其分配值

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

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

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

答案 11 :(得分:-1)

其他人已经回答了这个问题,但我想还有其他事情需要补充。

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

查看定义class的繁琐和传统方式。

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

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'