Python函数如何用装饰器给函数添加额外功能 Python函数装饰器基础应用的入门技巧​

星夢妙者
发布: 2025-08-14 14:28:02
原创
466人浏览过

装饰器是一个接收函数并返回新函数的高阶函数,用于在不修改原函数代码的情况下添加额外功能;2. 实现装饰器需定义外层函数接收原函数,内层wrapper函数封装原函数并添加逻辑,最后返回wrapper;3. 使用@decorator语法糖可简洁地应用装饰器,等价于func = decorator(func);4. wrapper函数应使用*args和**kwargs接收任意参数,以支持带参数的原函数;5. 为保留原函数的\_\_name\_\_、\_\_doc\_\_等元信息,应使用functools.wraps装饰wrapper;6. 带参数的装饰器需再增加一层函数嵌套,外层接收装饰器参数,中间层为真正的装饰器,内层为wrapper。装饰器通过函数嵌套和闭包机制,实现了对函数行为的动态增强,同时保持代码的清晰与复用性。

Python函数如何用装饰器给函数添加额外功能 Python函数装饰器基础应用的入门技巧​

Python的装饰器,说白了,就是一种非常优雅且强大的语法糖,它允许你在不修改原始函数代码的前提下,给函数动态地添加一些额外的功能或者修改它的行为。你可以把它想象成给函数穿上一件“外套”,这件外套能做很多事情,比如日志记录、性能监控、权限校验等等,而函数本身对此毫不知情,或者说,它根本不需要关心这些。

解决方案

要用装饰器给函数添加额外功能,核心思路是创建一个“高阶函数”。这个高阶函数会接收一个函数作为参数,然后返回一个新的函数。这个新的函数通常是一个“闭包”,它在内部调用了原始函数,并在调用前后(或者根据需要)执行一些额外的逻辑。Python通过

@
登录后复制
符号提供了一种非常方便的语法糖来使用装饰器,让代码看起来更简洁直观。

举个例子,假设我们想给一个函数添加一个日志功能,每次函数执行前都打印一条信息,执行后也打印一条。

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

def log_decorator(func):
    """这是一个简单的日志装饰器"""
    def wrapper(*args, **kwargs):
        print(f"--- 函数 '{func.__name__}' 即将执行 ---")
        result = func(*args, **kwargs) # 调用原始函数
        print(f"--- 函数 '{func.__name__}' 执行完毕 ---")
        return result
    return wrapper

@log_decorator
def greet(name):
    """一个简单的问候函数"""
    print(f"你好,{name}!")
    return f"Hello, {name}!"

@log_decorator
def calculate_sum(a, b):
    """计算两个数的和"""
    print(f"正在计算 {a} + {b}...")
    return a + b

# 使用被装饰的函数
greet("小明")
print("-" * 20)
total = calculate_sum(10, 20)
print(f"计算结果是: {total}")
登录后复制

在这个例子里,

log_decorator
登录后复制
就是我们的装饰器。它接收
greet
登录后复制
calculate_sum
登录后复制
函数,然后返回一个
wrapper
登录后复制
函数。当我们调用
greet("小明")
登录后复制
时,实际上执行的是
log_decorator
登录后复制
返回的那个
wrapper
登录后复制
函数,这个
wrapper
登录后复制
函数在内部调用了真正的
greet
登录后复制
函数,并在前后添加了日志打印。

装饰器究竟是个什么“东西”?

说实话,刚接触装饰器的时候,我个人觉得它有点绕,尤其是那个嵌套的函数结构。但当你理解了它的本质,就会发现它其实是Python里“函数是第一类对象”这个概念的极致体现。简单来说,装饰器就是一个能接受函数作为输入,并返回一个新函数的函数。

用更具体一点的说法,当你写下

@some_decorator
登录后复制
在函数定义上方时,这行代码等价于:

def original_function():
    pass

# original_function = some_decorator(original_function)
# 语法糖的背后就是这样
登录后复制

它把

original_function
登录后复制
作为参数传给了
some_decorator
登录后复制
,然后把
some_decorator
登录后复制
的返回值(通常是另一个函数,也就是我们上面例子里的
wrapper
登录后复制
)重新赋值给了
original_function
登录后复制
这个名字。所以,之后你再调用
original_function()
登录后复制
时,实际上调用的是被装饰器“加工”过的新函数。

这种设计哲学,我个人觉得非常棒,它让代码的关注点分离变得更容易。比如,你的核心业务逻辑可以保持干净,而那些通用的、横切面的功能(像日志、缓存、权限等)则可以通过装饰器优雅地注入进来,互不干扰。

写一个最简单的装饰器,我该从何入手?

要写一个最简单的装饰器,你得先理解那个“两层嵌套”的结构。这确实是初学者的一道坎,但一旦跨过去,就豁然开朗了。

即构数智人
即构数智人

即构数智人是由即构科技推出的AI虚拟数字人视频创作平台,支持数字人形象定制、短视频创作、数字人直播等。

即构数智人 36
查看详情 即构数智人

步骤拆解:

  1. 定义外层函数: 这个函数就是你的装饰器本身,它接受一个参数,这个参数就是你要装饰的那个函数(我们通常称之为
    func
    登录后复制
    original_func
    登录后复制
    )。
  2. 定义内层函数(wrapper): 在外层函数内部,你需要定义另一个函数,我们习惯叫它
    wrapper
    登录后复制
    。这个
    wrapper
    登录后复制
    函数才是真正用来替代原始函数的。它会负责调用原始函数,并在调用前后加入你想要实现的额外逻辑。
  3. 返回内层函数: 外层函数最后需要返回这个
    wrapper
    登录后复制
    函数。

我们来写一个最最简单的,比如一个统计函数执行时间的装饰器:

import time

def timer_decorator(func):
    """一个简单的计时装饰器"""
    def wrapper(*args, **kwargs): # 注意这里,需要接受任意参数
        start_time = time.time()
        result = func(*args, **kwargs) # 调用原始函数
        end_time = time.time()
        print(f"函数 '{func.__name__}' 执行耗时: {end_time - start_time:.4f} 秒")
        return result
    return wrapper

@timer_decorator
def long_running_task():
    """一个模拟耗时操作的函数"""
    print("开始执行耗时任务...")
    time.sleep(2) # 模拟IO或计算密集型操作
    print("耗时任务完成。")
    return "任务结果"

@timer_decorator
def greet_person(name, age):
    print(f"你好,{name}!你今年{age}岁了。")
    time.sleep(0.5)
    return "问候完毕"

# 调用被装饰的函数
task_result = long_running_task()
print(f"任务最终结果: {task_result}")
print("-" * 20)
greet_person("张三", 30)
登录后复制

这里有个小细节,你可能注意到了

wrapper(*args, **kwargs)
登录后复制
。这是为了确保你的装饰器能够处理任何带有参数(位置参数和关键字参数)的函数。如果没有它们,你的装饰器就只能装饰那些不带参数的函数了,这显然不符合实际需求。

还有一个常见的问题,就是被装饰后的函数,它的

__name__
登录后复制
__doc__
登录后复制
等元信息会变成
wrapper
登录后复制
函数的元信息,而不是原始函数的。这在调试或者一些框架反射时会造成困扰。解决方案也很简单,Python标准库里提供了
functools.wraps
登录后复制
这个装饰器,它可以帮助我们把原始函数的元信息“拷贝”到
wrapper
登录后复制
函数上。

import time
from functools import wraps # 导入wraps

def timer_decorator_with_wraps(func):
    """一个带functools.wraps的计时装饰器"""
    @wraps(func) # 使用wraps来保留原始函数的元信息
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"函数 '{func.__name__}' 执行耗时: {end_time - start_time:.4f} 秒")
        return result
    return wrapper

@timer_decorator_with_wraps
def another_task():
    """这是另一个任务的文档字符串"""
    time.sleep(1)
    return "另一个任务完成"

print(f"函数名: {another_task.__name__}") # 输出会是 'another_task' 而不是 'wrapper'
print(f"文档: {another_task.__doc__}")
登录后复制

这下,

another_task
登录后复制
__name__
登录后复制
__doc__
登录后复制
就正确了,这在实际开发中非常有用。

装饰器能处理带参数的函数吗?

当然可以,而且在实际应用中,绝大多数被装饰的函数都是带参数的。前面在写

timer_decorator
登录后复制
的时候,我已经悄悄地展示了这一点:
wrapper(*args, **kwargs)
登录后复制
就是处理带参数函数的关键。

*args
登录后复制
会收集所有传递给
wrapper
登录后复制
的位置参数,并将它们打包成一个元组。
**kwargs
登录后复制
会收集所有传递给
wrapper
登录后复制
的关键字参数,并将它们打包成一个字典。

然后,你只需要把这些收集到的参数原封不动地传递给原始函数

func(*args, **kwargs)
登录后复制
,这样无论原始函数需要什么参数,都能正确地接收到。

我们再来看一个稍微复杂一点的例子,比如一个权限检查的装饰器,它需要知道当前用户是否有权限访问某个函数,而这个函数本身可能也需要参数:

from functools import wraps

# 模拟一个用户权限列表
AUTHORIZED_USERS = {"admin", "editor"}

def permission_required(allowed_roles):
    """
    一个需要权限的装饰器,装饰器本身也带参数。
    它需要一个参数来指定哪些角色被允许。
    """
    def decorator(func): # 这是真正的装饰器函数,它接收被装饰的函数
        @wraps(func)
        def wrapper(current_user, *args, **kwargs): # wrapper需要接收用户参数以及原始函数的其他参数
            if current_user in allowed_roles:
                print(f"用户 '{current_user}' 权限通过,执行 '{func.__name__}'。")
                return func(current_user, *args, **kwargs) # 传递所有参数给原始函数
            else:
                print(f"用户 '{current_user}' 没有权限执行 '{func.__name__}'。所需权限: {allowed_roles}")
                return None # 或者抛出异常

        return wrapper
    return decorator

@permission_required(allowed_roles=AUTHORIZED_USERS)
def view_sensitive_data(user, data_id):
    """查看敏感数据的功能"""
    print(f"用户 '{user}' 正在查看数据ID: {data_id}")
    return f"敏感数据 {data_id} 的详细内容。"

@permission_required(allowed_roles={"admin"})
def delete_user_account(user, user_to_delete):
    """删除用户账户的功能"""
    print(f"用户 '{user}' 正在尝试删除用户: {user_to_delete}")
    return f"用户 '{user_to_delete}' 已被删除。"

# 测试
print("--- 尝试查看敏感数据 ---")
result1 = view_sensitive_data("admin", "ABC123")
print(f"结果: {result1}\n")

result2 = view_sensitive_data("guest", "XYZ789")
print(f"结果: {result2}\n")

print("--- 尝试删除用户账户 ---")
result3 = delete_user_account("admin", "john_doe")
print(f"结果: {result3}\n")

result4 = delete_user_account("editor", "jane_doe")
print(f"结果: {result4}\n")
登录后复制

这个例子里,

permission_required
登录后复制
本身也是一个函数,它接受
allowed_roles
登录后复制
参数,然后返回一个真正的装饰器函数
decorator
登录后复制
。这个
decorator
登录后复制
再接收
func
登录后复制
,并返回
wrapper
登录后复制
。这就是所谓的“带参数的装饰器”,它比前面不带参数的装饰器多了一层嵌套,但核心思想没变。
wrapper
登录后复制
函数依然通过
*args
登录后复制
**kwargs
登录后复制
完美地处理了原始函数
view_sensitive_data
登录后复制
delete_user_account
登录后复制
可能带有的其他参数。

所以,无论是原始函数有没有参数,装饰器都能灵活地处理,关键就在于

wrapper
登录后复制
函数签名中的
*args
登录后复制
**kwargs
登录后复制
,以及你如何把它们传给原始函数。

以上就是Python函数如何用装饰器给函数添加额外功能 Python函数装饰器基础应用的入门技巧​的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

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