typing.NamedTuple 和可变默认参数
typing.NamedTuple and mutable default arguments
考虑到我想正确使用类型注释来命名元组来自输入模块:
from typing import NamedTuple, List
class Foo(NamedTuple):
my_list: List[int] = []
foo1 = Foo()
foo1.my_list.append(42)
foo2 = Foo()
print(foo2.my_list) # prints [42]
在 Python 中避免可变默认值痛苦的最好或最干净的方法是什么?我有一些想法,但似乎没有什么好的
默认使用None
class Foo(NamedTuple):
my_list: Optional[List[int]] = None
foo1 = Foo()
if foo1.my_list is None
foo1 = foo1._replace(my_list=[]) # super ugly
foo1.my_list.append(42)
覆盖 __new__
或 __init__
将不起作用:
AttributeError: Cannot overwrite NamedTuple attribute __init__
AttributeError: Cannot overwrite NamedTuple attribute __new__
特别@classmethod
class Foo(NamedTuple):
my_list: List[int] = []
@classmethod
def use_me_instead(cls, my_list=None):
if not my_list:
my_list = []
return cls(my_list)
foo1 = Foo.use_me_instead()
foo1.my_list.append(42) # works!
也许使用 frozenset
并完全避免可变属性?但这不适用于 Dict
s,因为没有 frozendict
s.
有没有人有好的答案?
使用数据类而不是命名元组。数据类允许字段指定默认值 factory 而不是单个默认值。
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Foo:
my_list: List[int] = field(default_factory=list)
编辑:更新为使用 Alex 的方法,因为这比我以前的想法要好得多。
这是 Alex 的 Foo
class 放入装饰器中:
from typing import NamedTuple, List, Callable, TypeVar, cast, Type
T = TypeVar('T')
def default_factory(**factory_kw: Callable) -> Callable[[Type[T]], Type[T]]:
def wrapper(wcls: Type[T]) -> Type[T]:
def du_new(cls: Type[T], **kwargs) -> T:
for key, factory in factory_kw.items():
if key not in kwargs:
kwargs[key] = factory()
return super(cls, cls).__new__(cls, **kwargs) # type: ignore[misc]
return type(f'{wcls.__name__}_', (wcls, ), {'__new__': du_new})
return wrapper
@default_factory(my_list=list)
class Foo(NamedTuple):
my_list: List[int] = [] # you still need to define the default argument
foo1 = Foo()
foo1.my_list.append(42)
foo2 = Foo()
print(foo2.my_list) # prints []
#reveal_type(foo2) # prints Tuple[builtins.list[builtins.int], fallback=foo.Foo]
编辑:
将我的方法与塞巴斯蒂安瓦格纳的 相结合,我们可以实现如下目标:
from typing import NamedTuple, List, Callable, TypeVar, Type, Any, cast
from functools import wraps
T = TypeVar('T')
def default_factory(**factory_kw: Callable[[], Any]) -> Callable[[Type[T]], Type[T]]:
def wrapper(wcls: Type[T], /) -> Type[T]:
@wraps(wcls.__new__)
def __new__(cls: Type[T], *args: Any, **kwargs: Any) -> T:
for key, factory in factory_kw.items():
kwargs.setdefault(key, factory())
new = super(cls, cls).__new__(cls, *args, **kwargs) # type: ignore[misc]
# This call to cast() is necessary if you run MyPy with the --strict argument
return cast(T, new)
cls_name = wcls.__name__
wcls.__name__ = wcls.__qualname__ = f'_{cls_name}'
return type(cls_name, (wcls, ), {'__new__': __new__, '__slots__': ()})
return wrapper
@default_factory(my_list=list)
class Foo(NamedTuple):
# You do not *need* to have the default value in the class body,
# but it makes MyPy a lot happier
my_list: List[int] = []
foo1 = Foo()
foo1.my_list.append(42)
foo2 = Foo()
print(f'foo1 list: {foo1.my_list}') # prints [42]
print(f'foo2 list: {foo2.my_list}') # prints []
print(Foo) # prints <class '__main__.Foo'>
print(Foo.__mro__) # prints (<class '__main__.Foo'>, <class '__main__._Foo'>, <class 'tuple'>, <class 'object'>)
from inspect import signature
print(signature(Foo.__new__)) # prints (_cls, my_list: List[int] = [])
运行 它通过 MyPy,MyPy 告诉我们 foo1
和 foo2
的显示类型仍然是 "Tuple[builtins.list[builtins.int], fallback=__main__.Foo]"
下面是原始答案。
这个怎么样? (灵感来自 ):
from typing import NamedTuple, List, Optional, TypeVar, Type
class _Foo(NamedTuple):
my_list: List[int]
T = TypeVar('T', bound="Foo")
class Foo(_Foo):
"A namedtuple defined as `_Foo(mylist)`, with a default value of `[]`"
__slots__ = ()
def __new__(cls: Type[T], mylist: Optional[List[int]] = None) -> T:
mylist = [] if mylist is None else mylist
return super().__new__(cls, mylist) # type: ignore
f, g = Foo(), Foo()
print(isinstance(f, Foo)) # prints "True"
print(isinstance(f, _Foo)) # prints "True"
print(f.mylist is g.mylist) # prints "False"
运行 它通过 MyPy 和显示的类型 f
和 g
将是:"Tuple[builtins.list[builtins.int], fallback=__main__.Foo]"
.
我不确定为什么我必须添加 # type: ignore
才能让 MyPy 停止抱怨 — 如果有人可以启发我,我会很感兴趣。似乎在运行时工作正常。
考虑到我想正确使用类型注释来命名元组来自输入模块:
from typing import NamedTuple, List
class Foo(NamedTuple):
my_list: List[int] = []
foo1 = Foo()
foo1.my_list.append(42)
foo2 = Foo()
print(foo2.my_list) # prints [42]
在 Python 中避免可变默认值痛苦的最好或最干净的方法是什么?我有一些想法,但似乎没有什么好的
默认使用
None
class Foo(NamedTuple): my_list: Optional[List[int]] = None foo1 = Foo() if foo1.my_list is None foo1 = foo1._replace(my_list=[]) # super ugly foo1.my_list.append(42)
覆盖
__new__
或__init__
将不起作用:AttributeError: Cannot overwrite NamedTuple attribute __init__ AttributeError: Cannot overwrite NamedTuple attribute __new__
特别
@classmethod
class Foo(NamedTuple): my_list: List[int] = [] @classmethod def use_me_instead(cls, my_list=None): if not my_list: my_list = [] return cls(my_list) foo1 = Foo.use_me_instead() foo1.my_list.append(42) # works!
也许使用
frozenset
并完全避免可变属性?但这不适用于Dict
s,因为没有frozendict
s.
有没有人有好的答案?
使用数据类而不是命名元组。数据类允许字段指定默认值 factory 而不是单个默认值。
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Foo:
my_list: List[int] = field(default_factory=list)
编辑:更新为使用 Alex 的方法,因为这比我以前的想法要好得多。
这是 Alex 的 Foo
class 放入装饰器中:
from typing import NamedTuple, List, Callable, TypeVar, cast, Type
T = TypeVar('T')
def default_factory(**factory_kw: Callable) -> Callable[[Type[T]], Type[T]]:
def wrapper(wcls: Type[T]) -> Type[T]:
def du_new(cls: Type[T], **kwargs) -> T:
for key, factory in factory_kw.items():
if key not in kwargs:
kwargs[key] = factory()
return super(cls, cls).__new__(cls, **kwargs) # type: ignore[misc]
return type(f'{wcls.__name__}_', (wcls, ), {'__new__': du_new})
return wrapper
@default_factory(my_list=list)
class Foo(NamedTuple):
my_list: List[int] = [] # you still need to define the default argument
foo1 = Foo()
foo1.my_list.append(42)
foo2 = Foo()
print(foo2.my_list) # prints []
#reveal_type(foo2) # prints Tuple[builtins.list[builtins.int], fallback=foo.Foo]
编辑:
将我的方法与塞巴斯蒂安瓦格纳的
from typing import NamedTuple, List, Callable, TypeVar, Type, Any, cast
from functools import wraps
T = TypeVar('T')
def default_factory(**factory_kw: Callable[[], Any]) -> Callable[[Type[T]], Type[T]]:
def wrapper(wcls: Type[T], /) -> Type[T]:
@wraps(wcls.__new__)
def __new__(cls: Type[T], *args: Any, **kwargs: Any) -> T:
for key, factory in factory_kw.items():
kwargs.setdefault(key, factory())
new = super(cls, cls).__new__(cls, *args, **kwargs) # type: ignore[misc]
# This call to cast() is necessary if you run MyPy with the --strict argument
return cast(T, new)
cls_name = wcls.__name__
wcls.__name__ = wcls.__qualname__ = f'_{cls_name}'
return type(cls_name, (wcls, ), {'__new__': __new__, '__slots__': ()})
return wrapper
@default_factory(my_list=list)
class Foo(NamedTuple):
# You do not *need* to have the default value in the class body,
# but it makes MyPy a lot happier
my_list: List[int] = []
foo1 = Foo()
foo1.my_list.append(42)
foo2 = Foo()
print(f'foo1 list: {foo1.my_list}') # prints [42]
print(f'foo2 list: {foo2.my_list}') # prints []
print(Foo) # prints <class '__main__.Foo'>
print(Foo.__mro__) # prints (<class '__main__.Foo'>, <class '__main__._Foo'>, <class 'tuple'>, <class 'object'>)
from inspect import signature
print(signature(Foo.__new__)) # prints (_cls, my_list: List[int] = [])
运行 它通过 MyPy,MyPy 告诉我们 foo1
和 foo2
的显示类型仍然是 "Tuple[builtins.list[builtins.int], fallback=__main__.Foo]"
下面是原始答案。
这个怎么样? (灵感来自
from typing import NamedTuple, List, Optional, TypeVar, Type
class _Foo(NamedTuple):
my_list: List[int]
T = TypeVar('T', bound="Foo")
class Foo(_Foo):
"A namedtuple defined as `_Foo(mylist)`, with a default value of `[]`"
__slots__ = ()
def __new__(cls: Type[T], mylist: Optional[List[int]] = None) -> T:
mylist = [] if mylist is None else mylist
return super().__new__(cls, mylist) # type: ignore
f, g = Foo(), Foo()
print(isinstance(f, Foo)) # prints "True"
print(isinstance(f, _Foo)) # prints "True"
print(f.mylist is g.mylist) # prints "False"
运行 它通过 MyPy 和显示的类型 f
和 g
将是:"Tuple[builtins.list[builtins.int], fallback=__main__.Foo]"
.
我不确定为什么我必须添加 # type: ignore
才能让 MyPy 停止抱怨 — 如果有人可以启发我,我会很感兴趣。似乎在运行时工作正常。