【python进阶 笔记】闭包
目录
先通过一个问题的解决来引入闭包,再对闭包进行解释和执行过程分析。
1. 问题引入
以y=kx+b为例,请计算一条线上的多个点 即 给x值 计算出y值。
# 问题:初中里学过函数,例如 y=kx+b, y=ax^2 + bx + c
# 以y=kx+b为例,请计算一条线上的多个点 即 给x值 计算出y值
# 第1种
# k = 1
# b = 2
# y = k*x+b
# 缺点:如果需要多次计算,那么就的写多次y = k*x+b这样的式子,不能重用
# 第2种:定义一个函数
def line_2(k, b, x):
print(k*x+b)
line_2(1, 2, 0)
line_2(1, 2, 1)
line_2(1, 2, 2)
# 缺点:如果想要计算多次这条线上的y值,那么每次都需要传递k,b的值,麻烦
print("-"*50)
# 第3种: 全局变量,
k = 1
b = 2
def line_3(x):
print(k*x+b)
line_3(0)
line_3(1)
line_3(2)
k = 11
b = 22
line_3(0)
line_3(1)
line_3(2)
# 缺点:如果要计算多条线上的y值,那么需要每次对全局变量进行修改,代码会增多,麻烦
print("-"*50)
# 第4种:缺省参数(注意,缺省参数放在后面)
def line_4(x, k=1, b=2):
print(k*x+b)
line_4(0)
line_4(1)
line_4(2)
line_4(0, k=11, b=22)
line_4(1, k=11, b=22)
line_4(2, k=11, b=22)
# 优点:比全局变量的方式好在:k, b是函数line_4的一部分 而不是全局变量,因为全局变量可以任意的被其他函数所修改
# 缺点:如果要计算多条线上的y值,那么需要在调用的时候进行传递参数,麻烦
print("-"*50)
# 第5种:实例对象
# 创建实例对象的时候设置好k和b
class Line5(object):
def __init__(self, k, b):
self.k = k
self.b = b
def __call__(self, x): # __call__方法,对象后面加括号,触发执行
print(self.k * x + self.b)
line_5_1 = Line5(1, 2)
# 对象.方法()
# 对象():会触发__call__方法
line_5_1(0) # 该形式即 对象() ,会调用__call__
line_5_1(1)
line_5_1(2)
line_5_2 = Line5(11, 22)
line_5_2(0)
line_5_2(1)
line_5_2(2)
# 缺点:为了计算多条线上的y值,所以需要保存多个k, b的值,因此用了很多个实例对象, 浪费资源
print("-"*50)
# 第6种:闭包
def line_6(k, b):
def create_y(x):
print(k*x+b)
return create_y
line_6_1 = line_6(1, 2) #
line_6_1(0)
line_6_1(1)
line_6_1(2)
line_6_2 = line_6(11, 22)
line_6_2(0)
line_6_2(1)
line_6_2(2)
在第六种,闭包的解法中:
函数create_y与变量k,b构成闭包。在创建闭包的时候,我们通过 line_6 的参数k,b说明了这两个变量的取值。这样确定了函数的最终形式(y = kx + b)。我们只需要变换参数k,b,就可以获得不同的直线表达函数。可见,闭包也具有提高代码可复用性的作用。如果没有闭包,则需要每次创建直线函数的时候同时说明k,b,x。需要更多的参数传递,减少了代码的可移植性。
( 注意 :由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,会消耗内存)
思考:函数、匿名函数、闭包、对象 做实参时, 有什么区别?
- 匿名函数能够完成基本的简单功能,传递是这个函数的引用 只有功能(注:匿名函数由lambda关键字创建,冒号后面的表达式有且只能有一个);
- 普通函数能够完成较为复杂的功能,传递是这个函数的引用 只有功能;
- 闭包能够完成较为复杂的功能,传递是这个闭包中的函数以及数据,因此传递是 功能+数据(较简单的);
- 对象能够完成最为复杂的功能,传递是很多数据+很多功能(许多属性和方法可以用),因此传递是 功能+数据;
2. 什么是闭包?(重点)
在函数内部再定义一个函数(多层函数嵌套定义),并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包。
# 定义一个函数
def test(number):
# 在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包
def test_in(number_in):
print("in test_in 函数, number_in is %d" % number_in)
return number+number_in
# 其实这里返回的就是闭包的结果
return test_in
# 给test函数赋值,这个20就是给参数number
ret = test(20)
# 注意这里的100其实给参数number_in
print(ret(100))
#注 意这里的200其实给参数number_in
print(ret(200))
执行过程:
- ret = test(20): 执行test函数,def test_in 语句定义函数直接跳过不执行 ,return test_in 返回函数的引用;
- ret 此时指向 函数test_in ,执行 ret(100) 意味着调用函数 test_in ,传参为 100 。
3. 修改外部函数中的变量
在局部中修改全局变量可以通过 global 关键字来修改,在闭包中该如何修改外部函数的变量呢?
-
闭包中使用 nonlocal 修改外部函数的变量。
-
闭包和外部函数有同名的变量时 ,闭包中加上 nonlocal 才会访问到外部函数的变量。
demo1
def counter(start=0): def incr(): nonlocal start start += 1 return start return incr c1 = counter(5) print(c1()) # 6 print(c1()) # 7 c2 = counter(50) print(c2()) # 51 print(c2()) # 52 print(c1()) # 8 print(c1()) # 9 print(c2()) # 53 print(c2()) # 54
demo2
x = 300 def test1(): x = 200 def test2(): nonlocal x # 因为下面定义了局部的x,不写这句的话下面的输出语句会一位x是局部变量的x,报错 print("----1----x=%d" % x) x = 100 print("----2----x=%d" % x) return test2 t1 = test1() t1()
输出:
----1----x=200
----2----x=100
注:以上nonlocal方法不适用与python2 。
-----end-----