-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Protocol class type representation #10988
Comments
I'm having a hard time understanding what you're trying to do. Can you provide more details about what the Based on your code sample, I think you might misunderstand three key type checking concepts: protocols, A protocol class is intended to be a "template" that describes the interface or "shape" of a type. Generally, protocols are type checking constructs, not runtime constructs. You can't instantiate a protocol class, for example. It looks like you're trying to use protocol classes at runtime, which is generally not what they were designed for. When you specify a class name within a type annotation (whether it's a protocol class or a non-protocol class), a type checker assumes that you are referring to an instance of that class (or a subtype thereof). If you want to refer to the instantiable class itself, you need to use When a function is called and there are type variables within that function's signature, the type checker attempts to "solve" those type variables using the types of the provided arguments. In your example, the def __init__(self, proto: T impl: Type[T]) -> None: ... You're calling this I hope that helps. If you provide more details about what you're trying to do, I might be able to provide more suggestions. |
Thank you very much for that detailed response. I'll try and explain what I try to do in more detail (and also, given your emphasis on that the Protocol class is not intended for runtime use, I'm abusing that here, maybe need to reconsider.. well, here goes) Say I have a registry of classes. When I register a class, I do so by providing an interface as key to what the class supports when registering it. Many different classes can be registered for each interface key. At runtime, the registry is queried using such interface, giving a list of classes back that implements said interface. This is my attempt to use The registration is static and hardcoded, thus I'd like to be able to ensure that the registered classes in fact support the interface they are registered under, and thought this would be suitable for a type checker. However, the interface is re-used at runtime, to look up registered classes. From my draft PR pantsbuild/pants#12577 this example show case the client side of this scenario: from typing import Protocol, runtime_checkable
from pants.engine.unions import UnionRule
@runtime_checkable
class Vehicle(Protocol):
"""Union base for vehicle types."""
def model(self) -> str: ...
class Truck(Vehicle, Protocol):
"""Another union base, derived from the first one."""
wheel_count(self) -> int: ...
class Volvo:
"""Union member."""
def model(self) -> str: return "XC90"
class Peterbilt:
"""Another union member, for trucks."""
def model(self) -> str: return "359"
def wheel_count(self) -> int: return 6
def rules() ->
return [
UnionRule(Vehicle, Volvo),
UnionRule(Truck, Peterbilt),
] The So, what I'm trying to get at is, to achieve the same static type checkability for Does that make sense? |
Note that if I change my |
I don't think this approach will work. Here is an alternative solution to consider. Perhaps not as elegant as what you were hoping to achieve here. It involves defining a separate def register_class(interface: type, cls: type):
# If all protocol classes are runtime checkable,
# you can enforce this contract at runtime using
# the following.
if not issubclass(cls, interface):
raise ValueError()
...
def register_vehicle(cls: Type[Vehicle]):
register_class(Vehicle, cls)
def register_truck(cls: Type[Truck]):
register_class(Truck, cls)
register_vehicle(Volvo) # Works
register_vehicle(Peterbilt) # Works
register_truck(Volvo) # Type violation
register_truck(Peterbilt) # Works Another approach that might suit your needs is to use abstract base classes. |
Thank you. Much appreciated. I'll consider your suggestions, see where I end up. |
Rereading your initial feedback regarding mypy's join, wouldn't it be feasible to teach it to consider Protocol classes specially. That is, that it could accept T and T' to be that union if T is a protocol class and T' is a (any) class. If T' does not fulfill the protocol T, then that could result in an error further down.. I get that protocol classes are not meant for runtime use, however there is a runtime checkable decorator for them, which gives them some credible use at runtime any how.. just feel like it shouldn't be impossible to make this work.. (even though perhaps not worth it) Many thanks for your valuable input nonetheless. Abstract base classes may very well prove to work out well too. |
That would be a breaking change. It would also create a really odd special case in the constraint solver, and it's not at all clear how it would compose. For example, what happens if the union contains T and T' and some other type? Solving for TypeVars is already very complex and full of tricky edge cases when unions are involved. Of course, you're welcome to propose changes to the current typing standards by posting to the typing-sig, but I think that a special case like the one you've proposed is unlikely to get much support. It sounds like you're looking for a form of generic meta-programming. The current Python type system isn't designed for this. |
I'll close this, given that there is a suitable alternative in abstract base classes to use when the interface is used in a runtime setting. Thank you @erictraut for valuable information and feedback. It's been a very enlightening experience wrgt Protocol classes and their uses. Really powerful concept, when used right ;) |
Bug Report
To Reproduce
repro.py
:Expected Behavior
I expected there be a type representing a Protocol class.
I expected
T
to be inferred asMyProto
(asType[MyProto]
is for types implementing the protocol, not the protocol type itself.) for thewrong_T
variable. SinceType[T]
should accept any object whenT
is a protocol, and then if that object does not satisfy the Protocol, that is an error.Moreover, for
wrong_arg1
, I expectedMyProto
to be an acceptable argument for typeMyProto
, but its type is reported asType[MyProto]
, but that does not fly, as if that type is expected, and I passMyProto
, it complains (rightfully so):Actual Behavior
Your Environment
0.910
mypy.ini
(and other config files): nonePython 3.8.5
Darwin Kernel Version 20.6.0
Related StackOverflow question: https://stackoverflow.com/questions/68822297/protocol-implementation-class-type-check
The text was updated successfully, but these errors were encountered: