Skip to main content

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
note

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
tip

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:

  1. *args: Annotate the type of the items (e.g., *args: int), not the tuple.
  2. **kwargs: Annotate the type of the values (e.g., **kwargs: str), not the dictionary. Keys are implicitly strings.