Coming from languages with static types (C, C++, Java) I really welcome the addition of type checking to Python. So far, though, I often fail to type annotate things that are easy to the Python core language but can’t be expressed to MyPy
(or I am missing something).
For example, in our code base, we use decorators to inject parameters from context when not passed explicitly (mostly thread-local variables). This is used for progress reporting, which is too complicated for an example here.
Consider this simple Python decorator to reduce this to basics:
def inject_chunk_size(body):
def wrapper(*args, chunk_size=None, **kwargs):
return body(*args, chunk_size=chunk_size or 4, **kwargs)
return wrapper
@inject_chunk_size
def copy_file(source, target, chunk_size):
print(f"Copying {source} to {target}, chunk size {chunk_size}.")
copy_file("src.py", "tgt.py")
copy_file("backup.zip", "/nas/backup.zip", chunk_size=128)
Output:
Copying src.py to tgt.py, chunk size 4.
Copying backup.zip to /nas/backup.zip, chunk size 128.
I fail to add type annotations to this. I tried to simplify the task, changing it by
- make chunk_size a position argument
- make it the first argument
- drop the ability for override it
- add type annotations
ending up with this code:
from typing import Callable, Concatenate, ParamSpec, TypeVar
_P = ParamSpec("_P")
_T = TypeVar("_T")
def inject_chunk_size(body: Callable[Concatenate[int, _P], _T]) -> Callable[_P, _T]:
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T:
chunk_size = 4
return body(chunk_size, *args, **kwargs)
return wrapper
@inject_chunk_size
def copy_file(chunk_size: int, source: str, target: str) -> None:
print(f"Copying {source} to {target}, chunk size {chunk_size}.")
copy_file("src.py", "tgt.py")
copy_file("backup.zip", "/nas/backup.zip")
Obviously, that’s not what I want but given that I can type-annotate it when using the first positional argument for the chunk size, I thought it must be possible to do it when using a named argument?!
A similar discussion was opened here: Type hints for kwarg overrides.
The difference is that I try to motivate that it is possible with positional arguments and argue that it should work therefore with keyword arguments as well.