具有字典默认值的 typing.NamedTuple

10
我想创建一个NamedTuple,在其中一个字段的默认值为一个空字典。这基本上是可行的,但是默认值在NamedTuple的实例之间共享:
from typing import NamedTuple, Dict

class MyTuple(NamedTuple):
    foo: int
    bar: Dict[str, str] = {}


t1 = MyTuple(1, {})
t2 = MyTuple(2)
t3 = MyTuple(3)

t2.bar["test2"] = "t2"
t3.bar["test3"] = "t3"

print(t2)  # MyTuple(foo=2, bar={'test2': 't2', 'test3': 't3'})
print(t3)  # MyTuple(foo=3, bar={'test2': 't2', 'test3': 't3'})
assert "test3" not in t2.bar  # raises

我该如何确保每个实例的bar字段都是一个新字典?在PEP-526中,所有字典的示例似乎都使用了ClassVar,但这与我想要的相反。

我可能可以在这里使用dataclass和默认工厂函数(或attrs中的等价物),但我目前需要支持python 3.6.x和3.7.x,所以这会增加一些开销。

值得一提的是,我测试这个的Python版本是3.7.3。


如何在NamedTuple的__init__中添加'datetime.now()。isoformat()'? - wim
1个回答

6

typing.NamedTuple/collections.namedtuple不支持工厂函数,而且实现默认值的机制似乎无法以任何合理的方式进行重载。也不能通过直接编写您自己的__new__来手动实现这样的默认值。

据我所知,唯一有些合理的方法是编写一个namedtuple子类,该子类实现其自己的__new__,以编程方式生成默认值:

class MyTuple(NamedTuple):
    foo: int
    bar: Dict[str, str] = {}  # You can continue to declare the default, even though you never use it

class MyTuple(MyTuple):
    __slots__ = ()
    def __new__(cls, foo, bar=None):
        if bar is None:
            bar = {}
        return super().__new__(cls, foo, bar)

开销相对较小,但需要六行样板文件。您可以将父级 MyTuple 的定义压缩到最终 MyTuple 的定义中,以减少冗余(可能会牺牲过于密集的代码)。例如:
class MyTuple(typing.NamedTuple('MyTuple', [('foo', int), ('bar', Dict[str, str])])):
    __slots__ = ()
    def __new__(cls, foo, bar=None):
        if bar is None:
            bar = {}
        return super().__new__(cls, foo, bar)

但这仍会产生继承层次结构,而不仅仅是从tuple类继承一个单独的直接子类,就像从NamedTuple继承一样。这不应对性能产生实质性影响,只是需要注意的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接