python中的装饰器(修饰器)
1. 前言
在了解装饰器之前,需要知道:在python中一切皆为对象,变量是对象,函数是对象,类也是对象。既然函数是一个对象,而对象是可以被赋值给另外一个变量的,那么我们就可以将函数名赋给一个变量,然后通过该变量调用函数。代码示例如下(例子1):
# 例子1
def say_hello():
print("hello python")
my_func = say_hello # 将函数赋给变量my_func
my_func() # 通过变量my_func来调用函数say_hello()
# 输出:
hello python
在python中,我们可以在函数的函数体内定义函数。如下代码所示(例子2):我们在func
函数内部定义了一个函数say_hello
,在func
函数外部我们是无法直接调用say_hello
函数的,因为say_hello
函数的作用域仅在函数func
内部。如果我们想调用函数say_hello
,可以在func
函数体内返回函数对象say_hello
,并将返回结果赋给一个变量say_hello_copy
(此时say_hello_copy和say_hello是等价的,可以把它们看成是同一个对象)。然后我们就可以使用say_hello_copy()
来调用say_hello
函数。
# 例子2
def func():
def say_hello():
print("hello python")
return say_hello
# say_hello() # 报错, 因为say_hello在函数func内部,所以不能直接调用
say_hello_copy = func() # 返回函数对象say_hello
say_hello_copy() # 使用say_hello_copy来调用say_hello函数
输出:
hello python
2. 初识装饰器
装饰器(decorators)是 Python 中的一种高级功能,它允许你动态地修改函数或类的行为。装饰器是一种函数,它接受一个函数作为参数,并返回一个新的函数或修改原来的函数。装饰器的语法为@decorator_name
,可以将装饰器应用在函数和方法上。Python 还提供了一些内置的装饰器,比如 @staticmethod
和 @classmethod
,用于定义静态方法和类方法。
先看下面代码(例子3):我们定义了一个装饰器my_wrapper
函数,该函数的形参func
接收一个函数对象,并在函数体内部定义了一个函数wrapper
,然后将函数对象wrapper
返回。我们使用my_wrapper(say_hello)
来调用函数my_wrapper
,将函数对象say_hello
传递给形参func
,即wrapper
函数体内的func()
就等价于say_hello()
。我们将函数my_wrapper
的返回值函数对象wrapper
赋给func_obj
,因此func_obj()
等价于wrapper()
。进而实现了在函数my_wrapper
外部去调用函数体内部的wrapper
函数。
# 例子3
# 定义一个装饰器函数my_wrapper,该函数的形参接收一个函数对象
def my_wrapper(func):
# 内部又定义了一个函数
def wrapper():
print("my_wrapper是一个装饰器函数")
func()
return wrapper # 将定义的内部函数对象wrapper返回
def say_hello():
print("hello python")
# 我们调用my_wrapper(func)函数,并将返回的wrapper函数对象赋给变量func_obj。
func_obj = my_wrapper(say_hello)
func_obj() # 通过变量func_obj调用wrapper函数
输出:
my_wrapper是一个装饰器函数
hello python
但是上面的代码有些啰嗦,我们可以使用更为简洁的方式来实现,如下代码所示(例子4):
# 例子4
def my_wrapper(func):
def wrapper():
print("my_wrapper是一个装饰器函数")
func()
return wrapper
@my_wrapper
def say_hello():
print("hello python")
say_hello()
输出:
my_wrapper是一个装饰器函数
hello python
在上面的代码中,@my_wrapper
就是一个python的装饰器,即装饰器就是一个符号@
加上一个函数名
。因为函数say_hello
已经被my_wrapper
函数装饰过了,所以直接使用say_hello()
即可完成上面的调用。
3. 装饰器的执行过程(重点!!!)
对于上面的代码(例子4),为什么在say_hello
函数上面加上个装饰器@my_wrapper
,就可以通过say_hello()
调用wrapper
函数了?这个装饰器@my_wrapper
到底是怎么执行的啊?不要急,接下来就给你说装饰器的执行过程。
记住:在say_hello()
函数调用之前,会先执行say_hello
函数上面的装饰器@my_wrapper
。重点来了,python内部会自动执行这个my_wrapper(say_hello)
函数调用,将函数对象say_hello
传递给函数my_wrapper
的形参func
,并将函数my_wrapper
的返回值wrapper
函数对象赋给say_hello
对象。即:装饰器@my_wrapper
的执行过程为:say_hello = wrapper = my_wrapper(say_hello)
。因为此时say_hello = wrapper
,所以say_hello()
等价于wrapper()
,完成了wrapper
函数的调用。
...
@my_wrapper
def say_hello():
print("hello python")
say_hello()
"""上面的代码等价于下面的代码:"""
...
def say_hello():
print("hello python")
say_hello = my_wrapper(say_hello)
say_hello()
总之:当我们在say_hello
函数上面加上个装饰器@my_wrapper
,就等价于say_hello = my_wrapper(say_hello)
下面我再举个例子,如下代码所示(例子5)。简而言之,在my_func
函数上面加上一个装饰器@outer
,就等价于my_func = outer(my_func)
。outer
函数的返回值是inner
函数对象,并将inner
对象赋给my_func
对象。所以函数调用my_func()
就等价于inner()
# 例子5
def outer(func):
def inner():
print('inner函数开始执行...')
res =func()
print('inner函数执行结束...')
return res
return inner
@outer # my_func=outer(my_func)
def my_func():
print("我是func函数")
my_list = [1,2,3]
return my_list
result = my_func()
print(result)
输出:
inner函数开始执行...
我是func函数
inner函数执行结束...
[1, 2, 3]
总结一下,装饰器执行过程的"万能公式"如下所示:
@装饰器名字
def 函数名():
pass
上面代码等价于:函数名 = 装饰器名字(函数名)
4. 带参数的装饰器
上面的装饰器都是没有参数的,下面介绍一下带参数的装饰器。先说一下带参数装饰器执行过程的"万能公式",如下所示:
@装饰器名字(参数)
def 函数名():
pass
上面代码等价于:函数名 = 装饰器名字(参数)(函数名),
看着头大,优化一下:Temp = 装饰器名字(参数) 函数名 = Temp(函数名)
还是拿代码实践一下吧,如下代码所示(例子6)。
# 例子6
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
输出:
Hello, Alice!
Hello, Alice!
Hello, Alice!
看上面代码的第一眼,心里就想骂娘了吧,这写的是啥啊。不要慌不要慌,听我给你说。在greet
函数上面有个带参数的装饰器@repeat(3)
。利用上面的万能公式可知:等价于greet = repeat(3)(greet)
。repeat(3)
的返回值是函数对象decorator
,上述代码进一步优化为:greet = decorator(greet)
。decorator(greet)
返回值是函数对象wrapper
,即:greet = wrapper
。那么greet("Alice")
就等价于wrapper("Alice")
。上面的n=3,func = greet,参数*args, **kwargs
为"Alice",(*args, **kwargs
是可变参数,关于它的用法可以在网上查一下,这里就不说了)。
综上所述:上面的代码表示输出3次"Hello, Alice!"。
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
上面代码等价于:
def greet(name):
print(f"Hello, {name}!")
greet = repeat(3)(greet) 进一步优化--> Temp = repeat(3) greet = Temp(greet)
greet("Alice")
5. 类装饰器
除了函数装饰器,Python 还支持类装饰器。类装饰器是包含__call__
方法的类,它接受一个函数作为参数。大家记住:无论是函数装饰器还是类装饰器,它都是一个装饰器,用上面的"万能公式"来简化代码(也就是说将代码变成人能读懂的)。实例代码如下(例子7):
class DecoratorClass:
def __init__(self, func):
self.func = func
def __call__(self):
print("__call__方法被执行了...")
result = self.func()
return result
@DecoratorClass # my_function = DecoratorClass(my_function)
def my_function():
return "hello python"
print(my_function())
输出:
__call__方法被执行了...
hello python
在函数my_function
上面加上一个装饰器@DecoratorClass
,就等价于my_function = DecoratorClass(my_function)
。DecoratorClass(my_function)
表示实例化一个对象,会自动调用DecoratorClass
类的构造方法,将my_function传递给参数func,self.func = my_function
。将实例化的对象赋给my_function对象,my_function就是DecoratorClass类的一个实例对象。因此my_function()会调用__call__
方法,进而输出结果(__call__方法的作用是把一个类的实例化对象变成可调用对象,关于__call__
方法的具体用法可以自己去网上查一下)。
带参数的类装饰器执行过程和上面带参数的函数装饰器一样。
6. 总结
总的来说,我认为装饰器大致分为无参数的和有参数的。
(1)对于无参数的装饰器,其执行过程的"万能公式"如下所示:
@装饰器名字
def 函数名():
pass
上面代码等价于:函数名 = 装饰器名字(函数名)
(2)对于有参数的装饰器,其执行过程的"万能公式"如下所示:
@装饰器名字(参数)
def 函数名():
pass
上面代码等价于:函数名 = 装饰器名字(参数)(函数名),
看着头大,优化一下:Temp = 装饰器名字(参数) 函数名 = Temp(函数名)
无论是函数装饰器还是类装饰器,都可以使用上面的"万能公式"。如果大家还是不太懂,可以看一下下面的参考视频!!!
参考视频:
【python】装饰器超详细教学,用尽毕生所学给你解释清楚,以后再也不迷茫了!
【python】一个公式解决所有复杂的装饰器,理解了它以后任何装饰器都易如反掌!
扩展一下:@property和@overload
(1)装饰器@property:作用是允许类中的方法被当作属性来访问。我们可以像访问类中的成员变量一样来访问方法,即通过操作符.
来访问@property装饰的方法,不用加方法名后面的括号。示例代码如下:
class Student:
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
@property
def show(self):
print(f"name = {self.name}, age = {self.age}")
stu = Student("zhangsan", 18)
print(stu.name) # 访问属性
stu.show # 像访问属性一样来访问方法show()
输出:
zhangsan
name = zhangsan, age = 18
参考文章:
Python 中 property() 函数及 @property 装饰器的使用
(2)装饰器@overload :作用是声明函数的重载,所有加上装饰器@overload的方法都会被一个不加装饰器的方法覆盖掉。
from typing import overload
class Student:
@overload # 下面的省略号...等价于关键字pass
def sum(self) -> None: ...
@overload
def sum(self, a) ->None:...
def sum(self, a, b) -> int:
return a+b
stu = Student()
print(stu.sum(1,2)) # 输出:3
参考文章:Python中的@overload装饰器
作者:小李子-_-