-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Protocols should not care if a type is a class or an instance #4536
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
Comments
I just hit this as well. It looks like this and #5018 are the same issue, but about modules rather than classes. I think the same thing probably should be true of instances as well. If I do, for example:
I feel like that ought to work. Zope Interface has this notion of "provides" as distinct from "implements" to draw a clean separation between the different ways a type can relate to an Interface; it seems like Protocol should behave in the same way. It has a |
I agree with the OP's initial example (and with #5018), but I think Glyph's example goes a bit too far -- it dynamically modifies an object. While in the toy example it is easy to see that nothing has a jump attribute, in realistic applications I doubt that in most cases mypy would be able to make the connection. IIUC the provides/implements distinction is between a class whose instances can jump() vs. an instance that can jump()? (Though I'm not all that familiar with Zope Interface, and the two words don't have clearly distinct meanings to me -- I could easily have it backwards.) |
The same for me. |
Note to self: we need to add an OP example (simplified) to the PEP 544 (there is already a PR for modules). |
Yes. To restate: An object A type The distinction is important because if
|
Regarding my setting-attributes-on-an-instance example, I agree that as stated it's perhaps pushing mypy a bit far. Practically I do not expect it to be able to do that automatically without help. What I want is maybe something like |
OK, so "provides" is a property of objects while "implements" is a property of types -- with the understanding that a class is both an object and a type. And the distinction is an important once especially when you're doing runtime checking and all you can check is the presence of specific attributes, rather than the full signatures of methods. (I've got to admit that even after writing that down the choice of terms still feels a bit arbitrary. Especially "implements" could go either way in my intuition.) In general the checks that mypy performs are about "provides" -- e.g. we have a function foo: class P(Protocol): ...
def foo(arg: P):
... and we're calling it with an expression: foo(expr) then mypy checks whether But when Finally when I suppose in the wonderful world of Zope Interfaces there are other cases to consider, since providing an interface could be done dynamically. But mypy is no help here, and I think the examples of dynamically providing an interface that I've seen all deserve the term "hack". :-) |
This PR contains mostly minor wording tweaks plus a paragraph explicitly allowing class objects as implementations of protocols, previously there were questions whether it is actually allowed, see python/mypy#4536.
A small note about this, mypy should accept not only Also I raise priority to high, since this is a required part of PEP 544 in its accepted version. |
Seeing how the issue is still open, and how the described behaviour is still observed, I have to ask: are there any known workarounds for this outside of completely disabling typechecks for the relevant code parts? |
Bumping this as I just ran into it as well, any workarounds? |
Fixes #4536 This use case is specified by PEP 544 but was not implemented. Only instances (not class objects) were allowed as protocol implementations. The PR is quite straightforward, essentially I just pass a `class_obj` flag everywhere to know whether we need or not to bind self in a method.
Cool! What about modules? Can they implement protocols? |
Sure, tomorrow. |
#5018 :-) |
@matejcik (or anyone else on this issue) apologies for the somewhat random ping, but I'd be interested in knowing if you have any open source code that makes use of this feature |
@hauntsaninja sure, two off the top of my head two: this protocol is semantically a protocol in that it is just a namespace providing some functions. |
Awesome, thank you! I'll add https://github.com/trezor/trezor-firmware to https://github.com/hauntsaninja/mypy_primer once the next release is out |
This is causing a new issue with dataclass protocols. In my case, I need to verify that a ConfigurationClass is a dataclass, or has a dataclass_fields attribute. This changes gets us a lot closer, but It looks like there is a problem when both the Class and Instances provide the same interface from dataclasses import dataclass, Field
from typing import Union, Protocol, Dict, runtime_checkable
ConfigValue = Union['ConfigProtocol', str]
class FieldProtocol(Protocol):
type: ConfigValue
name: str
@runtime_checkable
class ConfigProtocol(Protocol):
__dataclass_fields__: Dict[str, Field[ConfigValue]]
__module__: str
class Configuration:
def __init__(self, config_class: ConfigProtocol):
self._config_class = config_class
@dataclass
class ConfigClass:
key: str = 'v_1'
@dataclass
class GlobalConfig:
TestModule: ConfigClass
config_file: str = 'GlobalConfig.yaml'
conf = Configuration(GlobalConfig) Results in a new mypy error:
In this particular case, the values are identical for both the Class and Instance objects: In []: GlobalConfig.__dataclass_fields__ == gc.__dataclass_fields__
Out[]: True
In []: GlobalConfig.__module__ == gc.__module__
Out[]: True |
@lundybernard thanks for testing! I think we just need to mark We'd need a similar change in typeshed for |
Thanks! I'll test out #13721 when it gets merged, or I have some extra time. module is technically a requirement, as our code needs to read it, but I suppose its safe to assume it will always exist on any object capable of satisfying the Protocol. |
As a developer of a protocol, I don't care whether that protocol is met by an instance or a class, as long as the necessary functions and variables are present and correct. Currently it appears that only instances can satisfy a protocol.
For example, the code below attempts to meet a protocol using an instance and a class. It executes in python fine, but mypy complains that
Type[Jumper1]
is not compatible with theJumps
protocol:The text was updated successfully, but these errors were encountered: