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

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

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

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

@

符号就能搞定。

解决方案

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

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

print

import functoolsdef 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_callsdef add(a, b):    """一个简单的加法函数。"""    print(f"正在执行 add({a}, {b})")    return a + b@log_callsdef 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

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

定义时执行:

add

函数被定义时,

log_calls(add)

会被立即调用。返回包装函数:

log_calls

函数执行,它接收

add

作为参数

func

,然后定义并返回一个名为

wrapper

的新函数。替换原函数: 此时,

add

这个名字不再指向原始的

add

函数,而是指向

log_calls

返回的那个

wrapper

函数。调用时执行: 当你之后调用

add(5, 3)

时,实际执行的是

wrapper(5, 3)

wrapper

函数内部会先打印一些信息,然后通过闭包机制访问到它“记住”的原始

add

函数,并调用它,获取结果,最后再打印一些信息,并将结果返回。

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

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

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

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

print

logging

语句。

import timeimport logginglogging.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_timedef complex_calculation(x, y):    time.sleep(0.1) # 模拟耗时操作    return x * y + 10complex_calculation(10, 20)

权限校验与认证 (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 wrapperclass User:    def __init__(self, name, authenticated=False, admin=False):        self.name = name        self.is_authenticated = authenticated        self.is_admin = admin@requires_admindef 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)

缓存 (Caching): 对于那些计算成本高昂且结果相对稳定的函数,我们可以用装饰器来缓存其返回值。当函数再次被相同的参数调用时,直接返回缓存结果,避免重复计算。

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))

重试机制 (Retry Mechanism): 当调用外部服务或执行可能失败的操作时,我们可能需要自动重试几次。一个重试装饰器可以优雅地处理这种场景。

import timeimport randomdef 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()) # 尝试调用,可能会重试几次

参数验证 (Argument Validation): 在函数内部对参数进行类型或值检查,可以用装饰器来集中处理,保持函数体的简洁。

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

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

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

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

wrapper

函数了。它需要先接收自己的参数,然后返回一个真正的装饰器函数,这个装饰器函数再接收被装饰的函数,最后返回

wrapper

。听起来有点绕,但看代码就清楚了。

import functoolsdef 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 resprint("调用 calculate_power(2, 3):")power_result = calculate_power(2, 3)print(f"calculate_power 函数的最终结果是: {power_result}n")

技巧和注意事项:

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

repeat

接收装饰器的参数(如

num_times

),它返回的是内层函数

decorator_repeat

decorator_repeat

才是那个接收被装饰函数

func

的“真正的”装饰器。

执行时机:

@repeat(num_times=3)

这行代码,首先会执行

repeat(num_times=3)

repeat

函数会立即执行,并返回

decorator_repeat

这个函数对象。然后,Python解释器再用

decorator_repeat

去装饰

say_hello

函数,即执行

say_hello = decorator_repeat(say_hello)

。所以,带参数的装饰器,它的参数是在定义被装饰函数时就确定了的,而不是在调用被装饰函数时才确定。

functools.wraps

的位置:

functools.wraps(func)

应该放在最内层的

wrapper

函数上,因为它负责将

func

的元信息复制到最终被返回的

wrapper

函数上。

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

num_times

)通过闭包机制,被

decorator_repeat

wrapper

函数“记住”并使用。

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

__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)@CountCallsdef say_whee():    print("Whee!")# say_whee() # 第一次调用# say_whee() # 第二次调用

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

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

以上就是Python怎么编写一个装饰器_Python装饰器原理与实战开发的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1372968.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
Python中NumPy计算加速:如何利用多进程避免数据拷贝瓶颈
上一篇 2025年12月14日 12:47:03
Python脚本中执行psql.exe并处理I/O重定向
下一篇 2025年12月14日 12:47:22

相关推荐

  • Flyway多数据库与多环境配置:实现测试与生产环境的灵活迁移管理

    本文深入探讨了Flyway在多数据库和多环境场景下的灵活配置策略,旨在解决开发、开发、测试与生产环境数据库迁移的挑战。文章首先分析了测试环境数据库选择的推荐方案,包括使用与生产一致的数据库服务或Testcontainers。随后,详细阐述了Flyway如何通过分离配置文件、编程化配置以及利用占位符来…

    2026年9月24日
    000
  • 无线鼠标的延迟还能被感知吗?

    无线鼠标延迟已低至0.8-1ms,媲美有线,罗技Lightspeed、雷蛇HyperSpeed技术成熟,办公电竞皆适用,职业选手广泛采用,仅极端电磁干扰、低电量或接收器位置不佳时可能影响性能。 无线鼠标的延迟在绝大多数情况下已经无法被普通用户感知了。 现在的顶级无线技术,比如罗技的Lightspee…

    2026年9月24日
    500
  • RTX 5070 Ti烧毁 出现一个大洞!竟然嫁接RX 580救活了

    RTX 5070 Ti烧毁 出现一个大洞!竟然嫁接RX 580救活了RTX 5070 Ti烧毁 出现一个大洞!竟然嫁接RX 580救活了RTX 5070 Ti烧毁 出现一个大洞!竟然嫁接RX 580救活了RTX 5070 Ti烧毁 出现一个大洞!竟然嫁接RX 580救活了

    10月13日,一则令人瞠目结舌的显卡修复案例引发关注。通常我们听说烧毁的显卡经过维修重新工作已经不算新鲜,但你见过PCB被打穿一个大洞还能救回来的吗?更离谱的是,修复过程中居然还“借”了另一块显卡的力量。 来自巴西的硬件发烧友兼维修高手Sidnelson和Paulo Gomes,近日就完成了这项近乎…

    2026年9月24日 用户投稿
    000
  • laravel怎么使用Str和Arr辅助类的常用方法_laravel Str/Arr辅助类常用方法教程

    Laravel的Str和Arr类提供字符串与数组处理方法,如Str::lower、Str::contains、Arr::get、Arr::pluck等,提升代码可读性与开发效率。 Laravel 提供了两个非常实用的辅助类 Str 和 Arr,用于处理字符串和数组。它们封装了许多常用操作,让代码更简…

    2026年9月24日
    000
  • VS Code微服务开发:Docker与Kubernetes集成

    VS Code通过Docker扩展实现本地容器化开发,支持自动生成Dockerfile、一键构建镜像及devcontainer环境一致性;2. Kubernetes扩展可连接集群并管理资源,结合Bridge to Kubernetes实现本地调试与集群网络集成;3. 使用Skaffold自动化构建部…

    2026年9月24日
    000
  • 使用正则表达式从JSON数组中提取JSON对象

    本文旨在提供一种使用Java正则表达式从包含多个JSON对象的JSON数组中提取单个JSON对象的方法。我们将详细介绍如何构建合适的正则表达式,并提供示例代码演示如何在Java中使用该表达式来实现JSON对象的提取,并对提取后的字符串进行优化处理,移除不必要的空白字符。 从JSON数组中提取JSON…

    2026年9月24日
    000
  • AI PC 新晋狠角色:5000 元价位 Arrow Lake 最优解 惠普战 66 2025 争当全能卷王

    AI PC 新晋狠角色:5000 元价位 Arrow Lake 最优解 惠普战 66 2025 争当全能卷王AI PC 新晋狠角色:5000 元价位 Arrow Lake 最优解 惠普战 66 2025 争当全能卷王AI PC 新晋狠角色:5000 元价位 Arrow Lake 最优解 惠普战 66 2025 争当全能卷王AI PC 新晋狠角色:5000 元价位 Arrow Lake 最优解 惠普战 66 2025 争当全能卷王

    在 5000 元级别的主流商务本市场,长久以来似乎都遵循着一套 ” 潜规则 “:追求性能就得牺牲便携,看重耐用又往往在外观和屏幕上妥协,想要全面的接口以及优质的售后服务,预算就得一加再加。但现在,一个 ” 新晋狠角色 ” 决意打破这一局面。 惠普商用产…

    2026年9月24日 用户投稿
    000
  • 智能平权下,燃油车如何升级?

    智能平权下,燃油车如何升级?智能平权下,燃油车如何升级?智能平权下,燃油车如何升级?智能平权下,燃油车如何升级?

    曾几何时,“智能驾驶是电动车的专属”成为汽车行业的共识。宝马、奔驰、奥迪等传统豪华品牌长期专注于机械精密性和驾驶质感,在智能化布局上尤为谨慎,一度被贴上保守与落后的标签。 与此同时,新能源品牌凭借智能化迅速打开市场缺口,成功构建起“电动即智能、燃油即传统”的认知框架,在舆论和市场销量中占据先机。 ☞…

    2026年9月24日 用户投稿
    000
  • mac怎么使用听写功能_mac听写输入开启方法

    首先启用高级听写功能,进入系统设置→键盘→听写,勾选“使用高级听写”并下载语言包;随后可设置快捷键(如双击Fn键)快速启动语音输入;在支持的应用中也可通过菜单栏“编辑→开始听写”直接调用;最后根据需要配置听写语言、自动纠正及连续听写选项以提升识别准确率。 如果您希望在Mac上通过语音输入文字以提高效…

    2026年9月24日
    200
  • Intel OpenCAS缓存加速方案

    open cas 架构概览:数据从hdd盘读取后被复制到open cas的缓存中,后续的读取操作从内存中进行,从而提高读写效率。在write-through模式下,所有数据同步刷新到open cas的ssd和后端的hdd中。在write-back模式下,数据同步写入到open cas的ssd中,然后…

    2026年9月24日
    400
  • 使用MySQL命令行客户端进行交互式管理

    使用MySQL命令行客户端进行交互式管理使用MySQL命令行客户端进行交互式管理使用MySQL命令行客户端进行交互式管理使用MySQL命令行客户端进行交互式管理

    mysql命令行客户端的常用命令包括:1. 使用mysql -u 用户名 -p命令连接数据库;2. 执行show databases;查看所有数据库;3. 使用use 数据库名;选择数据库;4. 使用select * from 表名;查询数据;5. 使用insert into 表名 (列1, 列2)…

    2026年9月24日 用户投稿
    500
  • 赋能AI未来!康盈半导体 AI 应用存储新品登陆 elexcon 2025 展会

    赋能AI未来!康盈半导体 AI 应用存储新品登陆 elexcon 2025 展会赋能AI未来!康盈半导体 AI 应用存储新品登陆 elexcon 2025 展会赋能AI未来!康盈半导体 AI 应用存储新品登陆 elexcon 2025 展会赋能AI未来!康盈半导体 AI 应用存储新品登陆 elexcon 2025 展会

    8 月 26 日,中国电子、嵌入式及半导体先进封测行业的风向标 ——elexcon2025 深圳国际电子展暨嵌入式展盛大开幕。作为本届展会的重磅环节之一,国产存储领军品牌康盈半导体携新而来,以 “小而不凡,速启 ai 未来” 为核心主题,正式发布 2025 年存储新品,同步拉开面向 ai 终端应用的…

    2026年9月24日 用户投稿
    000
  • Java中固定长度用户ID输入验证:解决int类型长度检查问题

    本文详细介绍了在Java程序中如何实现用户输入固定长度ID的验证机制。针对常见的int cannot be dereferenced错误,我们将探讨将ID作为字符串读取并进行长度及格式校验的最佳实践,并提供处理字母数字型和纯数字型ID的示例代码,确保数据输入的准确性和程序的健壮性。 引言:用户输入验…

    2026年9月24日
    500
  • 数据实时迁移同步工具 CloudCanal v5.2.0.0 发布,支持 SaaS 全托管

    cloudcanal 免费社区版 是 clougence 公司推出的一款全自研、可视化、自动化数据迁移同步工具,具备 结构迁移、数据迁移、数据同步、数据校验、数据订正 等功能,支持 60+ 款流行关系型数据库、实时数仓、消息中间件、缓存数据库和搜索引擎之间数据互通,其中包含国产数据库 oceanba…

    2026年9月24日
    000
  • VSCode如何实现AI代码反混淆 VSCode智能分析混淆代码的技巧

    vscode没有一键ai反混淆功能,但可通过智能扩展、调试器、ast查看器、代码格式化工具及外部ai工具集成来辅助分析和逐步还原混淆代码;2. 利用eslint、prettier等扩展提升代码可读性,通过“重命名符号”“转到定义”“查找引用”等功能追踪变量和函数流向,结合多光标编辑和代码片段进行手动…

    2026年9月24日
    100
  • Laravel 表单验证失败后保留输入值:最佳实践教程

    本文旨在帮助 Laravel 开发者解决表单验证失败后,如何保留用户已输入数据的问题。我们将深入探讨 withInput() 方法的使用,并提供清晰的代码示例,确保即使在验证失败的情况下,用户体验也能保持流畅。通过本文的学习,你将掌握在 Laravel 中优雅地处理表单验证,并提升应用的可用性。 在…

    2026年9月24日
    000
  • 怎么在mysql中创建数据库表 mysql建表完整流程解析

    在 mysql 中创建数据库表的步骤包括:1) 选择合适的数据类型,如 int、varchar、timestamp;2) 设置索引,如主键和唯一索引;3) 应用约束条件,如 not null 和 unique;4) 设计表结构以满足业务需求,如使用 foreign key 和 enum;5) 优化性…

    2026年9月24日
    000
  • 电脑网络连接限制为100Mbps怎么办_网速被限制的解决方法

    首先确认网卡是否支持千兆速率,进入设备管理器查看网络适配器型号并核实规格;接着在网卡属性中将“速度和双工”设置为1.0 Gbps全双工或自动协商;更换符合Cat 5e及以上标准的网线,确保物理连接可靠;检查路由器或交换机端口设置,确保未强制限制为100Mbps;更新或重装网卡驱动程序至最新版本;最后…

    2026年9月24日
    100
  • 生成Java中全范围正Double随机数的正确方法

    本文旨在指导开发者如何在Java中生成覆盖整个正Double范围的随机数,并解释了使用ThreadLocalRandom.nextDouble(Double.MIN_VALUE, Double.MAX_VALUE)可能产生偏差的原因。我们将提供一种基于位操作的替代方案,确保生成的随机数在Double…

    2026年9月24日
    100
  • 《明末:渊虚之羽》1.6更新奖励领不了?官方手把手教学来了!

    《明末:渊虚之羽》是一款类魂动作角色扮演游戏,故事发生在巴蜀之地,此时正值黑暗动荡的明末,战事四起,一场神秘的疫病催生了妖怪一般的生物。 今天早些时候我们曾报道,游戏官方发布了1.6版本更新公告,补丁大小约为5.3GB,其中包括豪华版专属内容、免费头饰和性能优化等内容。 官方表示,本次更新“豪华扩展…

    2026年9月24日
    100

发表回复

登录后才能评论
关注微信