The double_args Decorator in Python 2026 – Practical Example

Writing Functions Mar 16, 2026

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 *args and **kwargs properly
  • 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 *args and **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_args decorator to support different transformations (e.g., square_args, uppercase_args)

Related Tutorials

Share:
Last updated: March 2026
Built-in Functions in Python 2026 str() in Python 2026: String Conversion + Modern Formatting & Best Practices memoryview with JAX in Python 2026: Zero-Copy NumPy → JAX Array Interop + Efficient ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + GPU Pinning & ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + ML Examples memoryview with NumPy & PyTorch in Python 2026: Zero-Copy Views, Efficient Slicing & ML Interop Examples memoryview with NumPy in Python 2026: Zero-Copy Views, Efficient Slicing & Real ML Examples memoryview() in Python 2026: Zero-Copy Magic for Large Binary Data + Real Examples import() in Python 2026: How It Works, Security Risks & Modern Alternatives (importlib) zip() in Python 2026: How to Use It, strict=True Behavior & Real-World Examples vars() in Python 2026: Accessing Object Namespace + Modern Introspection Patterns type() in Python 2026: Dynamic Type Inspection & Object Creation + Modern Patterns tuple() in Python 2026: Immutable Sequences + Modern Patterns & Best Practices super() in Python 2026: Method Resolution & Modern Inheritance Patterns sum() in Python 2026: Summing Iterables + Modern Numeric Patterns & Best Practices staticmethod() in Python 2026: Static Methods, Utility Functions & Modern Best Practices sorted() in Python 2026: Sorting Iterables + Modern Patterns & Best Practices slice() in Python 2026: Creating Slice Objects + Modern Patterns & Best Practices setattr() in Python 2026: Dynamic Attribute Setting + Modern Patterns & Safety set() in Python 2026: Mutable Sets Creation + Modern Patterns & Best Practices