python 闭包 && 装饰器

  • 闭包

定义

官方:内部函数对外部函数作用域里变量的引用

个人理解:对于一个内部函数,如果它引用了外部函数的参数和局部变量,且外部函数返回这个内部函数,那么我们把这个内部函数成为闭包函数。闭包函数和作用域外部的变量统称为闭包。

用法

举例,如下是一组嵌套函数fun和fun1

def fun():  #外部函数
    print("This is fun")
    def fun1():    #内部函数
        print("This is fun1")

当我们运行fun()时,可以得到:

This is fun

当我们运行fun1()时,可以得到:

Traceback (most recent call last):
File "", line 1, in
NameError: name 'fun1' is not defined

说明内部函数不能直接被调用,func1 是建立在外部函数运行中才能调用。这里我们还需要去理解函数内部作用域的生命周期,只有执行期间才有效。如下:

>>>fun()   #外部函数调用,内部函数创建
>>>fun1()  #无法直接调用,创建在fun()中

这样连续执行命令,我们也无法调用fun1,因为fun()中的函数只有执行期间有效。

为此,我们要调用fun1,可以让fun外部函数返回fun1:

def fun():
    print("This is fun")
    def fun1():
        print("This is fun1")
    return fun1
  
  test = fun()  #fun1
  

这里test就为fun()返回的内部函数fun1;若要调用fun1这个函数,直接使用test()。

此外,可以让内部函数引用外部函数作用域中的变量,使这个变量保存下来,不会失效。如下变量num存在于test()中,相当于私有化了变量,完成了数据封装。

def fun():
    num = 1
    print("This is fun")
    def fun1():
        print(num)
    return fun1
  
>>>test = fun()
>>>test()
This is fun
1

此外,py3之后,新增了nonlocal关键词,可以使内部函数修改外部函数的变量:

def createCounter():
    n=0
    def counter():
        nonlocal n
        n = 2
        print(n)
    counter()
    print(n)

def createCounter2():
    n=0
    def counter():
        n = 2
        print(n)
    counter()
    print(n)
>>>createCounter()
2
2
>>>createCounter2()
2
0

注意:

返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

有这样一个例子:

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()
>>> f1()
9
>>> f2()
9
>>> f3()
9

全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。所以我们需要另外创建一个函数;

def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
    return fs
  • 装饰器

装饰器函数的标志是:@

装饰器是基于闭包

装饰器的作用是,不影响原函数功能,添加新的功能。他是使函数执行之前先执行装饰器的内容。如下

def log(fun):
    def f():
        print("log装饰器之后调用Toprint函数")
        # return fun    #返回的是原函数这个对象
        return fun()  # 返回的是原函数的调用
    return f

@log
def Toprint():
    print("This is Toprint")


>>>Toprint()
log装饰器之后调用Toprint函数
This is Toprint

他的相当于log(Toprint)(),流程是log(Toprint)() --> return f --> f() --> print("log装饰器之后调用Toprint函数") --> return fun() --> print("This is Toprint")

即接收被装饰器的函数作为参数,并进行调用一次,(如果不调用,返回的只是f函数对象),即执行f(),打印,并执行原函数;

装饰器函数带参

我们不能直接在装饰器函数中传入参数和函数两个参数,需要另外创建一个函数,将参数和原函数分别传入,且在内部接收函数,外部接收参数。

def log(tcp_type):
    def func(b_fun):
        def fun():
            if tcp_type == 'get':
                print('getget')
            if tcp_type == 'post':
                print('postpost')
            return b_fun()
        return fun  # 返回的是原函数的调用
    return func

@log(tcp_type='get')
def get():
    print("This is get")

@log(tcp_type='post')
def post():
    print("This is post")

他的相当于log(tcp_type='get')()(),流程是先调用装饰器函数,返回func函数并调用,返回fun函数并调用,判断tcp_type,打印,调用原函数。

同时支持装饰器带参或者不带参

即:支持:

@log
def f():
    pass

又支持:

@log('execute')
def f():
    pass

我们可以在装饰器函数中写一个默认变量,来进行判断;

def log(tcp_type=None):
    def func(b_fun):
        def fun():
            if tcp_type == 'get':
                print('getget')
            if tcp_type == 'post':
                print('postpost')
            return b_fun()
        return fun  # 返回的是原函数的调用
    return func

@log()
def notcp():
    print("notcp")

@log(tcp_type='get')
def get():
    print("This is get")

@log(tcp_type='post')
def post():
    print("This is post")
>>>notcp()
notcp

被装饰函数带参

被装饰函数的参数可以传入装饰函数的最内部函数中进行操作

def log(func):
    def f(x,y):
        x +=3
        y +=4
        return func(x,y)
    return f

@log
def fun(a,b):
    print(a+b)
>>>fun(3,4)
14

当我们内部函数可以接受任何参数调用时,可以这样写:def fun(*args, **kw)

def metric(fn):
    def f(*args, **kw):
        print('This is %s ' % (fn.__name__))
        return fn(*args, **kw)
    return f

@metric
def fast(x, y):
    print(x + y)

@metric
def slow(x, y, z):
    print(x * y * z)

f = fast(11, 22)
s = slow(11, 22, 33)

你可能感兴趣的:(python 闭包 && 装饰器)