python装饰器
装饰器
一、概念
-
学习文章
-
装饰器的前提
- python代码不像其他语言,它的函数可以像普通变量一样作为参数传递
1. 什么是装饰器?
-
装饰器是Python语言的一个特性,本质上是一个 Python 函数或类,可以在不修改原函数代码的情况下,为函数添加新的功能。
-
装饰器返回值
- 是一个函数/类对象
2. 装饰器的作用?
- 在不改动原来的函数代码情况下,为函数添加新的功能, 有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用
3. 需求场景
-
常用于插入日志、性能测试、事务处理、缓存、权限校验等场景
-
计时器:用于统计函数的执行时间,通常用于优化性能。
-
缓存:用于缓存函数的计算结果,以减少函数执行时间。
-
异常处理:用于处理函数抛出的异常,通常用于增加代码的健壮性。
-
权限控制:用于控制函数的访问权限,通常用于增强应用程序的安全性。
-
日志记录:用于记录函数执行的日志信息,通常用于调试和分析应用程序。
-
重试机制:用于在函数执行失败时自动重试,通常用于提高应用程序的可靠性。
-
AOP编程:用于实现面向切面编程,在不修改原函数代码的情况下增加新的功能。
-
Web框架:用于实现Web应用程序,通常用于控制URL路由、权限管理、请求处理等。
二、简单的装饰器
1. 没有装饰器前,如果要给一个函数加入日志功能,需要额外去写一个添加日志的逻辑,这会造成重复代码的冗余,
-
def foo(): print('i am foo') def foo(): print('i am foo') logging.info("foo is running")
2. 如果把日志逻辑放入一个新的函数中,又会破坏了原有的代码结构
-
def use_logging(func): logging.warn("%s is running" % func.__name__) func() def foo(): print('i am foo') # 日志函数的使用 use_logging(foo)
3. 简单的装饰器
-
def use_logging(func): def wrapper(): logging.warn("%s is running" % func.__name__) return func() # 把 foo 当做参数传递进来时,执行func()就相当于执行foo() return wrapper def foo(): print('i am foo') foo = use_logging(foo) # 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,这条语句相当于 foo = wrapper foo()
-
use_logging 就是一个装饰器,它一个普通的函数,它把执行真正业务逻辑的函数 func 包裹在其中,看起来像 foo 被 use_logging 装饰了一样,use_logging 返回的也是一个函数,这个函数的名字叫 wrapper。
-
在这个例子中,函数进入和退出时 ,被称为一个横切面,这种编程方式被称为面向切面的编程
三、@语法糖 - Syntactic sugar
- @ 符号是装饰器的语法糖,放在函数定义的地方,可以省略再次赋值的操作, 使得Python代码的编写更加高效、简洁,同时也提高了代码的可读性和可维护性
import logging
def use_logging(func):
def wrapper():
logging.warning("%s is running" % func.__name__)
return func()
return wrapper
@use_logging
def foo():
print("i am foo")
foo()
-
效果
- 有了 @ ,我们就可以省去
foo = use_logging(foo)
这一句了,直接调用 foo() 即可得到想要的结果
- 有了 @ ,我们就可以省去
-
优点
- 只需在定义的地方加上装饰器,调用的时候还是和以前一样,如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性
-
使用语法糖的前提(原因)
- 装饰器在 Python 使用如此方便都要归因于 Python 的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。
四、装饰器与参数
1. 装饰器接受单个参数
- 当需要业务逻辑函数需要参数
import logging
def use_logging(func):
def wrapper(name):
logging.warning("%s is running" % func.__name__)
return func(name)
return wrapper
@use_logging
def foo(name):
print("i an {0}".format(name))
foo('binge')
2. 装饰器接受多个参数
- 通过* args 接受数组, **kwargs 接受 字典
import logging
def use_logging(func):
def wrapper(*args):
logging.warning("%s is running" % func.__name__)
return func(*args)
return wrapper
@use_logging
def foo(*args):
print("i an {0}".format(args))
foo('binge', 'zhuzhu')
4. 装饰器接受关键字参数
import logging
def use_logging(func):
def wrapper(*args, **kwargs):
logging.warning("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper
@use_logging
def foo(name, age=None, height=None):
print("i an {0}, age is {1}, height is {2}.".format(name, age, height))
foo('binge', 23, 170)
5. 带参数的装饰器
- 装饰器的语法允许我们在调用时,提供其它参数
- 比如, 可以在装饰器中指定日志的等级,因为不同业务函数可能需要的日志级别是不一样的
import logging
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warning("%s is running" % func.__name__)
elif level == "info":
logging.info("%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator
@use_logging(level="warn")
def foo(name='foo'):
print("i am %s" % name)
foo()
- 使用
@use_logging(level="warn")
调用的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中,等价于使用 @decorator
五、类装饰器
1. 什么是类装饰器,有什么作用?
-
使用类来实现装饰器被称为类装饰器,具有灵活度大、高内聚、封装性等优点。
-
优点有:
- 与函数装饰器不同的是,类装饰器可以更方便地维护状态信息,因为它可以在类的实例中存储状态,而函数装饰器则需要使用闭包来保存状态信息
2. 类装饰器的实现
-
使用类装饰器主要依靠类的
__call__
方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法 -
__call__
- 该方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@Foo
def bar():
print ('bar')
# 调用使用了类装饰器的函数,就会调用装饰器类实例的__call__函数
bar()
3. 使用类装饰器实现一个缓存装饰器
class Cache:
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
if args in self.cache:
return self.cache[args]
else:
result = self.func(*args)
self.cache[args] = result
return result
- 分析
- 定义了一个Cache类,它接收一个函数作为参数,并在实例化时创建了一个空的cache字典用于保存函数的计算结果
- 果当前的参数已经在cache字典中,则直接返回缓存的结果,否则调用原函数计算结果,并将结果保存到cache字典中,然后返回计算结果
- 作用
- 避免重复计算,提高函数的执行效率
- 使用类装饰器还可以实现更复杂的功能,例如事件触发、状态监控等
六、functools.wraps 装饰器
1. 当前装饰器的优缺点
- 优点
- 极大地复用了代码
- 缺点
- 原函数的元信息不见了,比如函数的
docstring
、__name__
、参数列表
- 原函数的元信息不见了,比如函数的
# 装饰器
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ # 输出 'with_logging'
print func.__doc__ # 输出 None
return func(*args, **kwargs)
return with_logging
# 函数
@logged
def f(x):
"""does some math"""
return x + x * x
logged(f)
-
结果
- 函数 f 被
with_logging
取代了,当然它的docstring
,__name__
就是变成了with_logging
函数的信息了
- 函数 f 被
-
解决办法:functools.wraps
2. 什么是functools.wraps
- functools.wraps是一个用于函数装饰器的工具函数, 它本身也是一个装饰器,作用是将被装饰的函数的元信息(如函数名、参数列表、文档字符串等)复制到装饰器函数中,以便于调试和文档生成等操作
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ # 输出 'f'
print func.__doc__ # 输出 'does some math'
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
七、装饰器的嵌套以及调用顺序
- 一个函数可以有多个装饰器
- 嵌套装饰器的执行顺序是从内到外,即最内层的装饰器先执行,最外层的装饰器最后执行
@a
@b
@c
def f ():
pass
- 等价于 f = a(b(c(f)))
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现