从以前的字段向NamedTuple添加字段

时间:2018-07-11 00:00:33

标签: python python-3.x namedtuple

比方说,我想存储一些有关会议时间表的信息,包括演示时间和暂停时间。我可以在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)方法并将其作为实例变量存储在那里,但是它将是可变的。

3 个答案:

答案 0 :(得分:2)

您可以制作一个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 dataclasses aren't totally immutable,但它们非常接近,这使您可以自然地创建实例BlockTime(1.0, 2.0)

答案 1 :(得分:2)

如果它不是真正存储而是动态计算,可以使用一个简单的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

实际上您必须具有一个“计算的”字段,该字段必须与其他字段同步,这表明您可能根本不应该存储该字段,以免出现状态不一致的情况。

答案 2 :(得分:1)

好..您不能覆盖其父级为NamedTuple的类的__new____init__。但是您可以覆盖__new__的一个类,该类继承自另一个父类为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)