Title: `typing.Protocol` implementation means that `isinstance([], collections.abc.Mapping)` can sometimes evaluate to `True` · Issue #105280 · python/cpython · GitHub
Open Graph Title: `typing.Protocol` implementation means that `isinstance([], collections.abc.Mapping)` can sometimes evaluate to `True` · Issue #105280 · python/cpython
X Title: `typing.Protocol` implementation means that `isinstance([], collections.abc.Mapping)` can sometimes evaluate to `True` · Issue #105280 · python/cpython
Description: (In very specific circumstances) Bug report Due to some changes that have been made to the implementation of typing.Protocol in Python 3.12, whether or not isinstance([], collections.abc.Mapping) evaluates to True or False now depends on...
Open Graph Description: (In very specific circumstances) Bug report Due to some changes that have been made to the implementation of typing.Protocol in Python 3.12, whether or not isinstance([], collections.abc.Mapping) e...
X Description: (In very specific circumstances) Bug report Due to some changes that have been made to the implementation of typing.Protocol in Python 3.12, whether or not isinstance([], collections.abc.Mapping) e...
Opengraph URL: https://github.com/python/cpython/issues/105280
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"`typing.Protocol` implementation means that `isinstance([], collections.abc.Mapping)` can sometimes evaluate to `True`","articleBody":"(In very specific circumstances)\r\n\r\n# Bug report\r\n\r\nDue to some changes that have been made to the implementation of `typing.Protocol` in Python 3.12, whether or not `isinstance([], collections.abc.Mapping)` evaluates to `True` or `False` now depends on precisely when and if garbage collection happens.\r\n\r\nMinimal repro:\r\n\r\n```pycon\r\n\u003e\u003e\u003e import gc, collections.abc, typing\r\n\u003e\u003e\u003e gc.disable()\r\n\u003e\u003e\u003e try:\r\n... class Foo(collections.abc.Mapping, typing.Protocol): pass\r\n... except TypeError:\r\n... pass\r\n...\r\n\u003e\u003e\u003e isinstance([], collections.abc.Mapping)\r\nTrue\r\n```\r\n\r\nWhy does this happen?! It's because `Foo` is an illegal class, so `TypeError` is raised during class initialisation here:\r\n\r\nhttps://github.com/python/cpython/blob/ce558e69d4087dd3653207de78345fbb8a2c7835/Lib/typing.py#L1905-L1912\r\n\r\n`TypeError` being raised here means that `Foo` never has `__protocol_attrs__` set on it. _But_, the error is raised _after_ class construction, meaning that if garbage collection happens at the wrong time, a reference to `Foo` is still retained in `collections.abc.Mapping.__subclasses__()`. This then creates problems in the `isinstance()` check, because of some behaviour in the `abc` module where it iterates through all of the subclasses of `collections.abc.Mapping` in order to determine whether an object is an instance of `collections.abc.Mapping`.\r\n\r\n```pycon\r\n\u003e\u003e\u003e import gc, collections.abc, typing\r\n\u003e\u003e\u003e gc.disable()\r\n\u003e\u003e\u003e try:\r\n... class Foo(collections.abc.Mapping, typing.Protocol): pass\r\n... except TypeError:\r\n... pass\r\n...\r\n\u003e\u003e\u003e x = collections.abc.Mapping.__subclasses__()\r\n\u003e\u003e\u003e x\r\n[\u003cclass 'collections.abc.MutableMapping'\u003e, \u003cclass '__main__.Foo'\u003e]\r\n\u003e\u003e\u003e x[-1].__protocol_attrs__\r\nset()\r\n```\r\n\r\nThe fix is to raise `TypeError` before the class has actually been created, rather than during class initialisation.\r\n\r\n# Why does this matter?\r\n\r\nThis might seem like an absurdly specific bug report, but it would be good to see it fixed. It was causing bizarre test failures in the `typing_extensions` backport. The issue was that we did this in one test method:\r\n\r\n```py\r\n def test_protocols_bad_subscripts(self):\r\n T = TypeVar('T')\r\n S = TypeVar('S')\r\n with self.assertRaises(TypeError):\r\n class P(typing.Mapping[T, S], Protocol[T]): pass\r\n```\r\n\r\n...And that final `P` class _wasn't_ being cleaned up by the garbage collector immediately after that test having been run. That then caused an unrelated test elsewhere to fail due to the fact that `isinstance([], collections.abc.Mapping)` was evaluating to `True`, and it was _very_ hard to figure out why.\r\n\r\n\u003c!-- gh-linked-prs --\u003e\r\n### Linked PRs\r\n* gh-105281\r\n* gh-105318\n\u003c!-- /gh-linked-prs --\u003e\r\n","author":{"url":"https://github.com/AlexWaygood","@type":"Person","name":"AlexWaygood"},"datePublished":"2023-06-04T11:06:15.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":1},"url":"https://github.com/105280/cpython/issues/105280"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:c8e8ec17-1d41-4420-9281-a5e327912214 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 9B36:37B15B:3A7455:5305C3:696A4E22 |
| html-safe-nonce | 662e41b469dc24f8e9e71cbe0aa8ac3c1467441136815de270217995d6bb8644 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5QjM2OjM3QjE1QjozQTc0NTU6NTMwNUMzOjY5NkE0RTIyIiwidmlzaXRvcl9pZCI6IjMzNzg0NzAyNTY2MDkyMTgwODIiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | c345d25a152f83c535b59c5eaecfe641a288d8705631cfca4564c6d184cd9dfc |
| hovercard-subject-tag | issue:1740308836 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/python/cpython/105280/issue_layout |
| twitter:image | https://opengraph.githubassets.com/714c91bf722a823c98ceec8546e6339b1cb1a2ace416ed49672be2769adb8c5e/python/cpython/issues/105280 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/714c91bf722a823c98ceec8546e6339b1cb1a2ace416ed49672be2769adb8c5e/python/cpython/issues/105280 |
| og:image:alt | (In very specific circumstances) Bug report Due to some changes that have been made to the implementation of typing.Protocol in Python 3.12, whether or not isinstance([], collections.abc.Mapping) e... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | AlexWaygood |
| hostname | github.com |
| expected-hostname | github.com |
| None | 3f871c8e07f0ae1886fa8dac284166d28b09ad5bada6476fc10b674e489788ef |
| turbo-cache-control | no-preview |
| go-import | github.com/python/cpython git https://github.com/python/cpython.git |
| octolytics-dimension-user_id | 1525981 |
| octolytics-dimension-user_login | python |
| octolytics-dimension-repository_id | 81598961 |
| octolytics-dimension-repository_nwo | python/cpython |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 81598961 |
| octolytics-dimension-repository_network_root_nwo | python/cpython |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 63c426b30d262aba269ef14c40e3c817b384cd61 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width