Skip to content

Commit c5e1775

Browse files
authored
gh-133817: remove keyword arguments syntax for NamedTuple (#133822)
1 parent 92337f6 commit c5e1775

File tree

4 files changed

+33
-117
lines changed

4 files changed

+33
-117
lines changed

Doc/whatsnew/3.15.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,14 @@ Deprecated
121121
Removed
122122
=======
123123

124-
module_name
125-
-----------
126-
127-
* TODO
124+
typing
125+
------
126+
127+
* The undocumented keyword argument syntax for creating
128+
:class:`~typing.NamedTuple` classes (for example,
129+
``Point = NamedTuple("Point", x=int, y=int)``).
130+
Use the class-based syntax or the functional syntax instead.
131+
(Contributed by Bénédikt Tran in :gh:`133817`.)
128132

129133

130134
Porting to Python 3.15

Lib/test/test_typing.py

Lines changed: 22 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -8080,78 +8080,13 @@ class Group(NamedTuple):
80808080
self.assertIs(type(a), Group)
80818081
self.assertEqual(a, (1, [2]))
80828082

8083-
def test_namedtuple_keyword_usage(self):
8084-
with self.assertWarnsRegex(
8085-
DeprecationWarning,
8086-
"Creating NamedTuple classes using keyword arguments is deprecated"
8087-
):
8088-
LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int)
8089-
8090-
nick = LocalEmployee('Nick', 25)
8091-
self.assertIsInstance(nick, tuple)
8092-
self.assertEqual(nick.name, 'Nick')
8093-
self.assertEqual(LocalEmployee.__name__, 'LocalEmployee')
8094-
self.assertEqual(LocalEmployee._fields, ('name', 'age'))
8095-
self.assertEqual(LocalEmployee.__annotations__, dict(name=str, age=int))
8096-
8097-
with self.assertRaisesRegex(
8098-
TypeError,
8099-
"Either list of fields or keywords can be provided to NamedTuple, not both"
8100-
):
8101-
NamedTuple('Name', [('x', int)], y=str)
8102-
8103-
with self.assertRaisesRegex(
8104-
TypeError,
8105-
"Either list of fields or keywords can be provided to NamedTuple, not both"
8106-
):
8107-
NamedTuple('Name', [], y=str)
8108-
8109-
with self.assertRaisesRegex(
8110-
TypeError,
8111-
(
8112-
r"Cannot pass `None` as the 'fields' parameter "
8113-
r"and also specify fields using keyword arguments"
8114-
)
8115-
):
8116-
NamedTuple('Name', None, x=int)
8117-
8118-
def test_namedtuple_special_keyword_names(self):
8119-
with self.assertWarnsRegex(
8120-
DeprecationWarning,
8121-
"Creating NamedTuple classes using keyword arguments is deprecated"
8122-
):
8123-
NT = NamedTuple("NT", cls=type, self=object, typename=str, fields=list)
8124-
8125-
self.assertEqual(NT.__name__, 'NT')
8126-
self.assertEqual(NT._fields, ('cls', 'self', 'typename', 'fields'))
8127-
a = NT(cls=str, self=42, typename='foo', fields=[('bar', tuple)])
8128-
self.assertEqual(a.cls, str)
8129-
self.assertEqual(a.self, 42)
8130-
self.assertEqual(a.typename, 'foo')
8131-
self.assertEqual(a.fields, [('bar', tuple)])
8132-
81338083
def test_empty_namedtuple(self):
8134-
expected_warning = re.escape(
8135-
"Failing to pass a value for the 'fields' parameter is deprecated "
8136-
"and will be disallowed in Python 3.15. "
8137-
"To create a NamedTuple class with 0 fields "
8138-
"using the functional syntax, "
8139-
"pass an empty list, e.g. `NT1 = NamedTuple('NT1', [])`."
8140-
)
8141-
with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
8142-
NT1 = NamedTuple('NT1')
8143-
8144-
expected_warning = re.escape(
8145-
"Passing `None` as the 'fields' parameter is deprecated "
8146-
"and will be disallowed in Python 3.15. "
8147-
"To create a NamedTuple class with 0 fields "
8148-
"using the functional syntax, "
8149-
"pass an empty list, e.g. `NT2 = NamedTuple('NT2', [])`."
8150-
)
8151-
with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
8152-
NT2 = NamedTuple('NT2', None)
8084+
with self.assertRaisesRegex(TypeError, "missing.*required.*argument"):
8085+
BAD = NamedTuple('BAD')
81538086

8154-
NT3 = NamedTuple('NT2', [])
8087+
NT1 = NamedTuple('NT1', {})
8088+
NT2 = NamedTuple('NT2', ())
8089+
NT3 = NamedTuple('NT3', [])
81558090

81568091
class CNT(NamedTuple):
81578092
pass # empty body
@@ -8166,16 +8101,18 @@ class CNT(NamedTuple):
81668101
def test_namedtuple_errors(self):
81678102
with self.assertRaises(TypeError):
81688103
NamedTuple.__new__()
8104+
with self.assertRaisesRegex(TypeError, "object is not iterable"):
8105+
NamedTuple('Name', None)
81698106

81708107
with self.assertRaisesRegex(
81718108
TypeError,
8172-
"missing 1 required positional argument"
8109+
"missing 2 required positional arguments"
81738110
):
81748111
NamedTuple()
81758112

81768113
with self.assertRaisesRegex(
81778114
TypeError,
8178-
"takes from 1 to 2 positional arguments but 3 were given"
8115+
"takes 2 positional arguments but 3 were given"
81798116
):
81808117
NamedTuple('Emp', [('name', str)], None)
81818118

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

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

8131+
with self.assertRaisesRegex(
8132+
TypeError,
8133+
"got an unexpected keyword argument"
8134+
):
8135+
NamedTuple('Name', [('x', int)], y=str)
8136+
8137+
with self.assertRaisesRegex(
8138+
TypeError,
8139+
"got an unexpected keyword argument"
8140+
):
8141+
NamedTuple('Name', [], y=str)
8142+
81948143
def test_copy_and_pickle(self):
81958144
global Emp # pickle wants to reference the class by name
81968145
Emp = NamedTuple('Emp', [('name', str), ('cool', int)])

Lib/typing.py

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2968,7 +2968,7 @@ def annotate(format):
29682968
return nm_tpl
29692969

29702970

2971-
def NamedTuple(typename, fields=_sentinel, /, **kwargs):
2971+
def NamedTuple(typename, fields, /):
29722972
"""Typed version of namedtuple.
29732973
29742974
Usage::
@@ -2988,48 +2988,9 @@ class Employee(NamedTuple):
29882988
29892989
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
29902990
"""
2991-
if fields is _sentinel:
2992-
if kwargs:
2993-
deprecated_thing = "Creating NamedTuple classes using keyword arguments"
2994-
deprecation_msg = (
2995-
"{name} is deprecated and will be disallowed in Python {remove}. "
2996-
"Use the class-based or functional syntax instead."
2997-
)
2998-
else:
2999-
deprecated_thing = "Failing to pass a value for the 'fields' parameter"
3000-
example = f"`{typename} = NamedTuple({typename!r}, [])`"
3001-
deprecation_msg = (
3002-
"{name} is deprecated and will be disallowed in Python {remove}. "
3003-
"To create a NamedTuple class with 0 fields "
3004-
"using the functional syntax, "
3005-
"pass an empty list, e.g. "
3006-
) + example + "."
3007-
elif fields is None:
3008-
if kwargs:
3009-
raise TypeError(
3010-
"Cannot pass `None` as the 'fields' parameter "
3011-
"and also specify fields using keyword arguments"
3012-
)
3013-
else:
3014-
deprecated_thing = "Passing `None` as the 'fields' parameter"
3015-
example = f"`{typename} = NamedTuple({typename!r}, [])`"
3016-
deprecation_msg = (
3017-
"{name} is deprecated and will be disallowed in Python {remove}. "
3018-
"To create a NamedTuple class with 0 fields "
3019-
"using the functional syntax, "
3020-
"pass an empty list, e.g. "
3021-
) + example + "."
3022-
elif kwargs:
3023-
raise TypeError("Either list of fields or keywords"
3024-
" can be provided to NamedTuple, not both")
3025-
if fields is _sentinel or fields is None:
3026-
import warnings
3027-
warnings._deprecated(deprecated_thing, message=deprecation_msg, remove=(3, 15))
3028-
fields = kwargs.items()
30292991
types = {n: _type_check(t, f"field {n} annotation must be a type")
30302992
for n, t in fields}
30312993
field_names = [n for n, _ in fields]
3032-
30332994
nt = _make_nmtuple(typename, field_names, _make_eager_annotate(types), module=_caller())
30342995
nt.__orig_bases__ = (NamedTuple,)
30352996
return nt
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Remove support for creating :class:`~typing.NamedTuple` classes via the
2+
undocumented keyword argument syntax. Patch by Bénédikt Tran.

0 commit comments

Comments
 (0)