-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Convert selected ABCs to Protocols #1220
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
Conversation
stdlib/2/typing.pyi
Outdated
@@ -16,6 +16,7 @@ Type = object() | |||
_promote = object() | |||
no_type_check = object() | |||
ClassVar = object() | |||
Protocol = object() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know we have a lot of similar definitions already, but I'd prefer to keep the stub closer to reality, maybe with just class Protocol: pass
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This form is actually necessary (in some sense) since mypy will prohibit any other operations with Protocol
in runtime context, like:
Protocol() # instantiating is a runtime error
Protocol[int] # type application is a runtime error
def make_one(cls: type) -> Any: ...
make_one(Protocol) # passing to a function is not a good idea
def fun(x: Protocol) -> int: # Using it in type context is not a good idea either
...
This could be fixed by additional special-casing, but a simpler way is just make it an object()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's special either way: if I do Protocol = object()
elsewhere, mypy probably won't let me do class SupportsFloat(Protocol): pass
. Using a class is at least a little closer to what Protocol actually does at runtime, even if Protocol obviously isn't a normal class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but if I write class Protocol: ...
then mypy will allow most things I mentioned above. This is why object()
is used also for Generic
and friends. Otherwise prohibiting all possible invalid scenarios will require a lot of effort.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see, I thought it would just ignore the implementation in the stub. Ignore this then.
This looks good to merge once the mypy PR is in. I fixed a merge conflict that happened because I added |
Yes, this is a runtime protocol (since |
A few notes:
|
OK, I have updated |
OK, now all tests pass except for one. We are now in an interesting situation, since pythoneval tests are run with full stubs, this means that I cannot fix this in mypy before this one is merged and vice-versa. This means that either mypy of typeshed would need to merge a failing PR or this will involve a long loop of PRs with temporarily disabling this test in mypy then merging this PR and then re-enabling this one test again. @JelleZijlstra how such situations are typically treated? I think this can be considered unblocked and ready for review, since python/mypy#3132 is now merged. Also, we could think now what are other classes in stdlib that may be made protocols (maybe |
stdlib/2/typing.pyi
Outdated
@@ -62,46 +63,58 @@ _KT_co = TypeVar('_KT_co', covariant=True) # Key type covariant containers. | |||
_VT_co = TypeVar('_VT_co', covariant=True) # Value type covariant containers. | |||
_T_contra = TypeVar('_T_contra', contravariant=True) # Ditto contravariant. | |||
|
|||
class SupportsInt(metaclass=ABCMeta): | |||
def runtime(cls: _T) -> _T: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the argument be a TypeVar bound to Type[object]
or even Type[Protocol]
, so that type checkers will give an error when this decorator is applied to something that's not a class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type[Protocol]
will not work, since it does not appear explicitly in MRO, but Type[object]
looks like a good idea.
stdlib/3/typing.pyi
Outdated
def __aenter__(self) -> Awaitable[_T_co]: ... | ||
def __aexit__(self, exc_type: Optional[Type[BaseException]], | ||
exc_value: Optional[BaseException], | ||
traceback: Optional[TracebackType]) -> Awaitable[Optional[bool]]: ... | ||
|
||
class Mapping(_Collection[_KT], Generic[_KT, _VT_co]): | ||
@runtime # type: ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need the # type: ignore
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it looks like is is not necessary anymore. The point is that Mapping
is not really covariant in value (see signature of get
) and mypy is deliberately very strict about such situations with protocols, however it looks like treatment of unions has changed since this was added. I will remove it just in case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weird, somehow this works in Python 3 but fails in Python 2, will investigate later, have to run now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, found it, the return types for values()
etc. are list[_VT_co]
, so on Python 2 it is indeed clearly invariant in value and we need #type: ignore
.
I guess it makes the most sense to have the same person merge the mypy and typeshed changes at approximately the same time. I believe Jukka has push privileges to typeshed, so it might make the most sense for him to do it. I could also take care of doing the actual merges if Jukka or another mypy core team member approves the corresponding mypy PR. As for uses of protocols, my recent experience has been that protocols would be mostly useful in IO-related argument types, where now we often have to write |
@JelleZijlstra @JukkaL OK, here is the corresponding mypy PR with an updated test output python/mypy#3846 |
@JelleZijlstra @JukkaL The logistic issue here is that this PR and python/mypy#3846 mutually depend on each other. As proposed above the simplest way would be to just merge them simultaneously. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See also my suggestion at python/mypy#3846 (comment) on how to avoid the mutual dependency between the two PRs.
Finally, can you rebase? There are no merge conflicts but it's really old, which surprised me when testing this manually. (OTOH the manual test on internal codebases found no new issues, so green light from that POV.)
class SupportsInt(metaclass=ABCMeta): | ||
def runtime(cls: _TC) -> _TC: ... | ||
|
||
@runtime |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is a stub, why does it need @runtime
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise mypy will complain about e.g. issubclass(it, collections.abc.Iterable)
something like isinstance() can be used only with @runtime protocols
.
|
||
class SupportsInt(metaclass=ABCMeta): | ||
def runtime(cls: _TC) -> _TC: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC @runtime
is not defined (yet) in typing.py but (only) in typing_extensions.py?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and the same for Protocol
(there is only old internal _Protocol
in typing
). We however need them in typeshed to avoid circular dependencies with typing_extensions
. We discussed this with @JukkaL few moths ago, the plan (which I like) is this:
- Iterate on PR Make python 2 subprocess consistent with python 3 #3132 until it's approved.
- Publish
Protocol
intyping_extensions
. Also include it in the stub fortyping_extensions
.- Merge PR Make python 2 subprocess consistent with python 3 #3132 but don't merge typeshed changes yet. This lets mypy users (at least those who use GitHub master) experiment with user-defined protocols.
- Get an okay from PyCharm and Pytype that they are fine with merging the typeshed PR. They might need to implement some support for the
Protocol
special form first, but if they don't use thetyping
typeshed stub this may be a non-issue.- If major issues are found in the mypy implementation of protocols, fix them.
- Release a new mypy version with protocols as an experimental feature!
- Merge the typeshed PR and use it in mypy.
- Seek acceptance of the protocols PEP once we have some practical experience and protocols seem to work fine.
- Officially support using protocols in typeshed once Pytype and PyCharm are okay with it.
- Release Protocol in Python stdlib typing module.
This PR is step number 7
b3bcce4
to
4cfddfd
Compare
@gvanrossum OK, I rebased (but also squashed, since there were previous merge conflicts I don't want to fix them all again). |
4cfddfd
to
f6d7dea
Compare
OK, all tests pass now, this is ready to be merged. |
…ols) (#3846) This PR contains the tests output after python/typeshed#1220 is merged and synced (note that pythoneval tests are run with full typeshed stubs).
A basic set of updates to
typing
that converts selected ABCs to protocols, see PEP 544.This accompanies python/mypy#3132 and python/typing#417
The tests will fail, so that this PR can be merged after the
mypy
one.Then, if everything goes OK, we could convert more types in typeshed to protocols.
Attn: @JukkaL @ambv @gvanrossum