Python: Decorator
What is decorator
Python “decorator” is a language construct that lets you define a wrapper function g to another function f, such that, when f is called, your wrapper g is called instead. (it can also be applied to methods, and Class .)
The syntax for “decorator” is @name1 immediately before the line of def name2.
# suppose you defined a function my_wrapper. # now, # this syntax with the at sign @ @my_wrapper def f(): # whatever body print("something") # is equivalent to def f(): # whatever body print("something") f = my_wrapper(f) # note, to use the @my_wrapper syntax, you must place it right above the definition of another function f. # In other words, you must have access to the source code of f and be able to modify it. # if you don't have access to the source code of f you want to modify, # just do # f = my_wrapper(f) # s------------------------------ # Note that: # when the function f is called, the wrapper g will be called instead. # The wrapper function g receives f as its argument. # The wrapper function g must return a function. # Because the value of f is now g(f), and f is a function. # So, when f is called such as f(3), Python evaluates g(f)(3), so g(f) must be a function too. (technically, it just need to be anything
Example: Check or Modify Argument
Here's a example of decorator. It checks and modifies the function's argument.
# example of a decorator, without using the @ syntax def gg(xfun): """A decorator for function ff. Make sure input to ff is always even. If not, add 1 to make it even.""" def hh(y): if y % 2 == 0: # even return xfun(y) else: return xfun(y + 1) return hh def ff(x): return x ff = gg(ff) print(ff(3)) # 4 print(ff(4)) # 4 print(ff(5)) # 6 print(ff(6)) # 6
now the same example but using the decorator syntax:
# example of a decorator def gg(xfun): """A decorator for function ff. Make sure input to ff is always even. If not, add 1 to make it even.""" def hh(y): if y % 2 == 0: # even return xfun(y) else: return xfun(y + 1) return hh @gg def ff(x): return x print(ff(3)) # 4 print(ff(4)) # 4 print(ff(5)) # 6 print(ff(6)) # 6
Checklist for writing a decorator
Here's 4 things to remember when writing a decorator:
- The wrapper function
gmust take a function as argument. (because it'll receivefas argument.) - The wrapper function
gmust return a function (e.g.h). (because callingfresults in calling return value ofg.) - The returned function
hmust take the same number and type of arguments asf. (Tip: usedef h(*args, **keywords)to catch all.) - OFTEN, the returned function
hshould return the same type of value thatfreturns. Iffreturns a string,hprobably should too. IffreturnsNone,hprobably should too.
Example: Catch All Arguments (And Run Conditionally)
When defining the decorator function, often you need to catch all possible arguments of the original function. Here's a example of how.
In the following example, the original function is called only if some global variable is true.
# example of decorator that check condition to decide whether to call # the condition cc = True def gg(func): """a decorator for ff. If cc is true, run ff, else do nothing""" def hh(*args, **xkeywords): # param must catch all args of ff """do nothing function""" pass if cc: return func else: return hh @gg def ff(*args, **xkeywords): # ff can be sending email, or any callable with no return value print("ff") pass ff(3) ff(3, "Jane") ff(3, 4, k="thank you") # if cc is true, then ff is called 3 times # (different set of arguments are used, to illustrate that our wrapper work well with them) # if cc is false, Nothing's done, ff is not called.
[see Python: Function]
Decorator with parameters
Decorator itself can have parameters.
For example, the following decorator:
@g(3) def f(): print("xyz") # is equivalent to def f(): print("xyz") f = g(3)(f) # There's nothing special about this. # In the simplest decorator example, @g followed by def f: gets transformed into f = g(f). # Now, if we replace g by g(x), we get f = g(x)(f).
Decorators can get complex, because it's transforming code and doing function applications and involves nested functions. In this case, just remember that:
g(x) must return a function, this function will receive f as argument. thus we can see, g(x) must return a decorator.
Example of decorator with parameter
# example of decorator with argument def gg(num): """a decorator for ff. Ignore ff. Simply return num.""" def h1(f1): def h2(f2): return num return h2 return h1 # gg(1) must return a function h1, such that h1 accept function (the ff), and also return a function (to be applied to ff's args) @gg(1) def ff(x): return repr(x) + " rabbits" print(ff(3)) # 1 print(ff(4)) # 1 print(ff(5)) # 1
Example: decorator as a class
# example of decorator on class class Dog: def __init__(self, hh): print("Dog.__init__() called") hh() # Prove that function definition has completed def __call__(self): print("Dog.__call__() called") @Dog # this forces Dog to be called when ff is called def ff(): print("ff() called") ff() # Dog.__init__() called # ff() called # Dog.__call__() called
Example 2
class Gate(object): def __init__(self, f): self.f = f def __call__(self): print("enter", self.f.__name__) self.f() print("exit", self.f.__name__) @Gate def f1(): print("f1() called") @Gate def f2(): print("f2() called") f1() f2() # enter f1 # f1() called # exit f1 # enter f2 # f2() called # exit f2
Explain python decorator
explain python decorator
ai answer
Python decorators are a concise way to wrap (modify or enhance) a function or method without changing its source code. They are syntactic sugar for higher-order functions: functions that take another function as input and return a new function.
Core Idea
Functions in Python are first-class objects — they can be passed around, assigned to variables, and returned from other functions. A decorator takes a function, adds behavior around it, and returns the modified version.
Basic Example (Without the @ Syntax)
def my_decorator(func): def wrapper(): print("Something before the function runs") func() print("Something after the function runs") return wrapper def say_hello(): print("Hello!") # Manually applying the decorator say_hello = my_decorator(say_hello) say_hello()
Output:
Something before the function runs
Hello!
Something after the function runs
Using the @ Syntax
The @ symbol is just convenient syntax for the assignment above:
@my_decorator def say_hello(): print("Hello!") say_hello() # Same result as above
This is equivalent to say_hello = my_decorator(say_hello).
Decorators That Accept Arguments
Most real-world functions take arguments, so the wrapper must accept *args and **kwargs:
def my_decorator(func): def wrapper(*args, **kwargs): print("Before") result = func(*args, **kwargs) print("After") return result return wrapper @my_decorator def greet(name): print(f"Hello, {name}!") greet("Alice")
Preserving Function Metadata (functools.wraps)
Without help, the decorated function loses its original name, docstring, etc. Always use functools.wraps:
from functools import wraps def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print("Before") return func(*args, **kwargs) return wrapper
Decorator Factories (Decorators with Their Own Arguments)
When the decorator itself needs parameters, you write a function that returns a decorator:
def repeat(times): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for _ in range(times): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def say_hi(): print("Hi!") say_hi() # Prints "Hi!" three times
Common Built-in Decorators
@staticmethod/@classmethod@property(and its setter/deleter)@abstractmethod- Logging, timing, caching (
@lru_cache), authentication checks, etc. in frameworks (Flask/FastAPI routes, pytest fixtures, etc.)
Multiple Decorators
They apply bottom-up (closest to the function first):
@decorator_a @decorator_b def f(): pass # Equivalent to: f = decorator_a(decorator_b(f))
Class-Based Decorators
You can also write a class with a __call__ method:
class CountCalls: def __init__(self, func): self.func = func self.count = 0 def __call__(self, *args, **kwargs): self.count += 1 print(f"Call {self.count}") return self.func(*args, **kwargs) @CountCalls def hello(): print("Hello")
Key Takeaways
- Decorators let you add cross-cutting concerns (logging, timing, validation, retry logic, access control) cleanly.
- They work because functions are objects.
- Prefer the
@syntax +functools.wrapsfor readability and correctness. - Decorator factories give you configurable behavior.
This pattern is one of the most powerful and widely used features in modern Python.