阅读Python 3.1 中的变化,我发现了一些……意想不到的东西:
sys.version_info 元组现在是一个命名元组:
我以前从未听说过命名元组,我认为元素可以通过数字(如在元组和列表中)或键(如在 dicts 中)来索引。我从没想过它们会被双向索引。
因此,我的问题是:
- 什么是命名元组?
- 如何使用它们?
- 为什么/何时应该使用命名元组而不是普通元组?
- 为什么/何时应该使用普通元组而不是命名元组?
- 是否有任何类型的“命名列表”(命名元组的可变版本)?
阅读Python 3.1 中的变化,我发现了一些……意想不到的东西:
sys.version_info 元组现在是一个命名元组:
我以前从未听说过命名元组,我认为元素可以通过数字(如在元组和列表中)或键(如在 dicts 中)来索引。我从没想过它们会被双向索引。
因此,我的问题是:
命名元组基本上是易于创建的轻量级对象类型。命名元组实例可以使用类似对象的变量解引用或标准元组语法来引用。它们可以与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()
which 返回的字典,{'x': 1.0, 'y': 5.0}
并且可以使用所有常用的字典函数对其进行操作。
如前所述,您应该查看文档以获取构建这些示例的更多信息。
什么是命名元组?
命名元组是一个元组。
它完成了元组所能做的一切。
但它不仅仅是一个元组。
它是元组的一个特定子类,它是根据您的规范以编程方式创建的,具有命名字段和固定长度。
例如,这创建了一个元组的子类,除了具有固定长度(在本例中为三个)之外,它还可以在使用元组的任何地方使用而不会中断。这被称为 Liskov 可替代性。
在 Python 3.6中,我们可以使用类定义typing.NamedTuple
来创建命名元组:
from typing import NamedTuple
class ANamedTuple(NamedTuple):
"""a docstring"""
foo: int
bar: str
baz: list
上面和下面一样,除了上面还有类型注释和一个文档字符串。以下在 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'
您可以使用可迭代解包来扩展元组:
>>> 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)
为什么/何时应该使用普通元组而不是命名元组?
从使用命名元组切换到元组可能是一种回归。前期设计决策的核心是所涉及的额外代码的成本是否值得在使用元组时提高可读性。
命名元组与元组相比没有使用额外的内存。
是否有任何类型的“命名列表”(命名元组的可变版本)?
您正在寻找实现静态大小列表的所有功能的开槽对象或像命名元组一样工作的子类列表(并且以某种方式阻止列表改变大小。)
一个现在扩展的,甚至可能是 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
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)
namedtuples 是一个很棒的功能,它们是数据的完美容器。当您必须“存储”数据时,您将使用元组或字典,例如:
user = dict(name="John", age=20)
或者:
user = ("John", 20)
字典方法是压倒性的,因为 dict 是可变的并且比元组慢。另一方面,元组是不可变的和轻量级的,但是对于数据字段中的大量条目缺乏可读性。
namedtuples 是这两种方法的完美折衷方案,它们具有很好的可读性、轻量性和不变性(而且它们是多态的!)。
命名元组允许与像这样检查版本的代码向后兼容
>>> sys.version_info[0:2]
(3, 1)
同时通过使用此语法允许将来的代码更加明确
>>> sys.version_info.major
3
>>> sys.version_info.minor
1
是清理代码并使其更具可读性的最简单方法之一。它自我记录元组中发生的事情。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"
很难理解第一个示例中发生了什么。使用命名元组,每个字段都有一个名称。您可以通过名称而不是位置或索引来访问它。代替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
p.hue
而不是
p['hue']
.语法
collections.namedtuple(typename, field_names[, verbose=False][, rename=False])
['x', 'y', 'z']
或字符串中的序列x y z
(没有逗号,只有空格)或x, y, z
.True
,无效的字段名将自动替换为位置名称。例如,['abc', 'def', 'ghi','abc']
转换为['abc', '_1', 'ghi', '_3']
,消除关键字'def'
(因为这是定义函数的保留字)和重复的 fieldname 'abc'
。True
,则在构建之前打印类定义。如果您愿意,您仍然可以按位置访问命名元组。p[1] == p.saturation
. 它仍然像常规元组一样解包。
支持所有常规元组方法。例如:min()、max()、len()、in、not in、连接 (+)、index、slice 等。还有一些用于 namedtuple。注意:这些都以下划线开头。_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 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
返回一个新的OrderedDict,它将字段名称映射到其对应的值。
语法
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 是一个有名字的元组。在标准元组中,我们使用索引访问元素,而 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)
它们子类化元组,并添加一个层以将属性名称分配给位置元素
位于集合标准库模块中
from collections import namedtuple
“namedtuple”是一个函数,它生成一个继承自“tuple”的新类,但也提供“命名属性”来访问元组的元素。
“namedtuple”是一个类工厂。生成类需要一些东西
我们要使用的类名
我们要分配的字段名称序列,按照元组中元素的顺序。字段名称可以是任何有效的变量名称,但不能以“下划线”开头。
调用“namedtuple”的返回值将是一个类。我们需要将该类分配给代码中的变量名,以便我们可以使用它来构造实例。通常,我们使用与生成的类的名称相同的名称。
Coords = namedtuple('Coords', ['x', 'y'])
现在我们可以创建坐标实例:
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 # Unpacking
x=coord[0] # by index
for e in coord:
print(e)
现在我们还可以使用我们对类所做的字段名称来访问数据。
coord.x --> 10
coord.y --> 20
由于 namedtuple 是继承自 tuple 生成的类,我们可以这样写:
class Coord(tuple):
....
"coord" 是一个元组,因此是不可变的
字段名称不能以下划线开头
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'])
Coords._fields -> ("x","_1") # returns tuple
我们实际上可以看到该类的代码是什么,_source
如果您使用的python 低于 3-3.7,请使用 class 属性。由于可能对实现该属性的内存使用存在一些担忧,因此自 v3.7 起已将其删除
Coords = namedtuple('Coords', ['x', 'y'])
coord=Coords(10,20)
coord._asdict()
{'x': 10, 'y': 20}
如果你有这个课程:
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
类方法- 与 -元组方法
djia.symbol djia[0]
djia.open djia[4]
djia.close djia[7]
djia.high – djia.low djia[5] – djia[6]
如您所见,元组方法不可读。namedtuple
集合中的函数允许我们创建一个元组,该元组还具有附加到每个字段或属性的名称。这可以方便地通过“名称”引用元组结构中的数据,而不仅仅是依赖位置。但请记住,元组是不可变的,所以如果你想要可变性,坚持上课
在Python里面有一个很好用的容器叫做命名元组,它可以用来创建类的定义并且具有原始元组的所有特性。
使用命名元组将直接应用到默认类模板生成一个简单的类,这种方法允许大量代码提高可读性,在定义类时也很方便。
使用命名元组的另一种方法(一种新方法)是使用来自类型包的 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
继承 NamedTuple 类并在新类中定义变量名。test 是类的名称。
class test(NamedTuple):
x: float
y: float
从类创建实例并为其赋值
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)
我认为值得使用类型提示添加有关 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
尝试这个:
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
其他人都已经回答了,但我想我还有其他要补充的。
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'