一、闭包

​ 首先理解下python中的函数,在python中,函数是一个对象(可以通过type函数查看),在内存中占用空间;函数执行完成之后内部的变量会被解释器回收,但是如果某变量被返回,则不会回收,因为引用计数器的值不为0;既然函数也是一个对象,他也拥有自己的属性;对于python函数来说,返回的不一定是变量,也可以是函数。

​ 由此引出闭包的概念,当存在函数嵌套时(如上例中的func()和in_func()就是嵌套关系),外部函数的返回值为内部函数,且内部函数引用外部函数的变量、参数,并将引用的变量、参数封装在函数中一起作为返回值,其中的内部函数就称为一个闭包。

​ 形成闭包的必要条件:

1)必须有一个内嵌函数(函数里定义的函数)——这对应函数之间的嵌套

2)内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量——内部函数引用外部变量

3)外部函数必须返回内嵌函数——必须返回那个内部函数

二、闭包的使用场景

第一个例子:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
__author__ = "问道编程"
__date__ = "2018-06-13 15:53"

def funx():
    x=5   # 对于funx,是L作用域,对于funy,是E作用域
    def funy():    # 是一个闭包
        nonlocal x  # 绑定到外部的x,只在python3中使用
        x+=1
        return x
    return funy

a = funx()
print(a())
print(a())
print(a())
print(x)
-------------------------
6
7
8
NameError: name 'x' is not defined

​ 本来x只是funx()的局部变量,但是形成了闭包之后,它的行为就好像是一个全局变量一样,最后的错误说明x并不是一个全局变量。其实这就是闭包的一个十分浅显的作用,形成闭包之后,闭包变量能够随着闭包函数的调用而实时更新,就好像是一个全局变量那样。 第二个例子,也是讲闭包用的最多的例子:

def func_150(val):   # 总分为150时,及格分数为90
    passline = 90
    if val >= passline:
        print "pass"
    else:
        print "fail"

def func_100(val):   # 总分为100时,及格分数为60
    passline = 60 
    if val >= passline:
        print "pass"
    else:
        print "fail"

func_100(89)  
func_150(89)

# 使用闭包优化上面的代码:

def set_passline(passline):
    def cmp(val):
        if val >= passline:
            print "pass"
        else:
            print "fail"
    return cmp

f_100 = set_passline(60)    # f_100调用函数set_passline(),并将60赋值给变量passline,这是f_100等于函数的返回值,也就是函数cmp
f_150 = set_passline(90)
f_100(89)     # f_100()=cmp(),将89赋值给val,运行cmp()函数,输出结果
f_150(89)

第三个例子:

def my_sum(*arg):
    print('in my_sum,arg=',arg)
    return sum(arg)


def dec(func):
    def in_dec(*arg):
        print('in in_dec,arg=', arg)
        if len(arg) == 0:
            return 0
        for val in arg:
            if not isinstance(val, int):
                return 0
        return func(*arg)

    return in_dec


my_sums = dec(my_sum)  # 命名为my_sums,是为了和my_sum进行区分,便于理解
# ①调用dec()函数,将dec的返回值赋值给my_sums,相当于my_sums=in_dec,②将函数my_sum赋值给func,相当于func=my_sum
result = my_sums(1, 2, 3, 4, 5)  
# ③相当于将(1, 2, 3, 4, 5)赋值给in_dec函数中的arg,调用并执行函数in_dec(),
# ④in_dec()函数的return返回值是func()函数,将in_dec函数中的arg赋值给func()函数中的arg,也就是赋值给my_sum()中的arg
# ⑤调用并执行函数my_sum(),将sum(arg)结果返回给变量result
print(result)
------------
in in_dec,arg= (1, 2, 3, 4, 5)
in my_sum,arg= (1, 2, 3, 4, 5)
15

​ 整个过程可以简单的理解为,先运行dec()函数,其返回值是in_dec函数,再运行in_dec函数,其返回值为func函数,也就是my_sum函数,再运行my_sum函数dec() -> in_dec() -> my_sum()

第四个例子:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
__author__ = "问道编程"
__date__ = "2018-06-13 17:08"

variable = 300

def test_scopt():
    print(variable)  # variable是test_scopt()的局部变量,但是在打印时并没有绑定内存对象。
    variable = 200  # 因为这里,所以variable就变为了局部变量

test_scopt()
print(variable)
------------
UnboundLocalError: local variable 'variable' referenced before assignment

上面的例子会报出错误,因为在执行程序时的预编译能够在test_scopt()中找到局部变量variable(对variable进行了赋值)。在局部作用域找到了变量名,所以不会升级到嵌套作用域去寻找。但是在使用print语句将变量variable打印时,局部变量variable并有没绑定到一个内存对象(没有定义和初始化,即没有赋值)。本质上还是Python调用变量时遵循的LEGB法则和Python解析器的编译原理,决定了这个错误的发生。所以,在调用一个变量之前,需要为该变量赋值(绑定一个内存对象)。

注意:为什么在这个例子中触发的错误是UnboundLocalError而不是NameError:name ‘variable’ is not defined。因为变量variable不在全局作用域。Python中的模块代码在执行之前,并不会经过预编译,但是模块内的函数体代码在运行前会经过预编译,因此不管变量名的绑定发生在作用域的那个位置,都能被编译器知道。Python虽然是一个静态作用域语言,但变量名查找是动态发生的,直到在程序运行时,才会发现作用域方面的问题

可以使用global将变量variable声明为全局变量。

三、装饰器

概念性的知识,拗口的表述: 1.装饰器就是对闭包的使用; 2.装饰器的作用是装饰函数; 3.装饰器会返回一个函数对象,被装饰的函数接收; 4.被装饰函数标识符指向返回的函数对象。

将上面第三个例子用上装饰器:

def dec(func):
    def in_dec(*arg):
        print('in in_dec,arg=', arg)
        if len(arg) == 0:
            return 0
        for val in arg:
            if not isinstance(val, int):
                return 0
        return func(*arg)
   print('in dec')  # 新增的一行
    return in_dec
@dec 
def my_sum(*arg):
    print('in my_sum,arg=',arg)
    return sum(arg)
print(type(my_sum))

对概念的实例化:

1、@dec是一个装饰器,是调用dec()函数形成的装饰器;

2、被装饰的函数是my_sum(),即调用dec()函数形成装饰器时,dec()函数的参数为my_sum()函数,即:func = my_sum

3、装饰器的返回值是函数in_dec(),该返回值被函数my_sum()接收,其实是传给被装饰函数(即my_sum()函数)的标识符my_sum,即:my_sum=in_dec

直接运行该文件,输出:

--------------
in dec
<class 'function'>     # 此时的my_sum并非指定义的def my_sum(*arg)函数,而是变成接收了装饰器的返回值,in_dec函数。

说明只要使用了装饰器,就会调用装饰器里的函数;并且装饰器的返回值:in_dec(),被my_sum接收(如果没有return in_dec,直接运行,则my_sum格式为:,说明装饰器没有返回值时,会返回None,这一点跟函数一样)。

接下来,执行语句:

result = my_sum(1, 2, 3, 4, 5)  
print(result)
------------------
in dec
<class 'function'>
in in_dec,arg= (1, 2, 3, 4, 5)
in my_sum,arg= (1, 2, 3, 4, 5)
15

运行过程为(结合第三个例子进行理解):

1、@dec,调用dec函数,其参数就是被装饰的函数my_sum(),即func=my_sum,执行print('in dec'),返回in_dec函数,被my_sum接收,即:最后会将函数in_dec的运行结果,传给被装饰函数的标识符my_sum;

2、调用in_dec函数,参数为(1,2,3,4,5),执行print、if、for语句,执行return func(*arg),func=my_sum函数;

3、调用函数my_sum(),参数为(1,2,3,4,5),执行print语句,执行return sum(*arg),返回最终结果;