Skip to content

gh-133817: remove keyword argument syntax for NamedTuple objects #133822

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,14 @@ Deprecated
Removed
=======

module_name
-----------

* TODO
typing
------

* The undocumented keyword argument syntax for creating
:class:`~typing.NamedTuple` classes (for example,
``Point = NamedTuple("Point", x=int, y=int)``).
Use the class-based syntax or the functional syntax instead.
(Contributed by Bénédikt Tran in :gh:`133817`.)


Porting to Python 3.15
Expand Down
95 changes: 22 additions & 73 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8080,78 +8080,13 @@ class Group(NamedTuple):
self.assertIs(type(a), Group)
self.assertEqual(a, (1, [2]))

def test_namedtuple_keyword_usage(self):
with self.assertWarnsRegex(
DeprecationWarning,
"Creating NamedTuple classes using keyword arguments is deprecated"
):
LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int)

nick = LocalEmployee('Nick', 25)
self.assertIsInstance(nick, tuple)
self.assertEqual(nick.name, 'Nick')
self.assertEqual(LocalEmployee.__name__, 'LocalEmployee')
self.assertEqual(LocalEmployee._fields, ('name', 'age'))
self.assertEqual(LocalEmployee.__annotations__, dict(name=str, age=int))

with self.assertRaisesRegex(
TypeError,
"Either list of fields or keywords can be provided to NamedTuple, not both"
):
NamedTuple('Name', [('x', int)], y=str)

with self.assertRaisesRegex(
TypeError,
"Either list of fields or keywords can be provided to NamedTuple, not both"
):
NamedTuple('Name', [], y=str)

with self.assertRaisesRegex(
TypeError,
(
r"Cannot pass `None` as the 'fields' parameter "
r"and also specify fields using keyword arguments"
)
):
NamedTuple('Name', None, x=int)

def test_namedtuple_special_keyword_names(self):
with self.assertWarnsRegex(
DeprecationWarning,
"Creating NamedTuple classes using keyword arguments is deprecated"
):
NT = NamedTuple("NT", cls=type, self=object, typename=str, fields=list)

self.assertEqual(NT.__name__, 'NT')
self.assertEqual(NT._fields, ('cls', 'self', 'typename', 'fields'))
a = NT(cls=str, self=42, typename='foo', fields=[('bar', tuple)])
self.assertEqual(a.cls, str)
self.assertEqual(a.self, 42)
self.assertEqual(a.typename, 'foo')
self.assertEqual(a.fields, [('bar', tuple)])

def test_empty_namedtuple(self):
expected_warning = re.escape(
"Failing to pass a value for the 'fields' parameter is deprecated "
"and will be disallowed in Python 3.15. "
"To create a NamedTuple class with 0 fields "
"using the functional syntax, "
"pass an empty list, e.g. `NT1 = NamedTuple('NT1', [])`."
)
with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
NT1 = NamedTuple('NT1')

expected_warning = re.escape(
"Passing `None` as the 'fields' parameter is deprecated "
"and will be disallowed in Python 3.15. "
"To create a NamedTuple class with 0 fields "
"using the functional syntax, "
"pass an empty list, e.g. `NT2 = NamedTuple('NT2', [])`."
)
with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
NT2 = NamedTuple('NT2', None)
with self.assertRaisesRegex(TypeError, "missing.*required.*argument"):
BAD = NamedTuple('BAD')

NT3 = NamedTuple('NT2', [])
NT1 = NamedTuple('NT1', {})
NT2 = NamedTuple('NT2', ())
NT3 = NamedTuple('NT3', [])

class CNT(NamedTuple):
pass # empty body
Expand All @@ -8166,16 +8101,18 @@ class CNT(NamedTuple):
def test_namedtuple_errors(self):
with self.assertRaises(TypeError):
NamedTuple.__new__()
with self.assertRaisesRegex(TypeError, "object is not iterable"):
NamedTuple('Name', None)

with self.assertRaisesRegex(
TypeError,
"missing 1 required positional argument"
"missing 2 required positional arguments"
):
NamedTuple()

with self.assertRaisesRegex(
TypeError,
"takes from 1 to 2 positional arguments but 3 were given"
"takes 2 positional arguments but 3 were given"
):
NamedTuple('Emp', [('name', str)], None)

Expand All @@ -8187,10 +8124,22 @@ def test_namedtuple_errors(self):

with self.assertRaisesRegex(
TypeError,
"missing 1 required positional argument: 'typename'"
"got some positional-only arguments passed as keyword arguments"
):
NamedTuple(typename='Emp', name=str, id=int)

with self.assertRaisesRegex(
TypeError,
"got an unexpected keyword argument"
):
NamedTuple('Name', [('x', int)], y=str)

with self.assertRaisesRegex(
TypeError,
"got an unexpected keyword argument"
):
NamedTuple('Name', [], y=str)

def test_copy_and_pickle(self):
global Emp # pickle wants to reference the class by name
Emp = NamedTuple('Emp', [('name', str), ('cool', int)])
Expand Down
41 changes: 1 addition & 40 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2968,7 +2968,7 @@ def annotate(format):
return nm_tpl


def NamedTuple(typename, fields=_sentinel, /, **kwargs):
def NamedTuple(typename, fields, /):
"""Typed version of namedtuple.

Usage::
Expand All @@ -2988,48 +2988,9 @@ class Employee(NamedTuple):

Employee = NamedTuple('Employee', [('name', str), ('id', int)])
"""
if fields is _sentinel:
if kwargs:
deprecated_thing = "Creating NamedTuple classes using keyword arguments"
deprecation_msg = (
"{name} is deprecated and will be disallowed in Python {remove}. "
"Use the class-based or functional syntax instead."
)
else:
deprecated_thing = "Failing to pass a value for the 'fields' parameter"
example = f"`{typename} = NamedTuple({typename!r}, [])`"
deprecation_msg = (
"{name} is deprecated and will be disallowed in Python {remove}. "
"To create a NamedTuple class with 0 fields "
"using the functional syntax, "
"pass an empty list, e.g. "
) + example + "."
elif fields is None:
if kwargs:
raise TypeError(
"Cannot pass `None` as the 'fields' parameter "
"and also specify fields using keyword arguments"
)
else:
deprecated_thing = "Passing `None` as the 'fields' parameter"
example = f"`{typename} = NamedTuple({typename!r}, [])`"
deprecation_msg = (
"{name} is deprecated and will be disallowed in Python {remove}. "
"To create a NamedTuple class with 0 fields "
"using the functional syntax, "
"pass an empty list, e.g. "
) + example + "."
elif kwargs:
raise TypeError("Either list of fields or keywords"
" can be provided to NamedTuple, not both")
if fields is _sentinel or fields is None:
import warnings
warnings._deprecated(deprecated_thing, message=deprecation_msg, remove=(3, 15))
fields = kwargs.items()
types = {n: _type_check(t, f"field {n} annotation must be a type")
for n, t in fields}
field_names = [n for n, _ in fields]

nt = _make_nmtuple(typename, field_names, _make_eager_annotate(types), module=_caller())
nt.__orig_bases__ = (NamedTuple,)
return nt
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Remove support for creating :class:`~typing.NamedTuple` classes via the
undocumented keyword argument syntax. Patch by Bénédikt Tran.
Loading