The double_args Decorator in Python 2026 – Practical Example
The double_args decorator is a classic teaching example that demonstrates how to create a decorator that modifies the arguments passed to a function before calling it. In this case, it doubles every numeric argument passed to the decorated function.
TL;DR — What double_args Does
- Takes a function and returns a new function (wrapper)
- Doubles every integer or float argument before calling the original function
- Leaves non-numeric arguments unchanged
- Preserves the original function’s name and docstring using
@wraps
1. Complete Implementation
from functools import wraps
from typing import Callable, Any
def double_args(func: Callable) -> Callable:
"""Decorator that doubles all numeric arguments passed to the function."""
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
# Double every numeric argument
new_args = []
for arg in args:
if isinstance(arg, (int, float)):
new_args.append(arg * 2)
else:
new_args.append(arg)
# Call the original function with modified arguments
return func(*new_args, **kwargs)
return wrapper
2. Usage Examples
@double_args
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
@double_args
def greet(name: str, age: int) -> str:
return f"Hello {name}, you are {age} years old."
print(add(5, 10)) # 30 (5*2 + 10*2)
print(greet("Alice", 25)) # Hello Alice, you are 50 years old.
3. Best Practices When Writing Similar Decorators
- Always use
@wraps(func)to preserve metadata - Handle both
*argsand**kwargsproperly - Be careful with type checking inside the wrapper
- Document clearly what the decorator modifies
- Keep the wrapper logic as simple and fast as possible
How a Decorator Can Change Arguments
A decorator controls how the wrapped function is called, so it can change the arguments before passing them on. A doubling decorator multiplies every numeric argument by two, which is a simple way to see this mechanism at work.
The same idea is used in real code for unit conversion, input normalisation and validation. Because the wrapper sees both positional and keyword arguments, it should treat both, and leave non-numeric values unchanged.
Changing arguments silently can surprise readers, so give the decorator a clear name and document the change in its docstring.
Adding a parameter to the decorator, such as a factor, turns it into a decorator factory with one more level of nesting.
Worked Example: A Configurable Scaling Decorator
We generalise doubling into a decorator factory that scales numeric positional and keyword arguments by any factor.
import functools
def scale_args(factor=2):
"""Multiply every int or float argument by factor."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
sc = lambda v: v * factor if isinstance(v, (int, float)) and not isinstance(v, bool) else v
return func(*[sc(a) for a in args], **{k: sc(v) for k, v in kwargs.items()})
return wrapper
return decorator
@scale_args()
def total(a, b, label="sum"):
return f"{label}: {a + b}"
@scale_args(factor=10)
def area(w, h):
return w * h
print(total(3, 4))
print(total(1.5, b=2, label="mixed"))
print(area(2, 3), area.__name__)
The default factor of 2 turns total(3, 4) into sum: 14, and the keyword argument b is doubled as well, giving mixed: 7.0, while the label string is untouched. With factor=10, area(2, 3) becomes 20 * 30 = 600, and wraps keeps the name area.
Practical Tips
Test decorated functions with positional and keyword arguments, and with values of different types, to confirm that only the intended arguments are changed.
If a decorator changes the meaning of arguments, mention it in the decorated function's docstring as well, so callers do not need to read the decorator.
When the decorated function has a mix of numbers and flags, exclude booleans explicitly, since bool is a subclass of int.
Common Mistakes
- Forgetting to scale keyword arguments.
- Multiplying strings or booleans by accident.
- Writing @scale_args without parentheses for a factory.
- Changing arguments without documenting it.
Quick Summary
- Decorators can transform arguments.
- Handle both
*argsand**kwargs. - Skip non-numeric values and booleans.
- Add a factory level for options.
Conclusion
The double_args decorator is an excellent example for understanding how decorators can inspect and modify function arguments. In 2026, this pattern is commonly used as a teaching tool and as a foundation for building more complex decorators like validation, logging, or transformation decorators.
Next steps:
- Try modifying the
double_argsdecorator to support different transformations (e.g.,square_args,uppercase_args)