有什么办法可以将工厂函数赋予“typing.NamedTuple”子类吗?
Is there any way to give factory function(s) to `typing.NamedTuple` subclasses?
注意:我在 Python 3.6.7 中完成了此操作,但是 NamedTuple
中的文档没有更改,所以我怀疑它是否会更改。
所以我正在查看 typing 包中的 NamedTuple class,我想知道是否有办法向其添加可变默认值。第一次尝试是看看我是否可以使用 _make
class 方法对我有利,但后来我发现 class 检查是否覆盖 __new__
.
(回顾:如果将 []
放入默认值,最终每个对象共享同一个列表。旧的 collections.namedtuple
和新的 [=19] 都是如此=]。这就是为什么 collections.defaultdict
class 在构造函数中有一个 default_factory
参数。)
>>> from typing import NamedTuple, List
>>> class Person(NamedTuple):
... name: str
... children: List['Person']
... def __new__(self, name: str, children: List['Person'] = None):
... return Person._make(name, children if children is not None else [])
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.6/typing.py", line 2163, in __new__
raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
AttributeError: Cannot overwrite NamedTuple attribute __new__
>>>
我继续对如何将列表偷偷塞进顽固的 __new__
感到困惑,但后来它开始变得普遍没有意义。
>>> class Person(NamedTuple):
... name: str
... children: List['Person'] = None
...
>>>
>>> def new_new(name: str, children: List[Person] = None) -> Person:
... return Person(name, [] if children is None else children)
...
>>> old_new = Person.__new__
>>> def new_new(name: str, children: List[Person] = None) -> Person:
... return old_new(name, [] if children is None else children)
...
>>> Person.__new__ = new_new
>>>
>>> Person('John')
Person(name='John', children=None)
>>> Person.__new__
<function new_new at 0x7f776b2b2e18>
>>> new_new('John')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in new_new
File "<string>", line 14, in __new__
TypeError: tuple.__new__(X): X is not a type object (str)
>>> new_new(Person, 'John')
Person(name='John', children=None)
>>>
那么,应该怎么做呢?我习惯为 collections.namedtuple
classes 做同样的事情,这主要是出于好奇。
实际上,我应该这样做:
class Person(NamedTuple):
name: str
children: List['Person'] = None
@classmethod
def create(cls, name, children=None):
return cls(name, [] if children is None else children)
NamedTuple
的目的不只是提供经过类型检查的命名元组。 NamedTupleMeta
explicitly prohibits 覆盖 __new__
、__init__
和其他一些。您唯一的机会是改变 NamedTuple
的工作方式。
防止覆盖 __new__
本身不受保护。这允许您以各种方式对其进行猴子修补。
您可以从受保护的名称中删除 __new__
。
import typing
typing._prohibited = typing._prohibited[1:]
这允许您直接覆盖 class 中的 __new__
。
请注意,这将影响 所有 NamedTuple
子类型。
您可以派生一个不保护 __new__
的新元class,并将其用于您的 NamedTuple
实例:
class NamedTupleUnprotectedMeta(typing.NamedTupleMeta):
def __new__(cls, typename, bases, ns):
...
# copy verbatim from NamedTupleMeta
...
# update from user namespace without protection
for key in ns:
if key not in typing._special and key not in nm_tpl._fields:
setattr(nm_tpl, key, ns[key])
return nm_tpl
class Person(NamedTuple, metaclass=NamedTupleUnprotectedMeta):
...
这允许您直接覆盖 class 中的 __new__
,而不影响 NamedTuple
的其他子类型。请注意,您还可以让您的 metaclass 添加自定义 new
调用 class 正文中设置的默认值。
注意 NamedTupleMeta
也会忽略 bases
,所以你也不能使用 mixins 之类的。任何广泛的更改都需要先重写 NamedTupleMeta
。
如果您只需要一个带有默认值的存储-class,dataclasses
standard library package and the attrs
third-party package 会提供与您描述的相匹配的声明:
@attr.s(auto_attribs=True)
class Person:
name: str
children: List['Person'] = attr.Factory(list)
两个包都支持冻结,类似于元组的不变性
注意:我在 Python 3.6.7 中完成了此操作,但是 NamedTuple
中的文档没有更改,所以我怀疑它是否会更改。
所以我正在查看 typing 包中的 NamedTuple class,我想知道是否有办法向其添加可变默认值。第一次尝试是看看我是否可以使用 _make
class 方法对我有利,但后来我发现 class 检查是否覆盖 __new__
.
(回顾:如果将 []
放入默认值,最终每个对象共享同一个列表。旧的 collections.namedtuple
和新的 [=19] 都是如此=]。这就是为什么 collections.defaultdict
class 在构造函数中有一个 default_factory
参数。)
>>> from typing import NamedTuple, List
>>> class Person(NamedTuple):
... name: str
... children: List['Person']
... def __new__(self, name: str, children: List['Person'] = None):
... return Person._make(name, children if children is not None else [])
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.6/typing.py", line 2163, in __new__
raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
AttributeError: Cannot overwrite NamedTuple attribute __new__
>>>
我继续对如何将列表偷偷塞进顽固的 __new__
感到困惑,但后来它开始变得普遍没有意义。
>>> class Person(NamedTuple):
... name: str
... children: List['Person'] = None
...
>>>
>>> def new_new(name: str, children: List[Person] = None) -> Person:
... return Person(name, [] if children is None else children)
...
>>> old_new = Person.__new__
>>> def new_new(name: str, children: List[Person] = None) -> Person:
... return old_new(name, [] if children is None else children)
...
>>> Person.__new__ = new_new
>>>
>>> Person('John')
Person(name='John', children=None)
>>> Person.__new__
<function new_new at 0x7f776b2b2e18>
>>> new_new('John')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in new_new
File "<string>", line 14, in __new__
TypeError: tuple.__new__(X): X is not a type object (str)
>>> new_new(Person, 'John')
Person(name='John', children=None)
>>>
那么,应该怎么做呢?我习惯为 collections.namedtuple
classes 做同样的事情,这主要是出于好奇。
实际上,我应该这样做:
class Person(NamedTuple):
name: str
children: List['Person'] = None
@classmethod
def create(cls, name, children=None):
return cls(name, [] if children is None else children)
NamedTuple
的目的不只是提供经过类型检查的命名元组。 NamedTupleMeta
explicitly prohibits 覆盖 __new__
、__init__
和其他一些。您唯一的机会是改变 NamedTuple
的工作方式。
防止覆盖 __new__
本身不受保护。这允许您以各种方式对其进行猴子修补。
您可以从受保护的名称中删除 __new__
。
import typing
typing._prohibited = typing._prohibited[1:]
这允许您直接覆盖 class 中的 __new__
。
请注意,这将影响 所有 NamedTuple
子类型。
您可以派生一个不保护 __new__
的新元class,并将其用于您的 NamedTuple
实例:
class NamedTupleUnprotectedMeta(typing.NamedTupleMeta):
def __new__(cls, typename, bases, ns):
...
# copy verbatim from NamedTupleMeta
...
# update from user namespace without protection
for key in ns:
if key not in typing._special and key not in nm_tpl._fields:
setattr(nm_tpl, key, ns[key])
return nm_tpl
class Person(NamedTuple, metaclass=NamedTupleUnprotectedMeta):
...
这允许您直接覆盖 class 中的 __new__
,而不影响 NamedTuple
的其他子类型。请注意,您还可以让您的 metaclass 添加自定义 new
调用 class 正文中设置的默认值。
注意 NamedTupleMeta
也会忽略 bases
,所以你也不能使用 mixins 之类的。任何广泛的更改都需要先重写 NamedTupleMeta
。
如果您只需要一个带有默认值的存储-class,dataclasses
standard library package and the attrs
third-party package 会提供与您描述的相匹配的声明:
@attr.s(auto_attribs=True)
class Person:
name: str
children: List['Person'] = attr.Factory(list)
两个包都支持冻结,类似于元组的不变性