GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-104555: Runtime-checkable protocols: Don't let previous calls to `isinstance()` influence whether `issubclass()` raises an exception by AlexWaygood · Pull Request #104559 · python/cpython

Conversation

@AlexWaygood

@AlexWaygood AlexWaygood commented May 16, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

ABCMeta.__instancecheck__ caches isinstance() calls against classes that have ABCMeta as their metaclass. It uses these cache entries not only to inform how future isinstance() calls behave, but also how issubclass() calls behave. That means that on main we now have some rather unfortunate behaviour when it comes to runtime-checkable protocols, due to the fact that typing._ProtocolMeta is a subclass of ABCMeta, and typing._ProtocolMeta.__instancecheck__ calls super().__instancecheck__() too soon (following 47753ec):

Python 3.12.0a7+ (heads/main:1163782868, May 16 2023, 18:09:28) [MSC v.1932 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from typing import *
>>> @runtime_checkable
... class Foo(Protocol):
...     x: int
...
>>> class Bar:
...     x = 42
...
>>> issubclass(Bar, Foo)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\alexw\coding\cpython\Lib\abc.py", line 123, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\cpython\Lib\typing.py", line 1875, in _proto_hook
    raise TypeError("Protocols with non-method members"
TypeError: Protocols with non-method members don't support issubclass()
>>> isinstance(Bar(), Foo)
True
>>> issubclass(Bar, Foo)
False

This PR fixes the incorrect behaviour. It means that these isinstance() checks will be about twice as slow as they are on main:

@runtime_checkable
class Foo:
    def meth(self): ...

@Foo.register
class Bar: ...

isinstance(Bar(), Foo)

But they'll still be much faster than they are on 3.11. Use of ABCMeta.register with protocols is pretty rare anyway, as far as I know, since ABCMeta.register isn't supported by type checkers. Other kinds of isinstance() checks do not suffer a significant performance regression.

Fixes #104555.

(Skipping news, as this bug doesn't exist on any released version of Python.)

  • Issue: Typing: runtime-checkable protocols are broken on main #104555

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@AlexWaygood
pythongh-104555: Sidestep the ABCMeta.__instancecheck__ cache in typi…
2601138
…ng._ProtocolMeta.__instancecheck__
@AlexWaygood AlexWaygood added skip news topic-typing labels May 16, 2023
@AlexWaygood
AlexWaygood requested a review from carljm May 16, 2023 18:14
@AlexWaygood
AlexWaygood requested review from Fidget-Spinner, JelleZijlstra and gvanrossum as code owners May 16, 2023 18:14
@bedevere-bot bedevere-bot added the awaiting core review label May 16, 2023
@bedevere-bot bedevere-bot mentioned this pull request May 16, 2023
Typing: runtime-checkable protocols are broken on main #104555
Closed
JelleZijlstra
JelleZijlstra reviewed May 16, 2023
View reviewed changes
Comment thread Lib/test/test_typing.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@AlexWaygood
Simplify test
f3a4551
JelleZijlstra
JelleZijlstra approved these changes May 16, 2023
View reviewed changes
@bedevere-bot bedevere-bot added awaiting merge and removed awaiting core review labels May 16, 2023
@AlexWaygood

AlexWaygood commented May 16, 2023

Copy link
Copy Markdown
Member Author

On main, the pyperformance bm_typing_runtime_protocols benchmark takes 177us +- 5us on my machine. With this PR, it takes 192us +- 5us. So, a slight slowdown. But that's okay if we avoid incorrect behaviour.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@AlexWaygood
AlexWaygood marked this pull request as draft May 16, 2023 18:39
@bedevere-bot bedevere-bot removed the awaiting merge label May 16, 2023
@AlexWaygood
Optimise
714badc
sunmy2019
sunmy2019 approved these changes May 16, 2023
View reviewed changes

@sunmy2019 sunmy2019 left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

But I still think __subclasshook__ depending on the caller is the weirdest part (of #104555 (comment)). Though it cannot be changed or is hard to change.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

AlexWaygood and Eclips4 reacted with thumbs up emoji
All reactions
  • 👍 2 reactions
carljm
carljm reviewed May 16, 2023
View reviewed changes
Comment thread Lib/test/test_typing.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/test/test_typing.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

AlexWaygood and others added 2 commits May 16, 2023 20:11
@AlexWaygood @carljm
who needs codespell when we have carl
04ed974
Co-authored-by: Carl Meyer <[email protected]>
@AlexWaygood
Merge branch 'fix-runtime-protocols' of https://github.com/AlexWaygoo…
9c5c75f
…d/cpython into fix-runtime-protocols
AlexWaygood
AlexWaygood commented May 16, 2023
View reviewed changes
Comment thread Lib/typing.py Outdated
Comment on lines 1807 to 1808
if cls.__callable_proto_members_only__ and issubclass(type(instance), cls):
return True

@AlexWaygood AlexWaygood May 16, 2023

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this check, I realised that this patch would also have slowed down isinstance(3, SupportsIndex) quite dramatically.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@AlexWaygood
AlexWaygood marked this pull request as ready for review May 16, 2023 19:19
@bedevere-bot bedevere-bot added the awaiting core review label May 16, 2023
AlexWaygood added 2 commits May 16, 2023 20:24
@AlexWaygood
be more careful
49373a9
@AlexWaygood
Add comment
e114fe9
@AlexWaygood
AlexWaygood requested a review from carljm May 16, 2023 19:30
@AlexWaygood AlexWaygood mentioned this pull request May 16, 2023
Incorrect bedevere transition (the sequel) python/bedevere#559
Closed
@sunmy2019

sunmy2019 commented May 17, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Oh, we will still hit the problematic super().__instancecheck__ sooner or later.

Consider this

from typing import runtime_checkable, Generic, Protocol, TypeVar

T = TypeVar("T")


@runtime_checkable
class Spam(Protocol[T]):
    x: T


class Eggs(Generic[T]):
    def __init__(self, x: T) -> None:
        self._x = x

    def __getattr__(self, attr: str) -> T:
        if attr == "x":
            return self._x
        raise AttributeError(attr)


print(isinstance(Eggs(42), Spam))
print(issubclass(Eggs, Spam))

Running on 3.10, 3.11 -> TypeError
Running on current main and this PR -> no TypeError

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@AlexWaygood
Add more failing tests (thanks sunmy2019!)
b5cc9cd
@sunmy2019

sunmy2019 commented May 17, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

I think one possible solution is that "do not let super().__subclasscheck()__ cache __subclasscheck__ result in this case".

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@AlexWaygood
Better fix
5f72b82
@AlexWaygood AlexWaygood removed the DO-NOT-MERGE label May 17, 2023
@AlexWaygood

AlexWaygood commented May 17, 2023

Copy link
Copy Markdown
Member Author

@sunmy2019, I think we were thinking the same thought simultaneously :)

How does it look to you after 5f72b82? (It's basically a completely different approach now.)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@AlexWaygood
AlexWaygood requested review from JelleZijlstra and sunmy2019 May 17, 2023 12:18
AlexWaygood
AlexWaygood commented May 17, 2023
View reviewed changes
Comment thread Lib/test/test_typing.py Outdated
Comment on lines +2675 to +2689
def test_no_weird_caching_with_issubclass_after_isinstance2(self):
@runtime_checkable
class Spam(Protocol):
x: int

class Eggs: ...

# gh-104555: ABCMeta might cache the result of this isinstance check
# if we called super().__instancecheck__ in the wrong place
# in _ProtocolMeta.__instancecheck__...
self.assertNotIsInstance(Eggs(), Spam)

# ...and if it did, then TypeError wouldn't be raised here!
with self.assertRaises(TypeError):
issubclass(Eggs, Spam)

@AlexWaygood AlexWaygood May 17, 2023

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test fails on 3.11. We could consider backporting a version of this PR to 3.11, but I'm not sure if that would be a good idea or not. It's a somewhat invasive bugfix.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@AlexWaygood AlexWaygood changed the title gh-104555: Sidestep the ABCMeta.__instancecheck__ cache in typing._ProtocolMeta.__instancecheck__ gh-104555: Runtime-checkable protocols: Don't let previous calls to isinstance() influence whether issubclass() raises an exception May 17, 2023
@AlexWaygood
Improve comments
bb56e13
@AlexWaygood

AlexWaygood commented May 17, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

This new approach also has the advantage that it seems like it might provide a speedup relative to main, for isinstance checks that don't involve ABCMeta.register()? (But might just be noise.)

sunmy2019 reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gvanrossum
gvanrossum removed their request for review May 17, 2023 15:57
@blurb-it
📜🤖 Added by blurb_it.
430cacd
@AlexWaygood AlexWaygood added type-bug An unexpected behavior, bug, or error 3.12 only security fixes and removed skip news labels May 17, 2023
@sunmy2019

sunmy2019 commented May 17, 2023

Copy link
Copy Markdown
Member

This new approach looks better. Great Job!

AlexWaygood reacted with heart emoji
All reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

carljm
carljm approved these changes May 17, 2023
View reviewed changes

@carljm carljm left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/test/test_typing.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/test/test_typing.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot bedevere-bot added awaiting merge and removed awaiting core review labels May 17, 2023
@AlexWaygood @carljm
Apply suggestions from code review
f9273fc
Co-authored-by: Carl Meyer <[email protected]>
@AlexWaygood
AlexWaygood enabled auto-merge (squash) May 17, 2023 23:23
@AlexWaygood

AlexWaygood commented May 17, 2023

Copy link
Copy Markdown
Member Author

Thanks @JelleZijlstra for helping debug this, @sunmy2019 for spotting the flaws in my original approach, and everybody for reviewing!

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@AlexWaygood
AlexWaygood merged commit b27fe67 into python:main May 17, 2023
@bedevere-bot bedevere-bot removed the awaiting merge label May 17, 2023
@bedevere-bot

bedevere-bot commented May 18, 2023

Copy link
Copy Markdown

⚠️⚠️⚠️ Buildbot failure ⚠️⚠️⚠️

Hi! The buildbot AMD64 Windows11 Bigmem 3.x has failed when building commit b27fe67.

What do you need to do:

  1. Don't panic.
  2. Check the buildbot page in the devguide if you don't know what the buildbots are or how they work.
  3. Go to the page of the buildbot that failed (https://buildbot.python.org/all/#builders/1079/builds/1325) and take a look at the build logs.
  4. Check if the failure is related to this commit (b27fe67) or if it is a false positive.
  5. If the failure is related to this commit, please, reflect that on the issue and make a new Pull Request with a fix.

You can take a look at the buildbot page here:

https://buildbot.python.org/all/#builders/1079/builds/1325

Failed tests:

  • test_hashlib

Failed subtests:

  • test_case_md5_uintmax - test.test_hashlib.HashLibTestCase.test_case_md5_uintmax
  • test_case_md5_huge - test.test_hashlib.HashLibTestCase.test_case_md5_huge

Summary of the results of the build (if available):

== Tests result: FAILURE then FAILURE ==

428 tests OK.

10 slowest tests:

  • test_bigmem: 43 min 23 sec
  • test_lzma: 34 min 43 sec
  • test_bz2: 21 min 5 sec
  • test_array: 7 min 22 sec
  • test_zlib: 4 min 58 sec
  • test_multiprocessing_spawn: 2 min 2 sec
  • test_math: 1 min 27 sec
  • test_hashlib: 1 min 22 sec
  • test_pickle: 1 min 14 sec
  • test_concurrent_futures: 1 min 13 sec

1 test failed:
test_hashlib

36 tests skipped:
test.test_asyncio.test_unix_events test_clinic test_curses
test_dbm_gnu test_dbm_ndbm test_devpoll test_epoll test_fcntl
test_fork1 test_gdb test_grp test_ioctl test_kqueue
test_multiprocessing_fork test_multiprocessing_forkserver test_nis
test_openpty test_ossaudiodev test_peg_generator
test_perf_profiler test_pipes test_poll test_posix test_pty
test_pwd test_readline test_resource test_spwd test_syslog
test_threadsignals test_wait3 test_wait4 test_xxlimited
test_xxtestfuzz test_zipfile64 test_zoneinfo

1 re-run test:
test_hashlib

Total duration: 47 min 52 sec

Click to see traceback logs
Traceback (most recent call last):
  File "R:\buildarea\3.x.ambv-bb-win11.bigmem\build\Lib\test\support\__init__.py", line 979, in wrapper
    return f(self, maxsize)
           ^^^^^^^^^^^^^^^^
  File "R:\buildarea\3.x.ambv-bb-win11.bigmem\build\Lib\test\test_hashlib.py", line 540, in test_case_md5_uintmax
    self.check('md5', b'A'*size, '28138d306ff1b8281f1a9067e1a1a2b3')
  File "R:\buildarea\3.x.ambv-bb-win11.bigmem\build\Lib\test\test_hashlib.py", line 394, in check
    self.check_file_digest(name, data, hexdigest)
  File "R:\buildarea\3.x.ambv-bb-win11.bigmem\build\Lib\test\test_hashlib.py", line 407, in check_file_digest
    f.write(data)
OSError: [Errno 28] No space left on device


Traceback (most recent call last):
  File "R:\buildarea\3.x.ambv-bb-win11.bigmem\build\Lib\test\support\__init__.py", line 979, in wrapper
    return f(self, maxsize)
           ^^^^^^^^^^^^^^^^
  File "R:\buildarea\3.x.ambv-bb-win11.bigmem\build\Lib\test\test_hashlib.py", line 535, in test_case_md5_huge
    self.check('md5', b'A'*size, 'c9af2dff37468ce5dfee8f2cfc0a9c6d')
  File "R:\buildarea\3.x.ambv-bb-win11.bigmem\build\Lib\test\test_hashlib.py", line 394, in check
    self.check_file_digest(name, data, hexdigest)
  File "R:\buildarea\3.x.ambv-bb-win11.bigmem\build\Lib\test\test_hashlib.py", line 407, in check_file_digest
    f.write(data)
OSError: [Errno 28] No space left on device
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

JelleZijlstra pushed a commit to JelleZijlstra/cpython that referenced this pull request May 18, 2023
@AlexWaygood @carljm @JelleZijlstra
pythongh-104555: Runtime-checkable protocols: Don't let previous call…
42b48ce
…s to `isinstance()` influence whether `issubclass()` raises an exception (python#104559)

Co-authored-by: Carl Meyer <[email protected]>
@AlexWaygood
AlexWaygood deleted the fix-runtime-protocols branch May 18, 2023 05:34
This was referenced May 19, 2023
Backport some recent Protocol fixes from 3.12 python/typing_extensions#161
Merged
[Bug] typing_extensions.Protocol unsupported under typing_extensions ≥ 4.6.0 beartype/beartype#241
Closed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@carljm carljm carljm approved these changes
@Fidget-Spinner Fidget-Spinner Awaiting requested review from Fidget-Spinner
@JelleZijlstra JelleZijlstra Awaiting requested review from JelleZijlstra JelleZijlstra is a code owner
@sunmy2019 sunmy2019 Awaiting requested review from sunmy2019

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

3.12 only security fixes topic-typing type-bug An unexpected behavior, bug, or error

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Typing: runtime-checkable protocols are broken on main

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@AlexWaygood @sunmy2019 @bedevere-bot @carljm @JelleZijlstra
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.