mirror of https://github.com/python/cpython
gh-105570: Deprecate unusual ways of creating empty TypedDicts (#105780)
Deprecate two methods of creating typing.TypedDict classes with 0 fields using the functional syntax: `TD = TypedDict("TD")` and `TD = TypedDict("TD", None)`. Both will be disallowed in Python 3.15. To create a TypedDict class with 0 fields, either use `class TD(TypedDict): pass` or `TD = TypedDict("TD", {})`.
This commit is contained in:
parent
d32e8d6070
commit
7b1f0f204a
|
@ -2388,6 +2388,14 @@ These are not used in annotations. They are building blocks for declaring types.
|
||||||
.. versionchanged:: 3.13
|
.. versionchanged:: 3.13
|
||||||
Removed support for the keyword-argument method of creating ``TypedDict``\ s.
|
Removed support for the keyword-argument method of creating ``TypedDict``\ s.
|
||||||
|
|
||||||
|
.. deprecated-removed:: 3.13 3.15
|
||||||
|
When using the functional syntax to create a TypedDict class, failing to
|
||||||
|
pass a value to the 'fields' parameter (``TD = TypedDict("TD")``) is
|
||||||
|
deprecated. Passing ``None`` to the 'fields' parameter
|
||||||
|
(``TD = TypedDict("TD", None)``) is also deprecated. Both will be
|
||||||
|
disallowed in Python 3.15. To create a TypedDict class with 0 fields,
|
||||||
|
use ``class TD(TypedDict): pass`` or ``TD = TypedDict("TD", {})``.
|
||||||
|
|
||||||
Protocols
|
Protocols
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
|
|
@ -146,12 +146,15 @@ Deprecated
|
||||||
be disallowed in Python 3.15. Use the class-based syntax or the functional
|
be disallowed in Python 3.15. Use the class-based syntax or the functional
|
||||||
syntax instead. (Contributed by Alex Waygood in :gh:`105566`.)
|
syntax instead. (Contributed by Alex Waygood in :gh:`105566`.)
|
||||||
* When using the functional syntax to create a :class:`typing.NamedTuple`
|
* When using the functional syntax to create a :class:`typing.NamedTuple`
|
||||||
class, failing to pass a value to the 'fields' parameter
|
class or a :class:`typing.TypedDict` class, failing to pass a value to the
|
||||||
(``NT = NamedTuple("NT")``) is deprecated. Passing ``None`` to the 'fields'
|
'fields' parameter (``NT = NamedTuple("NT")`` or ``TD = TypedDict("TD")``) is
|
||||||
parameter (``NT = NamedTuple("NT", None)``) is also deprecated. Both will be
|
deprecated. Passing ``None`` to the 'fields' parameter
|
||||||
disallowed in Python 3.15. To create a NamedTuple class with 0 fields, use
|
(``NT = NamedTuple("NT", None)`` or ``TD = TypedDict("TD", None)``) is also
|
||||||
``class NT(NamedTuple): pass`` or ``NT = NamedTuple("NT", [])``.
|
deprecated. Both will be disallowed in Python 3.15. To create a NamedTuple
|
||||||
(Contributed by Alex Waygood in :gh:`105566`.)
|
class with 0 fields, use ``class NT(NamedTuple): pass`` or
|
||||||
|
``NT = NamedTuple("NT", [])``. To create a TypedDict class with 0 fields, use
|
||||||
|
``class TD(TypedDict): pass`` or ``TD = TypedDict("TD", {})``.
|
||||||
|
(Contributed by Alex Waygood in :gh:`105566` and :gh:`105570`.)
|
||||||
|
|
||||||
* :mod:`array`'s ``'u'`` format code, deprecated in docs since Python 3.3,
|
* :mod:`array`'s ``'u'`` format code, deprecated in docs since Python 3.3,
|
||||||
emits :exc:`DeprecationWarning` since 3.13
|
emits :exc:`DeprecationWarning` since 3.13
|
||||||
|
|
|
@ -7823,6 +7823,40 @@ class TypedDictTests(BaseTestCase):
|
||||||
self.assertEqual(MultipleGenericBases.__orig_bases__, (GenericParent[int], GenericParent[float]))
|
self.assertEqual(MultipleGenericBases.__orig_bases__, (GenericParent[int], GenericParent[float]))
|
||||||
self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,))
|
self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,))
|
||||||
|
|
||||||
|
def test_zero_fields_typeddicts(self):
|
||||||
|
T1 = TypedDict("T1", {})
|
||||||
|
class T2(TypedDict): pass
|
||||||
|
class T3[tvar](TypedDict): pass
|
||||||
|
S = TypeVar("S")
|
||||||
|
class T4(TypedDict, Generic[S]): pass
|
||||||
|
|
||||||
|
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 TypedDict class with 0 fields "
|
||||||
|
"using the functional syntax, "
|
||||||
|
"pass an empty dictionary, e.g. `T5 = TypedDict('T5', {})`."
|
||||||
|
)
|
||||||
|
with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
|
||||||
|
T5 = TypedDict('T5')
|
||||||
|
|
||||||
|
expected_warning = re.escape(
|
||||||
|
"Passing `None` as the 'fields' parameter is deprecated "
|
||||||
|
"and will be disallowed in Python 3.15. "
|
||||||
|
"To create a TypedDict class with 0 fields "
|
||||||
|
"using the functional syntax, "
|
||||||
|
"pass an empty dictionary, e.g. `T6 = TypedDict('T6', {})`."
|
||||||
|
)
|
||||||
|
with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
|
||||||
|
T6 = TypedDict('T6', None)
|
||||||
|
|
||||||
|
for klass in T1, T2, T3, T4, T5, T6:
|
||||||
|
with self.subTest(klass=klass.__name__):
|
||||||
|
self.assertEqual(klass.__annotations__, {})
|
||||||
|
self.assertEqual(klass.__required_keys__, set())
|
||||||
|
self.assertEqual(klass.__optional_keys__, set())
|
||||||
|
self.assertIsInstance(klass(), dict)
|
||||||
|
|
||||||
|
|
||||||
class RequiredTests(BaseTestCase):
|
class RequiredTests(BaseTestCase):
|
||||||
|
|
||||||
|
|
|
@ -2908,7 +2908,7 @@ class _TypedDictMeta(type):
|
||||||
__instancecheck__ = __subclasscheck__
|
__instancecheck__ = __subclasscheck__
|
||||||
|
|
||||||
|
|
||||||
def TypedDict(typename, fields=None, /, *, total=True):
|
def TypedDict(typename, fields=_sentinel, /, *, total=True):
|
||||||
"""A simple typed namespace. At runtime it is equivalent to a plain dict.
|
"""A simple typed namespace. At runtime it is equivalent to a plain dict.
|
||||||
|
|
||||||
TypedDict creates a dictionary type such that a type checker will expect all
|
TypedDict creates a dictionary type such that a type checker will expect all
|
||||||
|
@ -2955,7 +2955,22 @@ def TypedDict(typename, fields=None, /, *, total=True):
|
||||||
|
|
||||||
See PEP 655 for more details on Required and NotRequired.
|
See PEP 655 for more details on Required and NotRequired.
|
||||||
"""
|
"""
|
||||||
if fields is None:
|
if fields is _sentinel or fields is None:
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
if fields is _sentinel:
|
||||||
|
deprecated_thing = "Failing to pass a value for the 'fields' parameter"
|
||||||
|
else:
|
||||||
|
deprecated_thing = "Passing `None` as the 'fields' parameter"
|
||||||
|
|
||||||
|
example = f"`{typename} = TypedDict({typename!r}, {{{{}}}})`"
|
||||||
|
deprecation_msg = (
|
||||||
|
"{name} is deprecated and will be disallowed in Python {remove}. "
|
||||||
|
"To create a TypedDict class with 0 fields "
|
||||||
|
"using the functional syntax, "
|
||||||
|
"pass an empty dictionary, e.g. "
|
||||||
|
) + example + "."
|
||||||
|
warnings._deprecated(deprecated_thing, message=deprecation_msg, remove=(3, 15))
|
||||||
fields = {}
|
fields = {}
|
||||||
|
|
||||||
ns = {'__annotations__': dict(fields)}
|
ns = {'__annotations__': dict(fields)}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
Deprecate two methods of creating :class:`typing.TypedDict` classes with 0
|
||||||
|
fields using the functional syntax: ``TD = TypedDict("TD")`` and
|
||||||
|
``TD = TypedDict("TD", None)``. Both will be disallowed in Python 3.15. To create a
|
||||||
|
``TypedDict`` class with 0 fields, either use ``class TD(TypedDict): pass``
|
||||||
|
or ``TD = TypedDict("TD", {})``.
|
Loading…
Reference in New Issue