python之装饰器,多重装饰器,多层函数装饰器

理解装饰器之前先要理解闭包

  • 闭包

    定义:在一个函数内部的函数,同时内部函数又引用了外部函数的变量。
    本质:闭包是将内部函数和外部函数的执行环境绑定在一起的对象。
    优点:内部函数可以使用外部变量。
    缺点:外部变量一直存在于内存中,不会在调用结束后释放,占用内存。
    作用:实现python装饰器。

  • 闭包三要素:

    – 必须有一个内嵌函数。
    – 内嵌函数必须引用外部函数中变量。
    – 外部函数返回值必须是内嵌函数。

  • 闭包函数的调用方式:

    变量 = 外部函数名(参数)
    变量(参数)

  • 函数名变量存储着函数内存地址

    def test():
    	print("这是test函数")
    
    print(test) # <function test at 0x000001D2BE18C1E0>
    test() # 这是test函数
    

伪代码示例:

def 外部函数名(参数):
	外部变量
 	def 内部函数名(参数):
 		使用外部变量
 	# 仅返回内部函数名(内部函数的内存地址)
 	return 内部函数名

代码示例:

def test(name: str):
    age = 18

    def my_test():
        print("name:", name)
        print("age:", age)

    # 仅返回内部函数名(内部函数的内存地址)
    return my_test

print(test) # test函数的内存地址
print(test(name="test")) # test函数内部函数my_test 函数的内存地址
result = test(name="test") # 使用变量接收test 函数返回的 my_test函数内存地址
result() # 相当于 my_test()

装饰器

而装饰器decorators,正式利用了闭包的特性

  • 定义

    在不改变原函数的调用以及内部代码情况下,为其添加新功能的函数。

  • 伪代码示例

    def 函数装饰器名称(func):
    	需要添加的新功能
    	def 内嵌函数名(*args, **kwargs):
    		需要添加的新功能
    		return func(*args, **kwargs)
    	return 内嵌函数名
    
  • 装饰器的使用
    使用@符号,直接在函数上方添加装饰器

    @函数装饰器名称
    想要装饰的函数()
    
  • 本质:

    使用“@函数装饰器名称”修饰原函数,等同于创建与原函数名称相同的变量,关联内嵌函数;故调用原函数时执行内嵌函数。

    原函数名称 = 函数装饰器名称(原函数名称)

  • 代码示例

    • 定义两个普通函数,实现打招呼,与说再见功能
    def say_hello():
    	print("你好")
    
    def say_goodbye():
    	print("再见")
    
    • 由于需求变更,要求打印函数自身名字
    def say_hello():
    	print(say_hello.__name__)
    	print("你好")
    
    def say_goodbye():
    	print(say_goodbye.__name__)
    	print("再见")
    
    • 使用装饰器理念,变换函数
    # 提取处通用点: print(函数名.__name__), 改造为装饰器
    def print_func_name(func):  # 函数名作为参数传递
    	def wrapper(*args, **kwargs):  # 包装函数
        	print("函数名为:", func.__name__)  # 提供新功能
        	func(*args, **kwargs)  # 执行原函数
    	return wrapper  # 返回包装函数的内存地址
    
    @print_func_name # 使用装饰器,装饰原函数
    def say_hello():
    	print("你好")
    
    @print_func_name
    def say_goodbye():
    	print("再见")
    
    say_hello() # 执行函数
    say_goodbye()
    
  • 示例代码解析

    @print_func_name
    def say_hello():
    	print("你好")
    	
    say_hello()
    

    Step1 : 函数由上至下执行,遇到print_func_name, say_hello函数,在内存中开辟栈帧,存储函数
    Step2 : 程序遇到了执行函数say_hello(),调用栈帧中的的函数
    Step3 : @方法使其变为: say_hello = print_func_name(say_hello) 形式来执行函数
    Step4 : 遇到wrapper 函数,又开辟一块栈帧,接着return wrapper
    Step5 : say_hello = wrapper --> say_hello() => wrapper()
    Step6 :执行 wrapper函数: print("函数名为:", func.__name__),并且执行 传递的参数func,也就是say_hello,say_hello(*args, **kwargs)

    栈帧: 每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量

多重装饰器

一个函数可以被多个装饰器修饰, 多重装饰器,即多个装饰器修饰同一个函数对象,且执行顺序为从近到远

@装饰器1
@装饰器2
函数名()
执行顺序为 : 函数名= 装饰器1( 装饰器2(函数名) )
先执行 装饰器2(函数名) 将返回结果作为参数 传递为装饰器1,函数1 再将结果返回

来看一段伪代码示例:

  • 场景 : 编写一函数视图,必须登录才可以访问该视图,并且执行该视图时,打印出该函数名称
# 打印函数名称
def print_func_name(func1):  
    def wrapper(*args, **kwargs): 
        print("函数名为:", func.__name__)  
        func1(*args, **kwargs)  

    return wrapper  # 返回包装函数的内存地址


# 登录要求装饰器
def my_login_required(func2):
    def decorated_view(*args, **kwargs):
        print("my_login_required开始调用")
        return func2(*args, **kwargs)

    return decorated_view

@my_login_required
@print_func_name
def my_view():
    print("视图函数开始调用")

ps: 在编程时,通常需要对视图函数做出权限管理,比如说需要先登录才能访问某视图函数,不只是此函数需要此功能,其他函数也需要,因此提取出共同点变为装饰器,用时添加即可

问题思考:

前面提到过,多重函数执行顺序为从近到远,要求是: 先登录才能访问该函数,那么为什么@print_func_name@my_login_required之前?而不是

@print_func_name
@my_login_required
def my_view():
print(“视图函数开始调用”)

捋顺思路:

  • 执行结果
    my_login_required开始调用
    函数名为: my_view
    视图函数开始调用
    
  • 执行过程
    1. 项目由上至下执行,开辟栈帧存储函数
    2. 遇到真正函数执行体my_view(),开始执行函数
    3. 由于装饰器存在,并且按照规则 从近到远 来解析
    4. 所以先执行print_func_name
      • func1 = my_view
      • 开辟栈帧存储wrapper函数
      • return wrapper
    5. 将wrapper作为参数,传递给my_login_required
      • func2 = wrapper
      • 开辟栈帧存储decorated_view函数
      • return decorated_view
    6. 此时my_view() = decorated_view(),执行decorated_view()
      • 打印结果 : my_login_required开始调用
      • return wrapper(*args, **kwargs)
    7. 执行 wrapper(*args, **kwargs) 函数
      • 打印结果 : 函数名为: my_view
      • 执行wrapper(*args, **kwargs) 函数 函数中的func 1函数
      • 由于func1 = my_view(),所以执行my_view()函数
      • 打印结果: 视图函数开始调用

得出结论:

由先近后远原则,函数先执行靠近被函数,并将返回结果传递给上一层装饰器。
即:多重装饰器存在时,功能实现顺序: 先远后近

多层函数装饰器

来看示例:

  • 普通装饰器
def print_func_name(func):  
    def wrapper(*args, **kwargs): 
        print("函数名为:", func.__name__) 
        func(*args, **kwargs) 

    return wrapper  
  • 多层装饰器
# 3层函数装饰器
def out_test(*args):

    def test(func):
        def in_test(*args, **kwargs):
            print("in_test 被调用")

            return func(*args, **kwargs)

        return in_test

    return test

当普通装饰器满足不了需求时,需要多层装饰器,对函数进一步封装

代码示例:

def print_func_name(func):  # 函数名作为参数传递
    def wrapper(*args, **kwargs):  # 包装函数
        print("函数名为:", func.__name__)  # 提供新功能
        func(*args, **kwargs)  # 执行原函数

    return wrapper  # 返回包装函数的内存地址

def out_test(*args):
    def test(func):
        def in_test(*args, **kwargs):
            print("in_test 被调用")

            return func(*args, **kwargs)

        return in_test

    return test

@print_func_name # 使用装饰器,装饰原函数
def say_hello():
    print("你好")

@out_test() # 多层函数装饰器调用
def say_goodbye():
    print("再见")

say_hello() # 执行函数
say_goodbye()

结果:

函数名为: say_hello
你好
in_test 被调用
再见

函数从上至下执行
@out_test()执行到此处,发现了函数调用 : out_test()
执行out_test(),return test
解析test函数,打印 test 被调用,将test视为装饰器函数,等待被调用

再看示例:

# 代码上同,将say_goodbye函数改为 多重 + 多层函数装饰器
@print_func_name
@out_test() # 多层函数装饰器调用
def say_goodbye():
    print("再见")

结果如下:

函数名为: in_test
in_test 被调用
再见

或者:

@out_test() # 多层函数装饰器调用
@print_func_name
def say_goodbye():
    print("再见")

运行结果:

in_test 被调用
函数名为: say_goodbye
再见

由此可见:

多重 + 多层函数装饰器存在时,将多层函数装饰器的返回结果视为普通装饰器

让我们来看看4层函数装饰器

# 3层函数装饰器
def out_test(*args):

    def test(func):

        def in_test(*args, **kwargs):
            print("in_test 被调用")
            return func(*args, **kwargs)
            
        return in_test
    return test

# 4层装饰器
def final_test(my_array: list):
    return out_test(my_array)

@final_test(my_array=[1,2,3])
def say_goodbye():
    print("再见")

say_goodbye()

原理也是类似:

先执行函数,将结果返回作为普通装饰器,继续解析函数

写在最后: 如果把3层装饰器,写为普通的装饰器会这样?

3层装饰器函数 test()
装饰函数时写为 @test, [ ps : 正确写法: @test() ]

答案显而易见

内部传递参数错误,报出missing required positional argument错误
或者
没有由于装饰器函数没有的正确返回结果,被装饰函数未执行


创作不易,如您感觉有收获,点个赞呗!~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章