Python-闭包&装饰器

    技术2024-08-20  52

    文章目录

    闭包:闭包的格式:闭包的特点 装饰器装饰器的格式装饰的过程装饰带有参数的函数多层装饰器带有参数的装饰器

    闭包:

    闭包的格式:

    看下面一段代码

    def fun(a): b = 10 def inner_fun(): print(a + b) return inner_fun x = fun(5) print(type(x)) print(x) x() ''' 输出: <class 'function'> <function fun.<locals>.inner_fun at 0x000001BB0CB02948> 15 '''

    可以发现在一个函数里定义了一个内部函数,这个内部函数又被外部函数当做值返回了出去。 可以看到用type打印x,结果是<class ‘function’>证明它返回的是一个函数,而在下一段打印中表明了这个函数是fun函数的内部函数还可以看到一片内存地址。 也就是经过上面的操作x这个变量拿到了内部函数inner_fun的内存地址调用x就相当于调用inner_fun,像这种情况的函数以及使用称之为闭包

    闭包的特点
    首先,关于内部函数,是可以访问外部函数的变量的。但是在修改外部函数的变量时,默认情况下只能修改可变类型的变量。若想修改不可变类型的变量,则必须用nonlocal进行声明。 def fun(a): b = 10 def inner_fun(): nonlocal b # 这里需要声明 b += a return inner_fun x = fun(5) 闭包可以保存函数的状态,也就是函数在执行后不会立马被回收,仍可以继续使用。下面的例子可以看到函数执行完之后,仍可以再对cnt变量进行操作 def fun(ini): cnt = ini print(ini) def inner_fun(a): nonlocal cnt cnt += a print(cnt) return inner_fun F = fun(5) F(10) F(1) ''' 输出: 5 15 16 ''' 闭包外部拿到的函数互不干扰。在下面可以看到即使参数相同,他们得到的函数地址也不相同说明在之后在对F1,F2操作时不会相互干扰 def fun(ini): cnt = ini print(ini) def inner_fun(a): nonlocal cnt cnt += a print(cnt) return inner_fun F1 = fun(5) F2 = fun(5) print(F1) print(F2) ''' 输出: 5 5 <function fun.<locals>.inner_fun at 0x000002209D792948> <function fun.<locals>.inner_fun at 0x000002209D767678> '''

    装饰器

    装饰器的格式

    看下面这个例子,可以发现我把一个函数当做参数传入了fun中。从输出可以看到fun1被调用了,inner_fun也被调用了,可以得到结论,函数被当做参数时,仍可以被内部的函数当函数调用

    def fun(f): def inner_fun(): f() print('内部函数的内容') return inner_fun def fun1(): print('fun1的内容') x = fun(fun1) x() ''' 输出: fun1的内容 内部函数的内容 '''

    再来看下面的代码,可以发现即使没有通过闭包的方式返回内部函数,调用fun1时也执行了inner_fun。此时可以发现代码中多了一段@fun 这段代码的作用就是给下面的函数,即fun1,加上装饰器用的,而上面这个fun函数则就是装饰器

    def fun(f): def inner_fun(): f() print('内部函数的内容') return inner_fun @fun def fun1(): print('fun1的内容') fun1() ''' 输出: fun1的内容 内部函数的内容 '''
    装饰的过程

    先看下面这个代码,可以发现即使什么也没有调用,fun函数依然被使用了,而可以知道inner_fun并没有被使用

    def fun(f): print('装饰器开始执行') def inner_fun(): f() print('内部函数的内容') print('装饰器执行完毕') return inner_fun @fun def fun1(): print('fun1的内容') ''' 输出: 装饰器开始执行 装饰器执行完毕 '''

    接着看下面的代码,可以发现当打印fun1时返回的函数竟然是inner_fun。于是就可以大胆的做出推测在用@fun时,py就做了一个默认的处理操作也就是使用了fun1 = fun(fun1)从而对原本的fun1进行了修饰。

    def fun(f): print('装饰器开始执行') def inner_fun(): f() print('内部函数的内容') print('装饰器执行完毕') return inner_fun @fun def fun1(): print('fun1的内容') print(fun1) ''' 输出: 装饰器开始执行 装饰器执行完毕 <function fun.<locals>.inner_fun at 0x00000128B7F0E0D8> '''

    于是可以得出结论,在加了装饰器,运行时会事先默认对下面的函数当做参数传入装饰器函数,并用装饰器的返回值代替原函数

    装饰带有参数的函数

    当被装饰的函数带参数会如何呢?看看下面的这段代码。 可以发现会报错误,为什么呢? 因为此时的fun1已经不是原来的fun1了,它现在是inner_fun,而inner_fun并没有形式参数,所以发生了错误,要修正也很容易,只需要把inner_fun加上参数就可以了

    def fun(f): def inner_fun(): f() print('内部函数的内容') return inner_fun @fun def fun1(x): print('fun1的内容----{}'.format(x)) fun1(5)

    修正~

    def fun(f): def inner_fun(x): f(x)# 这里也要带参数,因为这里的调用的是原本的fun1 print('内部函数的内容') return inner_fun @fun def fun1(x): print('fun1的内容----{}'.format(x)) fun1(5)

    当参数为多个,甚至有关键字参数时,就可以和普通的函数一样,用可变参数即可

    def fun(f): def inner_fun(*args, **kwargs): f(*args, **kwargs)# 这里需要拆包,因为是要当实际参数使用的 print('内部函数的内容') return inner_fun @fun def fun1(x): print('fun1的内容----{}'.format(x)) @fun def fun2(x, y): print('fun2的内容----{}和{}'.format(x, y)) fun1(5) fun2(8, 9) ''' 输出: fun1的内容----5 内部函数的内容 fun2的内容----8和9 内部函数的内容 '''
    多层装饰器

    看下面的代码,如果对一个函数使用两个装饰器,会如何呢? 从输出结果可以很明确的看到,当时用多层装饰器时,python会优先加载距离函数最近的那个一装饰器。 也就是先会运行decorate2然后运行decorate1,可以输出查看,最后的函数时decorate1的内部函数

    def decorate1(f): print('装饰1载入') def inner_decorate(*args, **kwargs): f(*args, **kwargs) print('执行装饰1') return inner_decorate def decorate2(f): print('装饰2载入') def inner_decorate(*args, **kwargs): f(*args, **kwargs) print('执行装饰2') return inner_decorate @decorate1 @decorate2 def fun1(x): print('fun1的内容----{}'.format(x)) fun1(5) ''' 输出: 装饰2载入 装饰1载入 fun1的内容----5 执行装饰2 执行装饰1 '''
    带有参数的装饰器

    有时,装饰器可能要带一个参数,那么带参数的装饰器怎么写呢? 看一下下面的写法: 这是一个错误的写法,因为装饰器中要默认要传的是下面的那个函数,不能传其他参数,但是如果希望在装饰函数时根据一些参数对装饰器进行调整呢?

    def decorate1(f): print('装饰1载入') def inner_decorate(*args, **kwargs): f(*args, **kwargs) print('执行装饰1') return inner_decorate @decorate1(5) def fun1(x): print('fun1的内容----{}'.format(x)) print(fun1)

    那么,套娃大法无限好。 只需要再套一层外部函数,把原本的函数当成返回值返回出去就可以了,可以这么想,在执行@时,需要拿到的是一个函数,而decorate(5)返回的就是函数,那么就相当于@是一个内部的函数,即@的是dec

    def decorate(a: int): def dec(f): print('装饰1载入:{}'.format(5)) def inner_decorate(*args, **kwargs): f(*args, **kwargs) print('执行装饰1') return inner_decorate return dec @decorate(5) def fun1(x): print('fun1的内容----{}'.format(x)) fun1(100)
    Processed: 0.012, SQL: 9