cel*_*ion 10 python namedtuple
我正在尝试创建一个 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
Run Code Online (Sandbox Code Playgroud)
如何确保该bar字段对于每个实例都是一个新的字典?PEP-526中的所有字典示例似乎都使用 ClassVar,但这与我想要的相反。
我可能会在此处使用带有默认工厂函数(或 attrs 中的等效函数)的数据类,但我目前需要支持 python 3.6.x 和 3.7.x,因此这会增加一些开销。
就其价值而言,我正在测试的 python 版本是 3.7.3
typing.NamedTuple/collections.namedtuple不支持工厂函数,并且实现默认值的机制看起来不能以任何合理的方式重载。您也不能通过__new__直接编写自己的默认值来手动实现这样的默认值。
AFAICT,唯一半合理的方法是编写 a 的子类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)
Run Code Online (Sandbox Code Playgroud)
开销相对较小,但确实涉及六行样板文件。您可以减少样板文件(以可能过于密集的代码为代价)将父级的定义一行一行地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)
Run Code Online (Sandbox Code Playgroud)
但这仍然会形成一个继承层次结构,而不仅仅是tuple像“继承”一样的类的单个直接后代NamedTuple。这不会对性能产生有意义的影响,只是需要注意的事情。