Exploring Useful Python Decorators: @lru_cache, @jit, @do_twice, @count_calls, @dataclass, @singleton, @use_unit, @singledispatch
This article introduces Python decorators, explains how they can modify function behavior, and provides practical examples of eight useful decorators—including @lru_cache, @jit, @do_twice, @count_calls, @dataclass, @singleton, @use_unit, and @singledispatch—along with code snippets that demonstrate their implementation and benefits.
Python’s rich standard library and flexible language features make it easy to extend and accelerate code, and decorators are a powerful tool for altering function behavior without modifying the function’s core logic.
Decorators are higher‑order functions that wrap other functions or classes, allowing developers to add functionality such as caching, timing, or automatic method generation in a concise and reusable way.
@lru_cache
The @lru_cache decorator from functools caches the results of function calls, dramatically speeding up repeated executions of pure functions such as recursive calculations.
<code>def factorial(n):
return n * factorial(n-1) if n else 1</code>Applying the decorator caches the first few factorial results, reducing subsequent calls to simple look‑ups.
<code>@lru_cache
def factorial(n):
return n * factorial(n-1) if n else 1</code>@jit
The @jit decorator from the Numba library performs just‑in‑time compilation, converting Python code to fast machine code at runtime, which is especially useful for numerically intensive tasks like Monte Carlo simulations.
<code>from numba import jit
import random
@jit(nopython=True)
def monte_carlo_pi(nsamples):
acc = 0
for i in range(nsamples):
x = random.random()
y = random.random()
if (x**2 + y**2) < 1.0:
acc += 1
return 4.0 * acc / nsamples</code>@do_twice
The @do_twice decorator simply calls the wrapped function twice, which can be handy for quick performance checks or debugging.
<code>from decorators import do_twice
@do_twice
def timerfunc():
%timeit factorial(15)</code>@count_calls
@count_calls tracks how many times a function has been invoked, providing useful runtime diagnostics.
<code>from decorators import count_calls
@count_calls
def function_example():
print("Hello World!")
function_example()
function_example()
function_example()</code>@dataclass
The @dataclass decorator from the dataclasses module automatically generates common special methods like __init__ and __repr__ , simplifying class definitions.
<code>from dataclasses import dataclass
@dataclass
class Food:
name: str
unit_price: float
stock: int = 0
def stock_value(self) -> float:
return self.stock * self.unit_price</code>@singleton
A custom @singleton decorator ensures a class is instantiated only once, mimicking a global variable pattern.
<code>def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class cls:
def func(self):
pass</code>@use_unit
The @use_unit decorator (often built on the pint library) attaches a physical unit to a function’s return value, improving clarity in scientific calculations.
<code>def use_unit(unit):
"""Have a function return a Quantity with given unit"""
use_unit.ureg = pint.UnitRegistry()
def decorator_use_unit(func):
@functools.wraps(func)
def wrapper_use_unit(*args, **kwargs):
value = func(*args, **kwargs)
return value * use_unit.ureg(unit)
return wrapper_use_unit
return decorator_use_unit
@use_unit("meters per second")
def average_speed(distance, duration):
return distance / duration</code>@singledispatch
The @singledispatch decorator from functools enables function overloading based on the type of the first argument, allowing a single generic interface for multiple data types.
<code>from functools import singledispatch
@singledispatch
def fun(arg, verbose=False):
if verbose:
print("Let me just say", end=" ")
print(arg)
@fun.register
def _(arg: int, verbose=False):
if verbose:
print("Strength in numbers, eh?", end=" ")
print(arg)
@fun.register
def _(arg: list, verbose=False):
if verbose:
print("Enumerate this:")
for i, elem in enumerate(arg):
print(i, elem)</code>These examples demonstrate how decorators can streamline code, improve performance, and add expressive power to Python programs.
Python Programming Learning Circle
A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.