python基础:函数装饰器基础知识及高级用法

闭包 就是为了解决函数嵌套时,局部变量在赋值前引用的问题(不同作用域调用的问题)。除了使用global声明全局变量外,还可以使用nonlocal 声明自由变量,实现闭包。

在此之前我们先了解一下基础知识。

作用域

内建作用域(.py文件)、全局作用域、局部外的局部作用域(闭包函数外面– 局部外的局部)、局部作用域

g_count = 0  # 全局作用域
def outer():
    o_count = 1  # 闭包函数外面的 局部作用域
    def inner():
        i_count = 2  # 局部作用域

全局变量和局部变量

内部作用域想修改外部作用域的变量时,使用 global 和 nonlocal 关键字了。

1.内部修改全局变量

# 修改全局变量 num
num = 10
print(f'内部修改全局变量前 num:{num}')
def fun1():
    global num  # 需要使用 global 关键字声明
    num = num + 1   # 内部修改全局变量
fun1()
print(f'内部修改全局变量后 num:{num}')

2.嵌套函数内层函数 修改 局部外的 局部变量 ,使用nonlocal 关键字

num = 10
def outer():
    num = 555  # 闭包函数外的函数中
    print(f'嵌套函数 内部修改 局部外的局部变量 前num:{num}')
    def inner():
        nonlocal num  # nonlocal关键字声明
        num = num + 1
    inner()
    print(f'嵌套函数 内部修改 局部外的局部变量 后num:{num}')

outer()
print(f'全局变量num的值:{num}')

image-20230222172412079

稍微修改一下,去掉nonlocal num  # nonlocal关键字声明

# 内层嵌套函数 修改 局部外的局部(非全局作用域)变量则需要 nonlocal
num = 10
def outer():
    num = 555  # 闭包函数(inner)外面--局部外的局部变量
    print(f'嵌套函数 内部修改 局部外的局部变量 前num:{num}')
    def inner():
        num = num + 1
    inner()
    print(f'嵌套函数 内部修改 局部外的局部变量 后num:{num}')

outer()
print(f'全局变量num的值:{num}')

不使用nonlocal进行关键字声明,内嵌函数innernum = num + 1 本来想要调用 outer中的 num的值,会报错 - 变量在赋值前引用。

UnboundLocalError: local variable 'num' referenced before assignment

nonlocal作用具体作用:把变量num标记为自由变量,即使在函数中为变量赋值了,也仍然是自由变量。

自由变量

自由变量是相对 全局变量和局部变量 而言的。 子作用域内(inner)可以取到父作用域内(outer)的变量,对于 子作用域(inner)而言,就是自由变量。

注意点

  1. 对于列表、字典等可变类型来说,添加元素不是赋值,赋值 不会改变自有变量的身份。

  2. 对于数字、字符串、元组等不可变类型以及None来说,赋值会 将其 改变为 局部变量。

# 情况一 举例
def funA():
    # 可变类型
    count = {}

    def hello(new_value):
        print(f'打印:{count}')  # 成功
        count[new_value] = new_value  # 赋值

    return hello
    
a = funA()
a(111)
    
    
# 情况2 举例
def funA():
    # 不可变类型
    count = 1  # 或者 count = None

    def hello(new_value):
        print(f'打印:{count}')  # 报错
        count = new_value  # 赋值

    return hello

被装饰函数是否有入参,如何设计装饰器?

被装饰函数 无入参

函数装饰器funA()是一个可调用对象,它的参数fn是被装饰函数(无入参)。

# funA 作为装饰器函数
def funA(fn):

    def hello():
        print("在 被装饰函数 <前> 执行")
        fn()  # 被装饰函数(装饰器的入参)
        print("在 被装饰函数 <后> 执行")
        return fn()

    return hello

@funA
def funB():
    print("执行被装饰函数")
    return '执行完毕'

a = funB()

程序执行流程为:

 被装饰函数 <> 执行
执行被装饰函数
 被装饰函数 <> 执行
执行完毕

在此基础上,如果在上面程序末尾 打印funB 函数的返回值

print(a)

# 其输出结果为
执行完毕

被装饰函数有入参,如何写装饰器

# *args 和 **kwargs 作为装饰器内部嵌套函数的参数,*args 和 **kwargs 表示接受任意数量和类型的参数

def funA(fn):
    # 定义一个嵌套函数(作用:获取函数的运行时间)
    def hello(*args, **kwargs):
        print(f"在 被装饰函数:{fn.__name__} <前> 执行")
        ret = fn(*args, **kwargs)
        print("在 被装饰函数 <后> 执行")
        return ret
        
    return hello

@funA
def funB(data1, data2):
    print(f"执行被装饰函数,入参:{data1}, {data2}")


funB("oh", "it is ok")

image-20230224155908025

嵌套函数装饰器

@funA
@funB
@funC
def fun():
    #...

上面程序的执行顺序是里到外,所以它等效于下面这行代码

fun = funA( funB ( funC (fun) ) )

如果需要装饰器 对 带有函数的返回值进行修改,而不是直接返回被装饰函数的返回值 ,该怎么处理?

image-20230224155042330

函数装饰器小结:

函数装饰器 funA() 去装饰另一个函数 funB(),其底层执行了如下 2 步操作:

  1. 将 funB() 作为参数传给funA() 函数;

  2. funA()、funB()接受相同的参数

  3. 同时还可以做一些额外的操作

  4. 返回被装饰函数funB()本身的返回值

参数化装饰器

怎么让装饰器接受参数呢?答案是:创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。

待补充。。。。。。。。。

实际应用

我写过的几个装饰器。