Currently, instantiating explicit subclasses of protocols with "abstract" attributes is prohibited:
class P(Protocol):
x: int # Note, no r.h.s.
class C(P):
pass
class D(P):
def __init__(self) -> None:
self.x = 42
C() # E: Cannot instantiate abstract class 'C' with abstract attribute 'x'
D() # OK
This is feature appeared with little discussion, in particular should this be specific only to protocols or also to "normal" ABCs?
Honestly I think it should be allowed in both cases.
On Jan 3, 2018 10:41, "Ivan Levkivskyi" notifications@github.com wrote:
Currently, instantiating explicit subclasses of protocols with "abstract"
attributes is prohibited:class P(Protocol):
x: int # Note, no r.h.s.
class C(P):
passclass D(P):
def __init__(self) -> None:
self.x = 42C() # E: Cannot instantiate abstract class 'C' with abstract attribute 'x'
D() # OKThis is feature appeared with little discussion, in particular should this
be specific only to protocols or also to "normal" ABCs?—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/python/mypy/issues/4426, or mute the thread
https://github.com/notifications/unsubscribe-auth/ACwrMjC08CvwNnrUkQflbGJ_B-RVdgVoks5tG8ncgaJpZM4RSGqh
.
Honestly I think it should be allowed in both cases.
Note that allowing this will lead to runtime unsafety (although there is similar unsafety with nominal classes). IIRC additional argument that Jukka mentioned is that sometimes it can be a desirable feature (like @abstractmethod) so that subclasses will be forced to "implement" the attribute (currently this also can be done by @abstractproperty but it looks more verbose).
IIUC there are many cases where mypy can't verify whether a variable or attribute actually has a value, so it only checks that you use the type correctly. That's totally acceptable to me. (The only case where I think mypy should be held to higher standards is for local variables, where flow analysis is usually pretty straightforward.)
IIUC there are many cases where mypy can't verify whether a variable or attribute actually has a value, so it only checks that you use the type correctly. That's totally acceptable to me.
Yes, there are many cases where we can't detect detect this. But there are some ideas how to "tighten" this, see https://github.com/python/mypy/issues/4019
@JukkaL What do you think about the check for abstractness of variables in protocol subclasses?
A primary use case for explicitly subclassing a protocol is to get mypy to verify that everything in the protocol is actually implemented by the class. If mypy doesn't require attributes to be explicitly defined in the subclass, this use case is significantly less useful.
Also, if an attribute is declared in a class body but it gets never assigned a value in the body, mypy should perhaps complain about this:
class A:
success: bool # Maybe complain that 'success' is never initialized in "A"
def __init__(self) -> None:
self.succes = True # Typo!
I'd actually go even further into this direction -- I think that mypy should complain also about missing implementations of methods that don't have non-trivial bodies. For example, mypy allows this but I'd rather have it rejected:
from typing_extensions import Protocol
class P(Protocol):
def f(self) -> int: ...
class C(P):
pass
C() # No error, even though there is a missing implementation for 'f'
There are other potential ways to approach this, such as requiring @abstractmethod for f in P.
(Similarly, mypy should probably complain about empty method bodies more generally in strict optional mode, but that's a separate issue.)
A primary use case for explicitly subclassing a protocol is to get mypy to verify that everything in the protocol is actually implemented by the class. If mypy doesn't require attributes to be explicitly defined in the subclass, this use case is significantly less useful.
+1. This use case is already mentioned in PEP 544.
Similarly, mypy should probably complain about empty method bodies more generally in strict optional mode, but that's a separate issue.
Yes, I think we have a separate issue for this.
Yes, I think we have a separate issue for this.
Could you point me to this issue?
In the docs it currently says:
Explicitly including a protocol as a base class is also a way of documenting that your class implements a particular protocol, and it forces mypy to verify that your class implementation is actually compatible with the protocol.
However, I can't seem to get this functionality to work. For example, MyPy seems to accept subclasses of protocols that don't implement that protocol:
import typing
import typing_extensions
class TestProtocol(typing_extensions.Protocol):
def some_method(self) -> int:
...
class TestClass(TestProtocol):
pass
Is there any way I can tell MyPy to assert that a class follows a protocol?
For example, MyPy seems to accept subclasses of protocols that don't implement that protocol:
I don't see this in your example. The TestClass does have some_method() _inherited_ from TestProtocol, i.e. the implementation in the protocol acts as a default implementation. If you want an abstract method, then simply declare it as such.
Unfortunately, mypy allows literal ... and pass as valid bodies for any function signature, but this is a separate issue https://github.com/python/mypy/issues/2350
Ah I didn't realize that ... was interpreted as a method implementation. Thanks for the pointer to the other issue.
So you mean decorating the method in the protocol with abc.abstractmethod? I tried it with and without the metaclass and this still seems to type fine:
import abc
import typing
import typing_extensions
class TestProtocol(typing_extensions.Protocol, metaclass=abc.ABCMeta):
@abc.abstractmethod
def some_method(self) -> int:
...
class TestClass(TestProtocol):
pass
Instantiating TestClass will fail (both statically and at runtime). Also you don't need the metaclass there, Protocol is already an instance of ABCMeta.
Finally, for questions and help please use the Gitter chat, this question is off-topic for this issue.
Could mypy also detect errors in the implementation of protocols before even trying to instantiate the class? To protect myself from not implementing my own interfaces correctly I currently use the following decorator to check a class against a Protocol
T = TypeVar('T', bound=Type)
def check_class_implements(t: T) -> Callable[[T], T]:
def check(cls: T) -> T:
return cls
return check
Given a Protocol, for instance:
class GreeterProtocol(Protocol):
@abstractmethod
def greet(self, name: str) -> str: ...
it is used like this:
@check_class_implements(GreeterProtocol)
class HelloWorld:
def greet(self, name: str) -> str:
return f'{name}; Hello world'
@check_class_implements(GreeterProtocol)
class Foo:
def greet(self) -> str:
return 'FOOO!'
When a class is not compatible with the given Protocol, as is the case for Foo, mypy issues an error on check_class_implements:
Argument 1 has incompatible type "Type[Foo]"; expected "Type[GreeterProtocol]"mypy(error)
Ideally mypy would check if a class is compatible with the given Protocol when one simply inherits from it so that the above would be just the following with the same error as before:
class Foo(GreeterProtocol):
def greet(self) -> str: # mypy complains about the signature
return 'FOOO!'
More detailed error messages on what exactly went wrong (such as greet not having the right signature) would be nice to have but are imho not essential. Maybe the error message would be clearer already if it spelled something like Instances of class "Foo" do not implement "GreeterProtocol".
Note that in this case mypy already complains about the wrong signature of greet; this has nothing to do with Protocol however and if greet were missing no error would be shown:
class Bar(GreeterProtocol):
pass # typechecks, even though implementation is missing
Similar behavior would be useful with abstract classes that are final (so that there is no way to ever implement any of the abstract methods or propertys), for instance:
class Base(ABC):
@abstractproperty
def foo(self) -> str: ...
@final
class Derived(Base):
pass
Are there valid use cases for abstract classes that are final?
A primary use case for explicitly subclassing a protocol is to get mypy to verify that everything in the protocol is actually implemented by the class. If mypy doesn't require attributes to be explicitly defined in the subclass, this use case is significantly less useful.
100% agree. Specifying attributes in a protocol definition is a natural way to express that classes implementing the protocol should have the attribute. The alternative is using abstract (read-write) properties, which is definitely not as simple/elegant.
I'd also suggest to make this as strict as possible, allowing more relaxed checking only with explicit declarations, for example with a decorator, putting ABC/Protocol in the class hierarchy again or by redefining the abstract method/attributes:
class Base(Protocol):
foo: int
@abc.abstractmethod
def bar(self) -> int: ...
# extra decorator to show that this class is still abstract and not a implementation of Base
@mixin
class MixinA(Base):
def bar(self):
return 42
# declare class to be abstract by directly putting ABC or Protocol into the hierarchy:
class MixinB(Base, Protocol):
def bar(self):
return 23
# this declaration should imho fail, as it does not set the attr 'foo'
class Bar(MixinA):
pass
# OK: if you really set the attribute foo by an external mean, you can re-declare it in the class to state this explicitly
class Foo(MixinB):
foo: int
Most helpful comment
100% agree. Specifying attributes in a protocol definition is a natural way to express that classes implementing the protocol should have the attribute. The alternative is using abstract (read-write) properties, which is definitely not as simple/elegant.