从以前的字段向 NamedTuple 添加字段
Adding fields to NamedTuple from previous fields
假设我想存储一些关于会议日程的信息,包括演示时间和暂停时间。我可以在 NamedTuple
.
中做到这一点
from typing import NamedTuple
class BlockTime(NamedTuple):
t_present: float
t_pause: float
但是,如果我还想存储每个块需要多少 t_each = t_pause + t_present
,我不能只将其添加为属性:
class BlockTime(NamedTuple):
t_present: float
t_pause: float
# this causes an error
t_each = t_present + t_pause
在 Python 中执行此操作的正确方法是什么?如果我制作一个 __init__(self)
方法并将其作为实例变量存储在那里,但它将是可变的。
您可以创建一个 classmethod
构建 BlockTime
个对象
class BlockTime(NamedTuple):
t_present: float
t_pause: float
t_each: float
@classmethod
def factory(cls, present, pause):
return cls(present, pause, present+pause)
print(BlockTime.factory(1.0, 2.0))
# BlockTime(t_present=1.0, t_pause=2.0, t_each=3.0)
编辑:
这是一个使用新 Python 3.7 dataclass
的解决方案
from dataclasses import dataclass, field
@dataclass(frozen=True)
class BlockTime:
t_present: float
t_pause: float
t_each: float = field(init=False)
def __post_init__(self):
object.__setattr__(self, 't_each', self.t_present + self.t_pause)
Frozen dataclass
es aren't totally immutable 但它们非常接近,这让您可以创建看起来自然的实例 BlockTime(1.0, 2.0)
万一它不是真正存储而是动态计算的,你可以使用一个简单的 property
。
from typing import NamedTuple
class BlockTime(NamedTuple):
t_present: float
t_pause: float
@property
def t_each(self):
return self.t_present + self.t_pause
>>> b = BlockTime(10, 20)
>>> b.t_each # only available as property, not in the representation nor by indexing or iterating
30
这样做的好处是您永远不会(甚至不会意外地)为它存储错误的值。但是,以根本不实际存储它为代价。因此,为了看起来好像它已存储,您至少必须覆盖 __getitem__
、__iter__
、__repr__
,这可能太麻烦了。
例如,Patrick Haugh 给出的 NamedTuple
方法有一个缺点,即仍然可能创建不一致的 BlockTime
或丢失部分 namedtuple
便利:
>>> b = BlockTime.factory(1.0, 2.0)
>>> b._replace(t_present=20)
BlockTime(t_present=20, t_pause=2.0, t_each=3.0)
>>> b._make([1, 2])
TypeError: Expected 3 arguments, got 2
您实际上有一个必须与其他字段同步的 "computed" 字段这一事实已经表明您可能根本不应该存储它以避免状态不一致。
嗯.. 你不能覆盖 class 的 __new__
或 __init__
,其父级是 NamedTuple。但是您可以覆盖 class 的 __new__
,继承自另一个 class,其父级为 NamedTuple。
所以你可以这样做
from typing import NamedTuple
class BlockTimeParent(NamedTuple):
t_present: float
t_pause: float
t_each: float
class BlockTime(BlockTimeParent):
def __new__(cls, t_present, t_pause):
return super().__new__(cls, t_present, t_pause, t_present+ t_pause)
b = BlockTime(1,2)
print (b)
# BlockTime(t_present=1, t_pause=2, t_each=3)
假设我想存储一些关于会议日程的信息,包括演示时间和暂停时间。我可以在 NamedTuple
.
from typing import NamedTuple
class BlockTime(NamedTuple):
t_present: float
t_pause: float
但是,如果我还想存储每个块需要多少 t_each = t_pause + t_present
,我不能只将其添加为属性:
class BlockTime(NamedTuple):
t_present: float
t_pause: float
# this causes an error
t_each = t_present + t_pause
在 Python 中执行此操作的正确方法是什么?如果我制作一个 __init__(self)
方法并将其作为实例变量存储在那里,但它将是可变的。
您可以创建一个 classmethod
构建 BlockTime
个对象
class BlockTime(NamedTuple):
t_present: float
t_pause: float
t_each: float
@classmethod
def factory(cls, present, pause):
return cls(present, pause, present+pause)
print(BlockTime.factory(1.0, 2.0))
# BlockTime(t_present=1.0, t_pause=2.0, t_each=3.0)
编辑:
这是一个使用新 Python 3.7 dataclass
from dataclasses import dataclass, field
@dataclass(frozen=True)
class BlockTime:
t_present: float
t_pause: float
t_each: float = field(init=False)
def __post_init__(self):
object.__setattr__(self, 't_each', self.t_present + self.t_pause)
Frozen dataclass
es aren't totally immutable 但它们非常接近,这让您可以创建看起来自然的实例 BlockTime(1.0, 2.0)
万一它不是真正存储而是动态计算的,你可以使用一个简单的 property
。
from typing import NamedTuple
class BlockTime(NamedTuple):
t_present: float
t_pause: float
@property
def t_each(self):
return self.t_present + self.t_pause
>>> b = BlockTime(10, 20)
>>> b.t_each # only available as property, not in the representation nor by indexing or iterating
30
这样做的好处是您永远不会(甚至不会意外地)为它存储错误的值。但是,以根本不实际存储它为代价。因此,为了看起来好像它已存储,您至少必须覆盖 __getitem__
、__iter__
、__repr__
,这可能太麻烦了。
例如,Patrick Haugh 给出的 NamedTuple
方法有一个缺点,即仍然可能创建不一致的 BlockTime
或丢失部分 namedtuple
便利:
>>> b = BlockTime.factory(1.0, 2.0)
>>> b._replace(t_present=20)
BlockTime(t_present=20, t_pause=2.0, t_each=3.0)
>>> b._make([1, 2])
TypeError: Expected 3 arguments, got 2
您实际上有一个必须与其他字段同步的 "computed" 字段这一事实已经表明您可能根本不应该存储它以避免状态不一致。
嗯.. 你不能覆盖 class 的 __new__
或 __init__
,其父级是 NamedTuple。但是您可以覆盖 class 的 __new__
,继承自另一个 class,其父级为 NamedTuple。
所以你可以这样做
from typing import NamedTuple
class BlockTimeParent(NamedTuple):
t_present: float
t_pause: float
t_each: float
class BlockTime(BlockTimeParent):
def __new__(cls, t_present, t_pause):
return super().__new__(cls, t_present, t_pause, t_present+ t_pause)
b = BlockTime(1,2)
print (b)
# BlockTime(t_present=1, t_pause=2, t_each=3)