In our project we have a decorator that can be used both as a marker without parameters like @keyword
and with parameters like @keyword(name='Example')
. You can see a slightly simplified implementation below and the real code on GitHub.
def keyword(name=None, tags=()):
if isroutine(name):
return keyword()(name)
def decorator(func):
func.robot_name = name
func.robot_tags = tags
return func
return decorator
We’d like to add type hints to the decorator and obviously need to use typing.overload
because the return value depends on the arguments. I have some questions related to its usage:
- Must argument names match in all implementations? In our case the first argument can either be a name or a function, and using names like
name
andfunc
would be better than usingname
everywhere. - Must all arguments be listed with all implementations? In our case no other argument is accepted when passing a function as the first argument.
- Is it ok to omit typing from the actual implementation altogether? With complex signatures proper typing will get rather complicated.
- Is it ok to use just
Callable
instead of uglyCallable[[...], Any]
when we don’t know anything about the signature of the callable? This isn’t really related tooverload
, but is relevant in this particular case.
I hope answers to the above would be “no”, “no”, “yes” and “yes”, because it seems to result with pretty clean and easy to understand code (which isn’t always the case with complex type hints):
from inspect import isroutine
from typing import Callable, overload, Sequence, TypeVar
F = TypeVar('F', bound=Callable)
@overload
def keyword(func: F) -> F:
...
@overload
def keyword(name: str | None = None,
tags: Sequence[str] = ()) -> Callable[[F], F]:
...
def keyword(name=None, tags=()):
if isroutine(name):
return keyword()(name)
def decorator(func):
func.robot_name = name
func.robot_tags = tags
return func
return decorator
@keyword
def example1():
pass
@keyword(name='Example')
def example2():
pass
print(example1.robot_name, example2.robot_name)
The only problem MyPy reports with the above code is accessing the robot.name
attribute at the end and that’s something I’m fine with. Pyright (VS Code) doesn’t seem to like argument names being different, though. That would be trivial to fix, but using name: F
when the argument is a function feels wrong. Is Pyright right that argument names must match or is it overly strict here?