Repository navigation
Relaxing usage of type with typing.Protocol in typing spec #2306
Description
Some time ago I opened a thread on the Python discourse about implementing an AbstractType semantic that would allow functions to receive non-instantiable types so to try and conform a bit the situation presented in the the thread.
Consider the snippet:
from abc import abstractmethod
from typing import runtime_checkable, Protocol, TypeVar, TypeGuard
@runtime_checkable
class MyProtocol(Protocol):
@abstractmethod
def my_method(self) -> str:
...
class MyClass:
def my_method(self) -> str:
return "Hello from MyClass!"
class MyOtherClass:
def another_method(self) -> str:
return "Hello from MyOtherClass!"
P = TypeVar('P', bound=MyProtocol)
def check_protocol(obj: object, protocol: type[P]) -> TypeGuard[P]:
return isinstance(obj, protocol)
# Example usage
my_obj = MyClass()
my_other_obj = MyOtherClass()
if check_protocol(my_obj, MyProtocol):
print(my_obj.my_method())
if check_protocol(my_other_obj, MyProtocol):
print(my_other_obj.my_method())
else:
print("Object does not implement MyProtocol.")Output:
Hello from MyClass!
my_obj does not implement MyOtherClass protocol.
At runtime, this works as intended. At type checking time, mypy complains that check_protocol cannot accept a non-instantiable type, while pyright and ty are accepting this behavior.
According to the typing specification, mypy is technically correct (the best kind of correct), but this code is perfectly acceptable. There are other examples referenced in python/mypy#4717 (which is the 3rd most upvoted issue by 👍 by the way).
I was hoping for more discussion points presented in the thread but not many replied. I didn't want to monopolize it, so I'm trying my luck here.
@JelleZijlstra (sorry for the ping) did point out that it might be worthwhile to change the spec to allow this behavior but I'm guessing there should be some discussion on how to deal with it first. Jelle suggested: "If you want it to be instantiable, use Callable[] instead."
I do agree it makes sense, but this snippet:
from typing import Protocol
class MyProtocol(Protocol):
def method(self) -> str:
...
print(callable(MyProtocol)) # prints Trueconfuses me.
So my point is: could the spec allow for type to accept non-instantiable types, since it seems to be a pretty common pattern? Is there a way to effectively deal with the separation between instantiable and non-instantiable types?
I tried adding the "typing spec" label but couldn't. At any rate, thank you for the consideration.
Activity
davidhalter commentedon Jun 18, 2026
I haven't looked into this in detail, but that's exactly the question somebody needs to answer before the spec could be changed. In your specific case the protocol is
@runtime_checkableand is therefore not just abstract. My feeling tells me that for this specific case we might be able to relax the spec, but not protocols/abstract types in general. Now thattype<some-runtime-checkable-protocol>seems fine, the question is wheter this generalizes beyond guards.I think a few more example might help.
jacopoabramo commentedon Jun 18, 2026
This is perfectly fine. Protocols that aren't marked as runtime checkable shouldn't be used for runtime checks anyway but only for static typing annotations and this is acceptable.
What kind of examples are you looking for exactly? I can do some digging in the github issue I mentioned and copy relevant examples from there if needed.
jacopoabramo commentedon Jul 13, 2026
I re-read the issue I linked in my initial post to try and find possible other use cases not involving guards, as @davidhalter pointed out.
I think this usage is wrong. I'm just putting it for completion.
mypy) this usage with attrs:I'm only speculating but I'm guessing that
Triggerin this context is aProtocolclass. Again, this falls into the category of type guarding I supposes.I think that this might be a wider problem than the one I initially proposed.
More or less people use
ABCand/orProtocolclasses as interface types, in order to perform certain actions that go beyond simply creating an object.As far as I can tell, type narrowing via
isinstanceis the primary use case, but given what I read it wouldn't surprise me if protocols might be used for other contexts as well.For me, simply allowing protocols marked as
runtime_checkablewould be an acceptable (initial) trade-off to solve most of the inconsistencies between different type checkers, but then again it still stands thatmypycould simply decide not to comply with this change in the specification. That's a decision for their mantainers anyway.On how to formalize this... I really don't know.