Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose type(Protocol) via types. #1478

Closed
randolf-scholz opened this issue Sep 26, 2023 · 1 comment
Closed

Expose type(Protocol) via types. #1478

randolf-scholz opened this issue Sep 26, 2023 · 1 comment
Labels
topic: feature Discussions about new features for Python's type annotations

Comments

@randolf-scholz
Copy link

randolf-scholz commented Sep 26, 2023

class SomeProtocol(Protocol):
     """Some Protocol."""
     @abstractmethod
     def some_method(self): ...

class MyMetaClass(type(Protocol)):
    """MetaClass-customization for MyProtocolABC."""

class MyProtocolABC(SomeProtocol, metaclass=MyMetaClass):
     """An ABC for implementing SomeProtocol."""

Library A might expose SomeProtocol, but internally realize classes that implement SomeProtocol via some abstract base class.
Since Protocols also provide the @abstractmethod checking that abc.ABC does, and might provide default implementations for mixin methods, it makes sense for MyProtocolABC to subclass SomeProtocol.

If further class customization is required, a custom metaclass can be used. However, to avoid metaclass-conflicts, MyMetaClass must subclass type(Protocol). Type-Checkers do not like this, since it's considered a dynamic type. On the other hand, one doesn't want to import the private member typing._ProtocolMeta.

Therefore, it would be useful to expose ProtocolType=type(Protocol) in the types module.

@randolf-scholz randolf-scholz added the topic: feature Discussions about new features for Python's type annotations label Sep 26, 2023
@AlexWaygood
Copy link
Member

AlexWaygood commented Sep 26, 2023

If we were to do this, I think we'd probably just make _ProtocolMeta public in typing.py (rename it as ProtocolMeta, and possibly add a _ProtocolMeta = ProtocolMeta alias for backwards compatibility, since there are people who access it in the wild even thought it's private).

However, I'm afraid I would strongly oppose making _ProtocolMeta public. Whether it's exposed in typing.py or types.py doesn't really matter here. As soon as it is exposed publicly, it will become extremely difficult to refactor the metaclass: as with any other class that is publicly exposed and documented, even very small behaviour changes to the class could have the potential to break somebody's working code. Keeping the class private gives us far more flexibility to change the internal implementation details of Protocol without being bound by backwards-compatibility constraints.

As an example: through a series of PRs, I managed to achieve a 20x speedup for isinstance() checks against runtime-checkable protocols in Python 3.12. I don't think this would have been possible if _ProtocolMeta had been public: the Python 3.12 implementation of _ProtocolMeta looks very different to the Python 3.11 implementation. Backwards compatibility requirements would have made such a thorough refactoring impossible.

For your use case, I would consider doing something like this instead:

import abc
from typing import TYPE_CHECKING, Protocol

if TYPE_CHECKING:
    _ProtocolMeta = abc.ABCMeta
else:
    _ProtocolMeta = type(Protocol)

Please be aware, though, that you're using a private class at your own risk, and there are no backwards compatibility guarantees for you if you're doing something like this :)

@AlexWaygood AlexWaygood closed this as not planned Won't fix, can't repro, duplicate, stale Oct 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: feature Discussions about new features for Python's type annotations
Projects
None yet
Development

No branches or pull requests

2 participants