0. 函数相关知识
1)Python中“一切皆对象”,函数也不例外
先定义一个函数:
def func():
print('我是函数:{}'.format(func.__name__)) # __name__属性返回函数的名称
打印函数名:
>>> print(func)
<function func at 0x00000209EA722E18>
返回的是函数对象func,并指出了它的内存地址。
对函数使用type():
>>> type(func)
<class 'function'>
以上可以看出 函数 func 是 function类型的一个对象,属于可调用对象(具有__call__魔法方法)。
2)函数可以赋值给一个变量
函数作为一个对象,它可以作为其他函数的参数,也可以作为函数的返回值,还能够赋值给一个变量:
>>> f = func
>>> f() # 可调用对象后加()即可执行调用
我是函数:func # func赋值给变量f,f可以作为函数被调用,但函数名仍为func
将函数赋值给变量,是将函数的引用传递给变量。至此,函数名和变量共同指向 function对象func。
3)函数作为函数的返回值
定义一个可变参数的求和函数:
def sum(*args):
result = 0
for n in args:
result += n
return result
如果此时调用函数,将会立即求和。
再定义一个函数,它能够延迟求和:
def lazy_sum(*args):
def sum():
result = 0
for n in args:
result += n
return result
return sum # 函数sum作为返回值
调用函数lazy_sum()时,返回的是求和函数sum,而不是求和结果:
>>> f = lazy_sum(1, 2, 3)
>>> f
# f = 函数lazy_sum局部空间中的sum函数
<function lazy_sum.<locals>.sum at 0x000001ECB6C19840>
当调用函数f时,才会真正计算求和的结果:
>>> f()
6
这种嵌套定义函数,具有两个特征:
1)内部定义的函数使用了外部函数的参数或局部变量;
2)外部函数的返回值为内部函数(注意不是返回内部函数的调用);
最后有一点需要注意,每次调用lazy_sum(),都会返回一个新的函数,即使传入相同的参数:
>>> f1 = lazy_sum(1, 2, 3)
>>> f1 = lazy_sum(1, 2, 3)
>>> f1 == f2
False # f1和f2指向的是不同内存地址的sum函数
1.闭包
1)概念:
函数嵌套定义的前提下,如果内部函数使用了外部函数的变量,并且外部函数返回值为内部函数,此时我们就把这个内部函数称为“闭包”。
2)特征:
也即闭包的构成条件为:
(1)函数嵌套定义为前提;
(2)内部函数使用了外部函数的变量;
(3)外部函数的返回值为内部函数;
3)原理:
在上面的lazy_sum()函数中:
def lazy_sum(*args):
def sum():
result = 0
for n in args:
result += n
return result
return sum # 函数sum作为返回值
lazy_sum是外部函数,sum是内部函数,此时的sum就是闭包。
闭包(内部函数)sum引用了外部函数lazy_sum的变量,当lazy_sum返回sum时,其相关参数和变量就保存在了闭包sum中。其中的过程:定义外部函数 --> 操作系统为外部函数分配局部空间 --> 局部空间中定义变量和内部函数 --> 为内部函数开辟嵌套的局部空间 --> 返回内部函数。
过程中存在嵌套的局部空间(Enclosing),对于这个嵌套的局部空间来说,外部函数所在的局部空间就相当于其全局空间,其中的变量也相当于全局变量,因此内部函数可以像调用全局变量一样调用外部函数的变量。而也是由于这个嵌套的局部空间,使得内部函数保存了外部函数的变量。
闭包原理复杂,应用的时候牢记三点就可以:
1)闭包(内部函数)使用了外部函数的变量,并且能够保存外部函数的相关变量;
2)外部函数返回的是闭包函数,不是一个计算结果,该函数不会立即执行;
3)调用外部函数并赋值给变量,则该变量就等于保存了外部函数相关变量的内部函数;
4)应用:两个小伙伴聊天
# 定义闭包
def person(a):
def inner(b):
print('{}:{}'.format(a, b))
return inner
david = person('David') # 建立一个闭包对象david
jane = person('Jane') # 建立闭包对象jane
david('天王盖地虎')
jane('宝塔镇河妖')
>>> 运行结果:
David:天王盖地虎
Jane:宝塔镇河妖
可以看到两种现象:1)调用person()时,inner延迟执行;2)inner保存了person的变量a;
5)关键字nonlocal
关键字nonlocal能够实现对闭包外部函数变量的修改操作。nonlocal --> 声明内部函数的变量是非本地的。类似于定义普通函数时的声明全局变量的关键字global。
# 定义闭包:不使用nonlocal
def outer():
a = 10
def inner():
a = 20
result = a + 1
print(result)
inner()
print(a)
return inner
test1 = outer() # 调用外层函数
test1() # 调用内层函数
>>> 运行结果:
21
10
21
# a的值仍为10,说明内部函数没有修改掉外部函数变量a的值
# 此种情况下内部函数并没有使用外部函数的变量,而是自建了一个新的变量a
# 定义闭包:使用nonlocal
def outer():
a = 10
def inner():
nonlocal a
a = 20
result = a + 1
print(result)
inner()
print(a)
return inner
test1 = outer() # 调用外层函数
test1() # 调用内层函数
>>> 运行结果
21
20
21
# a的值被修改为20
2.总结
闭包是Python中函数式编程的重要语法结构,属于函数的高阶应用。通过它的原理也能够实现“装饰器”的定义。
3.Enclosing 补充
当定义闭包函数时,外部函数的局部命名空间中会存在“Enclosing作用域”。闭包函数会存在于这个Enclosing作用域中,可以通过闭包函数的__closure__属性调出。Enclosing除了存放闭包函数外,还会自动添加闭包函数用到的外部函数的对象,但仅限自己所用的。
上面闭包应用的例子:
# 定义闭包
def person(a):
a1 = 10
def inner(b):
print(inner.__closure__) # 打印闭包函数的__closure__属性
print('{}:{}'.format(a, b))
return inner
inner.__closure__ = (<cell at 0x000001DC0A6D7E88: str object at 0x000001DC0A768E30>, <cell at 0x000001DC0A6D76A8: function object at 0x000001DC0A799620>)
str object 即是inner用到的参数a;function object为它自己;而变量a1由于inner不会使用,所以不在其中。