第一章、装饰器概述
一、定义
装饰器(Decorator)是 Python 中一种强大而灵活的语法特性,用于修改或扩展函数或方法的行为。装饰器本质上是一个函数,它接受一个函数作为输入,并返回一个新的函数。装饰器通常用于修改函数的功能、添加额外的逻辑、或者执行预处理和后处理等操作。
装饰器本质上是一个Python函数(其实就是闭包),它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。装饰器用于有以下场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
装饰器就是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能
1、符合OCP开放封闭原则
装饰器的使用符合了面向对象编程的开放封闭原则。
开放封闭原则主要体现在两个方面:
- 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
- 对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类进行任何修改。
二、装饰器的意义
使用装饰器之前,我们要知道,其实python里是万物皆对象,也就是万物都可传参。
函数也可以作为函数的参数进行传递的。
通过下面这个简单的例子可以更直观知道函数名是如何直接作为参数进行传递
def jzq():
print("我是jzq")
def blog(name):
print("进入blog函数")
name()
print("退出blog函数")
if __name__ == '__main__':
func = jzq # 这里把jzq这个函数名赋值给变量func
func() # 执行func函数
print("*" * 20)
blog(jzq) # 把jzq这个函数当做参数传递给blog函数
执行结果如下所示:
接下来,我想知道这jzq和blog
两个函数分别的执行时间是多少,我就把代码修改如下:
import time
def jzq():
start = time.time()
print("我是jzq")
time.sleep(2)
print(f"执行时间为:{time.time() - start}")
def blog(name):
start = time.time()
print("进入blog函数")
name()
print("退出blog函数")
print(f"执行时间为:{time.time() - start}")
if __name__ == '__main__':
func = jzq # 这里把jzq这个函数名赋值给变量func
func() # 执行func函数
print("*" * 20)
blog(jzq) # 把jzq这个函数当做参数传递给blog函数
上述的改写已经实现了我需要的功能,但是,当我有另外一个新的函数【python_blog_list】,具体如下:
def python_blog_list():
print("""[Python IO流]
https://blog.youkuaiyun.com/Fddadd/article/details/132813191""")
print("""[Python面向对象设计原则]
https://blog.youkuaiyun.com/Fddadd/article/details/132134146""")
也需要计算函数执行时间的,那按之前的逻辑,就是改写如下:
def python_blog_list():
start = time.time()
print("""[Python IO流]
https://blog.youkuaiyun.com/Fddadd/article/details/132813191""")
print("""[Python面向对象设计原则]
https://blog.youkuaiyun.com/Fddadd/article/details/132134146""")
print("函数执行时间:{}".format(time.time() - start))
如果也要这样子写的话,不就重复造轮子了吗?虽说人类的本质是鸽王和复读机,但作为一个优秀的cv攻城狮(ctrl+c和ctrl+v)肯定是要想办法偷懒的呀
装饰器,就是可以让我们拓展一些原有函数没有的功能。
三、装饰器原理
装饰器实现分两步:
- 同名赋值:调用装饰器函数将被修饰的函数作为参数传入装饰器函数,将返回的闭包函数同名赋值
- 将增加的新功能代码以及被装饰函数运行代码func()一同打包返回,返回的是一个闭包函数(内部函数),这个被返回的函数就是装饰器
- 将被修饰的函数放入闭包函数中执行,只是增加了原被修饰函数的功能,原被修饰函数并未改变,所以使用原被修饰函数名接收增加了功能的自己
- 闭包函数调用:将第一步同名赋值得到闭包函数进行调用
基于上面的函数执行时间的需求,我们就手写一个简单的装饰器进行实现。
import time
def jzq():
print("我是jzq")
time.sleep(2)
def count_time(func):
def wrapper():
start = time.time()
func()
end = time.time()
print(f"执行时间为:{end - start}")
return wrapper
if __name__ == '__main__':
# 因为装饰器 count_time(jzq) 返回是函数对象 wrapper ,这条语句相当于 jzq = wrapper
jzq = count_time(jzq)
# 执行jzq() 就相当于执行 wrapper()
jzq()
这里的count_time是一个装饰器,装饰器函数里面定义一个wrapper函数,把func这个函数当作参数传入,函数实现的功能是把func包裹起来,并且返回wrapper函数。wrapper函数体就是要实现装饰器的内容。
当然,这里的wrapper函数名是可以自定义的,只要你定义的函数名,跟你return的函数名是相同的就好了
# 为函数添加一个统计运行时长的功能
import time
import threading
def how_much_time(func):
def inner(): # 闭包函数
t_start = time.time()
func()
t_end = time.time()
print("一共花费了{0}秒时间".format(t_end - t_start, ))
return inner
# 将增加的新功能代码以及被装饰函数运行代码func()一同打包返回,返回的是一个内部函数,这个被返回的函数就是装饰器
def sleep_5s():
time.sleep(5)
print("%d秒结束了" % (5,))
def sleep_6s():
time.sleep(6)
print("%d秒结束了" % (6))
sleep_5s = how_much_time(sleep_5s)
# 因为sleep_5s函数的功能就是睡5秒钟,虽然增加了统计运行时间的功能,但是他本身功能没变(还是睡5秒钟),所以仍然用原来函数名接收增加功能了的自己
sleep_6s = how_much_time(sleep_6s)
t1 = threading.Thread(target=sleep_5s)
t2 = threading.Thread(target=sleep_6s)
t1.start()
t2.start()
# 5秒结束了
# 一共花费了5.014161109924316秒时间
# 6秒结束了
# 一共花费了6.011810302734375秒时间
四、装饰器的语法糖@
1、语法糖@原理
装饰器语法糖
@decorator
实际上是一种更方便的写法,用于应用装饰器函数。这个语法糖的本质是将被装饰的函数传递给装饰器函数,并将装饰器的返回值赋给原始函数的名称。这样可以在不修改原始函数定义的情况下,通过在函数定义前使用@decorator
来应用装饰器。使用装饰器语法糖可以使得装饰器的应用更加清晰和简洁。这种写法不仅更易读,还符合 Pythonic 的风格。需要注意的是,装饰器语法糖只是一种更便捷的写法,其本质仍然是通过函数调用来应用装饰器。
python中的装饰器(decorator)一般采用语法糖的形式,是一种语法格式。比如:@classmethod,@staticmethod,@property,@xxx.setter,@wraps(),@func_name等都是python中的装饰器。
装饰器,装饰的对象是函数或者方法。各种装饰器的作用都是一样的:改变被装饰函数或者方法的功能,性质。
下面主要讲解@wraps(),@func_name,类装饰器这两种装饰器。
你如果看过其他python项目里面的代码,难免会看到@符号,这个@符号就是装饰器的语法糖。因此上面简单的装饰器还是可以通过语法糖来实现的,这样就可以省去
jzq = count_time(jzq)
这一句代码,而直接调用jzq()这个函数
换句话说,其实使用装饰器的是,默认传入的参数就是被装饰的函数。
import time
def count_time(func):
def wrapper():
start = time.time()
result = func()
end = time.time()
print(f"执行时间为:{end - start}")
return result
return wrapper
@count_time
def jzq():
print("我是jzq")
time.sleep(2)
if __name__ == '__main__':
# # 因为装饰器 count_time(jzq) 返回是函数对象 wrapper ,这条语句相当于 jzq = wrapper
# jzq = count_time(jzq)
# # 执行jzq() 就相当于执行 wrapper()
# jzq()
jzq()
2、示例
# 为函数添加一个统计运行时长的功能
import time
import threading
def how_much_time(func):
def inner():
t_start = time.time()
func()
t_end = time.time()
print("一共花费了{}秒时间".format(t_end - t_start, ))
return inner
@how_much_time
# @how_much_time等价于sleep_5s = how_much_time(sleep_5s)
def sleep_5s():
time.sleep(5)
print("%d秒结束了" % (5,))
@how_much_time
def sleep_6s():
time.sleep(6)
print("%d秒结束了" % (6,))
t1 = threading.Thread(target=sleep_5s)
t2 = threading.Thread(target=sleep_6s)
t1.start()
t2.start()
第二章、装饰器传参
一、概述
在装饰器函数内部的闭包函数(真正的装饰器)中定义形参(*args, **kwargs)
二、案例
1、统计时间案例
当我们的被装饰的函数是带参数的,此时要怎么写装饰器呢?
上面我们有定义了一个blog函数是带参数的
def blog(name):
print("进入blog函数")
name()
print("退出blog函数")
此时我们的装饰器函数要优化一下下,修改成为可以接受任意参数的装饰器
def count_time(func):
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
print(f"执行时间为:{end - start}")
return wrapper
此处,我们的wrapper函数的参数为*args和**kwargs,表示可以接受任意参数
这时我们就可以调用我们的装饰器了。
import time
def count_time(func):
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
print(f"执行时间为:{end - start}")
return wrapper
@count_time
def blog(name):
print("进入blog函数")
name()
print("退出blog函数")
if __name__ == '__main__':
def jzq():
print("我是jzq")
time.sleep(1)
# blog() # TypeError: blog() missing 1 required positional argument: 'name'
blog(jzq)
2、装饰器演示登录案例
import time
def login(func):
def wrapper(*args, **kwargs):
username = input("请输入用户名:").strip()
password = input("请输入密码:").strip()
if username == "admin" and password == "123":
start_time = time.time()
res = func(*args, **kwargs)
end_time = time.time()
print(f"执行{func.__name__}()花费{end_time - start_time}秒")
return res
else:
print("用户名或密码错误")
return wrapper
@login
def index():
print("进入index页面")
time.sleep(1)
if __name__ == '__main__':
index()
第三章、带参数的装饰器
一、概述
在装饰器函数外在包一层外部函数,在该外部函数中传入参数
在 Python 中,装饰器可以传递参数,这使得装饰器更加灵活。装饰器可以接受参数,这些参数可以在装饰器内部进行处理,以影响被装饰的函数的行为。为了传递参数给装饰器,需要在装饰器函数外再嵌套一层函数。
二、案例
1、案例一
前面咱们知道,装饰器函数也是函数,既然是函数,那么就可以进行参数传递,咱们怎么写一个带参数的装饰器呢?
前面咱们的装饰器只是实现了一个计数,那么我想在使用该装饰器的时候,传入一些备注的msg信息,怎么办呢?咱们一起看一下下面的代码
import time
def count_time_args(msg):
def count_time(func):
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
print(f"[{msg}]执行时间为:{end - start}")
return wrapper
return count_time
@count_time_args("fun_one")
def func_one():
print("fun_one")
time.sleep(1)
@count_time_args("fun_two")
def func_two():
print("fun_two")
time.sleep(1)
@count_time_args("fun_three")
def func_three():
print("fun_three")
time.sleep(1)
if __name__ == '__main__':
func_one()
func_two()
func_three()
"""
fun_one
[fun_one]执行时间为:1.0002338886260986
fun_two
[fun_two]执行时间为:1.0010912418365479
fun_three
[fun_three]执行时间为:1.000817060470581
"""
基于原来的count_time函数外部再包一层用于接收参数的count_time_args,接收回来的参数就可以直接在内部的函数里面调用了。
2、案例二
def my_decorator(param1, param2):
def decorator(func):
def wrapper(*args, **kwargs):
print(f"Decorator parameter: {param1}, {param2}")
result = func(*args, **kwargs)
return result
return wrapper
return decorator
# 使用装饰器语法糖传递参数
@my_decorator(param1="a", param2="b")
def say_hello(name):
print(f"Hello, {name}!")
# 调用被装饰后的函数
say_hello("Alice")
在上述示例中,my_decorator
是一个接受参数的装饰器工厂函数,它返回一个真正的装饰器函数 decorator
。decorator
函数才是真正应用于被装饰函数的装饰器。通过嵌套的结构,可以在装饰器内部使用传递的参数,以定制装饰器的行为。
等效的非语法糖写法如下:
def say_hello(name):
print(f"Hello, {name}!")
# 将函数传递给装饰器工厂,并传递参数
decorated_say_hello = my_decorator(param1="a", param2="b")(say_hello)
# 调用被装饰后的函数
decorated_say_hello("Alice")
这样,装饰器可以根据传递的参数在运行时动态地调整其行为。这种模式允许我们创建更具通用性和可配置性的装饰器。
第四章、类装饰器
一、概述
在 Python 中,类也可以用作装饰器。类装饰器是指实现了 __call__
方法的类,该方法使得对象可以像函数一样被调用。通过类装饰器,你可以在类的实例化、调用和销毁过程中执行一些额外的逻辑
1、__init__()方法
传入被修饰函数对象,返回修饰类的实例对象,并进行同名赋值给被修饰函数名
2、__call__()方法
调用被修饰的函数,实现装饰器的逻辑
二、案例
1、案例一
上面咱们一起学习了怎么写装饰器函数,在python中,其实也可以使用类来实现装饰器的功能,称之为类装饰器。类装饰器的实现是调用了类里面的__call__函数。类装饰器的写法比我们装饰器函数的写法更加简单。
当我们将类作为一个装饰器,工作流程:
通过__init__()方法初始化类
通过__call__()方法调用真正的装饰方法
import time
class MyDecorator(object):
def __init__(self, func):
self.func = func
print("执行init方法")
def __call__(self, *args, **kwargs):
print("执行call方法")
start = time.time()
self.func(*args, **kwargs)
end = time.time()
print(f"执行时间:{start - end}")
@MyDecorator
def jzq():
print("我是jzq")
time.sleep(1)
def python_blog_list():
time.sleep(1)
print("我是python_blog_list")
@MyDecorator
def blog(name):
print("进入blog函数")
name()
if __name__ == '__main__':
jzq()
print("=" * 50)
blog(python_blog_list)
"""
执行init方法
执行init方法
执行call方法
我是jzq
执行时间:-1.0009431838989258
==================================================
执行call方法
进入blog函数
我是python_blog_list
执行时间:-1.000255823135376
"""
2、案例二
class MyDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("Something is happening before the function is called.")
result = self.func(*args, **kwargs)
print("Something is happening after the function is called.")
return result
@MyDecorator
def say_hello(name):
print(f"Hello, {name}!")
# 调用被装饰后的函数
say_hello("Alice")
"""
Something is happening before the function is called.
Hello, Alice!
Something is happening after the function is called.
"""
在上述示例中,MyDecorator
是一个类装饰器,它接受一个函数 func
作为参数,并在 __call__
方法中定义了装饰器的行为。通过在 say_hello
函数定义前使用 @MyDecorator
,实际上是将 say_hello
函数传递给 MyDecorator
类的实例,然后将返回的实例重新赋给 say_hello
。因此,say_hello
现在是 MyDecorator
类的实例,可以像函数一样被调用。
第五章、带参数的类装饰器
一、概述
在__init__方法中定义装饰器类参数,在__call__方法中有且仅有一个定义被修饰函数对象,在__call__方法内部定义装饰器
二、案例
1、案例一
当装饰器有参数的时候,__init__() 函数就不能传入func(func代表要装饰的函数)了,而func是在__call__函数调用的时候传入的。
import time
class MyDecorator(object):
def __init__(self, arg1, arg2):
self.arg1 = arg1
self.arg2 = arg2
print("执行init方法")
def __call__(self, func):
print("调用call方法")
def wrapper(*args, **kwargs):
start = time.time()
print("执行wrapper方法")
print("装饰器参数:", self.arg1, self.arg2)
print("执行" + func.__name__ + "()")
func(*args, **kwargs)
print(func.__name__ + "()执行完毕")
end = time.time()
print(f"执行时间:{end - start}")
return wrapper
@MyDecorator("hello", "jzq")
def example(a1, a2, a3):
print("传入example的参数:", a1, a2, a3)
time.sleep(1)
if __name__ == '__main__':
example(1, 2, 3)
2、案例二
类装饰器的优势之一是可以在实例化过程中接受参数,并在 __call__
方法中使用这些参数。这使得类装饰器更加灵活,可以根据不同的配置对被装饰的函数进行不同的处理。
class ConfigurableDecorator:
def __init__(self, config_param):
self.config_param = config_param
def __call__(self, func):
def wrapper(*args, **kwargs):
print(f"Decorator is configured with: {self.config_param}")
result = func(*args, **kwargs)
return result
return wrapper
@ConfigurableDecorator(config_param="Custom Configuration")
def my_function():
print("Function is called.")
# 调用被装饰后的函数
my_function()
"""
Decorator is configured with: Custom Configuration
Function is called.
"""
在这个例子中,ConfigurableDecorator
类接受一个配置参数 config_param
,并在 __call__
方法中使用该参数进行装饰。这样,可以根据不同的配置参数对函数进行不同的装饰。
第六章、装饰器的顺序
一、概述
装饰器函数从里到外执行,装饰器从外到里执行。
二、案例
1、案例一
2、案例二
def my_decorator_1(func):
print(f"装饰器1接受的func: {func}")
def wrapper1(*args, **kwargs):
print("装饰器1")
func(*args, **kwargs)
print(f"装饰器1执行{func.__name__}()执行完毕")
return wrapper1
def my_decorator_2(func):
print(f"装饰器2接受的func: {func}")
def wrapper2(*args, **kwargs):
print("装饰器2")
func(*args, **kwargs)
print(f"装饰器2执行{func.__name__}()执行完毕")
return wrapper2
def my_decorator_3(func):
print(f"装饰器3接受的func: {func}")
def wrapper3(*args, **kwargs):
print("装饰器3")
func(*args, **kwargs)
print(f"装饰器3执行{func.__name__}()执行完毕")
return wrapper3
# @my_decorator_1
# @my_decorator_2
# @my_decorator_3
def jzq():
print("我是jzq")
if __name__ == '__main__':
# jzq()
# 等同于
jzq1 = my_decorator_1(my_decorator_2(my_decorator_3(jzq)))
jzq1()
"""
装饰器3接受的func: <function jzq at 0x0000023A497C4AE0>
装饰器2接受的func: <function my_decorator_3.<locals>.wrapper3 at 0x0000023A497C4B80>
装饰器1接受的func: <function my_decorator_2.<locals>.wrapper2 at 0x0000023A497C4C20>
装饰器1
装饰器2
装饰器3
我是jzq
装饰器3执行jzq()执行完毕
装饰器2执行wrapper3()执行完毕
装饰器1执行wrapper2()执行完毕
"""
我们带三个装饰器的函数的代码如下:
@my_decorator_1
@my_decorator_2
@my_decorator_3
def jzq():
print("我是jzq")
上述的代码可以看作如下代码,就能理解为何是由里到外执行了
jzq1 = my_decorator_1(my_decorator_2(my_decorator_3(jzq)))
jzq1()
第七章、特殊装饰器
一、wraps()装饰器
functools.wraps
是一个装饰器,用于解决装饰器可能导致的函数元信息丢失问题。当一个函数被装饰时,原始函数的一些属性(如名称、文档字符串、参数签名等)可能会被覆盖。functools.wraps
的作用就是将原始函数的属性复制到装饰器函数中,以保留原始函数的元信息
下面是一个示例,演示了 @wraps
的使用:
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""Wrapper function docstring."""
print("Something is happening before the function is called.")
result = func(*args, **kwargs)
print("Something is happening after the function is called.")
return result
return wrapper
@my_decorator
def say_hello(name):
"""Original function docstring."""
print(f"Hello, {name}!")
# 调用被装饰后的函数
say_hello("Alice")
# 输出函数的元信息
print("Function name:", say_hello.__name__)
print("Function docstring:", say_hello.__doc__)
"""
没加 @wraps(func)
Something is happening before the function is called.
Hello, Alice!
Something is happening after the function is called.
Function name: wrapper
Function docstring: Wrapper function docstring.
加 @wraps(func)
Something is happening before the function is called.
Hello, Alice!
Something is happening after the function is called.
Function name: say_hello
Function docstring: Original function docstring.
"""
在上述示例中,my_decorator
使用 @wraps(func)
将 wrapper
函数与原始函数 func
的属性进行了关联。这样,在调用 say_hello
函数时,其元信息就不会被覆盖,而是被正确保留。
如果没有使用 @wraps
,那么 say_hello
的元信息可能会被覆盖成 wrapper
函数的元信息。通过使用 @wraps
,我们确保了装饰器不会破坏原始函数的属性。
@wraps
的主要作用是提高装饰器的健壮性和可维护性,确保被装饰的函数保留了原始函数的标识信息。