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装饰器

  • 23
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: 在Python装饰器可以用来修饰类。装饰器可以在不修改原始类代码的情况下,为类添加额外的功能。通常,装饰器是通过在类定义之前使用@符号来应用的。装饰器可以在类定义之前对类进行修饰,从而为类添加新的功能。例如,可以定义一个装饰器函数,然后使用@符号将其应用于类定义之前。装饰器函数可以在返回之前对类进行修改,从而为类添加新的方法或属性。\[2\]通过装饰器修饰的类在实例化时会自动调用装饰器函数,并在返回之前对类进行修改。这样,原始类就被装饰器修饰后,具有了额外的功能。\[2\] #### 引用[.reference_title] - *1* [python装饰器详解](https://blog.csdn.net/weixin_44992737/article/details/125868592)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Python 使用装饰器装饰类](https://blog.csdn.net/weixin_30721899/article/details/96861904)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值