Question: Given I want to properly using type annotations for named tuples from the typing module: 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]  What is the best or cleanest ways to avoid the mutable default value misery in Python? I have a few ideas, but nothing really seems to be good  Using None as default 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)   Overwriting __new__ or __init__ won't work: AttributeError: Cannot overwrite NamedTuple attribute __init__ AttributeError: Cannot overwrite NamedTuple attribute __new__   Special @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!   Maybe using frozenset and avoid mutable attributes altogether? But that won't work with Dicts as there are no frozendicts.   Does anyone have a good answer?

Answer: EDIT: Blending my approach with Sebastian Wagner's idea of using a decorator, we can achieve something like this: 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] = [])  Run it through MyPy, and MyPy informs us that the revealed type of foo1 and foo2 is still "Tuple[builtins.list[builtins.int], fallback=__main__.Foo]" Original answer below.  How about this? (Inspired by this answer here): 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"  Run it through MyPy and the revealed type of f and g will be: "Tuple[builtins.list[builtins.int], fallback=__main__.Foo]". I'm not sure why I had to add the # type: ignore to get MyPy to stop complaining — if anybody can enlighten me on that, I'd be interested. Seems to work fine at runtime.