typing.NamedTuple,默认为字典

问题描述 投票:1回答:1

我正在尝试创建一个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,但这与我在这里想要的相反。

我可能在这里将数据类与default factory function(或attrs中的等效项)一起使用,但是我目前需要支持python 3.6.x和3.7.x,这样会增加一些开销。

对于它的价值,我正在测试的python版本是3.7.3

python namedtuple
1个回答
0
投票

typing.NamedTuple / collections.namedtuple不支持工厂功能,并且实现默认值的机制看起来不会以任何合理的方式重载。您也不能通过直接编写自己的__new__来手动实现这样的默认设置。

[AFAICT,做到这一点的唯一半合理的方法是编写一个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类的单个直接后代。这不应该对性能产生有意义的影响,只是需要注意的一点。

© www.soinside.com 2019 - 2024. All rights reserved.