如何使用 Python 装饰器(附函数式与类式示例)

更新于 2026-01-12

通过动手示例学习 Python 装饰器。深入理解闭包、函数式与类式装饰器,并编写可复用、优雅的代码。

装饰器是 Python 中一种强大而优雅的特性,它允许你在不修改原有函数代码的前提下,修改或扩展函数和方法的行为。

装饰器是一种设计模式,允许用户在不改变对象结构的情况下为其添加新功能。装饰器通常应用于函数,在增强或修改函数行为方面起着关键作用。传统上,装饰器被放置在要装饰的函数定义之前。本教程将演示如何在 Python 函数中有效使用装饰器。


函数是一等公民(First-Class Objects)

在 Python 中,函数是一等公民。这意味着它们支持以下操作:

  • 作为参数传递
  • 从函数中返回
  • 被修改
  • 被赋值给变量

这一特性至关重要,因为它使函数可以像其他对象一样被灵活处理。

将函数赋值给变量

我们首先创建一个函数,每次调用时将数字加一。然后将该函数赋值给一个变量,并通过该变量调用函数。

def plus_one(number):
    return number + 1

add_one = plus_one
add_one(5)  # 输出: 6

在函数内部定义函数

Python 允许在函数内部定义函数,这是构建装饰器的核心特性之一。

def plus_one(number):
    def add_one(number):
        return number + 1
    result = add_one(number)
    return result

plus_one(4)  # 输出: 5

将函数作为参数传递

函数也可以作为参数传递给其他函数:

def plus_one(number):
    return number + 1

def function_call(function):
    number_to_add = 5
    return function(number_to_add)

function_call(plus_one)  # 输出: 6

函数返回另一个函数

函数还可以生成并返回另一个函数:

def hello_function():
    def say_hi():
        return "Hi"
    return say_hi

hello = hello_function()
hello()  # 输出: 'Hi'

内部函数与闭包(Closures)

Python 允许嵌套函数访问其外层函数的作用域。这一概念称为闭包(Closure),对理解装饰器至关重要。

闭包:一个函数“记住”了它被创建时的环境,即使该环境已不再活跃。

闭包示例

def outer_function(message):
    def inner_function():
        print(f"Message from closure: {message}")
    return inner_function

closure_function = outer_function("Hello, closures!")
closure_function()
# 输出: Message from closure: Hello, closures!

在此示例中:

  • inner_function 是一个闭包,因为它访问了外层作用域中的变量 message
  • 即使 outer_function 已执行完毕,inner_function 仍能访问 message

在装饰器中,包装函数(wrapper)就是一个闭包,它会记住被装饰的函数以及装饰器中定义的任何状态。

def simple_decorator(func):
    def wrapper():
        print("Before the function call")
        func()
        print("After the function call")
    return wrapper

@simple_decorator
def greet():
    print("Hello!")

greet()
# 输出:
# Before the function call
# Hello!
# After the function call

创建你的第一个装饰器

现在你已经理解了闭包,我们可以创建第一个真正的装饰器了。

下面是一个将句子转为大写的装饰器:

def uppercase_decorator(function):
    def wrapper():
        func = function()
        make_uppercase = func.upper()
        return make_uppercase
    return wrapper

手动应用装饰器

def say_hi():
    return 'hello there'

decorate = uppercase_decorator(say_hi)
decorate()  # 输出: 'HELLO THERE'

使用 @ 语法(推荐)

Python 提供了更简洁的语法——使用 @ 符号:

@uppercase_decorator
def say_hi():
    return 'hello there'

say_hi()  # 输出: 'HELLO THERE'

堆叠多个装饰器

你可以将多个装饰器叠加到同一个函数上,顺序很重要!

import functools

def split_string(function):
    @functools.wraps(function)
    def wrapper():
        func = function()
        splitted_string = func.split()
        return splitted_string
    return wrapper

@split_string
@uppercase_decorator
def say_hi():
    return 'hello there'

say_hi()  # 输出: ['HELLO', 'THERE']

注意:装饰器从下往上应用。如果顺序颠倒(先 split_stringuppercase_decorator),会出错,因为列表没有 .upper() 方法。

最佳实践:使用 functools.wraps 保留原始函数的元数据(如 __name____doc__),便于调试。


装饰器接受函数参数

装饰器也可以处理带参数的函数:

def decorator_with_arguments(function):
    def wrapper_accepting_arguments(arg1, arg2):
        print("My arguments are: {0}, {1}".format(arg1, arg2))
        function(arg1, arg2)
    return wrapper_accepting_arguments

@decorator_with_arguments
def cities(city_one, city_two):
    print("Cities I love are {0} and {1}".format(city_one, city_two))

cities("Nairobi", "Accra")
# 输出:
# My arguments are: Nairobi, Accra
# Cities I love are Nairobi and Accra

⚠️ 注意:装饰器内部的 wrapper 参数必须与被装饰函数的参数匹配。


通用装饰器:使用 *args**kwargs

为了适用于任意函数,使用 *args**kwargs

def a_decorator_passing_arbitrary_arguments(function_to_decorate):
    def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
        print('The positional arguments are', args)
        print('The keyword arguments are', kwargs)
        function_to_decorate(*args, **kwargs)  # 修复:应传递 **kwargs
    return a_wrapper_accepting_arbitrary_arguments

示例

@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
    print("No arguments here.")

function_with_no_argument()
# 输出:
# The positional arguments are ()
# The keyword arguments are {}
# No arguments here.
@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
    print(a, b, c)

function_with_arguments(1, 2, 3)
# 输出:
# The positional arguments are (1, 2, 3)
# The keyword arguments are {}
# 1 2 3
@a_decorator_passing_arbitrary_arguments
def function_with_keyword_arguments(first_name, last_name):
    print(f"Hello {first_name} {last_name}")

function_with_keyword_arguments(first_name="Derrick", last_name="Mwiti")
# 输出:
# The positional arguments are ()
# The keyword arguments are {'first_name': 'Derrick', 'last_name': 'Mwiti'}
# Hello Derrick Mwiti

向装饰器本身传递参数

有时你需要配置装饰器本身(如设置日志级别、缓存大小等)。这时需要装饰器工厂

def decorator_maker_with_arguments(decorator_arg1, decorator_arg2, decorator_arg3):
    def decorator(func):
        def wrapper(function_arg1, function_arg2, function_arg3):
            print("The wrapper can access all the variables\n"
                  "\t- from the decorator maker: {0} {1} {2}\n"
                  "\t- from the function call: {3} {4} {5}\n"
                  "and pass them to the decorated function"
                  .format(decorator_arg1, decorator_arg2, decorator_arg3,
                          function_arg1, function_arg2, function_arg3))
            return func(function_arg1, function_arg2, function_arg3)
        return wrapper
    return decorator

pandas = "Pandas"
@decorator_maker_with_arguments(pandas, "Numpy", "Scikit-learn")
def decorated_function_with_arguments(function_arg1, function_arg2, function_arg3):
    print("This is the decorated function and it only knows about its arguments: {0} {1} {2}"
          .format(function_arg1, function_arg2, function_arg3))

decorated_function_with_arguments(pandas, "Science", "Tools")

输出:

The wrapper can access all the variables
	- from the decorator maker: Pandas Numpy Scikit-learn
	- from the function call: Pandas Science Tools
and pass them to the decorated function
This is the decorated function and it only knows about its arguments: Pandas Science Tools

调试装饰器:保留元数据

装饰器会“隐藏”原始函数的元信息:

decorated_function_with_arguments.__name__  # 输出: 'wrapper'
decorated_function_with_arguments.__doc__   # 输出: 'This is the wrapper function'

解决方案:使用 functools.wraps

import functools

def uppercase_decorator(func):
    @functools.wraps(func)
    def wrapper():
        return func().upper()
    return wrapper

@uppercase_decorator
def say_hi():
    "This will say hi"
    return 'hello there'

say_hi()  # 'HELLO THERE'
say_hi.__name__  # 'say_hi'
say_hi.__doc__   # 'This will say hi'

强烈建议:始终在自定义装饰器中使用 @functools.wraps(func)


类式装饰器(Class-Based Decorators)

除了函数式装饰器,Python 还支持类式装饰器,通过实现 __call__ 方法使其可调用。

基本示例

class UppercaseDecorator:
    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):
        result = self.function(*args, **kwargs)
        return result.upper()

@UppercaseDecorator
def greet():
    return "hello there"

print(greet())  # 输出: HELLO THERE

工作原理

  • __init__:接收被装饰的函数。
  • __call__:当调用被装饰函数时触发,可修改其行为。

优势

  • 有状态(Stateful):可通过实例变量保存状态。
  • 可读性强:复杂逻辑封装在类中更清晰。

有状态装饰器示例:调用计数器

class CallCounter:
    def __init__(self, function):
        self.function = function
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"Function {self.function.__name__} has been called {self.count} times.")
        return self.function(*args, **kwargs)

@CallCounter
def say_hello():
    print("Hello!")

say_hello()
say_hello()
# 输出:
# Function say_hello has been called 1 times.
# Hello!
# Function say_hello has been called 2 times.
# Hello!

真实场景:缓存(Caching)

Python 内置的 @lru_cache 是一个经典装饰器,用于缓存昂贵函数的计算结果:

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(50))  # 第二次调用极快

装饰器的常见用途

  • 日志记录:跟踪函数调用、参数和返回值
  • 身份验证:在 Flask/Django 中控制访问权限
  • 执行时间测量:性能分析
  • 重试机制:网络请求失败时自动重试
  • 输入验证:在函数执行前校验参数

总结

装饰器让你能在不修改源码的情况下动态改变函数、方法或类的行为。它遵循 DRY(Don't Repeat Yourself) 原则,广泛应用于:

  • Web 框架中的权限控制(Flask、Django)
  • 日志系统
  • 性能监控
  • 同步控制

最佳实践回顾

  1. 使用 @functools.wraps 保留元数据
  2. 通用装饰器使用 *args, **kwargs
  3. 复杂逻辑考虑类式装饰器
  4. 多装饰器注意堆叠顺序
  5. 谨慎处理异常和返回值

掌握装饰器,你将写出更优雅、更 Pythonic 的代码!