什么是命名元组,我如何使用它们? 什么时候我应该使用命名元组而不是正常的元组,反之亦然? 也有“名单”吗?(即可变命名元组)
关于最后一个问题,请参见Python中是否存在可变命名元组。
什么是命名元组,我如何使用它们? 什么时候我应该使用命名元组而不是正常的元组,反之亦然? 也有“名单”吗?(即可变命名元组)
关于最后一个问题,请参见Python中是否存在可变命名元组。
当前回答
Namedtuple是一个用于创建元组类的工厂函数。通过这个类,我们还可以创建可按名称调用的元组。
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: 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)
其他回答
命名元组允许与这样检查版本的代码向后兼容
>>> sys.version_info[0:2]
(3, 1)
同时通过使用此语法允许未来的代码更加显式
>>> sys.version_info.major
3
>>> sys.version_info.minor
1
from collections import namedtuple
他们子类化tuple,并添加一个层来为位置元素分配属性名
'namedtuple'是一个函数,它生成一个继承自“tuple”的新类,但也提供了“命名属性”来访问元组的元素。
生成命名元组类
"namedtuple"是一个类工厂。它需要一些东西来生成类
the class name A sequence of field names we want to assign, in the order of elements in the tuple. Field names can be any valid variable names except that they cannot start with an "underscore". The return value of the call to "namedtuple" will be a class. We need to assign that class to a variable name in our code so we can use it to construct instances. In general, we use the same name as the name of the class that was generated. # Coords is a class Coords = namedtuple('Coords', ['x', 'y']) Now we can create instances of Coords class: pt=Coords(10,20) There are many ways we can provide the list of field names to the namedtuple function. a list of strings namedtuple('Coords',['x','y']) a tuple of strings namedtuple('Coords',('x','y')) a single string with the field names separated by whitespace or commas namedtuple('Coords','x, y'])
实例化命名元组
创建了命名的tuple类之后,可以像实例化普通类一样实例化它们。事实上,生成类的__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 # Unpacking
x=coord[0] # by index
for e in coord:
print(e)
现在,我们还可以使用字段名访问数据,就像使用类一样。 坐标。x——> 10 坐标。y——> 20 由于namedtuple是由继承自tuple的类生成的,我们可以这样写: 类Coord(元组): .... "coord"是一个元组,因此是不可变的
"rename"关键字参数为namedtuple
字段名不能以下划线开头
Coords = namedtuple('Coords', ['x', '_y']) # does not work
namedtuple有一个只包含关键字的参数rename(默认为False),它将自动重命名任何无效的字段名。
Coords = namedtuple('Coords', ['x', '_y'], rename=True)
字段名称“x”不会改变,但“_y”将改变为_1。1是字段名的索引。
想象一下这样的场景:您需要更新应用程序,因此希望使用namedTuple存储应用程序的用户。您需要提取列名,但它们对于命名元组无效,并且会抛出异常。在本例中,使用rename=True。
将命名元组值提取到字典中
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 = symbol
self.year = year
self.month = month
self.day = day
self.open = open
self.high = high
self.low = low
self.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是可迭代的,你可以使用iterable方法。例如,如果你有" coordinates "作为类实例,你不能查找最大坐标是多少,但是使用name -tuple,你可以。
命名元组基本上是易于创建的轻量级对象类型。命名元组实例可以使用类似对象的变量解引用或标准元组语法来引用。它们可以类似于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 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
如果您希望能够更改值,则需要另一种类型。对于可变记录类型有一个方便的方法,它允许您为属性设置新值。
>>> 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},并且可以使用所有常用的字典函数进行操作。
如前所述,您应该查看文档以获得构建这些示例的更多信息。
什么是命名元组?
命名元组是一个元组。
它做了元组能做的一切。
但它不仅仅是一个元组。
它是元组的特定子类,通过编程方式根据您的规范创建,具有命名字段和固定长度。
例如,它创建了一个tuple的子类,除了长度固定(在本例中为3)之外,它可以在任何使用tuple的地方使用而不中断。这就是所谓的利斯科夫可替代性。
在Python 3.6中,我们可以使用带有类型的类定义。创建一个NamedTuple:
from typing import NamedTuple
class ANamedTuple(NamedTuple):
"""a docstring"""
foo: int
bar: str
baz: list
上面的内容与collections.namedtuple相同,只是上面的内容额外增加了类型注释和文档字符串。以下代码在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'
你可以用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
为什么/什么时候我应该使用命名元组而不是普通元组?
在改进代码时使用它们,以便在代码中表达元组元素的语义。
如果要使用具有不变数据属性且没有功能的对象,则可以使用它们而不是对象。
你也可以子类化它们来添加功能,例如:
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)
为什么/什么时候我应该使用普通元组而不是命名元组?
从使用命名元组切换到使用元组可能是一种倒退。前期设计决策的核心是,当使用元组时,所涉及的额外代码成本是否值得改进的可读性。
与元组相比,命名元组不使用额外的内存。
是否存在任何类型的“命名列表”(命名元组的可变版本)?
您要寻找的要么是实现静态大小列表的所有功能的插槽对象,要么是工作方式类似于命名元组的子类列表(并且以某种方式阻止列表大小的变化)。
现在扩展的,甚至可以用利斯科夫代替的,第一个例子:
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
命名元组是一个很好的特性,它们是数据的完美容器。当你必须“存储”数据时,你会使用元组或字典,比如:
user = dict(name="John", age=20)
or:
user = ("John", 20)
字典方法是压倒性的,因为字典是可变的,比元组慢。另一方面,元组是不可变和轻量级的,但是对于数据字段中的大量条目缺乏可读性。
命名元组是这两种方法的完美折衷,它们具有很高的可读性、轻量级性和不可变性(而且它们是多态的!)