From 4558ee127db791c2e7b1710998f9ccb84e5cbd42 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 7 Apr 2023 15:18:28 +0100 Subject: [PATCH 1/8] gh-74690: Document changes made to runtime-checkable protocols in Python 3.12 --- Doc/library/typing.rst | 43 +++++++++++++++---- Doc/whatsnew/3.12.rst | 33 ++++++++++++++ ...3-04-07-15-09-26.gh-issue-74690.0f886b.rst | 3 ++ ...3-04-07-15-15-40.gh-issue-74690.un84hh.rst | 8 ++++ 4 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-04-07-15-09-26.gh-issue-74690.0f886b.rst create mode 100644 Misc/NEWS.d/next/Library/2023-04-07-15-15-40.gh-issue-74690.un84hh.rst diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 8728ca7b6b358c..870b9e2aaa5398 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -1598,15 +1598,6 @@ These are not used in annotations. They are building blocks for creating generic import threading assert isinstance(threading.Thread(name='Bob'), Named) - .. versionchanged:: 3.12 - The internal implementation of :func:`isinstance` checks against - runtime-checkable protocols now uses :func:`inspect.getattr_static` - to look up attributes (previously, :func:`hasattr` was used). - As a result, some objects which used to be considered instances - of a runtime-checkable protocol may no longer be considered instances - of that protocol on Python 3.12+, and vice versa. - Most users are unlikely to be affected by this change. - .. note:: :func:`!runtime_checkable` will check only the presence of the required @@ -1628,6 +1619,40 @@ These are not used in annotations. They are building blocks for creating generic .. versionadded:: 3.8 + .. versionchanged:: 3.12 + The internal implementation of :func:`isinstance` checks against + runtime-checkable protocols now uses :func:`inspect.getattr_static` + to look up attributes (previously, :func:`hasattr` was used). + As a result, some objects which used to be considered instances + of a runtime-checkable protocol may no longer be considered instances + of that protocol on Python 3.12+, and vice versa. + Most users are unlikely to be affected by this change. + + .. versionchanged:: 3.12 + The members of a runtime-checkable protocol are now considered "frozen" + at runtime as soon as the class has been created. Monkey-patching + attributes onto a runtime-checkable protocol will still work, but will + have no impact on :func:`isinstance` checks comparing objects to the + protocol. For example:: + + >>> from typing import Protocol, runtime_checkable + >>> @runtime_checkable + ... class HasX(Protocol): + ... x = 1 + ... + >>> class Foo: ... + ... + >>> f = Foo() + >>> isinstance(f, HasX) + False + >>> f.x = 1 + >>> isinstance(f, HasX) + True + >>> HasX.y = 2 + >>> isinstance(f, HasX) # unchanged, even though HasX now also has a `y` attribute + True + + Other special directives """""""""""""""""""""""" diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 23524ec5d7d452..7e91d67e35ab14 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -437,6 +437,39 @@ typing vice versa. Most users are unlikely to be affected by this change. (Contributed by Alex Waygood in :gh:`102433`.) +* The members of a runtime-checkable protocol are now considered "frozen" at + runtime as soon as the class has been created. Monkey-patching attributes + onto a runtime-checkable protocol will still work, but will have no impact on + :func:`isinstance` checks comparing objects to the protocol. For example:: + + >>> from typing import Protocol, runtime_checkable + >>> @runtime_checkable + ... class HasX(Protocol): + ... x = 1 + ... + >>> class Foo: ... + ... + >>> f = Foo() + >>> isinstance(f, HasX) + False + >>> f.x = 1 + >>> isinstance(f, HasX) + True + >>> HasX.y = 2 + >>> isinstance(f, HasX) # unchanged, even though HasX now also has a `y` attribute + True + + This change was made in order to speed up ``isinstance()` checks against + runtime-checkable protocols. + +* The performance profile of :func:`isinstance` checks against + :func:`runtime-checkable protocols ` has changed + significantly. Most ``isinstance()`` checks against protocols with only a few + members should be at least 2x faster than in 3.11, and some may be 20x + faster or more. However, ``isinstance()`` checks against protocols with seven + or more members may be slower than in Python 3.11. (Contributed by Alex + Waygood in :gh:`74690` and :gh:`103193`.) + sys --- diff --git a/Misc/NEWS.d/next/Library/2023-04-07-15-09-26.gh-issue-74690.0f886b.rst b/Misc/NEWS.d/next/Library/2023-04-07-15-09-26.gh-issue-74690.0f886b.rst new file mode 100644 index 00000000000000..0eba6e427bd7f5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-07-15-09-26.gh-issue-74690.0f886b.rst @@ -0,0 +1,3 @@ +The members of a runtime-checkable protocol are now considered "frozen" at +runtime as soon as the class has been created. See the documentation for +:func:`typing.runtime_checkable` for more details. Patch by Alex Waygood. diff --git a/Misc/NEWS.d/next/Library/2023-04-07-15-15-40.gh-issue-74690.un84hh.rst b/Misc/NEWS.d/next/Library/2023-04-07-15-15-40.gh-issue-74690.un84hh.rst new file mode 100644 index 00000000000000..48f11aac692ddb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-07-15-15-40.gh-issue-74690.un84hh.rst @@ -0,0 +1,8 @@ +The performance of :func:`isinstance` checks against +:func:`runtime-checkable protocols ` has been +considerably improved for protocols that only have a few members. To achieve +this improvement, several internal implementation details of the +:mod:`typing` module have been refactored, including +``typing._ProtocolMeta.__instancecheck__``, +``typing._is_callable_members_only``, and ``typing._get_protocol_attrs``. +Patches by Alex Waygood. From 46fd03b9ec1a59a9a8c08b4daa98d694929bb3ab Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 7 Apr 2023 15:24:09 +0100 Subject: [PATCH 2/8] Fix markup --- Doc/whatsnew/3.12.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 7e91d67e35ab14..a2ae4c64a6be9d 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -459,7 +459,7 @@ typing >>> isinstance(f, HasX) # unchanged, even though HasX now also has a `y` attribute True - This change was made in order to speed up ``isinstance()` checks against + This change was made in order to speed up ``isinstance()`` checks against runtime-checkable protocols. * The performance profile of :func:`isinstance` checks against From 8cc89d7ff794e8d8316acd89ccf087cf31bd5c1c Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 7 Apr 2023 15:29:54 +0100 Subject: [PATCH 3/8] Fix markup --- Doc/library/typing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 870b9e2aaa5398..307479742478ef 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -1633,7 +1633,7 @@ These are not used in annotations. They are building blocks for creating generic at runtime as soon as the class has been created. Monkey-patching attributes onto a runtime-checkable protocol will still work, but will have no impact on :func:`isinstance` checks comparing objects to the - protocol. For example:: + protocol. For example: >>> from typing import Protocol, runtime_checkable >>> @runtime_checkable From 216b87510a1e32983168e9e325bc879e964a6a4e Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 7 Apr 2023 15:32:25 +0100 Subject: [PATCH 4/8] Make sphinx-lint happy --- Doc/library/typing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 307479742478ef..96e9e765bf2e90 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -1649,7 +1649,7 @@ These are not used in annotations. They are building blocks for creating generic >>> isinstance(f, HasX) True >>> HasX.y = 2 - >>> isinstance(f, HasX) # unchanged, even though HasX now also has a `y` attribute + >>> isinstance(f, HasX) # unchanged, even though HasX now also has a "y" attribute True From 713fbf8c192d0730c6a706a2fc031643bc22aa07 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 7 Apr 2023 15:33:11 +0100 Subject: [PATCH 5/8] Sync whatsnew --- Doc/whatsnew/3.12.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index a2ae4c64a6be9d..88acc0f9bb81d4 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -456,7 +456,7 @@ typing >>> isinstance(f, HasX) True >>> HasX.y = 2 - >>> isinstance(f, HasX) # unchanged, even though HasX now also has a `y` attribute + >>> isinstance(f, HasX) # unchanged, even though HasX now also has a "y" attribute True This change was made in order to speed up ``isinstance()`` checks against From 7e4410cf83d916bf711b01205d2d4e3aa476f47f Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 7 Apr 2023 20:06:27 +0100 Subject: [PATCH 6/8] Address review: make more concise --- Doc/library/typing.rst | 20 ++----------------- Doc/whatsnew/3.12.rst | 2 ++ ...3-04-07-15-09-26.gh-issue-74690.0f886b.rst | 4 ++-- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 96e9e765bf2e90..03ff259bbdf75f 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -1633,24 +1633,8 @@ These are not used in annotations. They are building blocks for creating generic at runtime as soon as the class has been created. Monkey-patching attributes onto a runtime-checkable protocol will still work, but will have no impact on :func:`isinstance` checks comparing objects to the - protocol. For example: - - >>> from typing import Protocol, runtime_checkable - >>> @runtime_checkable - ... class HasX(Protocol): - ... x = 1 - ... - >>> class Foo: ... - ... - >>> f = Foo() - >>> isinstance(f, HasX) - False - >>> f.x = 1 - >>> isinstance(f, HasX) - True - >>> HasX.y = 2 - >>> isinstance(f, HasX) # unchanged, even though HasX now also has a "y" attribute - True + protocol. See :ref:`"What's new in Python 3.12" ` + for more details. Other special directives diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 88acc0f9bb81d4..67a3c4eb0e9525 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -418,6 +418,8 @@ tempfile The :class:`tempfile.NamedTemporaryFile` function has a new optional parameter *delete_on_close* (Contributed by Evgeny Zorin in :gh:`58451`.) +.. _whatsnew-typing-py312 + typing ------ diff --git a/Misc/NEWS.d/next/Library/2023-04-07-15-09-26.gh-issue-74690.0f886b.rst b/Misc/NEWS.d/next/Library/2023-04-07-15-09-26.gh-issue-74690.0f886b.rst index 0eba6e427bd7f5..1888f43e3d6f85 100644 --- a/Misc/NEWS.d/next/Library/2023-04-07-15-09-26.gh-issue-74690.0f886b.rst +++ b/Misc/NEWS.d/next/Library/2023-04-07-15-09-26.gh-issue-74690.0f886b.rst @@ -1,3 +1,3 @@ The members of a runtime-checkable protocol are now considered "frozen" at -runtime as soon as the class has been created. See the documentation for -:func:`typing.runtime_checkable` for more details. Patch by Alex Waygood. +runtime as soon as the class has been created. See +:ref:`"What's new in Python 3.12" `for more details. From 22de85463b32de043d4b710c8605e845d9a25fc3 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 7 Apr 2023 20:09:40 +0100 Subject: [PATCH 7/8] Fix markup --- Doc/whatsnew/3.12.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 67a3c4eb0e9525..1df4748d68497b 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -418,7 +418,7 @@ tempfile The :class:`tempfile.NamedTemporaryFile` function has a new optional parameter *delete_on_close* (Contributed by Evgeny Zorin in :gh:`58451`.) -.. _whatsnew-typing-py312 +.. _whatsnew-typing-py312: typing ------ From 18e1f74f41fc37d143f699882f9e771fffd0b4b6 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 7 Apr 2023 12:20:23 -0700 Subject: [PATCH 8/8] Update Misc/NEWS.d/next/Library/2023-04-07-15-09-26.gh-issue-74690.0f886b.rst --- .../next/Library/2023-04-07-15-09-26.gh-issue-74690.0f886b.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2023-04-07-15-09-26.gh-issue-74690.0f886b.rst b/Misc/NEWS.d/next/Library/2023-04-07-15-09-26.gh-issue-74690.0f886b.rst index 1888f43e3d6f85..0a103ae11970d4 100644 --- a/Misc/NEWS.d/next/Library/2023-04-07-15-09-26.gh-issue-74690.0f886b.rst +++ b/Misc/NEWS.d/next/Library/2023-04-07-15-09-26.gh-issue-74690.0f886b.rst @@ -1,3 +1,3 @@ The members of a runtime-checkable protocol are now considered "frozen" at runtime as soon as the class has been created. See -:ref:`"What's new in Python 3.12" `for more details. +:ref:`"What's new in Python 3.12" ` for more details.