-
-
Notifications
You must be signed in to change notification settings - Fork 30.3k
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
typing.Protocol silently overrides __init__ method of delivered class #88970
Comments
typing.Protocol silently overrides __init__ method of delivered class. I think it should be forbidden to define __init__ method at Protocol delivered class in case if cls is not Protocol. |
I'm not sure whether we should downright reject There seems to be a gap in the PEP-544 definition of protocols, i.e. whether Consider this example: https://gist.github.com/ambv/0ed9c9ec5b8469878f47074bdcd37b0c Mypy doesn't consider >>> class C:
... def __init__(self) -> None:
... print('init!')
...
>>> c = C()
init!
>>> c.__init__()
init!
>>> c.__init__()
init! In the linked gist example, Mypy currently ignores So... would that be useful? Do we ever want to define protocols on the basis of how their initializers look like? My uninformed intuition is that this might be potentially useful, however I fail to come up with a realistic example here. |
I wonder if part of the problem here isn't that protocols are primarily focused on instances, where the __init__ method is explicitly *not* considered part of the type. IIRC supporting protocols for classes was an afterthought. That said you have made a good case for seeing it as part of the protocol when the Type[] operator is applied to the protocol. So yes, I think we should be careful ruling that out too soon. Do we have any evidence that users are confused and define __init__ methods on protocols that are being ignored by their type checkers? The OP didn't say. When in doubt, let the status quo win. |
Yurri, is the issue you reported your original finding? If you haven't found this issue in the wild, we might want to let this be as-is until we specify exactly whether |
Łukasz this issue is my original finding. I have found this case when I was working on another issue that was related to I agree that we can leave it as it is for now because I didn't find any information from python community about this case. |
Marking issue as "pending" until we figure out how PEP-544 should be amended. |
While this is figured out, would it be possible to remove the silent overriding? This seems like something type checkers should be doing, not silent runtime modification of classes. Pyright already (correctly) checks this, so I think it would just need to be added to MyPy. >>> class C(Protocol):
... def __init__(self) -> None:
... print('init!')
...
>>> c = C() # Pyright error, MyPy says it's okay I came across this while trying to create a class that requires concrete subclasses to define a specific field, and it seems like Protocol is the only thing that can pull this off because of how type checkers special case it: https://gist.github.com/adriangb/6c1a001ee7035bad5bd56df70e0cf5e6 I don't particularly like this use of Protocol, but it works (aside from the overriding issue). I don't know if this usage of implementing But I do know that making type checkers enforce this instead runtime would allow this use case to work while still prohibiting the (in my opinion invalid) use case of calling |
If the problem is in mypy, it should be filed in the mypy tracker, not here. |
Apologies if that was noise, I filed an issue on the MyPy issue tracker: python/mypy#12261 |
Regardless of mypy's behavior (which isn't impacted by what typing.py does), there's a legitimate complaint here about the runtime behavior: any >>> from typing import Protocol
>>> class X(Protocol):
... def __init__(self, x, y): pass
...
>>> X.__init__
<function _no_init_or_replace_init at 0x10de4e5c0> Fixing that won't be easy though, unless we give up on making it impossible to instantiate a Protocol. |
Oops. So this is an intentional feature -- Protocol replaces __init__ so that you can't (accidentally) instantiate a protocol. And the code to do this has changed a couple of times recently to deal with some edge cases. At least one of the PRs was by Yurii, who created this issue. I didn't read through all that when I closed the issue, so I'm reopening it. Maybe Yurii can devise a solution? (Although apparently their first attempt, #27543 was closed without merging.) Yurii and Lukasz should probably figure this out. |
Agreed. What if we allow protocols that implement |
Guido, it looks like you replied while I was typing my reply out. Yurii can correct me here but I believe PR bpo-27543 was an attempt to disallow defining I'm not sure which approach is right. |
It doesn't make logical sense to instantiate any Protocol, whether it has __init__ or not, because a Protocol is inherently an abstract class. But we can just leave enforcement of that rule to static type checkers, so Adrian's proposed change makes sense to me. |
Would it make sense to enforce the no-instantiation rule in __new__ instead of __init__? |
I am not sure if that solves anything (other than the fact that __new__ is much less common to implement than __init__), but I may just be slow to pick up the implications of moving the check to __new__ |
With the current implementation, it is impossible to derive a Mixin class from a Protocol, whether or not any Protocol actually defines an Using Mixin classes based on Protocols should be a common use case to add optional functionality to classes. For instance, most classes supporting copy, could be adapted as context managers using ContextVar. Some classes could make Iterable support optional using a Mixin class, just to save the auxiliary Iterator class. Of course, implementations need not explicitly derive from Protocols they implement. It is just a convenience offered by PEP 544 to inherit a default implementation of some methods. The workaround is simply not to explicitly derive from the Protocol class, and to implement all the methods in each implementation class. A module, which publishes a Protocol, may also publish a concrete generic implementation to avoid semantic protocol bugs and reduce code duplication.
If Protocols have an Protocol should probably save the original It probably makes sense to have |
I just merged #31628 addressing this issue. |
The changed implementation relies on inheritance of `__init__`, making this no longer a clean "Protocol", but a base class. While this is apparently supported in Python 3.11, Python versions earlier than 3.11 would override the inherited `__init__` method, thereby breaking this implementation. See python/cpython#88970 for details
The changed implementation relies on inheritance of `__init__`, making this no longer a clean "Protocol", but a base class. While this is apparently supported in Python 3.11, Python versions earlier than 3.11 would override the inherited `__init__` method, thereby breaking this implementation. See python/cpython#88970 for details
The changed implementation relies on inheritance of `__init__`, making this no longer a clean "Protocol", but a base class. While this is apparently supported in Python 3.11, Python versions earlier than 3.11 would override the inherited `__init__` method, thereby breaking this implementation. See python/cpython#88970 for details
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: