上篇你不能低估的Python數(shù)據(jù)結(jié)構(gòu)Namedtuple(一)講了namedtuple的一些基本用法,本篇繼續(xù)。
namedtuples和數(shù)據(jù)類(Data Class)之間有什么區(qū)別?
功能
在Python 3.7之前,可使用以下任一方法創(chuàng)建一個(gè)簡(jiǎn)單的數(shù)據(jù)容器:
namedtuple
常規(guī)類
第三方庫(kù),attrs
如果您想使用常規(guī)類,那意味著您將必須實(shí)現(xiàn)幾個(gè)方法。例如,常規(guī)類將需要一種__init__方法來(lái)在類實(shí)例化期間設(shè)置屬性。如果您希望該類是可哈希的,則意味著自己實(shí)現(xiàn)一個(gè)__hash__方法。為了比較不同的對(duì)象,還需要__eq__實(shí)現(xiàn)一個(gè)方法。最后,為了簡(jiǎn)化調(diào)試,您需要一種__repr__方法。
?

如果大家在學(xué)習(xí)中遇到困難,想找一個(gè)python學(xué)習(xí)交流環(huán)境,可以加入我們的python裙,裙號(hào)930900780,可領(lǐng)取python學(xué)習(xí)資料,會(huì)節(jié)約很多時(shí)間,減少很多遇到的難題。
讓我們使用常規(guī)類來(lái)實(shí)現(xiàn)下我們的顏色用例。
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
? ? ? ? )
復(fù)制代碼
如上,你需要實(shí)現(xiàn)好多方法。您只需要一個(gè)容器來(lái)為您保存數(shù)據(jù),而不必?fù)?dān)心分散注意力的細(xì)節(jié)。同樣,人們偏愛(ài)實(shí)現(xiàn)類的一個(gè)關(guān)鍵區(qū)別是常規(guī)類是可變的。
實(shí)際上,引入數(shù)據(jù)類(Data Class)的PEP將它們稱為“具有默認(rèn)值的可變namedtuple”(譯者注:Data Class python 3.7引入,參考:docs.python.org/zh-cn/3/lib…
現(xiàn)在,讓我們看看如何用數(shù)據(jù)類來(lái)實(shí)現(xiàn)。
from dataclasses import dataclass
...
@dataclass
class Color:
? ? """A regular class that represents a color."""
? ? r: float
? ? g: float
? ? b: float
? ? alpha: float
復(fù)制代碼
哇!就是這么簡(jiǎn)單。由于沒(méi)有__init__,您只需在docstring后面定義屬性即可。此外,必須使用類型提示對(duì)其進(jìn)行注釋。
?

除了可變之外,數(shù)據(jù)類還可以開(kāi)箱即用提供可選字段。假設(shè)我們的Color類不需要alpha字段。然后我們可以設(shè)置為可選。
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]
復(fù)制代碼
我們可以像這樣實(shí)例化它:
>>> blue = Color(r=0, g=0, b=255)
復(fù)制代碼
由于它們是可變的,因此我們可以更改所需的任何字段。我們可以像這樣實(shí)例化它:
>>> blue = Color(r=0, g=0, b=255)
>>> blue.r = 1
>>> # 可以設(shè)置更多的屬性字段
>>> blue.e = 10
復(fù)制代碼
相較之下,namedtuple默認(rèn)情況下沒(méi)有可選字段。要添加它們,我們需要一點(diǎn)技巧和一些元編程。
?

提示:要添加__hash__方法,您需要通過(guò)將設(shè)置unsafe_hash為使其不可變True:
@dataclass(unsafe_hash=True)
class Color:
? ? ...
復(fù)制代碼
另一個(gè)區(qū)別是,拆箱(unpacking)是namedtuples的自帶的功能(first-class citizen)。如果希望數(shù)據(jù)類具有相同的行為,則必須實(shí)現(xiàn)自己。
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)
復(fù)制代碼
性能比較
僅比較功能是不夠的,namedtuple和數(shù)據(jù)類在性能上也有所不同。數(shù)據(jù)類基于純Python實(shí)現(xiàn)dict。這使得它們?cè)谠L問(wèn)字段時(shí)更快。另一方面,namedtuples只是常規(guī)的擴(kuò)展tuple。這意味著它們的實(shí)現(xiàn)基于更快的C代碼并具有較小的內(nèi)存占用量。
為了證明這一點(diǎn),請(qǐng)考慮在Python 3.8.5上進(jìn)行此實(shí)驗(yàn)。
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
復(fù)制代碼
如上,數(shù)據(jù)類在中訪問(wèn)字段的速度稍快一些,但是它們比nametuple占用更多的內(nèi)存空間。
如何將類型提示添加到 namedtuple
數(shù)據(jù)類默認(rèn)使用類型提示。我們也可以將它們放在namedtuples上。通過(guò)導(dǎo)入Namedtuple注釋類型并從中繼承,我們可以對(duì)Color元組進(jìn)行注釋。
from typing import NamedTuple
...
class Color(NamedTuple):
? ? """A namedtuple that represents a color."""
? ? r: float
? ? g: float
? ? b: float
? ? alpha: float
復(fù)制代碼
另一個(gè)可能未引起注意的細(xì)節(jié)是,這種方式還允許我們使用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.
復(fù)制代碼
如何將可選的默認(rèn)值添加到 namedtuple
在上一節(jié)中,我們了解了數(shù)據(jù)類可以具有可選值。另外,我提到要模仿上的相同行為,namedtuple需要進(jìn)行一些技巧修改操作。事實(shí)證明,我們可以使用繼承,如下例所示。
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)
復(fù)制代碼
結(jié)論
元組是一個(gè)非常強(qiáng)大的數(shù)據(jù)結(jié)構(gòu)。它們使我們的代碼更清潔,更可靠。盡管與新的數(shù)據(jù)類競(jìng)爭(zhēng)激烈,但他們?nèi)杂写罅康膱?chǎng)景可用。在本教程中,我們學(xué)習(xí)了使用namedtuples的幾種方法,希望您可以使用它們。
最后多說(shuō)一句,小編是一名python開(kāi)發(fā)工程師,這里有我自己整理的一套最新的python系統(tǒng)學(xué)習(xí)教程。想要這些資料的可以進(jìn)q裙930900780領(lǐng)取。
本文章素材來(lái)源于網(wǎng)絡(luò),如有侵權(quán)請(qǐng)聯(lián)系刪除。