原文地址:https://miguendes.me/everythi…
作者:Miguel Brito
译者:DeanWu
本文将探讨python中namedtuple
的重点用法。咱们将由浅入深的介绍namedtuple
的各概念。您将理解为什么要应用它们,以及如何应用它们,从而是代码更简洁。在学习本指南之后,你肯定会喜爱上应用它。
学习指标
在本教程完结时,您应该可能:
- 理解为什么以及何时应用它
- 将惯例元组和字典转换为
Namedtuple
- 将
Namedtuple
转化为字典或惯例元组 - 对
Namedtuple
列表进行排序 - 理解
Namedtuple
和数据类(DataClass)之间的区别 - 应用可选字段创立
Namedtuple
- 将
Namedtuple
序列化为JSON - 增加文档字符串(docstring)
为什么要应用namedtuple
?
namedtuple
是一个十分乏味(也被低估了)的数据结构。咱们能够轻松找到重大依赖惯例元组和字典来存储数据的Python代码。我并不是说,这样不好,只是有时候他们经常被滥用,且听我缓缓道来。
假如你有一个将字符串转换为色彩的函数。色彩必须在4维空间RGBA中示意。
def convert_string_to_color(desc: str, alpha: float = 0.0): if desc == "green": return 50, 205, 50, alpha elif desc == "blue": return 0, 0, 255, alpha else: return 0, 0, 0, alpha
而后,咱们能够像这样应用它:
r, g, b, a = convert_string_to_color(desc="blue", alpha=1.0)
好的,能够。然而咱们这里有几个问题。第一个是,无奈确保返回值的程序。也就是说,没有什么能够阻止其余开发者这样调用
convert_string_to_color: g, b, r, a = convert_string_to_color(desc="blue", alpha=1.0)
另外,咱们可能不晓得该函数返回4个值,可能会这样调用该函数:
r, g, b = convert_string_to_color(desc="blue", alpha=1.0)
于是,因为返回值不够,抛出ValueError
谬误,调用失败。
的确如此。然而,你可能会问,为什么不应用字典呢?
Python的字典是一种十分通用的数据结构。它们是一种存储多个值的简便办法。然而,字典并非没有毛病。因为其灵活性,字典很容易被滥用。让
咱们看看应用字典之后的例子。
def convert_string_to_color(desc: str, alpha: float = 0.0): if desc == "green": return {"r": 50, "g": 205, "b": 50, "alpha": alpha} elif desc == "blue": return {"r": 0, "g": 0, "b": 255, "alpha": alpha} else: return {"r": 0, "g": 0, "b": 0, "alpha": alpha}
好的,咱们当初能够像这样应用它,冀望只返回一个值:
color = convert_string_to_color(desc="blue", alpha=1.0)
无需记住程序,但它至多有两个毛病。第一个是咱们必须跟踪密钥的名称。如果咱们将其更改{"r": 0, “g”: 0, “b”: 0, “alpha”: alpha}
为{”red": 0, “green”: 0, “blue”: 0, “a”: alpha}
,则在拜访字段时会失去KeyError
返回,因为键r,g,b
和alpha
不再存在。
字典的第二个问题是它们不可散列。这意味着咱们无奈将它们存储在set或其余字典中。假如咱们要跟踪特定图像有多少种颜色。如果咱们应用collections.Counter
计数,咱们将失去TypeError: unhashable type: ‘dict’
。
而且,字典是可变的,因而咱们能够依据须要增加任意数量的新键。置信我,这是一些很难发现的令人讨厌的谬误点。
好的,很好。那么当初怎么办?我能够用什么代替呢?
namedtuple
!对,就是它!
将咱们的函数转换为应用namedtuple
:
from collections import namedtuple ... Color = namedtuple("Color", "r g b alpha") ... def convert_string_to_color(desc: str, alpha: float = 0.0): if desc == "green": return Color(r=50, g=205, b=50, alpha=alpha) elif desc == "blue": return Color(r=50, g=0, b=255, alpha=alpha) else: return Color(r=50, g=0, b=0, alpha=alpha)
与dict的状况一样,咱们能够将值调配给单个变量并依据须要应用。无需记住程序。而且,如果你应用的是诸如PyCharm和VSCode之类的IDE ,还能够主动提醒补全。
color = convert_string_to_color(desc="blue", alpha=1.0) ... has_alpha = color.alpha > 0.0 ... is_black = color.r == 0 and color.g == 0 and color.b == 0
最重要的是namedtuple
是不可变的。如果团队中的另一位开发人员认为在运行时增加新字段是个好主见,则该程序将报错。
>>> blue = Color(r=0, g=0, b=255, alpha=1.0) >>> blue.e = 0 --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-13-8c7f9b29c633> in <module> ----> 1 blue.e = 0 AttributeError: 'Color' object has no attribute 'e'
不仅如此,当初咱们能够应用它Counter来跟踪一个汇合有多少种颜色。
>>> Counter([blue, blue]) >>> Counter({Color(r=0, g=0, b=255, alpha=1.0): 2})
如何将惯例元组或字典转换为 namedtuple
当初咱们理解了为什么应用namedtuple,当初该学习如何将惯例元组和字典转换为namedtuple了。假如因为某种原因,你有蕴含黑白RGBA值的字典实例。如果要将其转换为Color namedtuple
,则能够按以下步骤进行:
>>> c = {"r": 50, "g": 205, "b": 50, "alpha": alpha} >>> Color(**c) >>> Color(r=50, g=205, b=50, alpha=0)
咱们能够利用该**
构造将包解压缩dict
为namedtuple
。
如果我想从dict创立一个namedtupe,如何做?
没问题,上面这样做就能够了:
>>> c = {"r": 50, "g": 205, "b": 50, "alpha": alpha} >>> Color = namedtuple("Color", c) >>> Color(**c) Color(r=50, g=205, b=50, alpha=0)
通过将dict实例传递给namedtuple工厂函数,它将为你创立字段。而后,Color像上边的例子一样解压字典c,创立新实例。
如何将 namedtuple 转换为字典或惯例元组
咱们刚刚学习了如何将转换namedtuple
为dict
。反过来呢?咱们又如何将其转换为字典实例?
试验证实,namedtuple它带有一种称为的办法._asdict()
。因而,转换它就像调用办法一样简略。
>>> blue = Color(r=0, g=0, b=255, alpha=1.0) >>> blue._asdict() {'r': 0, 'g': 0, 'b': 255, 'alpha': 1.0}
您可能想晓得为什么该办法以_
结尾。这是与Python的惯例标准不统一的一个中央。通常,_
代表公有办法或属性。然而,namedtuple
为了防止命名抵触将它们增加到了公共办法中。除了_asdict
,还有_replace
,_fields
和_field_defaults
。您能够在这里找到所有这些。
要将namedtupe
转换为惯例元组,只需将其传递给tuple构造函数即可。
>>> tuple(Color(r=50, g=205, b=50, alpha=0.1)) (50, 205, 50, 0.1)
如何对namedtuples列表进行排序
另一个常见的用例是将多个namedtuple
实例存储在列表中,并依据某些条件对它们进行排序。例如,假如咱们有一个色彩列表,咱们须要按alpha强度对其进行排序。
侥幸的是,Python容许应用十分Python化的形式来执行此操作。咱们能够应用operator.attrgetter
运算符。依据文档,attrgetter
“返回从其操作数获取attr的可调用对象”。简略来说就是,咱们能够通过该运算符,来获取传递给sorted函数排序的字段。例:
from operator import attrgetter ... colors = [ Color(r=50, g=205, b=50, alpha=0.1), Color(r=50, g=205, b=50, alpha=0.5), Color(r=50, g=0, b=0, alpha=0.3) ] ... >>> sorted(colors, key=attrgetter("alpha")) [Color(r=50, g=205, b=50, alpha=0.1), Color(r=50, g=0, b=0, alpha=0.3), Color(r=50, g=205, b=50, alpha=0.5)]
当初,色彩列表按alpha强度升序排列!
如何将namedtuples序列化为JSON
有时你可能须要将贮存namedtuple
转为JSON。Python的字典能够通过json模块转换为JSON。那么咱们能够应用_asdict办法将元组转换为字典,而后接下来就和字典一样了。例如:
>>> blue = Color(r=0, g=0, b=255, alpha=1.0) >>> import json >>> json.dumps(blue._asdict()) '{"r": 0, "g": 0, "b": 255, "alpha": 1.0}'
如何给namedtuple增加docstring
在Python中,咱们能够应用纯字符串来记录办法,类和模块。而后,此字符串可作为名为的非凡属性应用__doc__
。话虽这么说,咱们如何向咱们的Color namedtuple
增加docstring的?
咱们能够通过两种形式做到这一点。第一个(比拟麻烦)是应用包装器扩大元组。这样,咱们便能够docstring在此包装器中定义。例如,请思考以下代码片段:
_Color = namedtuple("Color", "r g b alpha") class Color(_Color): """A namedtuple that represents a color. It has 4 fields: r - red g - green b - blue alpha - the alpha channel """ >>> print(Color.__doc__) A namedtuple that represents a color. It has 4 fields: r - red g - green b - blue alpha - the alpha channel >>> help(Color) Help on class Color in module __main__: class Color(Color) | Color(r, g, b, alpha) | | A namedtuple that represents a color. | It has 4 fields: | r - red | g - green | b - blue | alpha - the alpha channel | | Method resolution order: | Color | Color | builtins.tuple | builtins.object | | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined)
如上,通过继承_Color
元组,咱们为namedtupe增加了一个__doc__
属性。
增加的第二种办法,间接设置__doc__
属性。这种办法不须要扩大元组。
>>> Color.__doc__ = """A namedtuple that represents a color. It has 4 fields: r - red g - green b - blue alpha - the alpha channel """
留神,这些办法仅实用于Python 3+
。
namedtuples和数据类(Data Class)之间有什么区别?
性能
在Python 3.7之前,可应用以下任一办法创立一个简略的数据容器:
- namedtuple
- 惯例类
- 第三方库,
attrs
如果您想应用惯例类,那意味着您将必须实现几个办法。例如,惯例类将须要一种__init__
办法来在类实例化期间设置属性。如果您心愿该类是可哈希的,则意味着本人实现一个__hash__
办法。为了比拟不同的对象,还须要__eq__
实现一个办法。最初,为了简化调试,您须要一种__repr__
办法。
让咱们应用惯例类来实现下咱们的色彩用例。
class Color: """A regular class that represents a color.""" def __init__(self, r, g, b, alpha=0.0): self.r = r self.g = g self.b = b self.alpha = alpha def __hash__(self): return hash((self.r, self.g, self.b, self.alpha)) def __repr__(self): return "{0}({1}, {2}, {3}, {4})".format( self.__class__.__name__, self.r, self.g, self.b, self.alpha ) def __eq__(self, other): if not isinstance(other, Color): return False return ( self.r == other.r and self.g == other.g and self.b == other.b and self.alpha == other.alpha )
如上,你须要实现好多办法。您只须要一个容器来为您保留数据,而不用放心扩散注意力的细节。同样,人们偏爱实现类的一个要害区别是惯例类是可变的。
实际上,引入数据类(Data Class)
的PEP将它们称为“具备默认值的可变namedtuple”(译者注:Data Class python 3.7引入,参考:https://docs.python.org/zh-cn…。
当初,让咱们看看如何用数据类
来实现。
from dataclasses import dataclass ... @dataclass class Color: """A regular class that represents a color.""" r: float g: float b: float alpha: float
哇!就是这么简略。因为没有__init__
,您只需在docstring前面定义属性即可。此外,必须应用类型提醒对其进行正文。
除了可变之外,数据类还能够开箱即用提供可选字段。假如咱们的Color类不须要alpha字段。而后咱们能够设置为可选。
from dataclasses import dataclass from typing import Optional ... @dataclass class Color: """A regular class that represents a color.""" r: float g: float b: float alpha: Optional[float]
咱们能够像这样实例化它:
<code class="pyuthon">>>> blue = Color(r=0, g=0, b=255)
因为它们是可变的,因而咱们能够更改所需的任何字段。咱们能够像这样实例化它:
>>> blue = Color(r=0, g=0, b=255) >>> blue.r = 1 >>> # 能够设置更多的属性字段 >>> blue.e = 10
相较之下,namedtuple
默认状况下没有可选字段。要增加它们,咱们须要一点技巧和一些元编程。
提醒:要增加__hash__
办法,您须要通过将设置unsafe_hash
为使其不可变True
:
@dataclass(unsafe_hash=True) class Color: ...
另一个区别是,拆箱(unpacking)是namedtuples的自带的性能(first-class citizen)。如果心愿数据类
具备雷同的行为,则必须实现本人。
from dataclasses import dataclass, astuple ... @dataclass class Color: """A regular class that represents a color.""" r: float g: float b: float alpha: float def __iter__(self): yield from dataclasses.astuple(self)
性能比拟
仅比拟性能是不够的,namedtuple和数据类在性能上也有所不同。数据类基于纯Python实现dict。这使得它们在拜访字段时更快。另一方面,namedtuples只是惯例的扩大tuple。这意味着它们的实现基于更快的C代码并具备较小的内存占用量。
为了证实这一点,请思考在Python 3.8.5上进行此试验。
In [6]: import sys In [7]: ColorTuple = namedtuple("Color", "r g b alpha") In [8]: @dataclass ...: class ColorClass: ...: """A regular class that represents a color.""" ...: r: float ...: g: float ...: b: float ...: alpha: float ...: In [9]: color_tup = ColorTuple(r=50, g=205, b=50, alpha=1.0) In [10]: color_cls = ColorClass(r=50, g=205, b=50, alpha=1.0) In [11]: %timeit color_tup.r 36.8 ns ± 0.109 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each) In [12]: %timeit color_cls.r 38.4 ns ± 0.112 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each) In [15]: sys.getsizeof(color_tup) Out[15]: 72 In [16]: sys.getsizeof(color_cls) + sys.getsizeof(vars(color_cls)) Out[16]: 152
如上,数据类在中拜访字段的速度稍快一些,然而它们比nametuple占用更多的内存空间。
如何将类型提醒增加到 namedtuple
数据类默认应用类型提醒。咱们也能够将它们放在namedtuples上。通过导入Namedtuple正文类型并从中继承,咱们能够对Color元组进行正文。
from typing import NamedTuple ... class Color(NamedTuple): """A namedtuple that represents a color.""" r: float g: float b: float alpha: float
另一个可能未引起留神的细节是,这种形式还容许咱们应用docstring。如果输出,help(Color)咱们将可能看到它们。
Help on class Color in module __main__: class Color(builtins.tuple) | Color(r: float, g: float, b: float, alpha: Union[float, NoneType]) | | A namedtuple that represents a color. | | Method resolution order: | Color | builtins.tuple | builtins.object | | Methods defined here: | | __getnewargs__(self) | Return self as a plain tuple. Used by copy and pickle. | | __repr__(self) | Return a nicely formatted representation string | | _asdict(self) | Return a new dict which maps field names to their values.
如何将可选的默认值增加到 namedtuple
在上一节中,咱们理解了数据类能够具备可选值。另外,我提到要模拟上的雷同行为,namedtuple
须要进行一些技巧批改操作。事实证明,咱们能够应用继承,如下例所示。
from collections import namedtuple class Color(namedtuple("Color", "r g b alpha")): __slots__ = () def __new__(cls, r, g, b, alpha=None): return super().__new__(cls, r, g, b, alpha) >>> c = Color(r=0, g=0, b=0) >>> c Color(r=0, g=0, b=0, alpha=None)
论断
元组是一个十分弱小的数据结构。它们使咱们的代码更清洁,更牢靠。只管与新的数据类
竞争强烈,但他们仍有大量的场景可用。在本教程中,咱们学习了应用namedtuples
的几种办法,心愿您能够应用它们。