【Python函数式编程】——闭包

89 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情

Python函数式编程——闭包

在这里插入图片描述

1.什么是闭包

  什么是闭包:一个函数定义中引入了函数定义以外的变量,并且该函数可以在其定义以外被执行,这样的一个函数称为闭包。

闭包的三个条件,缺一不可

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

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

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

闭包代码实现:

# -*- coding: utf-8 -*-
# @File  : 闭包.py
# @author: Flyme awei 
# @email : Flymeawei@163.com
# @Time  : 2022/8/21 15:58


# 定义一个函数
def fun_a(num_a):
    # 在函数内部再定义⼀个函数
    # 并且这个内部函数⽤到了外部的变量,这个函数以及⽤到外部函数的变量及参数叫 闭包
    def fun_b(num_b):
        print('内嵌函数fun_b的参数是:%s,外部函数fun_a的参数是:%s' % (num_b, num_a))
        return num_a + num_b
    # 这里返回的就是闭包的结果
    return fun_b


# 给fun_a函数赋值,这个10就是传参给fun_a
ret = fun_a(10)

# 注意这里的10其实是赋值给fun_b
print(ret(10))

# 注意这里的90其实是赋值给fun_b
print(ret(90))

在这里插入图片描述

  此时,内部函数对外部函数作⽤域⾥变量的引⽤(⾮全局变量),则称内部函数为闭包。


2.闭包修改外部变量

python交互环境idle

>>> def counter(start = 0):
	count = [start]
	def incr():
		count[0] += 1
		return count[0]
	return incr

>>> c1 = counter(5)
>>> print(c1())
6
>>> print(c1())
7
>>> print(c2())
51
>>> print(c2())
52
>>>

  当一个函数在本地作用域找不到变量申明时会向外层函数寻找,这在函数闭包中很常见但是在本地作用域中使用的变量后,还想对此变量进行更改就会报错。

看一段代码:

# -*- coding: utf-8 -*-
# @File  : 闭包修改外部变量.py
# @author: Flyme awei 
# @email : Flymeawei@163.com
# @Time  : 2022/8/21 16:30


# 闭包修改外部变量的值
def test1():
    c = 1
    # c不是局部变量,是介于局部变量和全局变量之间的一种变量,用 nonlocal标识

    def add1():

        print(c)  # 1
        c += 1
        return c  # 2
    return add1


print(test1()())

报错信息: 在这里插入图片描述

此时,如果我在函数内加一行nonlocal c就可解决这个问题

代码:

# -*- coding: utf-8 -*-
# @File  : 闭包修改外部变量.py
# @author: Flyme awei 
# @email : Flymeawei@163.com
# @Time  : 2022/8/21 16:30


# 闭包修改外部变量的值
def test1():
    c = 1
    # c不是局部变量,是介于局部变量和全局变量之间的一种变量,用 nonlocal标识

    def add1():
        nonlocal c
        print(c)  # 1
        c += 1
        return c  # 2
    return add1


print(test1()())

nonlocal声明的变量不是局部变量,也不是全局变量,而是外部嵌套函数内的变量(介于局部变量和全局变量之间的一种变量)。

使用nonlocal的好处是,在为函数添加状态时不用额外地添加全局变量,因此可以大量地调用此函数并同时记录着多个函数状态,每个函数都是独立、独特的。


3.闭包的应用

闭包实现 y = a*x + b

# -*- coding: utf-8 -*-
# @File  : 闭包的应用.py
# @author: Flyme awei 
# @email : Flymeawei@163.com
# @Time  : 2022/8/21 16:18


# y = a*x + b
def create_line(a, b):
    def line(x):
        return a * x + b

    return line


line1 = create_line(1, 1)  # a:1 b:1
line2 = create_line(4, 5)  # a:4 b:5

print(line1(5))  # 6
print(line2(5))  # 25

从这段代码中,函数line与变量a,b构成闭包。在创建闭包的时候,我们通过create_line的参数a,b说明了这两个变量的取值,这样,我们就确定了函数的最终形式(y = x + 1y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。

由此,我们可以看到,闭包也具有提⾼代码可复⽤性的作⽤。

如果没有闭包,我们需要每次创建函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。

1.闭包优化了变量,原来需要类对象完成的⼯作,闭包也可以完成
2.由于闭包引⽤了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存

4.闭包的陷阱

  函数内部函数,引用外部函数参数或值,进行内部函数运算执行,并不是完全返回一个函数,也有可能是一个在外部函数的值,我们还需要知道返回的函数不会立刻执行,而是直到调用了函数才会执行。

看代码:

# -*- coding: utf-8 -*-
# @File  : 闭包的陷阱.py
# @author: Flyme awei 
# @email : Flymeawei@163.com
# @Time  : 2022/8/21 17:09


def fun_a():
    fun_list = []
    for i in range(1, 4):

        def fun_b():
            return i * i

        fun_list.append(fun_b)
    return fun_list


f1, f2, f3 = fun_a()
print(f1(), f2(), f3())  
# 9 9 9 

  这里创建了一个fun_a函数,外部函数的参数fun_list定义了一个列表,在进行遍历,循环函数fun_b,引用外部变量i 计算返回结果,加入列表,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了,但是实际结果并不是我们想要的1,4,9,而是999,这是为什么呢?

  这是因为,返回的函数引用了变量 i ,但不是立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,每一个独立的函数引用的对象是相同的变量,但是返回的值时候,3个函数都返回时,此时值已经完整了运算,并存储,当调用函数,产生值不会达成想要的,返回函数不要引用任何循环变量,或者将来会发生变化的变量,但是如果一定需要呢,如何修改这个函数呢?

我们fun_b()把这里的参数i赋值给x就可以解决

# -*- coding: utf-8 -*-
# @File  : 闭包的陷阱.py
# @author: Flyme awei 
# @email : Flymeawei@163.com
# @Time  : 2022/8/21 17:09


def fun_a():
    fun_list = []
    for i in range(1, 4):

        def fun_b(x=i):
            return x ** 2

        fun_list.append(fun_b)
    return fun_list


f1, f2, f3 = fun_a()
print(f1(), f2(), f3())  
# 1 4 9

  可以再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变,那我们就可以完成下面的代码:

# -*- coding: UTF-8 -*- # 
def fun_a(): 
	def fun_c(i): 
		def fun_b(): 
			return i * i 
			
		return fun_b 

	fun_list = [] 
	for i in range(1, 4): 
		# f(i)立刻被执行,因此i的当前值被传入f() 
		fun_list.append(fun_c(i)) 
	return fun_list 


f1, f2, f3 = fun_a() 
print(f1(), f2(), f3()) 
# 1 4 9