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

  • 什么是命名元组以及如何使用它们?
  • 什么时候应该使用命名元组而不是普通元组,反之亦然?
  • 是否也有“命名列表”?(即可变命名元组)
574071 次浏览

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

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

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

>>> sys.version_info.major3>>> sys.version_info.minor1

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

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

pt1 = (1.0, 5.0)pt2 = (2.5, 1.5)
from math import sqrtline_length = sqrt((pt1[0]-pt2[0])**2 + (pt1[1]-pt2[1])**2)

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

from collections import namedtuplePoint = namedtuple('Point', 'x y')pt1 = Point(1.0, 5.0)pt2 = Point(2.5, 1.5)
from math import sqrtline_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 referencingline_length = sqrt((pt1[0]-pt2[0])**2 + (pt1[1]-pt2[1])**2)# use tuple unpackingx1, y1 = pt1

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

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

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

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

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

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

>>> 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},并且可以使用所有常见的字典函数进行操作。

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

命名元组是一个很棒的功能,它们是数据的完美容器。当你必须“存储”数据时,你可以使用元组或字典,例如:

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

或:

user = ("John", 20)

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

命名元组是这两种方法的完美折衷,它们具有很强的易读性、轻量级和不变性(另外它们是多态的!)。

命名元组是用于创建元组类的工厂函数。使用该类,我们可以创建也可以通过名称调用的元组。

import collections
#Create a namedtuple class with names "a" "b" "c"Row = collections.namedtuple("Row", ["a", "b", "c"])
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: 1print 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)

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

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

什么是namedtuple?

顾名思义,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 readerfrom 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)

什么叫元组?

命名元组是一个元组。

它做了元组可以做的一切。

但它不仅仅是一个元组。

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

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

Python 3.6中的新功能,我们可以使用#0的类定义来创建一个命名元组:

from typing import NamedTuple
class ANamedTuple(NamedTuple):"""a docstring"""foo: intbar: strbaz: list

上面的与#0相同,除了上面还有类型注释和文档字符串。以下在Python 2+中可用:

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

这实例化它:

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

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

>>> antANamedTuple(foo=1, bar='bar', baz=[])>>> ant.foo1>>> 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'

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

你做了一个这样的namedtuple:

>>> 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_tupleStudent(first='Bart', last='Simpson', grade='C')

一位评论者问道:

在大型脚本或程序中,通常在哪里定义命名元组?

您使用namedtuple创建的类型基本上是您可以通过简单速记创建的类。像对待类一样对待它们。在模块级别定义它们,以便泡菜和其他用户可以找到它们。

在全局模块级别的工作示例:

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

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

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

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

你也可以子类化它们以添加功能,例如

class Point(namedtuple('Point', 'x y')):"""adding functionality to a named tuple"""__slots__ = ()@propertydef hypot(self):return (self.x ** 2 + self.y ** 2) ** 0.5def __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 mutablenamedtuples. 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'sdef __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')>>> studentStudent('Lisa', 'Simpson', 'A')>>> first, last, grade = student>>> first'Lisa'>>> last'Simpson'>>> grade'A'>>> student[0]'Lisa'>>> student[2]'A'>>> len(student)3>>> 'Lisa' in studentTrue>>> 'Bart' in studentFalse>>> student.first = 'Bart'>>> for i in student: print(i)...BartSimpsonA

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

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"

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

创建命名元组的实例比创建字典更容易。

# 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.hue170

什么时候可以使用namedtuple

  1. 如上所述,namedtuple使得对元组的理解变得更加重要因此,如果您需要引用元组中的项目,那么将它们创建为命名元组才有意义。
  2. 除了比字典更轻量级之外,namedtuple还保持与字典不同的顺序。
  3. 与上面的示例一样,创建一个实例更简单名称元组比字典。并引用命名中的项目元组看起来比字典更干净。p.hue而不是p['hue'].

的语法

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

如果您选择,您仍然可以通过它们的位置访问命名元组。p[1] == p.saturation。它仍然像常规元组一样解包。

方法

支持所有正则元组方法。例如:min()、max()、len()、in、not in、连接(+)、索引、切片等。还有一些额外的命名元组。注意:这些都以下划线开头。_replace_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 iterableColor(hue=170, saturation=0.1, luminosity=0.6)
>>>Color._make((170, 0.1, 0.6))  #the tuple is an iterableColor(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 _makeTypeError: 'float' object is not callable

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

_asdict

返回一个新的订单号,它将字段名称映射到它们对应的值。

的语法

somenamedtuple._asdict()

示例

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

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

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

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

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

请参阅定义class的繁琐而传统的方法。

class Duck:def __init__(self, color, weight):self.color = colorself.weight = weightred_duck = Duck('red', '10')
In [50]: red_duckOut[50]: <__main__.Duck at 0x1068e4e10>In [51]: red_duck.colorOut[51]: 'red'

至于namedtuple

from collections import namedtupleDuck = namedtuple('Duck', ['color', 'weight'])red_duck = Duck('red', '10')
In [54]: red_duckOut[54]: Duck(color='red', weight='10')In [55]: red_duck.colorOut[55]: 'red'

试试这个:

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_product11

代码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.ClassY

使用命名元组的另一种方法(一种新方法)是从键入包中使用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

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

class test(NamedTuple):x: floaty: float

从类中创建实例并为它们赋值

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

使用实例中的变量来计算

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

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

# dependenciesfrom typing import NamedTuple, Optional
# definitionclass MyNamedTuple(NamedTuple):an_attribute: strmy_attribute: Optional[str] = Nonenext_attribute: int = 1
# instantiationmy_named_tuple = MyNamedTuple("abc", "def")# or more explicitly:other_tuple = MyNamedTuple(an_attribute="abc", my_attribute="def")
# accessassert "abc" == my_named_tuple.an_attributeassert 1 == other_tuple.next_attribute
  • 它们对元组进行子类化,并添加一个层来为位置元素分配属性名称

  • 位于集合标准库模块中

      from collections import namedtuple

“namedtuple”是一个函数,它生成一个从“tuple”继承的新类,但也提供“命名属性”来访问元组的元素。

生成命名元组类

"namedtuple"是一个类厂.它需要一些东西来生成类

  • 我们要使用的类名

  • 我们要分配的字段名称序列,按照元组中元素的顺序。字段名称可以是任何有效的变量名称,除非它们不能以“下划线”开头。

  • 调用“namedtuple”的返回值将是一个类。我们需要在代码中将该类分配给一个变量名,以便我们可以用它来构造实例。通常,我们使用与生成的类名相同的名称。

    使用名称元组('x','y','x','x')

  • 现在我们可以创建COODS的实例:

    pt=Coords(10,20)
  • 有很多方法可以将字段名称列表提供给namedtuple函数。

    • 一个字符串列表

         namedtuple('Coords',['x','y'])
    • 一个字符串元组

         namedtuple('Coords',('x','y'))
    • 一个字符串,字段名称由空格或逗号分隔

         namedtuple('Coords','x, y'])

实例化命名元组

创建命名元组类后,我们可以像普通类一样实例化它们。实际上,生成类的__new__方法使用我们提供的字段名作为参数名。

Coords = namedtuple('Coords', ['x', 'y'])coord=Coords(10,20)

访问命名元组中的数据:

由于命名元组只是元组,我们仍然可以像处理任何其他元组一样处理它们:通过索引、切片、迭代

Coords = namedtuple('Coords', ['x', 'y'])coord=Coords(10,20)       isinstance(coord,tuple) --> True # namedtuple is subclass of tuple
x,y=coord  # Unpackingx=coord[0] # by indexfor e in coord:print(e)
  • 现在我们还可以使用我们对类所做的字段名称来访问数据。

       coord.x --> 10coord.y --> 20
  • 由于namedtuple是从元组继承的类生成的,我们可以这样写:

     class Coord(tuple):....
  • “coord”是一个元组,因此是不可变的

namedtuple的"rename"关键字arg

字段名称不能以下划线开头

  Coords = namedtuple('Coords', ['x', '_y']) # does not work

namedtuple有一个仅限关键字的参数rename(默认为False),它会自动重命名任何无效的字段名称。

Coords = namedtuple('Coords', ['x', '_y'], rename=True)

字段名“x”不会改变,但“_y”会改变为_1。1是字段名的索引。

将命名元组值提取到字典中

Coords = namedtuple('Coords', ['x', 'y'])coord=Coords(10,20)coord._asdict(){'x': 10, 'y': 20}

为什么我们使用namedtuple

如果你有这个类:

class Stock:def __init__(self, symbol, year, month, day, open, high, low, close):self.symbol = symbolself.year = yearself.month = monthself.day = dayself.open = openself.high = highself.low = lowself.close = close

类方法-vs-元组方法

stock.symbol              stock[0]stock.open                stock[4]stock.close               stock[7]stock.high – stock.low     stock[5] – stock[6]

如你所见,元组方法不可读。集合中的namedtuple 函数允许我们创建一个元组,该元组还将名称附加到每个字段或属性。这可以方便地通过“名称”引用元组结构中的数据,而不仅仅是依赖于位置。但请记住,元组是不可变,所以如果你想要可变性,请坚持类

  • 由于namedtuple是可迭代的,因此您可以使用可迭代的方法。例如,如果您将“coord”作为类实例,则无法查找最大coord。但使用namedtuple,您可以。