Title: Troubles with @runtime_checkable protocols
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.11, Python 3.10, Python 3.9
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: Kevin Shweh, gvanrossum, kj, levkivskyi, miss-islington, uriyyo
Priority: normal Keywords: patch

Created on 2019-11-24 17:39 by levkivskyi, last changed 2021-05-27 13:51 by miss-islington. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 26067 merged uriyyo, 2021-05-12 13:54
PR 26073 merged miss-islington, 2021-05-12 15:48
PR 26075 merged kj, 2021-05-12 16:04
PR 26077 merged kj, 2021-05-12 17:21
PR 26096 merged kj, 2021-05-13 09:17
PR 26337 merged miss-islington, 2021-05-24 23:51
Messages (12)
msg357400 - (view) Author: Ivan Levkivskyi (levkivskyi) * (Python committer) Date: 2019-11-24 17:39
The PEP 544 specifies that:

A protocol can be used as a second argument in isinstance() and issubclass() only if it is explicitly opt-in by @runtime_checkable decorator.

It is not specified exactly whether this should be enforced by static type checkers or at runtime. Currently it is enforced in both cases: mypy flags this as error, and a TypeError is raised at runtime.

There is however a problem with current runtime implementation: abc and functools modules may call issubclass() on non-runtime checkable protocols if they appear as explicit superclasses. This is currently solved by a sys._getframe() hack in  typing module.

The problem is that current solution is incomplete. For example, the TypeError is not raised in the case of non-method protocols:

>>> class P(Protocol):
...     x: int
>>> class C: ...
>>> isinstance(C(), P)
False  # should be TypeError

I tried to fix it this morning but after an hour of attempts to tweak the existing hack I gave up. It looks like there are only two reasonable solutions:

* Don't enforce @typing.runtime_checkable at runtime, make it a type-checker-only feature (like
* Add a little helper to abc module that would skip classes in MRO for which C._is_protocol is True but C._is_runtime_protocol is False.

Any thoughts/preferences?
msg357555 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2019-11-27 04:43
If the "little helper in abc" solves it, let's do that. The sys._getframe() hack has always been a bit smelly -- I assume we can get rid of that then?
msg391292 - (view) Author: Kevin Shweh (Kevin Shweh) Date: 2021-04-17 17:24
It seems like the straightforward, minimal fix would be to just add

if (getattr(cls, '_is_protocol', False) and
    not getattr(cls, '_is_runtime_protocol', False) and
    not _allow_reckless_class_cheks()):
    raise TypeError(...)

to _ProtocolMeta.__instancecheck__. Does that fail on some edge case (that the current implementation works on)? It's a little weird that _ProtocolMeta.__instancecheck__ doesn't explicitly check that the protocol is runtime-checkable.
msg391318 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2021-04-18 00:46
Hi Kevin,

If you want to work on this, could you come up with some test cases that
try to explore edge cases for this behavior? Ideally some that already
pass, and some that currently don't pass (because of the limitation
mentioned by Ivan). It's possible that the tests for already
contain some suitable passing tests. You could then investigate whether
your proposal seems to work. If it does, you can submit a PR and we can lay
this issue to rest!

msg393511 - (view) Author: Yurii Karabas (uriyyo) * (Python triager) Date: 2021-05-12 13:56
Hi Guido,

I decided to help with this issue as far as Kevin did not respond.
msg393516 - (view) Author: Yurii Karabas (uriyyo) * (Python triager) Date: 2021-05-12 14:19
I checked and this bug is not present at 3.11 version. So I simply added test to cover case mentioned by Ivan.
msg393517 - (view) Author: Yurii Karabas (uriyyo) * (Python triager) Date: 2021-05-12 14:32
Please forget my previous msg, this bug still present at 3.11 version. PR contains fix proposed by Kevin.

Sorry for making to much noise.
msg393528 - (view) Author: miss-islington (miss-islington) Date: 2021-05-12 16:09
New changeset a2d94a0a9b8ae95d7d2b7fc34b501da5242ec22c by Miss Islington (bot) in branch '3.10':
bpo-38908: Fix issue when non runtime_protocol failed to raise TypeError (GH-26067)
msg393532 - (view) Author: miss-islington (miss-islington) Date: 2021-05-12 17:04
New changeset 88136bbd0500b688c05e914be031cd3c243e42d8 by Ken Jin in branch '3.9':
[3.9] bpo-38908: Fix issue when non runtime_protocol does not raise TypeError (GH-26067) (GH-26075)
msg393536 - (view) Author: Ken Jin (kj) * (Python committer) Date: 2021-05-12 17:29
Yurii and Kevin, thanks for pushing the patch forward! Thanks for the review too Guido.

I'm closing this issue as all bugfix PRs have landed to bugfix branches. This will make its way into the next versions of Python for those respective branches.

One point of contention is whether to introduce this in 3.9.6. This will cause code previously working in 3.9.5 to throw an error if people were using it incorrectly. So it might be better to only enforce this in 3.10 onwards.

I created GH-26077 just in case the decision is made to revert. Sorry for any inconvenience caused everyone!
msg393537 - (view) Author: miss-islington (miss-islington) Date: 2021-05-12 17:44
New changeset 9b90ce68503f4861ce4e9ac9444d9a82b3d943a5 by Ken Jin in branch '3.9':
[3.9] Revert "[3.9] bpo-38908: Fix issue when non runtime_protocol does not raise TypeError (GH-26067)" (GH-26077)
msg394540 - (view) Author: miss-islington (miss-islington) Date: 2021-05-27 13:51
New changeset 09696a3e218404e77f8c1fbf187ca29a4a357a9d by Miss Islington (bot) in branch '3.10':
[3.10] bpo-38908: [docs] Add changes to 3.10 whatsnew and fix some minor inaccuracies in news (GH-26096) (GH-26337)
Date User Action Args
2021-05-27 13:51:05miss-islingtonsetmessages: + msg394540
2021-05-24 23:51:16miss-islingtonsetpull_requests: + pull_request24929
2021-05-13 09:17:39kjsetpull_requests: + pull_request24736
2021-05-12 17:44:21miss-islingtonsetmessages: + msg393537
2021-05-12 17:29:07kjsetstatus: open -> closed
versions: + Python 3.10, Python 3.11, - Python 3.8
messages: + msg393536

resolution: fixed
stage: patch review -> resolved
2021-05-12 17:21:10kjsetpull_requests: + pull_request24717
2021-05-12 17:04:54miss-islingtonsetmessages: + msg393532
2021-05-12 16:09:12miss-islingtonsetmessages: + msg393528
2021-05-12 16:04:15kjsetnosy: + kj
pull_requests: + pull_request24715
2021-05-12 15:48:04miss-islingtonsetnosy: + miss-islington
pull_requests: + pull_request24713
2021-05-12 14:32:17uriyyosetmessages: + msg393517
2021-05-12 14:19:30uriyyosetmessages: + msg393516
2021-05-12 13:56:25uriyyosetmessages: + msg393511
2021-05-12 13:54:32uriyyosetkeywords: + patch
nosy: + uriyyo

pull_requests: + pull_request24708
stage: patch review
2021-04-18 00:46:45gvanrossumsetmessages: + msg391318
2021-04-17 17:24:23Kevin Shwehsetnosy: + Kevin Shweh
messages: + msg391292
2019-11-27 04:43:12gvanrossumsetmessages: + msg357555
2019-11-24 17:39:56levkivskyicreate