Python-闭包&装饰器

闭包:

闭包的格式:

看下面一段代码

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,像这种情况的函数以及使用称之为闭包

闭包的特点
  1. 首先,关于内部函数,是可以访问外部函数的变量的。但是在修改外部函数的变量时,默认情况下只能修改可变类型的变量。若想修改不可变类型的变量,则必须用nonlocal进行声明。
def fun(a):
    b = 10

    def inner_fun():
    nonlocal b # 这里需要声明
        b += a 
    return inner_fun


x = fun(5)
  1. 闭包可以保存函数的状态,也就是函数在执行后不会立马被回收,仍可以继续使用。下面的例子可以看到函数执行完之后,仍可以再对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
'''
  1. 闭包外部拿到的函数互不干扰。在下面可以看到即使参数相同,他们得到的函数地址也不相同说明在之后在对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)

猜你喜欢

转载自blog.csdn.net/qq_36102055/article/details/107115948