I am trying type hint a protocol to encompass a small variety of similar API interfaces. I want to allow certain methods to be flexible and allow for extra arguments and keyword arguments, since different implementations have different 'bonus' args, but all of them share a common set of arguments at the first half of the signature.
This is awkward because I'm trying to write compatibility for some classes that I don't control directly, so I can't edit the implementation signatures.
from typing import Protocol
class ExampleProtocol(Protocol):
def test(self, important_arg:str, *args, **kwargs):
pass
class ExampleImplementation:
def test(self, important_arg:str):
pass
class ExampleImplementation2:
def test(self, important_arg:str, unnessary_arg:bool):
pass
implementation: ExampleProtocol = ExampleImplementation()
implementation2: ExampleProtocol = ExampleImplementation2()
Pylance gives me this:
"ExampleImplementation" is incompatible with protocol "ExampleProtocol"
"test" is an incompatible type
Type "(important_arg: str) -> None" cannot be assigned to type "(important_arg: str, *args: Unknown, **kwargs: Unknown) -> None"
Parameter "**kwargs" has no corresponding parameter
Is it possible to type hint in this case, or do I need to just use Any instead of the protocol? Does python allow hinting "dynamic" signatures that have a few important arguments?
EDIT: It seems that this isn't possible, so the best solution I could come up with is to use the Callable type to at the very least indicate that a method with that name should exist, without enforcing any arguments, like so:
class ExampleProtocol(Protocol):
test: Callable
This seems to work and not give any errors on the two examples.
The short answer here is 'no', mostly because your ExampleImplementation method test (or ExampleImplementation2) has a subset of the parameters allowed by ExampleProtocol. That is, there is some set of parameters that ExampleProtocol accepts that ExampleImplementation would break on - anything past that first argument.
Without knowing what you're trying to get at exactly, you're left with manually filtering the *agrs and **kwargs parameters for those that you care about. The former can be difficult because they're not named, they're only positional.
That said, the type-hint you're reaching for is Optional. It doesn't solve your problem on it's own, but it's a type that declares 'either this is None or it is of type x'. For instance:
def f(a: Optional[bool]) -> None:
# a is either None or a boolean value
Any doesn't really do anything for you here, I think.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With