Skip to content

Conversation

@JelleZijlstra
Copy link
Member

@JelleZijlstraJelleZijlstra commented Apr 14, 2025

class DeferredProto(Protocol):
x: DoesNotExist
self.assertEqual(get_protocol_members(DeferredProto),{"x"})
self.assertEqual(
Copy link
Member

Choose a reason for hiding this comment

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

Question: when should we test all formats and when just one is enough?

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

I guess in this case Protocol is not doing anything very sophisticated with its annotations, so we can just run a basic test to make sure it works.

Copy link
Member

@AlexWaygoodAlexWaygood left a comment

Choose a reason for hiding this comment

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

I guess this might slow down protocol class creation a bit? Which might in turn slow down the import time of typing.py, since typing defines a lot of protocols... still, this feature seems worth it.

@JelleZijlstra
Copy link
MemberAuthor

I wonder if we could lazily create the protocol members list? But we can explore that separately.

@JelleZijlstraJelleZijlstra merged commit 666eeda into python:mainApr 15, 2025
45 checks passed
JelleZijlstra added a commit to JelleZijlstra/cpython that referenced this pull request Apr 16, 2025
pythongh-132494 made typing.py eagerly import annotationlib again because typing contains several protocols. Avoid this by determining annotations lazily. This should also make protocol creation faster: Unpatched: $ ./python.exe -m timeit -s 'from typing import Protocol, runtime_checkable' '''@runtime_checkable class MyProtocol(Protocol): def meth(self): pass ''' 50000 loops, best of 5: 9.28 usec per loop $ ./python.exe -m timeit -s 'from typing import Protocol, runtime_checkable' '''class MyProtocol(Protocol): def meth(self): pass ''' 50000 loops, best of 5: 9.05 usec per loop Patched: $ ./python.exe -m timeit -s 'from typing import Protocol, runtime_checkable' '''@runtime_checkable class MyProtocol(Protocol): def meth(self): pass ''' 50000 loops, best of 5: 7.69 usec per loop $ ./python.exe -m timeit -s 'from typing import Protocol, runtime_checkable' '''class MyProtocol(Protocol): def meth(self): pass ''' 50000 loops, best of 5: 7.78 usec per loop This was on a debug build though and I haven't compared it with versions where Protocol just accessed `.__annotations__` directly, and it's not a huge difference, so I don't think it's worth calling out the optimization too much. A downside of this change is that any errors that happen during the determination of attributes now happen only the first time isinstance() is called. This seems OK since these errors happen only in fairly exotic circumstances. Another downside is that any attributes added after class initialization now get picked up as protocol members. This came up in the typing test suite due to `@final`, but may cause issues elsewhere too.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@JelleZijlstra@sobolevn@AlexWaygood