diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 9007c0daf59a4d..fe0f475a492cd7 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -1389,11 +1389,20 @@ These are not used in annotations. They are building blocks for declaring types. ``Point2D.__optional_keys__``. To allow using this feature with older versions of Python that do not support :pep:`526`, ``TypedDict`` supports two additional equivalent - syntactic forms:: + syntactic forms: + + * Using a literal :class:`dict` as the second argument:: - Point2D = TypedDict('Point2D', x=int, y=int, label=str) Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) + * Using keyword arguments:: + + Point2D = TypedDict('Point2D', x=int, y=int, label=str) + + .. deprecated-removed:: 3.11 3.13 + The keyword-argument syntax is deprecated in 3.11 and will be removed + in 3.13. It may also be unsupported by static type checkers. + By default, all keys must be present in a ``TypedDict``. It is possible to override this by specifying totality. Usage:: @@ -1402,6 +1411,9 @@ These are not used in annotations. They are building blocks for declaring types. x: int y: int + # Alternative syntax + Point2D = TypedDict('Point2D', {'x': int, 'y': int}, total=False) + This means that a ``Point2D`` ``TypedDict`` can have any of the keys omitted. A type checker is only expected to support a literal ``False`` or ``True`` as the value of the ``total`` argument. ``True`` is the default, diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 85f74064458f2d..b0754c9500edb9 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4300,7 +4300,8 @@ def test_basics_functional_syntax(self): self.assertEqual(Emp.__total__, True) def test_basics_keywords_syntax(self): - Emp = TypedDict('Emp', name=str, id=int) + with self.assertWarns(DeprecationWarning): + Emp = TypedDict('Emp', name=str, id=int) self.assertIsSubclass(Emp, dict) self.assertIsSubclass(Emp, typing.MutableMapping) self.assertNotIsSubclass(Emp, collections.abc.Sequence) @@ -4315,7 +4316,8 @@ def test_basics_keywords_syntax(self): self.assertEqual(Emp.__total__, True) def test_typeddict_special_keyword_names(self): - TD = TypedDict("TD", cls=type, self=object, typename=str, _typename=int, fields=list, _fields=dict) + with self.assertWarns(DeprecationWarning): + TD = TypedDict("TD", cls=type, self=object, typename=str, _typename=int, fields=list, _fields=dict) self.assertEqual(TD.__name__, 'TD') self.assertEqual(TD.__annotations__, {'cls': type, 'self': object, 'typename': str, '_typename': int, 'fields': list, '_fields': dict}) a = TD(cls=str, self=42, typename='foo', _typename=53, fields=[('bar', tuple)], _fields={'baz', set}) @@ -4371,7 +4373,7 @@ def test_py36_class_syntax_usage(self): def test_pickle(self): global EmpD # pickle wants to reference the class by name - EmpD = TypedDict('EmpD', name=str, id=int) + EmpD = TypedDict('EmpD', {'name': str, 'id': int}) jane = EmpD({'name': 'jane', 'id': 37}) for proto in range(pickle.HIGHEST_PROTOCOL + 1): z = pickle.dumps(jane, proto) @@ -4383,7 +4385,7 @@ def test_pickle(self): self.assertEqual(EmpDnew({'name': 'jane', 'id': 37}), jane) def test_optional(self): - EmpD = TypedDict('EmpD', name=str, id=int) + EmpD = TypedDict('EmpD', {'name': str, 'id': int}) self.assertEqual(typing.Optional[EmpD], typing.Union[None, EmpD]) self.assertNotEqual(typing.List[EmpD], typing.Tuple[EmpD]) diff --git a/Lib/typing.py b/Lib/typing.py index 0cf9755022e9b8..4189771e673a31 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2399,9 +2399,8 @@ class Point2D(TypedDict): The type info can be accessed via the Point2D.__annotations__ dict, and the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets. - TypedDict supports two additional equivalent forms:: + TypedDict supports an additional equivalent form:: - Point2D = TypedDict('Point2D', x=int, y=int, label=str) Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) By default, all keys must be present in a TypedDict. It is possible @@ -2417,14 +2416,22 @@ class point2D(TypedDict, total=False): the total argument. True is the default, and makes all items defined in the class body be required. - The class syntax is only supported in Python 3.6+, while two other - syntax forms work for Python 2.7 and 3.2+ + The class syntax is only supported in Python 3.6+, while the other + syntax form works for Python 2.7 and 3.2+ """ if fields is None: fields = kwargs elif kwargs: raise TypeError("TypedDict takes either a dict or keyword arguments," " but not both") + if kwargs: + warnings.warn( + "The kwargs-based syntax for TypedDict definitions is deprecated " + "in Python 3.11, will be removed in Python 3.13, and may not be " + "understood by third-party type checkers.", + DeprecationWarning, + stacklevel=2, + ) ns = {'__annotations__': dict(fields)} module = _caller() diff --git a/Misc/ACKS b/Misc/ACKS index 6df339c3e145bf..eaaaf7259dc192 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1973,6 +1973,7 @@ Arnon Yaari Alakshendra Yadav Hirokazu Yamamoto Masayuki Yamamoto +Jingchen Ye Ka-Ping Yee Chi Hsuan Yen Jason Yeo diff --git a/Misc/NEWS.d/next/Library/2022-02-08-16-42-20.bpo-46066.m32Hl0.rst b/Misc/NEWS.d/next/Library/2022-02-08-16-42-20.bpo-46066.m32Hl0.rst new file mode 100644 index 00000000000000..d13d9421e748b8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-02-08-16-42-20.bpo-46066.m32Hl0.rst @@ -0,0 +1,3 @@ +Deprecate kwargs-based syntax for :class:`typing.TypedDict` definitions. +It had confusing semantics when specifying totality, and was largely unused. +Patch by Jingchen Ye.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: