From 7ba1cc8049fbcb94ac039ab02522f78177130588 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe <gobot1234yt@gmail.com> Date: Mon, 7 Feb 2022 20:47:48 +0000 Subject: bpo-46534: Implement PEP 673 Self in typing.py (GH-30924) Co-authored-by: Pradeep Kumar Srinivasan <gohanpra@gmail.com> Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com> --- Doc/library/typing.rst | 47 ++++++++++++++++++++++ Doc/whatsnew/3.11.rst | 2 + Lib/test/test_typing.py | 43 +++++++++++++++++++- Lib/typing.py | 24 ++++++++++- .../2022-01-26-18-06-08.bpo-46534.vhzUM4.rst | 2 + 5 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-01-26-18-06-08.bpo-46534.vhzUM4.rst diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 9007c0d..8c1c34e 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -68,6 +68,8 @@ annotations. These include: *Introducing* :data:`TypeAlias` * :pep:`647`: User-Defined Type Guards *Introducing* :data:`TypeGuard` +* :pep:`673`: Self type + *Introducing* :data:`Self` .. _type-aliases: @@ -585,6 +587,51 @@ These can be used as types in annotations and do not support ``[]``. .. versionadded:: 3.5.4 .. versionadded:: 3.6.2 +.. data:: Self + + Special type to represent the current enclosed class. + For example:: + + from typing import Self + + class Foo: + def returns_self(self) -> Self: + ... + return self + + + This annotation is semantically equivalent to the following, + albeit in a more succinct fashion:: + + from typing import TypeVar + + Self = TypeVar("Self", bound="Foo") + + class Foo: + def returns_self(self: Self) -> Self: + ... + return self + + In general if something currently follows the pattern of:: + + class Foo: + def return_self(self) -> "Foo": + ... + return self + + You should use use :data:`Self` as calls to ``SubclassOfFoo.returns_self`` would have + ``Foo`` as the return type and not ``SubclassOfFoo``. + + Other common use cases include: + + - :class:`classmethod`\s that are used as alternative constructors and return instances + of the ``cls`` parameter. + - Annotating an :meth:`object.__enter__` method which returns self. + + For more information, see :pep:`673`. + + .. versionadded:: 3.11 + .. data:: TypeAlias Special annotation for explicitly declaring a :ref:`type alias <type-aliases>`. diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index c3c2c67..c1f267a 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -67,6 +67,8 @@ Summary -- Release highlights PEP-654: Exception Groups and ``except*``. (Contributed by Irit Katriel in :issue:`45292`.) +PEP-673: ``Self`` Type. +(Contributed by James Hilton-Balfe and Pradeep Kumar in :issue:`30924`.) New Features ============ diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 9eab461..a37bb43 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -27,6 +27,7 @@ from typing import NamedTuple, TypedDict from typing import IO, TextIO, BinaryIO from typing import Pattern, Match from typing import Annotated, ForwardRef +from typing import Self from typing import TypeAlias from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs from typing import TypeGuard @@ -157,8 +158,48 @@ class NoReturnTests(BaseTestCase): type(NoReturn)() -class TypeVarTests(BaseTestCase): +class SelfTests(BaseTestCase): + def test_basics(self): + class Foo: + def bar(self) -> Self: ... + + self.assertEqual(gth(Foo.bar), {'return': Self}) + + def test_repr(self): + self.assertEqual(repr(Self), 'typing.Self') + + def test_cannot_subscript(self): + with self.assertRaises(TypeError): + Self[int] + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(Self)): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + Self() + with self.assertRaises(TypeError): + type(Self)() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, Self) + with self.assertRaises(TypeError): + issubclass(int, Self) + def test_alias(self): + # TypeAliases are not actually part of the spec + alias_1 = Tuple[Self, Self] + alias_2 = List[Self] + alias_3 = ClassVar[Self] + self.assertEqual(get_args(alias_1), (Self, Self)) + self.assertEqual(get_args(alias_2), (Self,)) + self.assertEqual(get_args(alias_3), (Self,)) + + +class TypeVarTests(BaseTestCase): def test_basic_plain(self): T = TypeVar('T') # T equals itself. diff --git a/Lib/typing.py b/Lib/typing.py index f733efe..d1d5130 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -132,6 +132,7 @@ __all__ = [ 'ParamSpecKwargs', 'reveal_type', 'runtime_checkable', + 'Self', 'Text', 'TYPE_CHECKING', 'TypeAlias', @@ -174,7 +175,7 @@ def _type_check(arg, msg, is_argument=True, module=None, *, allow_special_forms= if (isinstance(arg, _GenericAlias) and arg.__origin__ in invalid_generic_forms): raise TypeError(f"{arg} is not valid as type argument") - if arg in (Any, NoReturn, ClassVar, Final, TypeAlias): + if arg in (Any, NoReturn, Self, ClassVar, Final, TypeAlias): return arg if isinstance(arg, _SpecialForm) or arg in (Generic, Protocol): raise TypeError(f"Plain {arg} is not valid as type argument") @@ -445,6 +446,27 @@ def NoReturn(self, parameters): """ raise TypeError(f"{self} is not subscriptable") + +@_SpecialForm +def Self(self, parameters): + """Used to spell the type of "self" in classes. + + Example:: + + from typing import Self + + class Foo: + def returns_self(self) -> Self: + ... + return self + + This is especially useful for: + - classmethods that are used as alternative constructors + - annotating an `__enter__` method which returns self + """ + raise TypeError(f"{self} is not subscriptable") + + @_SpecialForm def ClassVar(self, parameters): """Special type construct to mark class variables. diff --git a/Misc/NEWS.d/next/Library/2022-01-26-18-06-08.bpo-46534.vhzUM4.rst b/Misc/NEWS.d/next/Library/2022-01-26-18-06-08.bpo-46534.vhzUM4.rst new file mode 100644 index 0000000..35a70aa --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-26-18-06-08.bpo-46534.vhzUM4.rst @@ -0,0 +1,2 @@ +Implement :pep:`673` :class:`typing.Self`. +Patch by James Hilton-Balfe. -- cgit v0.12