A Beginner’s Guide to Understanding Decorators in Python in 5 Minutes

For those new to Python, have you ever felt confused by “decorators”? They are essentially tools that add “buffs” to your code! Today, I will help you grasp this concept with 5 key points and simple code examples, so you can get started right away.

1. Function Decorators: Adding “Functionality Coats” to Functions

1. @ Syntax: A “Sugar” to Simplify Code

If you want to add extra functionality to a function (like logging), you can do so without modifying the original function by simply adding an @decorator name:

# Define a decorator: print function execution prompt
def log_decorator(func):
    def wrapper():
        print("The function is about to run!")
        func()  # Call the original function
    return wrapper

# Use @ to add a decorator to the hello function
@log_decorator
def hello():
    print("Hello Python!")

hello()  # Output: The function is about to run! → Hello Python!

2. The Principle of Decorators: Essentially “Function Nesting”

@log_decorator is essentially hello = log_decorator(hello), to put it simply:

Pass the original function to the decorator → The decorator returns a new function → The original function name points to the new function, triggering the added functionality when called.

2. Class Decorators: Adding “Dynamic Plugins” to Classes

Class decorators can add attributes/methods to classes in bulk, making it more flexible than directly modifying class code.

1. Example of Decorating a Class: Adding “Creation Time”

import time
class TimeDecorator:
    def __init__(self, cls):
        self.cls = cls  # Receive the decorated class
    def __call__(self):  # Triggered when the class is instantiated
        obj = self.cls()
        obj.create_time = time.ctime()  # Add new attribute
        return obj

# Use class decorator to decorate MyClass
@TimeDecorator
class MyClass:
    pass

obj = MyClass()
print(obj.create_time)  # Output the creation time of the class instance

2. A Brief Introduction to Metaclasses

Metaclasses are “classes of classes”, and class decorators are a simplified version of metaclasses. If you need complex customization (like controlling the creation of all subclasses), you can delve deeper into metaclasses; for beginners, mastering class decorators is sufficient!

3. Built-in Decorators: Python’s Own “Tools”

These 3 decorators are extremely useful, remember them for direct use!

1. @property: Turning Methods into “Attributes”

Instead of writing get_xxx(), you can directly access the value using object.attribute, and you can also control modifications:

class Student:
    def __init__(self, score):
        self._score = score  # Underscore indicates "private"
    @property
    def score(self):  # Read property
        return self._score
    @score.setter  # Allow modification (if not added, cannot modify)
    def score(self, new_score):
        if 0 <= new_score <= 100:
            self._score = new_score
        else:
            print("Score must be between 0-100!")
s = Student(80)
print(s.score)  # Direct read: 80
s.score = 95    # Valid modification
s.score = 105   # Invalid: print prompt

2. @staticmethod: Static Method

No need for self/cls, it has no relation to the class or instance, like a regular function:

class Math:
    @staticmethod
    def add(a, b):  # No self/cls
        return a + b
print(Math.add(2, 3))  # Direct call: 5

3. @classmethod: Class Method

Requires cls parameter, can only access class attributes, cannot access instance attributes:

class Car:
    brand = "BYD"  # Class attribute
    @classmethod
    def get_brand(cls):  # Has cls
        return cls.brand
print(Car.get_brand())  # Call: BYD

4. Parameterized Decorators: More Flexible “Customization”

Want to make the decorator support custom parameters? Just add a layer of function:

# First layer: receive decorator parameters
def log_decorator(custom_msg):
    # Second layer: receive the original function
    def wrapper(func):
        # Third layer: actual execution
        def inner():
            print(custom_msg)
            func()
        return inner
    return wrapper

# Add a parameterized decorator to hello
@log_decorator("Custom message: Starting execution!")
def hello():
    print("Hello!")
hello()  # Output: Custom message → Hello!

5. functools Module: Solving “Minor Troubles”

1. @functools.wraps: Preserve Original Function Information

Decorators can “lose” the original function’s name and docstring; adding this can preserve them:

import functools
def decorator(func):
    @functools.wraps(func)  # Key: preserve original function information
    def wrapper():
        func()
    return wrapper
@decorator
def test():
    """This is the docstring for test"""
    pass
print(test.__name__)  # Output: test (if not added, it would be wrapper)
print(test.__doc__)   # Output: docstring (if not added, it would be None)

2. @functools.lru_cache: Cache Results to Save Time

When repeatedly calling a function with the same parameters, it directly returns the cached result (for example, calculating Fibonacci):

import functools
@functools.lru_cache(maxsize=None)  # Cache all results
def fib(n):
    if n <= 1:
        return n
    return fib(n-1) + fib(n-2)
print(fib(100))  # Super fast! The results of repeated calculations are cached

A Beginner's Guide to Understanding Decorators in Python in 5 Minutes

Leave a Comment