This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: typing.NamedTuple with default arguments without type annotations is falsy
Type: behavior Stage:
Components: Library (Lib) Versions: Python 3.9, Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: AlexWaygood, Spencer Brown, emontnemery, eric.smith, gvanrossum, kj
Priority: normal Keywords:

Created on 2021-12-03 08:45 by emontnemery, last changed 2022-04-11 14:59 by admin.

Messages (7)
msg407570 - (view) Author: Erik Montnemery (emontnemery) Date: 2021-12-03 08:45
typing.NamedTuple behaves in surprising ways when it has default arguments which lack type annotations:

>>> from typing import NamedTuple
>>> class MyTuple(NamedTuple):
...     a = 1000
...
>>> tmp = MyTuple()
>>> tmp.a
1000
>>> len(tmp)
0
>>> bool(tmp)
False

Tested in Python 3.8 and 3.9
msg407571 - (view) Author: Spencer Brown (Spencer Brown) * Date: 2021-12-03 08:58
What's happening is that typing.NamedTuple ignores non-annotated attributes entirely when computing the names it passes along to namedtuple(), so here "a" is just a class attribute. You're accessing it from there, but the tuple itself is entirely empty. Perhaps it should error out if no names at all are found?
msg407572 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2021-12-03 09:27
I don't think we'd want to prohibit zero-length namedtuples (or NamedTuples). I've used them, especially when I'm dynamically creating them.

This is just a side effect of how Python works. I don't think there's anything to do here, except maybe mention it in the docs, and I'm not even sure that's a good idea.
msg407573 - (view) Author: Alex Waygood (AlexWaygood) * (Python triager) Date: 2021-12-03 09:33
I agree that prohibiting zero-length NamedTuples seems like a bad idea, and also agree that this probably doesn't need to be documented.

The behaviour here definitely looks weird at first glance, but it's probably not a good idea to tamper with the __bool__() methods of NamedTuple classes either: an empty NamedTuple probably *should* be falsey by default, even if it has a class attribute.
msg407575 - (view) Author: Erik Montnemery (emontnemery) Date: 2021-12-03 10:20
I think elaborating in the documentation that only annotated attributes make it to the underlying namedtuple() would be helpful, it's not obvious that they are instead just class attributes.
msg407576 - (view) Author: Alex Waygood (AlexWaygood) * (Python triager) Date: 2021-12-03 10:32
To me, the fact that NamedTuple uses class attributes to provide field defaults feels like an implementation detail that is only relevant to an unusual edge case.

Where do you think the documentation should be improved, and what is your suggested wording? :)
msg407579 - (view) Author: Erik Montnemery (emontnemery) Date: 2021-12-03 11:44
Maybe something like this:

diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index 735d477db4..8de913d8db 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -1291,7 +1291,8 @@ These are not used in annotations. They are building blocks for declaring types.

 .. class:: NamedTuple

-   Typed version of :func:`collections.namedtuple`.
+   Typed version of :func:`collections.namedtuple`, annotated fields are passed
+   to an underlying `collections.namedtuple`.

    Usage::

@@ -1311,9 +1312,20 @@ These are not used in annotations. They are building blocks for declaring types.

       employee = Employee('Guido')
       assert employee.id == 3
+      assert employee == ('Guido', 3)

    Fields with a default value must come after any fields without a default.

+   Non-annotated fields will not be part of the `collections.namedtuple`::
+
+      class Employee(NamedTuple):
+          name = 'Guido'
+          id = 3
+
+      employee = Employee()
+      assert employee.id == 3
+      assert not employee  # Passes because the collections.namedtuple is empty
+
    The resulting class has an extra attribute ``__annotations__`` giving a
    dict that maps the field names to the field types.  (The field names are in
History
Date User Action Args
2022-04-11 14:59:53adminsetgithub: 90130
2021-12-03 11:44:35emontnemerysetmessages: + msg407579
2021-12-03 10:32:54AlexWaygoodsetmessages: + msg407576
2021-12-03 10:20:56emontnemerysetmessages: + msg407575
2021-12-03 09:33:21AlexWaygoodsetmessages: + msg407573
2021-12-03 09:27:05eric.smithsetnosy: + eric.smith
messages: + msg407572
2021-12-03 08:58:36Spencer Brownsetnosy: + Spencer Brown
messages: + msg407571
2021-12-03 08:50:08AlexWaygoodsetnosy: + gvanrossum, kj, AlexWaygood
components: + Library (Lib)
2021-12-03 08:45:38emontnemerycreate