Skip to content

Runtime-checkable protocols are broken on py312 (the sequel) #104935

Closed
@AlexWaygood

Description

@AlexWaygood

Bug report

On 3.8-3.11, all subclasses of typing.Generic were guaranteed to have an _is_protocol class attribute, which is used as an internal marker:

_is_protocol = False

Two places in typing.py rely on all subclasses of Generic having this marker:

cpython/Lib/typing.py

Lines 1895 to 1897 in dbc8216

if (isinstance(annotations, collections.abc.Mapping) and
attr in annotations and
issubclass(other, Generic) and other._is_protocol):

cpython/Lib/typing.py

Lines 2062 to 2064 in dbc8216

if not issubclass(cls, Generic) or not cls._is_protocol:
raise TypeError('@runtime_checkable can be only applied to protocol classes,'
' got %r' % cls)

However, on Python 3.12 (due to the implementation of PEP-695), subclasses of Generic no longer have this marker:

>>> class Foo[T]: ...
...
>>> Foo._is_protocol
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Foo' has no attribute '_is_protocol'

This leads to AttributeError being raised in two situations where it shouldn't be:

Python 3.13.0a0 (heads/main:1080c4386d, May 25 2023, 13:11:38) [MSC v.1932 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from typing import Protocol, runtime_checkable
>>> @runtime_checkable
... class Foo[T]: ...
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\alexw\coding\cpython\Lib\typing.py", line 2062, in runtime_checkable
    if not issubclass(cls, Generic) or not cls._is_protocol:
                                           ^^^^^^^^^^^^^^^^
AttributeError: type object 'Foo' has no attribute '_is_protocol'
>>> @runtime_checkable
... class HasX(Protocol):
...     x: int
...
>>> class Bar[T]:
...     x: T
...     def __init__(self, x):
...         self.x = x
...
>>> isinstance(Bar(42), HasX)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\alexw\coding\cpython\Lib\typing.py", line 1810, in __instancecheck__
    if super().__instancecheck__(instance):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\cpython\Lib\abc.py", line 119, in __instancecheck__
    return _abc_instancecheck(cls, instance)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\cpython\Lib\typing.py", line 1794, in __subclasscheck__
    return super().__subclasscheck__(other)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  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 1897, in _proto_hook
    issubclass(other, Generic) and other._is_protocol):
                                   ^^^^^^^^^^^^^^^^^^
AttributeError: type object 'Bar' has no attribute '_is_protocol'

Cc. @JelleZijlstra for PEP-695

Linked PRs

Metadata

Metadata

Assignees

Labels

3.12only security fixes3.13bugs and security fixestopic-typingtype-bugAn unexpected behavior, bug, or error

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions