Reputation: 202
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.
Upvotes: 3
Views: 1941
Reputation: 21230
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.
Upvotes: 1