mirror of https://github.com/python/cpython
gh-105566: Deprecate unusual ways of creating `typing.NamedTuple` classes (#105609)
Deprecate creating a 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", [])`.
This commit is contained in:
parent
fc8037d84c
commit
ad56340b66
|
@ -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>`.
|
||||
|
|
|
@ -141,6 +141,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`.)
|
||||
|
||||
* :mod:`array`'s ``'u'`` format code, deprecated in docs since Python 3.3,
|
||||
emits :exc:`DeprecationWarning` since 3.13
|
||||
|
|
|
@ -7189,18 +7189,47 @@ class NamedTupleTests(BaseTestCase):
|
|||
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)])
|
||||
|
@ -7210,12 +7239,32 @@ class NamedTupleTests(BaseTestCase):
|
|||
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, {})
|
||||
|
@ -7225,13 +7274,29 @@ class NamedTupleTests(BaseTestCase):
|
|||
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):
|
||||
|
|
|
@ -2755,7 +2755,16 @@ class NamedTupleMeta(type):
|
|||
return nm_tpl
|
||||
|
||||
|
||||
def NamedTuple(typename, fields=None, /, **kwargs):
|
||||
class _Sentinel:
|
||||
__slots__ = ()
|
||||
def __repr__(self):
|
||||
return '<sentinel>'
|
||||
|
||||
|
||||
_sentinel = _Sentinel()
|
||||
|
||||
|
||||
def NamedTuple(typename, fields=_sentinel, /, **kwargs):
|
||||
"""Typed version of namedtuple.
|
||||
|
||||
Usage::
|
||||
|
@ -2775,11 +2784,44 @@ def NamedTuple(typename, fields=None, /, **kwargs):
|
|||
|
||||
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
|
||||
|
|
|
@ -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", [])``.
|
Loading…
Reference in New Issue