Thanks to visit codestin.com
Credit goes to github.com

Skip to content

gh-105566: Deprecate unusual ways of creating typing.NamedTuple classes #105609

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 3 commits into from
Jun 14, 2023
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
13 changes: 13 additions & 0 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2038,6 +2038,19 @@ These are not used in annotations. They are building blocks for declaring types.
.. versionchanged:: 3.11
Added support for generic namedtuples.

.. deprecated-removed:: 3.13 3.15
The undocumented keyword argument syntax for creating NamedTuple classes
(``NT = NamedTuple("NT", x=int)``) is deprecated, and will be disallowed
in 3.15. Use the class-based syntax or the functional syntax instead.

.. deprecated-removed:: 3.13 3.15
When using the functional syntax to create a NamedTuple class, failing to
pass a value to the 'fields' parameter (``NT = NamedTuple("NT")``) is
deprecated. Passing ``None`` to the 'fields' parameter
(``NT = NamedTuple("NT", None)``) is also deprecated. Both will be
disallowed in Python 3.15. To create a NamedTuple class with 0 fields,
use ``class NT(NamedTuple): pass`` or ``NT = NamedTuple("NT", [])``.

.. class:: NewType(name, tp)

Helper class to create low-overhead :ref:`distinct types <distinct>`.
Expand Down
11 changes: 11 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,17 @@ Deprecated
methods of the :class:`wave.Wave_read` and :class:`wave.Wave_write` classes.
They will be removed in Python 3.15.
(Contributed by Victor Stinner in :gh:`105096`.)
* Creating a :class:`typing.NamedTuple` class using keyword arguments to denote
the fields (``NT = NamedTuple("NT", x=int, y=int)``) is deprecated, and will
be disallowed in Python 3.15. Use the class-based syntax or the functional
syntax instead. (Contributed by Alex Waygood in :gh:`105566`.)
* When using the functional syntax to create a :class:`typing.NamedTuple`
class, failing to pass a value to the 'fields' parameter
(``NT = NamedTuple("NT")``) is deprecated. Passing ``None`` to the 'fields'
parameter (``NT = NamedTuple("NT", None)``) is also deprecated. Both will be
disallowed in Python 3.15. To create a NamedTuple class with 0 fields, use
``class NT(NamedTuple): pass`` or ``NT = NamedTuple("NT", [])``.
(Contributed by Alex Waygood in :gh:`105566`.)


Removed
Expand Down
83 changes: 74 additions & 9 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -7124,18 +7124,47 @@ class Group(NamedTuple):
self.assertEqual(a, (1, [2]))

def test_namedtuple_keyword_usage(self):
LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int)
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.assertRaises(TypeError):

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):
NT = NamedTuple("NT", cls=type, self=object, typename=str, fields=list)
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)])
Expand All @@ -7145,12 +7174,32 @@ def test_namedtuple_special_keyword_names(self):
self.assertEqual(a.fields, [('bar', tuple)])

def test_empty_namedtuple(self):
NT = NamedTuple('NT')
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)

NT3 = NamedTuple('NT2', [])

class CNT(NamedTuple):
pass # empty body

for struct in [NT, CNT]:
for struct in NT1, NT2, NT3, CNT:
with self.subTest(struct=struct):
self.assertEqual(struct._fields, ())
self.assertEqual(struct._field_defaults, {})
Expand All @@ -7160,13 +7209,29 @@ class CNT(NamedTuple):
def test_namedtuple_errors(self):
with self.assertRaises(TypeError):
NamedTuple.__new__()
with self.assertRaises(TypeError):

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

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

with self.assertRaisesRegex(
ValueError,
"Field names cannot start with an underscore"
):
NamedTuple('Emp', [('_name', str)])
with self.assertRaises(TypeError):

with self.assertRaisesRegex(
TypeError,
"missing 1 required positional argument: 'typename'"
):
NamedTuple(typename='Emp', name=str, id=int)

def test_copy_and_pickle(self):
Expand Down
48 changes: 45 additions & 3 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2753,7 +2753,16 @@ def __new__(cls, typename, bases, ns):
return nm_tpl


def NamedTuple(typename, fields=None, /, **kwargs):
class _Sentinel:
__slots__ = ()
def __repr__(self):
return '<sentinel>'


_sentinel = _Sentinel()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that = object() is good enough for this case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree. Using = object() would mean that the displayed signature would be very ugly if somebody did help(typing.NamedTuple) in the REPL

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we'll be able to reuse the _Sentinel class when we do #105570 :)



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

Usage::
Expand All @@ -2773,11 +2782,44 @@ class Employee(NamedTuple):

Employee = NamedTuple('Employee', [('name', str), ('id', int)])
"""
if fields is None:
fields = kwargs.items()
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()
nt = _make_nmtuple(typename, fields, module=_caller())
nt.__orig_bases__ = (NamedTuple,)
return nt
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Deprecate creating a :class:`typing.NamedTuple` class using keyword
arguments to denote the fields (``NT = NamedTuple("NT", x=int, y=str)``).
This will be disallowed in Python 3.15.
Use the class-based syntax or the functional syntax instead.

Two methods of creating ``NamedTuple`` classes with 0 fields using the
functional syntax are also deprecated, and will be disallowed in Python 3.15:
``NT = NamedTuple("NT")`` and ``NT = NamedTuple("NT", None)``. To create a
``NamedTuple`` class with 0 fields, either use ``class NT(NamedTuple): pass`` or
``NT = NamedTuple("NT", [])``.