Python Type Annotations: How to Annotate Variable Arguments
In Python, the special syntax *args and **kwargs allows functions to accept a variable number of arguments. However, when using type hints, beginners often misinterpret how to annotate them.
The key rule is: You annotate the type of the individual element, not the container (tuple or dict) that holds them.
Annotating *args (Positional Arguments)
When you define *args, Python internally collects all extra positional arguments into a tuple.
However, the type hint should specify the type of each item inside that tuple, not the tuple itself.
Incorrect Way
# ⛔️ Incorrect: This implies that EACH argument passed is a tuple of integers
def sum_numbers(*args: tuple[int, ...]) -> int:
return sum(args)
Correct Way
# ✅ Correct: This implies that EACH argument passed must be an 'int'
def sum_numbers(*args: int) -> int:
return sum(args)
# Usage
sum_numbers(1, 2, 3) # Valid
If *args can accept multiple types (e.g., integers and strings), use Union.
def process(*args: Union[int, str]): ...
Annotating **kwargs (Keyword Arguments)
When you define **kwargs, Python collects keyword arguments into a dict where keys are always strings.
The type hint should specify the type of the values associated with those keys.
Incorrect Way
# ⛔️ Incorrect: This implies each argument is a dictionary
def print_config(**kwargs: dict[str, str]):
pass
Correct Way
# ✅ Correct: This implies every keyword argument value must be a 'str'
def print_config(**kwargs: str) -> None:
for k, v in kwargs.items():
print(f"{k}: {v}")
# Usage
print_config(host="localhost", port="8080")
# Note: 'port' here is passed as a string to satisfy the hint
If your kwargs can hold anything (common in decorators or wrappers), use Any.
def wrapper(**kwargs: Any): ...
Combining Annotations
Here is a complete example showing both variable positional and keyword arguments annotated correctly.
from typing import Any
def logger(message: str, *args: Any, **kwargs: Any) -> None:
"""
Logs a message with optional extra details.
"""
print(f"MSG: {message}")
if args:
print(f"Extra data: {args}")
if kwargs:
print(f"Context: {kwargs}")
# Usage
logger("System Start", 1, [2, 3], user="admin", id=101)
Conclusion
To annotate variable arguments correctly:
*args: Annotate the type of the items (e.g.,*args: int), not the tuple.**kwargs: Annotate the type of the values (e.g.,**kwargs: str), not the dictionary. Keys are implicitly strings.