Python的装饰器还是不会?来看看这篇文章(建议收藏)
点赞、收藏、加关注,下次找我不迷路
哈喽,各位 Python 爱好者们!今天咱们要一起来攻克 Python 里一个超实用却又让不少新手犯迷糊的知识点 —— 装饰器。别担心,全程用大白话,搭配例子,还有口诀和记忆诀窍,保准让你轻松弄懂,以后遇到装饰器再也不头疼!
一、啥是装饰器?先从生活例子说起
咱先不聊代码,聊聊生活里的事儿。比如说,你有一件普通的 T 恤(这就好比咱们写的一个普通函数),夏天穿挺舒服,但要是去参加个稍微正式点的场合,就显得有点随意了。这时候咋办呢?你可以给它套上一件帅气的外套(这外套就是装饰器啦),瞬间就变得更得体、更实用了。而且重点是,你的 T 恤本身没被破坏,还是原来的那件 T 恤,只是多了外套的加持。
回到 Python 里,装饰器的作用其实差不多。它就是一个函数(或者说一个工具),可以给其他函数添加额外的功能,比如日志记录、权限检查、性能统计等等。而且关键的是,原来的函数不需要做任何修改,就像 T 恤不用剪剪裁裁,直接套上外套就行。这就叫 "开放封闭原则",对函数功能开放扩展,对函数本身封闭修改,是不是很巧妙?
二、先看一个简单例子,感受装饰器的魅力
咱先写一个简单的函数,比如计算两个数的和:
def add(a, b):
return a + b
现在,我们想在调用这个函数前后,打印一些日志,记录函数的调用情况。如果不用装饰器,咱们可能会这么做:
def add(a, b):
print(f"开始调用add函数,参数是{a}和{b}")
result = a + b
print(f"add函数调用结束,结果是{result}")
return result
这样确实能实现功能,但问题来了,如果有很多个函数都需要添加日志功能,难道每个函数都要这么改吗?那得多麻烦啊,而且违反了 "开放封闭原则"。这时候装饰器就派上用场了!
咱们来定义一个装饰器函数,专门给其他函数添加日志功能:
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"开始调用{func.__name__}函数,参数是{args}和{kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__}函数调用结束,结果是{result}")
return result
return wrapper
然后,我们就可以用这个装饰器来装饰我们的 add 函数啦:
@log_decorator
def add(a, b):
return a + b
这样,当我们调用 add (3, 5) 时,就会自动打印日志,而且 add 函数本身没有任何修改。是不是很神奇?这就是装饰器的基本用法。
三、装饰器的定义和基本结构
(一)装饰器的定义
装饰器本质上就是一个函数,它接收一个函数作为参数,返回一个新的函数。这个新的函数通常会在原函数的基础上添加一些额外的功能。
(二)基本结构
装饰器一般有三层结构:
层次 | 名称 | 作用 |
第一层 | 装饰器函数(外函数) | 接收被装饰的函数作为参数 |
第二层 | 包装函数(内函数) | 定义新的功能逻辑,包裹原函数 |
第三层 | 原函数 | 被装饰的函数,保持不变 |
就像咱们上面的 log_decorator 就是外函数,接收 func(原函数)作为参数;wrapper 是内函数,在里面添加了日志功能,然后调用原函数 func,并返回结果。
四、装饰器离不开闭包,啥是闭包?
在讲装饰器的时候,经常会提到闭包,那闭包到底是啥呢?其实闭包就是一个内部函数引用了外部函数的变量,并且外部函数返回了这个内部函数,这样就在内部函数和外部函数的变量之间形成了一个封闭的环境,这个内部函数就被称为闭包。
举个简单的例子:
def outer():
x = 10
def inner():
print(x)
return inner
这里的 inner 函数就是一个闭包,它引用了 outer 函数中的变量 x,当我们调用 outer () 返回 inner 函数后,即使 outer 函数已经执行完毕,x 的值仍然被保留在 inner 函数中,这就是闭包的作用。装饰器正是利用了闭包的这个特性,才能在不修改原函数的情况下,为其添加功能。
五、装饰器的常见应用场景
装饰器在实际开发中应用非常广泛,下面咱们列举几个常见的场景:
(一)日志记录
就像咱们上面的例子,给函数添加日志,记录函数的调用时间、参数、结果等信息,方便调试和监控。
(二)权限校验
比如在一个网站中,有些函数(比如修改用户信息的函数)需要用户登录后才能调用,我们可以用装饰器来检查用户是否登录,如果没登录就提示用户先登录。(Python的Web开发框架常用到这种功能)
def login_required(func):
def wrapper(*args, **kwargs):
if not is_logged_in(): # 假设is_logged_in()是检查用户是否登录的函数
print("请先登录!")
return
return func(*args, **kwargs)
return wrapper
(三)性能测试
我们可以用装饰器来计算函数的执行时间,看看函数运行得快不快,有没有优化的空间。
import time
def timer(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}秒")
return result
return wrapper
(四)缓存结果
对于一些计算耗时的函数,如果我们希望多次调用时不需要重复计算,可以用装饰器来缓存函数的结果。Python 中有一个 lru_cache 装饰器就是专门做这个的。
六、带参数的装饰器,稍微复杂点,但也不难
前面咱们的装饰器都是不带参数的,那如果装饰器需要接收参数呢?比如,我们想让日志装饰器可以自定义日志的前缀,这时候就需要带参数的装饰器了。
带参数的装饰器其实就是在原来的装饰器外面再套一层函数,用来接收参数。咱们来看例子:
def log_decorator_with_param(prefix):
def decorator(func):
def wrapper(*args, **kwargs):
print(f"{prefix}开始调用{func.__name__}函数,参数是{args}和{kwargs}")
result = func(*args, **kwargs)
print(f"{prefix}{func.__name__}函数调用结束,结果是{result}")
return result
return wrapper
return decorator
使用的时候就需要多一层调用,比如:
@log_decorator_with_param("【重要日志】")
def add(a, b):
return a + b
这样,日志的前缀就变成了 "【重要日志】",可以根据不同的需求传入不同的参数,是不是很灵活?
七、装饰器的语法糖 @,到底是啥?
咱们前面一直在用 @符号来应用装饰器,比如 @log_decorator,这个 @符号其实就是 Python 提供的一个语法糖,让我们使用装饰器更方便。它的本质就是把被装饰的函数作为参数传递给装饰器函数,并将返回的新函数赋值给原来的函数名。
比如,@log_decorator 等价于:
add = log_decorator(add)
有了这个语法糖,代码看起来更简洁,可读性更好。
八、记忆诀窍
(一)注意事项
- 装饰器的顺序很重要,如果有多个装饰器装饰同一个函数,装饰器的执行顺序是从下往上的,而调用顺序是从上往下的。
- 装饰器会改变原函数的元信息(比如函数名、文档字符串等),如果需要保留原函数的元信息,可以使用 functools.wraps 装饰器,比如在包装函数上加上 @functools.wraps (func)。
(二)记忆诀窍
咱们来编个口诀,帮助大家记忆装饰器的核心要点:
" 装饰器,像外套,函数穿上功能妙;
闭包里面套一套,原函不改新效到;
@符号,真方便,语法糖儿来相伴;
参数若要传进去,外层再把函数建;
日志权限和性能,各种场景都能用;
新手别怕慢慢学,多练多写就会懂!"
装饰器是 Python 中非常强大和实用的一个特性,学会使用装饰器可以让我们的代码更加简洁、优雅,提高代码的复用性。新手朋友们刚开始可能会觉得有点难,没关系,多动手写几个例子,结合咱们的口诀和记忆诀窍,慢慢就会掌握啦。希望这篇文章能让你对装饰器有一个清晰的认识,以后在编程中能灵活运用装饰器来提升自己的代码质量。
如果大家还有什么疑问,或者想了解更多 Python 编程技巧,欢迎在评论区留言,咱们一起交流学习!