summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Waygood <Alex.Waygood@Gmail.com>2023-06-14 12:38:49 (GMT)
committerGitHub <noreply@github.com>2023-06-14 12:38:49 (GMT)
commitad56340b665c5d8ac1f318964f71697bba41acb7 (patch)
tree85e07e09edb9ae29c96bcd4fcdf01b5378765f60
parentfc8037d84c5f886849a05ec993dd0f79a356d372 (diff)
downloadcpython-ad56340b665c5d8ac1f318964f71697bba41acb7.zip
cpython-ad56340b665c5d8ac1f318964f71697bba41acb7.tar.gz
cpython-ad56340b665c5d8ac1f318964f71697bba41acb7.tar.bz2
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", [])`.
-rw-r--r--Doc/library/typing.rst13
-rw-r--r--Doc/whatsnew/3.13.rst11
-rw-r--r--Lib/test/test_typing.py83
-rw-r--r--Lib/typing.py48
-rw-r--r--Misc/NEWS.d/next/Library/2023-06-09-20-34-23.gh-issue-105566.YxlGg1.rst10
5 files changed, 153 insertions, 12 deletions
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index 487be8f..aedef09 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -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>`.
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index fcd10e5..cf7c2ca 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -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
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index a36d801..92f3804 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -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):
diff --git a/Lib/typing.py b/Lib/typing.py
index 4e6dc44..570cb80 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -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
diff --git a/Misc/NEWS.d/next/Library/2023-06-09-20-34-23.gh-issue-105566.YxlGg1.rst b/Misc/NEWS.d/next/Library/2023-06-09-20-34-23.gh-issue-105566.YxlGg1.rst
new file mode 100644
index 0000000..c2c497a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-06-09-20-34-23.gh-issue-105566.YxlGg1.rst
@@ -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", [])``.