Austin Witherspoon
Austin Witherspoon

Reputation: 202

Python 3 Protocols - is it possible to require specific arguments in method, but allow extra arguments as well?

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

Answers (1)

Nathaniel Ford
Nathaniel Ford

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

Related Questions