0

0

Python怎么编写一个装饰器_Python装饰器原理与实战开发

冰火之心

冰火之心

发布时间:2025-09-21 16:10:01

|

197人浏览过

|

来源于php中文网

原创

Python装饰器核心是函数作为一等公民和闭包机制,通过@语法在不修改原函数代码的情况下为其添加新功能,如日志、权限控制、缓存等,提升代码复用性和可维护性。

python怎么编写一个装饰器_python装饰器原理与实战开发

Python装饰器,说白了,就是一种特殊函数,它能接收一个函数作为输入,然后给这个函数增加一些额外功能,最终返回一个全新的函数。它就像给你的老朋友穿上了一件新衣服,朋友还是那个朋友,但现在他可能更酷、更强大了,而且这一切都发生在不改变朋友本身代码的前提下。用起来很方便,一个

@
符号就能搞定。

解决方案

编写一个Python装饰器,核心在于理解函数作为一等公民的特性以及闭包的概念。最基础的装饰器,通常是一个接收函数、定义一个内部包装函数、然后返回这个包装函数的高阶函数。

我们先从一个最简单的例子开始。假设我们想在每次调用某个函数之前和之后都打印一些信息,但又不想每次都手动加

print

import functools

def log_calls(func):
    """
    这是一个简单的装饰器,用于记录函数被调用的信息。
    """
    @functools.wraps(func) # 这一行很重要,它能保留原函数的元信息,比如函数名、文档字符串等。
    def wrapper(*args, **kwargs):
        print(f"--- 函数 '{func.__name__}' 即将被调用 ---")
        # 执行原函数
        result = func(*args, **kwargs)
        print(f"--- 函数 '{func.__name__}' 调用完毕,返回值为: {result} ---")
        return result
    return wrapper

# 现在,我们用这个装饰器来装饰一个函数
@log_calls
def add(a, b):
    """一个简单的加法函数。"""
    print(f"正在执行 add({a}, {b})")
    return a + b

@log_calls
def greet(name, greeting="Hello"):
    """向指定的人打招呼。"""
    print(f"正在执行 greet('{name}', '{greeting}')")
    return f"{greeting}, {name}!"

# 调用被装饰的函数
print("调用 add(5, 3):")
sum_result = add(5, 3)
print(f"add 函数的最终结果是: {sum_result}\n")

print("调用 greet('Alice'):")
greet_result = greet("Alice")
print(f"greet 函数的最终结果是: {greet_result}\n")

print("调用 greet('Bob', greeting='Hi'):")
greet_result_hi = greet("Bob", greeting="Hi")
print(f"greet 函数的最终结果是: {greet_result_hi}\n")

# 如果没有 @log_calls 语法糖,手动装饰是这样的:
# original_add = add
# add = log_calls(original_add)
# print(add(1, 2))

在这个例子里,

log_calls
就是我们的装饰器。它接收一个函数
func
,内部定义了一个
wrapper
函数,
wrapper
函数负责在调用
func
前后添加逻辑,并最终返回
func
的执行结果。最后,
log_calls
返回这个
wrapper
函数。当我们把
@log_calls
放在
add
函数上方时,Python解释器其实做了一件等价于
add = log_calls(add)
的事情,也就是说,
add
这个名字现在指向的不再是原来的加法函数,而是
log_calls
返回的那个
wrapper
函数。

立即学习Python免费学习笔记(深入)”;

functools.wraps
非常关键,它能把原函数的一些重要元信息(比如
__name__
__doc__
__module__
等)复制到
wrapper
函数上。如果没有它,当你调试或者查看
add.__name__
时,你会发现它变成了
wrapper
,而不是
add
,这会给调试带来不小的麻烦。

Python装饰器的核心工作原理是什么?

在我看来,理解Python装饰器的核心,主要抓住两点:函数是“一等公民”闭包

首先,Python中的函数是“一等公民”(First-Class Citizen)。这意味着函数可以像普通变量一样被赋值给其他变量,可以作为参数传递给其他函数,也可以作为其他函数的返回值。正是因为这个特性,我们才能把一个函数(被装饰的函数)传给另一个函数(装饰器),让装饰器对它进行操作。

其次,也是更关键的一点,是闭包(Closure)。当我们定义

log_calls
装饰器时,它内部的
wrapper
函数引用了外部
log_calls
函数的参数
func
。当
log_calls
执行完毕并返回
wrapper
时,即使
log_calls
的局部作用域已经消失,
wrapper
函数仍然能够“记住”并访问到它被创建时所处的环境中的
func
变量。这种机制就是闭包。

所以,当Python解释器看到

@log_calls
装饰器语法糖时,它会做以下几步:

  1. 定义时执行:
    add
    函数被定义时,
    log_calls(add)
    会被立即调用。
  2. 返回包装函数:
    log_calls
    函数执行,它接收
    add
    作为参数
    func
    ,然后定义并返回一个名为
    wrapper
    的新函数。
  3. 替换原函数: 此时,
    add
    这个名字不再指向原始的
    add
    函数,而是指向
    log_calls
    返回的那个
    wrapper
    函数。
  4. 调用时执行: 当你之后调用
    add(5, 3)
    时,实际执行的是
    wrapper(5, 3)
    wrapper
    函数内部会先打印一些信息,然后通过闭包机制访问到它“记住”的原始
    add
    函数,并调用它,获取结果,最后再打印一些信息,并将结果返回。

整个过程巧妙地实现了在不修改原函数代码的情况下,为其添加新功能,这在很多场景下都非常有用,比如日志、权限控制、性能测量等等。

装饰器在实际项目中能解决哪些常见问题

实际开发中,装饰器简直是“万金油”,能优雅地解决很多跨领域、重复性的问题。它避免了代码的重复,让业务逻辑更聚焦。

  1. 日志记录与调试 (Logging & Debugging): 这是最常见的用途。我们经常需要知道哪个函数在什么时候被调用了,传入了什么参数,返回了什么结果,或者执行了多久。一个通用的日志装饰器能完美解决这些需求,而不需要在每个函数内部都写一堆

    print
    logging
    语句。

    import time
    import logging
    
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
    
    def log_and_time(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            logging.info(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            logging.info(f"{func.__name__} finished in {end_time - start_time:.4f}s. Result: {result}")
            return result
        return wrapper
    
    @log_and_time
    def complex_calculation(x, y):
        time.sleep(0.1) # 模拟耗时操作
        return x * y + 10
    
    complex_calculation(10, 20)
  2. 权限校验与认证 (Authentication & Authorization): 在Web应用中,很多视图函数都需要检查用户是否已登录,或者是否有足够的权限来访问某个资源。把这些检查逻辑封装成装饰器,能让视图函数专注于处理业务逻辑,而不是权限验证

    def requires_admin(func):
        @functools.wraps(func)
        def wrapper(user, *args, **kwargs):
            if not user.is_authenticated:
                raise PermissionError("User not logged in.")
            if not user.is_admin:
                raise PermissionError("User does not have admin privileges.")
            return func(user, *args, **kwargs)
        return wrapper
    
    class User:
        def __init__(self, name, authenticated=False, admin=False):
            self.name = name
            self.is_authenticated = authenticated
            self.is_admin = admin
    
    @requires_admin
    def delete_user_data(current_user, user_id):
        print(f"Admin '{current_user.name}' deleting data for user {user_id}")
        return True
    
    # try:
    #     admin_user = User("Alice", authenticated=True, admin=True)
    #     delete_user_data(admin_user, 123)
    #     guest_user = User("Bob", authenticated=True, admin=False)
    #     delete_user_data(guest_user, 456)
    # except PermissionError as e:
    #     print(e)
  3. 缓存 (Caching): 对于那些计算成本高昂且结果相对稳定的函数,我们可以用装饰器来缓存其返回值。当函数再次被相同的参数调用时,直接返回缓存结果,避免重复计算。

    Bolt.new
    Bolt.new

    Bolt.new是一个免费的AI全栈开发工具

    下载
    from functools import lru_cache
    
    @lru_cache(maxsize=None) # Python内置的LRU缓存装饰器
    def fibonacci(n):
        if n < 2:
            return n
        return fibonacci(n-1) + fibonacci(n-2)
    
    # 第一次计算会比较慢
    print(fibonacci(30))
    # 第二次计算(相同参数)会非常快,因为结果已被缓存
    print(fibonacci(30))
  4. 重试机制 (Retry Mechanism): 当调用外部服务或执行可能失败的操作时,我们可能需要自动重试几次。一个重试装饰器可以优雅地处理这种场景。

    import time
    import random
    
    def retry(max_attempts=3, delay=1):
        def decorator_retry(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                for attempt in range(1, max_attempts + 1):
                    try:
                        return func(*args, **kwargs)
                    except Exception as e:
                        print(f"Attempt {attempt} failed: {e}")
                        if attempt < max_attempts:
                            time.sleep(delay)
                raise Exception(f"Function {func.__name__} failed after {max_attempts} attempts.")
            return wrapper
        return decorator_retry
    
    @retry(max_attempts=5, delay=0.5)
    def unstable_api_call():
        if random.random() < 0.7: # 70%的几率失败
            raise ConnectionError("Simulated API connection error.")
        return "Data fetched successfully!"
    
    # print(unstable_api_call()) # 尝试调用,可能会重试几次
  5. 参数验证 (Argument Validation): 在函数内部对参数进行类型或值检查,可以用装饰器来集中处理,保持函数体的简洁。

这些例子只是冰山一角,装饰器在Web框架(如Flask、Django路由装饰器)、ORM(如SQLAlchemy的事件监听)、以及各种库中都扮演着重要角色,极大地提高了代码的复用性和可维护性。

编写带参数的装饰器有哪些技巧和注意事项?

带参数的装饰器,相比不带参数的,多了一层嵌套。这其实是理解装饰器更深一步的关键。

当装饰器本身需要接收参数时,它就不能直接返回

wrapper
函数了。它需要先接收自己的参数,然后返回一个真正的装饰器函数,这个装饰器函数再接收被装饰的函数,最后返回
wrapper
。听起来有点绕,但看代码就清楚了。

import functools

def repeat(num_times):
    """
    一个带参数的装饰器,让被装饰的函数重复执行指定次数。
    `num_times` 是装饰器自身的参数。
    """
    def decorator_repeat(func): # 这一层是真正的装饰器,它接收被装饰的函数
        @functools.wraps(func)
        def wrapper(*args, **kwargs): # 这一层是包装函数,它执行原函数
            print(f"--- 函数 '{func.__name__}' 将重复执行 {num_times} 次 ---")
            results = []
            for i in range(num_times):
                print(f"  执行第 {i+1} 次...")
                result = func(*args, **kwargs)
                results.append(result)
            print(f"--- 函数 '{func.__name__}' 执行完毕 ---")
            # 通常只返回最后一次执行的结果,或者根据需求返回所有结果
            return results[-1] if results else None
        return wrapper
    return decorator_repeat # 装饰器函数返回的是真正的装饰器

@repeat(num_times=3) # 这里 num_times=3 就是装饰器的参数
def say_hello(name):
    print(f"Hello, {name}!")
    return f"Hello result for {name}"

print("调用 say_hello('Charlie'):")
final_result = say_hello("Charlie")
print(f"say_hello 函数的最终结果是: {final_result}\n")

@repeat(num_times=2)
def calculate_power(base, exp):
    res = base ** exp
    print(f"{base}^{exp} = {res}")
    return res

print("调用 calculate_power(2, 3):")
power_result = calculate_power(2, 3)
print(f"calculate_power 函数的最终结果是: {power_result}\n")

技巧和注意事项:

  1. 多一层嵌套: 最直观的变化就是多了一层函数嵌套。外层函数

    repeat
    接收装饰器的参数(如
    num_times
    ),它返回的是内层函数
    decorator_repeat
    decorator_repeat
    才是那个接收被装饰函数
    func
    的“真正的”装饰器。

  2. 执行时机:

    • @repeat(num_times=3)
      这行代码,首先会执行
      repeat(num_times=3)
    • repeat
      函数会立即执行,并返回
      decorator_repeat
      这个函数对象。
    • 然后,Python解释器再用
      decorator_repeat
      去装饰
      say_hello
      函数,即执行
      say_hello = decorator_repeat(say_hello)
    • 所以,带参数的装饰器,它的参数是在定义被装饰函数时就确定了的,而不是在调用被装饰函数时才确定。
  3. functools.wraps
    的位置:
    functools.wraps(func)
    应该放在最内层的
    wrapper
    函数上,因为它负责将
    func
    的元信息复制到最终被返回的
    wrapper
    函数上。

  4. 参数传递: 装饰器参数(如

    num_times
    )通过闭包机制,被
    decorator_repeat
    wrapper
    函数“记住”并使用。

  5. 类作为装饰器: 除了函数,类也可以作为装饰器。如果一个类实现了

    __call__
    方法,它就可以被用作装饰器。当类被用作装饰器时,类的实例就是那个“包装函数”。

    class CountCalls:
        def __init__(self, func):
            functools.update_wrapper(self, func) # 类似 functools.wraps
            self.func = func
            self.num_calls = 0
    
        def __call__(self, *args, **kwargs):
            self.num_calls += 1
            print(f"函数 '{self.func.__name__}' 已被调用 {self.num_calls} 次")
            return self.func(*args, **kwargs)
    
    @CountCalls
    def say_whee():
        print("Whee!")
    
    # say_whee() # 第一次调用
    # say_whee() # 第二次调用

    类装饰器在需要维护状态(如上面的调用次数)时非常方便,因为状态可以直接存储在实例属性中。带参数的类装饰器也同样需要多一层嵌套,即外层函数返回一个类实例。

理解这些,就能更灵活地运用装饰器,为你的Python代码注入更多“魔力”和可维护性。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Python Web 框架 Django 深度开发
Python Web 框架 Django 深度开发

本专题系统讲解 Python Django 框架的核心功能与进阶开发技巧,包括 Django 项目结构、数据库模型与迁移、视图与模板渲染、表单与认证管理、RESTful API 开发、Django 中间件与缓存优化、部署与性能调优。通过实战案例,帮助学习者掌握 使用 Django 快速构建功能全面的 Web 应用与全栈开发能力。

166

2026.02.04

Python Flask框架
Python Flask框架

本专题专注于 Python 轻量级 Web 框架 Flask 的学习与实战,内容涵盖路由与视图、模板渲染、表单处理、数据库集成、用户认证以及RESTful API 开发。通过博客系统、任务管理工具与微服务接口等项目实战,帮助学员掌握 Flask 在快速构建小型到中型 Web 应用中的核心技能。

104

2025.08.25

Python Flask Web框架与API开发
Python Flask Web框架与API开发

本专题系统介绍 Python Flask Web框架的基础与进阶应用,包括Flask路由、请求与响应、模板渲染、表单处理、安全性加固、数据库集成(SQLAlchemy)、以及使用Flask构建 RESTful API 服务。通过多个实战项目,帮助学习者掌握使用 Flask 开发高效、可扩展的 Web 应用与 API。

81

2025.12.15

python中print函数的用法
python中print函数的用法

python中print函数的语法是“print(value1, value2, ..., sep=' ', end=' ', file=sys.stdout, flush=False)”。本专题为大家提供print相关的文章、下载、课程内容,供大家免费下载体验。

192

2023.09.27

python print用法与作用
python print用法与作用

本专题整合了python print的用法、作用、函数功能相关内容,阅读专题下面的文章了解更多详细教程。

19

2026.02.03

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

443

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

605

2023.08.10

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

870

2024.01.03

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 4.9万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号