Home / Decorators / Chapter 4

Chapter 4: Decorators with arguments

Jun 16, 2025
4 of 7

What if you want to pass arguments to the decorator itself? For example, to customize the time_it decorator to measure in nanoseconds instead of seconds.

This requires an extra layer of nesting:


import time
# import debugpy

def time_it_configurable(*, use_nanos: bool = False): # Decorator now takes an argument
  def actual_decorator(func):                          # This is the actual decorator
    def wrapper(*args, **kwargs):
      start = time.perf_counter_ns() if use_nanos else time.perf_counter()
      result = func(*args, **kwargs)
      end = time.perf_counter_ns() if use_nanos else time.perf_counter()
      duration = end - start
      unit = 'nanoseconds' if use_nanos else 'seconds'
      print(f"Function '{func.__name__}' took: {duration:.4f} {unit}")
      return result
    return wrapper
  return actual_decorator # Decorator factory returns the actual decorator

@time_it_configurable(use_nanos=True)
def another_database_operation(sleep_time: float = 0.5):
  time.sleep(sleep_time)
  return "Another DB Op Complete"

@time_it_configurable(use_nanos=False) # Or use default seconds
def yet_another_op():
    time.sleep(0.2)
    return "Yet another op done"

another_database_operation()
yet_another_op()

Here, time_it_configurable is a “decorator factory.” When you call another_database_operation there are three layers happening and without the @ syntactic sugar it can be written as: time_it_configurable(use_nanos=True)(another_database_operation)(sleep_time=1.0)