From d638989b57b515c51999d92d0cc4c274d278b1f2 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 17 Nov 2023 12:50:10 +0300 Subject: [PATCH 1/4] gh-112194: Convert more examples to doctests in `typing.py` --- Lib/typing.py | 83 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 5e3e1f298295c7..aaa495bf9c0aa5 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -212,8 +212,12 @@ def _should_unflatten_callable_args(typ, args): For example:: - assert collections.abc.Callable[[int, int], str].__args__ == (int, int, str) - assert collections.abc.Callable[ParamSpec, str].__args__ == (ParamSpec, str) + >>> import collections.abc + + >>> P = ParamSpec('P') + + >>> assert collections.abc.Callable[[int, int], str].__args__ == (int, int, str) + >>> assert collections.abc.Callable[P, str].__args__ == (P, str) As a result, if we need to reconstruct the Callable from its __args__, we need to unflatten it. @@ -255,7 +259,11 @@ def _collect_parameters(args): For example:: - assert _collect_parameters((T, Callable[P, T])) == (T, P) + >>> P = ParamSpec('P') + >>> T = TypeVar('T') + + >>> assert _collect_parameters((T, Callable[P, T])) == (T, P) + """ parameters = [] for t in args: @@ -671,19 +679,19 @@ def Union(self, parameters): type(None). - Unions of unions are flattened, e.g.:: - assert Union[Union[int, str], float] == Union[int, str, float] + >>> assert Union[Union[int, str], float] == Union[int, str, float] - Unions of a single argument vanish, e.g.:: - assert Union[int] == int # The constructor actually returns int + >>> assert Union[int] == int # The constructor actually returns int - Redundant arguments are skipped, e.g.:: - assert Union[int, str, int] == Union[int, str] + >>> assert Union[int, str, int] == Union[int, str] - When comparing unions, the argument order is ignored, e.g.:: - assert Union[int, str] == Union[str, int] + >>> assert Union[int, str] == Union[str, int] - You cannot subclass or instantiate a union. - You can use Optional[X] as a shorthand for Union[X, None]. @@ -2244,14 +2252,17 @@ def get_origin(tp): Examples:: - assert get_origin(Literal[42]) is Literal - assert get_origin(int) is None - assert get_origin(ClassVar[int]) is ClassVar - assert get_origin(Generic) is Generic - assert get_origin(Generic[T]) is Generic - assert get_origin(Union[T, int]) is Union - assert get_origin(List[Tuple[T, T]][int]) is list - assert get_origin(P.args) is P + >>> P = ParamSpec('P') + + >>> assert get_origin(Literal[42]) is Literal + >>> assert get_origin(int) is None + >>> assert get_origin(ClassVar[int]) is ClassVar + >>> assert get_origin(Generic) is Generic + >>> assert get_origin(Generic[T]) is Generic + >>> assert get_origin(Union[T, int]) is Union + >>> assert get_origin(List[Tuple[T, T]][int]) is list + >>> assert get_origin(P.args) is P + """ if isinstance(tp, _AnnotatedAlias): return Annotated @@ -2272,11 +2283,14 @@ def get_args(tp): Examples:: - assert get_args(Dict[str, int]) == (str, int) - assert get_args(int) == () - assert get_args(Union[int, Union[T, int], str][int]) == (int, str) - assert get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) - assert get_args(Callable[[], T][int]) == ([], int) + >>> T = TypeVar('T') + + >>> assert get_args(Dict[str, int]) == (str, int) + >>> assert get_args(int) == () + >>> assert get_args(Union[int, Union[T, int], str][int]) == (int, str) + >>> assert get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) + >>> assert get_args(Callable[[], T][int]) == ([], int) + """ if isinstance(tp, _AnnotatedAlias): return (tp.__origin__,) + tp.__metadata__ @@ -2295,12 +2309,17 @@ def is_typeddict(tp): For example:: - class Film(TypedDict): - title: str - year: int + >>> from typing import TypedDict + + >>> class Film(TypedDict): + ... title: str + ... year: int + + >>> is_typeddict(Film) + True + >>> is_typeddict(dict) + False - is_typeddict(Film) # => True - is_typeddict(Union[list, str]) # => False """ return isinstance(tp, _TypedDictMeta) @@ -2898,15 +2917,15 @@ def TypedDict(typename, fields=_sentinel, /, *, total=True): Usage:: - class Point2D(TypedDict): - x: int - y: int - label: str + >>> class Point2D(TypedDict): + ... x: int + ... y: int + ... label: str - a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK - b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check + >>> a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK + >>> b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check - assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') + >>> assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') The type info can be accessed via the Point2D.__annotations__ dict, and the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets. From faac9a38034390a07b28cefa309ca9991ee6dc66 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Fri, 17 Nov 2023 18:23:30 +0300 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Alex Waygood --- Lib/typing.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index aaa495bf9c0aa5..b8e04d6cdb9be8 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -213,11 +213,11 @@ def _should_unflatten_callable_args(typ, args): For example:: >>> import collections.abc - >>> P = ParamSpec('P') - - >>> assert collections.abc.Callable[[int, int], str].__args__ == (int, int, str) - >>> assert collections.abc.Callable[P, str].__args__ == (P, str) + >>> collections.abc.Callable[[int, int], str].__args__ == (int, int, str) + True + >>> collections.abc.Callable[P, str].__args__ == (P, str) + True As a result, if we need to reconstruct the Callable from its __args__, we need to unflatten it. @@ -261,9 +261,8 @@ def _collect_parameters(args): >>> P = ParamSpec('P') >>> T = TypeVar('T') - - >>> assert _collect_parameters((T, Callable[P, T])) == (T, P) - + >>> _collect_parameters((T, Callable[P, T])) + (~T, ~P) """ parameters = [] for t in args: @@ -2253,7 +2252,6 @@ def get_origin(tp): Examples:: >>> P = ParamSpec('P') - >>> assert get_origin(Literal[42]) is Literal >>> assert get_origin(int) is None >>> assert get_origin(ClassVar[int]) is ClassVar @@ -2262,7 +2260,6 @@ def get_origin(tp): >>> assert get_origin(Union[T, int]) is Union >>> assert get_origin(List[Tuple[T, T]][int]) is list >>> assert get_origin(P.args) is P - """ if isinstance(tp, _AnnotatedAlias): return Annotated @@ -2284,13 +2281,11 @@ def get_args(tp): Examples:: >>> T = TypeVar('T') - >>> assert get_args(Dict[str, int]) == (str, int) >>> assert get_args(int) == () >>> assert get_args(Union[int, Union[T, int], str][int]) == (int, str) >>> assert get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) >>> assert get_args(Callable[[], T][int]) == ([], int) - """ if isinstance(tp, _AnnotatedAlias): return (tp.__origin__,) + tp.__metadata__ @@ -2310,16 +2305,14 @@ def is_typeddict(tp): For example:: >>> from typing import TypedDict - >>> class Film(TypedDict): ... title: str ... year: int - + ... >>> is_typeddict(Film) True >>> is_typeddict(dict) False - """ return isinstance(tp, _TypedDictMeta) @@ -2921,10 +2914,9 @@ def TypedDict(typename, fields=_sentinel, /, *, total=True): ... x: int ... y: int ... label: str - + ... >>> a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK >>> b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check - >>> assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') The type info can be accessed via the Point2D.__annotations__ dict, and From 7b835b754e8a892a0bbb387cd71462574f6d3420 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 17 Nov 2023 15:27:53 +0000 Subject: [PATCH 3/4] one more nit --- Lib/typing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index b8e04d6cdb9be8..3ba7ff9ec61915 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2917,7 +2917,8 @@ def TypedDict(typename, fields=_sentinel, /, *, total=True): ... >>> a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK >>> b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check - >>> assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') + >>> Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') + True The type info can be accessed via the Point2D.__annotations__ dict, and the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets. From 5e1d5d35d8701641d4ca8495d9b287a55fc89227 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 17 Nov 2023 20:41:32 +0300 Subject: [PATCH 4/4] Address review --- Lib/typing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 3ba7ff9ec61915..a96c7083eb785e 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -678,19 +678,19 @@ def Union(self, parameters): type(None). - Unions of unions are flattened, e.g.:: - >>> assert Union[Union[int, str], float] == Union[int, str, float] + assert Union[Union[int, str], float] == Union[int, str, float] - Unions of a single argument vanish, e.g.:: - >>> assert Union[int] == int # The constructor actually returns int + assert Union[int] == int # The constructor actually returns int - Redundant arguments are skipped, e.g.:: - >>> assert Union[int, str, int] == Union[int, str] + assert Union[int, str, int] == Union[int, str] - When comparing unions, the argument order is ignored, e.g.:: - >>> assert Union[int, str] == Union[str, int] + assert Union[int, str] == Union[str, int] - You cannot subclass or instantiate a union. - You can use Optional[X] as a shorthand for Union[X, None].