python必学-装饰器
一、概念及作用
Python 装饰器(Decorator)允许在不修改原有函数或类代码的情况下,为其动态地添加新功能。类比于生活中的“包装袋”,在不改变内部物品(原函数)的前提下,增加新的特性,如印上Logo(日志记录)等。
应用到项目中,设想一个场景,为项目中的多个函数都加上计时功能,以监控它们的性能,该如何设计?如果不使用装饰器,可能需要在每个函数的开头和结尾都写上记录时间的代码,这会导致大量重复代码。装饰器就是为了解决这类“横切面”而生的,它将通用逻辑(如日志、计时、权限校验)封装起来,然后通过 @ 语法糖应用到需要的地方,让代码更简洁、优雅且易于复用。
二、pyhton装饰器的核心原理
基于以下三个 Python 的核心概念:
-
函数即对象:在 Python 中,函数可以被赋值给变量、作为参数传递给其他函数,也可以作为另一个函数的返回值。
-
高阶函数:装饰器本质上就是一个高阶函数,它接收一个函数作为参数,并返回一个新的函数。
-
闭包:装饰器通常利用闭包来“记住”被装饰的函数,并在其内部定义的包装函数中使用它。
也就是说,装饰器在函数定义时就被执行了,它用返回的新函数替换了原来的函数。
原理拆解:
# 函数也是一个对象,也可以作为参数。且函数对象可以作为返回值,赋值给变量。
def decor_a(func):
def decor_b():
print("decor b")
func()
print("after decor b")
return decor_b
def decor_c():
print("decor_c")
decor_c = decor_a(decor_c)
decor_c()
输出结果:
decor b
decor_c
after decor b
三、用法
1、无参数装饰器(基础装饰器)
def my_decorator(func):
def wrapper():
print("在函数执行前做点什么")
func() # 调用原函数
print("在函数执行后做点什么")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
# 输出:
# 在函数执行前做点什么
# Hello!
# 在函数执行后做点什么
2、支持任意参数的装饰器
为了让装饰器能应用于任何函数,包装函数需要使用*args和**kwargs来接收并传递所有参数。
def logger(func):
def wrapper(*args, **kwargs):
print(f"正在调用函数: {func.__name__}")
result = func(*args, **kwargs)
# 记得要返回原函数的结果
return result
return wrapper
@logger
def add(a, b):
return a + b
print(add(2, 6))
# 输出:
# 正在调用函数: add
# 8
3、保留原函数元信息 (@functools.wraps)
使用装饰器后,原函数的__name__、__doc__等属性会变成包装函数的属性。为了保留这些信息,必须使用functools.wraps。这是一个非常重要的好习惯。
import functools
def logger(func):
# 修复元信息
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"正在调用: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@logger
def my_func():
"""测试字符串"""
pass
print(my_func.__name__) # 输出: my_func (而不是 wrapper)
print(my_func.__doc__) # 输出: 测试字符串
4、类装饰器
如果装饰器需要维护状态(如统计函数被调用的次数),使用类来实现会更清晰。关键是实现__call__方法,使类的实例可以像函数一样被调用。
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} 已被调用 {self.count} 次")
return self.func(*args, **kwargs)
@CountCalls
def greet(name):
print(f"Hello, {name}")
greet("Tom")
# 输出: greet 已被调用 1 次; Hello, Tom
greet("Bob")
# 输出: greet 已被调用 2 次; Hello, Bob
当一个函数被多个装饰器修饰时,它们的执行顺序是就近原则,即从下往上(由内向外)。
@a
@b
@c
def my_function():
pass
-
装饰阶段:先执行 c,再执行 b,最后执行 a。
-
调用阶段:调用 my_function() 时,会先进入 a 的逻辑,然后是 b,再到 c,最后才执行 my_function 本身。
更多推荐
所有评论(0)