Introduction to Python Decorators: Concepts, Principles, and Practical Examples
Python decorators are a powerful syntactic feature that allow functions, methods, or classes to be wrapped and extended, enabling reusable code for logging, caching, authentication, and more, with detailed explanations of their underlying principles, manual implementations, syntax sugar, and usage with classes and built‑in decorators.
Decorator Introduction
Decorators are an advanced Python syntax that can modify a function, method, or class. They provide a simple, readable way to add cross‑cutting concerns such as logging, performance testing, transaction handling, web permission checks, or caching.
The advantage of decorators is that they can extract repetitive code unrelated to the core functionality of many functions and reuse it. In other words, a decorator can "decorate" a function with completely different behavior, effectively orthogonalizing business logic.
Python decorators are syntactically similar to Java/C# annotations (e.g., @XXX) but are far simpler because they do not require learning a separate annotation library. They are a functional‑programming technique built directly into the language.
How Decorators Work
In Python, implementing a decorator is straightforward because functions are first‑class objects.
Functions Are Objects
To understand decorators, you must first know that functions in Python are objects. The following example shows that a function can be assigned to another variable, passed around, and even deleted while the new name still works.
def shout(word="yes"):
return word.capitalize() + "!"
print(shout()) # outputs: 'Yes!'
# Assign the function to another variable
scream = shout
print(scream()) # outputs: 'Yes!'
# Delete the original name
del shout
try:
print(shout())
except NameError as e:
print(e) # outputs: "name 'shout' is not defined"
print(scream()) # still works, outputs: 'Yes!'Functions can also be defined inside other functions, which enables returning a function from another function.
def talk():
def whisper(word="yes"):
return word.lower() + "..."
print(whisper())
talk() # outputs: "yes..."
# The inner function is not accessible outside
try:
print(whisper())
except NameError as e:
print(e) # outputs: "name 'whisper' is not defined"Function References
Because functions are objects, they can be returned from other functions:
def get_talk(type="shout"):
def shout(word="yes"):
return word.capitalize() + "!"
def whisper(word="yes"):
return word.lower() + "..."
if type == "shout":
return shout # return the function object, not the result
else:
return whisper
talk = get_talk()
print(talk) # <function shout at 0x...>
print(talk()) # outputs: Yes!
print(get_talk("whisper")()) # outputs: yes...Functions can also be passed as arguments:
def shout(word="yes"):
return word.capitalize() + "!"
scream = shout
def do_something_before(func):
print("I do something before then I call the function you gave me")
print(func())
do_something_before(scream)
# outputs:
# I do something before then I call the function you gave me
# Yes!Decorator in Practice
Now that the basics are clear, decorators can be used to wrap additional code before or after a function runs without modifying the original function.
Manually Creating a Decorator
# A decorator is a function that takes another function as an argument
def my_shiny_new_decorator(a_function_to_decorate):
# Define the wrapper that will run before and after the original function
def the_wrapper_around_the_original_function():
print("Before the function runs")
a_function_to_decorate()
print("After the function runs")
# Return the wrapper so it can be called later
return the_wrapper_around_the_original_function
# Example usage
def a_stand_alone_function():
print("I am a stand alone function, don't you dare modify me")
# Decorate the function
a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
# outputs:
# Before the function runs
# I am a stand alone function, don't you dare modify me
# After the function runsDecorator Syntax Sugar
Using the @ syntax makes the previous example more concise:
def my_shiny_new_decorator(a_function_to_decorate):
def the_wrapper_around_the_original_function():
print("Before the function runs")
a_function_to_decorate()
print("After the function runs")
return the_wrapper_around_the_original_function
@my_shiny_new_decorator
def another_stand_alone_function():
print("Leave me alone")
another_stand_alone_function()
# outputs:
# Before the function runs
# Leave me alone
# After the function runsApplying multiple decorators stacks them, with the decorator closest to the function being applied first.
def bread(func):
def wrapper():
print("</''''''\>")
func()
print("<\______/>")
return wrapper
def ingredients(func):
def wrapper():
print("#tomatoes#")
func()
print("~salad~")
return wrapper
@ingredients
@bread
def sandwich(food="--ham--"):
print(food)
sandwich()
# outputs:
# </''''''\>
# #tomatoes#
# --ham--
# ~salad~
# <\______/>Passing Arguments to Decorators
When a decorator needs its own parameters, it must return a function that returns the actual wrapper.
def pre_str(pre=''):
def decorator(F):
def new_F(a, b):
print(pre + " input", a, b)
return F(a, b)
return new_F
return decorator
@pre_str('^_^')
def square_sum(a, b):
return a**2 + b**2
@pre_str('T_T')
def square_diff(a, b):
return a**2 - b**2
print(square_sum(3, 4)) # prints: ('^_^ input', 3, 4) and then 25
print(square_diff(3, 4)) # prints: ('T_T input', 3, 4) and then -7Decorating Methods and Classes
Because methods are just functions that receive the instance as the first argument (self), the same decorator pattern works for them.
def method_friendly_decorator(method_to_decorate):
def wrapper(self, lie):
lie = lie - 3 # make the age appear younger
return method_to_decorate(self, lie)
return wrapper
class Lucy(object):
def __init__(self):
self.age = 32
@method_friendly_decorator
def say_your_age(self, lie):
print("I am %s, what did you think?" % (self.age + lie))
l = Lucy()
l.say_your_age(-3) # outputs: I am 26, what did you think?A generic decorator that accepts any arguments can be written with *args and **kwargs.
def a_decorator_passing_arbitrary_arguments(function_to_decorate):
def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
print("Do I have args?:")
print(args)
print(kwargs)
return function_to_decorate(*args, **kwargs)
return a_wrapper_accepting_arbitrary_arguments
@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
print("Python is cool, no argument here.")
function_with_no_argument()
# outputs:
# Do I have args?:
# ()
# {}
# Python is cool, no argument here.Class Decorators
Since Python 2.6, decorators can also wrap entire classes.
def decorator(aClass):
class newClass:
def __init__(self, age):
self.total_display = 0
self.wrapped = aClass(age)
def display(self):
self.total_display += 1
print("total display", self.total_display)
self.wrapped.display()
return newClass
@decorator
class Bird:
def __init__(self, age):
self.age = age
def display(self):
print("My age is", self.age)
eagleLord = Bird(5)
for i in range(3):
eagleLord.display()
# each call prints the total display count and the bird's ageBuilt‑in Decorators
Python provides three commonly used built‑in decorators for class members: property, staticmethod, and classmethod.
class XiaoMing:
first_name = '明'
last_name = '小'
@property
def full_name(self):
return self.last_name + self.first_name
xiaoming = XiaoMing()
print(xiaoming.full_name) # accesses the method like an attribute class XiaoMing:
@staticmethod
def say_hello():
print('同学你好')
XiaoMing.say_hello()
# also works on an instance
xiaoming = XiaoMing()
xiaoming.say_hello() class XiaoMing:
name = '小明'
@classmethod
def say_hello(cls):
print('同学你好,我是' + cls.name)
print(cls)
XiaoMing.say_hello()Preserving Function Metadata with functools.wraps
When a decorator returns a wrapper function, the original function’s name and docstring are lost. Using functools.wraps copies these attributes back to the wrapper.
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('123')
return func(*args, **kwargs)
return wrapper
@decorator
def say_hello():
"""doc of say hello"""
print('同学你好')
print(say_hello.__name__) # still 'say_hello'
print(say_hello.__doc__) # still 'doc of say hello'Summary
Decorators are a core Python feature that enable name binding and code reuse. While they are not the same as the object‑oriented Decorator Pattern, they provide a concise, readable way to extend functionality such as logging, caching, authentication, and more.
Understanding decorators is valuable for any Python developer because they appear frequently in real‑world projects and libraries.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
