Draft of typing spec chapter for named tuples

I’ve written a first draft of a new chapter for the typing spec that focuses on type checker behaviors for named tuples.

Named tuples have some atypical behaviors, and type checkers need to include special-case logic to handle these behaviors. As @gvanrossum pointed out here, the type checking behaviors for named tuples are currently under-defined. This is an attempt to define and standardize the desired behaviors.

If you are interested in reviewing the draft, you can find it in this PR . Minor feedback (typos, wording suggestions, etc.) can be added directly to the PR. For significant questions or areas of potential disagreement, please post to this thread so community members get better visibility and can engage in the discussion.

3 Likes

In addition to __new__, namedtuples add a bunch of methods and some class attributes that we might want to specify behaviour for:

  • _make() we can’t really type precisely, so I guess it’d just take an Iterable[Union[...]].
  • _asdict() and _field_defaults could potentially produce a synthesised TypedDict, but we might want to just say a regular dict is fine also.
  • _fields should probably be specified to be a tuple of literal names.
  • _replace() should be like __new__() except with all parameters optional.
  • __annotations__ exists on NamedTuple classes, but I don’t know how much that needs to be specified.

@TeamSpen210, that’s a good question about additional attributes. Mypy and pyright both leverage the class definition for NamedTuple in typing.pyi to pick up the class attributes that you mentioned. For that reason, I don’t know if it’s important for the typing spec to discuss them.

The only reason I would suggest including something in the typing spec is if we think there’s a compelling reason for type checkers to synthesize specialized versions of these attributes, for example, a customized _replace method. I don’t recall ever receiving a request from pyright users for such functionality, so I presume that it’s not important. The only related issue I could find in the mypy issue tracker is this one, and it doesn’t mention anything about synthesizing customized versions of these attributes.

I would tend to agree that most of these probably don’t have a very compelling use-case[1], although in the case of _replace you could make the argument, that the only reason why nobody has complained so far, is that for one it is a false negative, so in order for someone to report it, they would have to make a mistake and notice it in their test suite somewhere or at runtime and determine that it is something a type checker should’ve caught, but it’s also a fairly niche use, since when you start wanting to create mutated copies of NamedTuple it might be time to switch to a dataclass instead.


  1. In the case of _asdict it would probably even be pretty annoying, since we don’t have a way yet to mark the TypedDict as complete and synthesize the value union, so it doesn’t play well with functions that take mappings rather than a TypedDict ↩︎

I have a slight queasiness about the way that mypy and pyright both use the typing._NamedTuple class currently. At runtime, NamedTuple is not a class: it’s a function that can be used as a way to dynamically generate custom tuple subclasses. The function can be inherited from due to the way that __mro_entries__ is monkey-patched onto the function after the function is defined: cpython/Lib/typing.py at da8f9fb2ea65cc2784c2400fc39ad8c800a67a42 · python/cpython · GitHub

All of this runtime magic is inexpressible in typeshed’s stubs, and I’m not sure how much type checkers really need to care about it. But typeshed’s current approach is a bit of a hack all the same, so I’m not sure we should be relying on typing._NamedTuple always being there in its current form. Still, this is all something of a hypothetical concern, so I don’t really have a strong objection here.

I do have a more concrete question, though, which is: should type checkers allow or disallow this?

from typing import NamedTuple

x: NamedTuple

Mypy currently permits this, but I don’t know that it really makes much sense since, as outlined above, NamedTuple isn’t really a class at runtime. I also don’t think it should really be considered a type by a type checker – even if you inherit from it, it’s more of a factory for constructing types than a type itself.

I think it makes sense to disallow this, since TypedDict is also not a valid as a type and essentially in the same boat, so it doesn’t make sense that one of them works, but the other one doesn’t.

That being said, in some niche use-cases it would be nice to be able to say, “hey, this will return you a named tuple, but I can’t statically tell you what it looks like without writing a mypy plugin”. But I think it would be better to have an explicit symbol to signify this[1] rather than misuse the type constructor.


  1. e.g. AnyNamedTuple, which would essentially be a gradual type that derives from tuple[Any, ...], allows arbitrary __getattr__ for the named field access and is compatible with any more specific NamedTuple ↩︎

Thanks to everyone who provided feedback. This chapter has been accepted by the TC and officially incorporated into the typing spec.