python 函数进阶-闭包函数

闭包函数

什么是闭包函数

如果内函数使用了外函数的局部变量,并且外函数把内函数返回出来的过程叫做闭包,里面的内函数是闭包函数。

# 外函数 outer
def outer():
	# 外函数变量 num
	var = '外函数局部变量'

	# 内函数 inner
	def inner():
		# 内函数使用了外函数的变量 num
		print('内函数使用了:' + var)

	# 外函数将使用了外函数的局部变量的内函数返回
	return inner

# 返回出的结果就是内函数 inner,现在inner就是一个闭包函数
func = outer()

# 执行返回出的 inner 函数
func()  # 内函数使用了:外函数局部变量

下面是一个复杂的版本。

inner函数返回了函数x 和 y,x 和 y是外函数的内函数,虽然覆盖了原有的外函数的局部变量,但是这两个函数本质上还是外函数的布局变量,所以外函数返回了inner,inner就是一个闭包函数。

inner返回了外函数的x和y函数,x和y函数都是用了外函数的内函数num3,外函数返回inner,inner返回了x和y,所以变相的就是外函数返回了x和y,所以x和y也是闭包函数。

# 外函数
def outer():
   # 外函数的局部变量
   x = 1
   y = 2
   num3 = 3

   # 内函数 x 重名变量 x
   def x():
      # 调用修改了 变量 num3
      nonlocal num3
      num3 *= 10
      print(num3)

   # 内函数 y 重名变量y
   def y():
      # 调用修改了 变量num3
      nonlocal num3
      num3 += 10
      print(num3)
   
   # 内函数inner
   def inner():
      # 返回了同级内函数 x y
      return x, y
   
   # 外函数最终返回了 inner函数
   return inner

判断是否是闭包函数

方法 作用
_closure_ 获取闭包函数使用的局部变量
cell_contents 获取单元格对象当中的闭包函数
_closure_

可以使用这个方法判断一个函数是否是一个闭包函数,因为闭包函数必须要使用外函数的局部变量,如果返回None就说明这个函数不是闭包函数,如果返回的是一个元组,说明这是一个闭包函数,元组中有cell单元格对象,一个单元格对象表示这个闭包函数使用了几个外函数的局部变量。

拿上述版本测试。

# 外函数
def outer():
	# 外函数的局部变量
	x = 1
	y = 2
	num3 = 3

	# 内函数 x 重名变量 x
	def x():
		# 调用修改了 变量 num3
		nonlocal num3
		num3 *= 10
		print(num3)

	# 内函数 y 重名变量y
	def y():
		# 调用修改了 变量num3
		nonlocal num3
		num3 += 10
		print(num3)

	# 内函数inner
	def inner():
		# 返回了同级内函数 x y
		return x, y

	# 外函数最终返回了 inner函数
	return inner


# 执行outer返回的结果是inner
func = outer()  # func == inner

# 执行func返回的是 x y 函数
a, b = func()

# 使用__closure__测试这个几个函数是否是闭包函数
print(outer.__closure__)
print(func.__closure__)
print(a.__closure__)
print(b.__closure__)

'''
结果:除了外函数outer之外都返回了cell对象,说明inner x y 都是闭包函数
None
(<cell at 0x0000022F246AECA8: function object at 0x0000022F2466C400>, <cell at 0x0000022F247F3558: function object at 0x0000022F24850730>)
(<cell at 0x0000022F245D8708: int object at 0x00000000542280B0>,)
(<cell at 0x0000022F245D8708: int object at 0x00000000542280B0>,)
'''
cell_contents

虽然用__closure__获取到了闭包函数使用的元素,但是是以cell单元格对象的形式展示的,我们并不能看出这个使用的 元素到底是什么东西,可以使用cell_contents查看。

# 外函数
def outer():
   # 外函数的局部变量
   x = 1
   y = 2
   num3 = 3

   # 内函数 x 重名变量 x
   def x():
      # 调用修改了 变量 num3
      nonlocal num3
      num3 *= 10
      print(num3)

   # 内函数 y 重名变量y
   def y():
      # 调用修改了 变量num3
      nonlocal num3
      num3 += 10
      print(num3)

   # 内函数inner
   def inner():
      # 返回了同级内函数 x y
      return x, y

   # 外函数最终返回了 inner函数
   return inner


# 执行outer返回的结果是inner
func = outer()  # func == inner


# 使用__closure__返回了闭包函数使用的局部变量
tup = func.__closure__

# 使用 cell_contents 查看这些局部变量都是些什么
res = tup[0].cell_contents
print(res)
res = tup[1].cell_contents
print(res)

'''
结果:可以看到inner 使用的局部变量使用外函数的内函数 x 和 y
None
<function outer.<locals>.x at 0x0000018D5A66C400>
<function outer.<locals>.y at 0x0000018D5A850730>
'''

闭包函数的特点

让我们回忆一下,函数中创建的变量是一个什么变量?是一个局部变量。

局部变量的生命周期是多久?是等局部作用结束之后就会被释放掉。

如果内函数使用了外函数的局部变量,那么这个变量就与闭包函数发生了绑定关系,就延长该变量的生命周期。实际上就是内存给它存储了这个值,暂时不释放。

下面的例子中,我们调用了函数outer并赋予了参数val的值为10,但是outer执行完之后,outer的val并没有被释放,而是被闭包函数inner延长了生命周期,所以val可以一直在inner中按照调用outer函数的时候赋予的值10进行运算。

因为内函数inner使用了外函数outer的变量val,且outer返回了inner,所以inner是一个闭包函数。因为inner是一个闭包函数,当它调用outer的变量val时就会延长val的生命周期,val就不会随着outer的调用结束而被释放
而是存储在了内存当中,当inner再次使用val时,val就会将值赋予inner。

def outer(val):
   def inner(num):
      return val + num

   return inner

func = outer(10)
res = func(10)
print(res)  # 20
res = func(20)
print(res)  # 30

闭包函数的意义

闭包可以优先使用外函数中的变量,并对闭包中的值起到了封装包保护的作用,使外部无法访问。

我们做一个模拟鼠标点击的事件,可以看得出闭包函数封装保护数据的作用。

现在只是一个普通的函数,它无法对我们使用的变量的数据进行保护,在全局中这个数据可以被随意的修改。

# 不使用闭包,当函数中调用全局变量时,外部也可以控制变量

# 全局变量
num = 0

# 点击事件
def click_num():
	# 每执行一次数值 +1
	global num
	num += 1
	print(num)

# 执行点击事件
click_num()  # 1
click_num()  # 2
click_num()  # 3

# 在全局重新定义了num的值,num的值就被彻底的改变了,但是我们的程序的数据本不该如此。
num = 1231231

click_num()  # 1231232
click_num()  # 1231233
click_num()  # 1231234

现在使用闭包函数对数据进行封装保护,就不能在全局中随意的修改我们使用的数据。

# 我们将需要使用的数据放在外函数中,点击事件作为内函数也放在外函数中,然后作为闭包返回。
def clickNum():
	# 需要使用的数据
	num = 0

	# 内函数(真正执行点击事件的函数)
	def inner():
		# 执行点击事件
		nonlocal num
		num += 1
		print(num)

	# 作为闭包返回
	return inner

# 返回闭包
click_num = clickNum()  # # click_num == inner

# 执行点击事件
click_num()  # 1
click_num()  # 2
click_num()  # 3

# 全局中修改 num 的值
num = 123412341234

# 闭包函数对数据的封装保护起到了作用
click_num()  # 4
click_num()  # 5
click_num()  # 6

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:python 函数进阶-闭包函数 - Python技术站

(0)
上一篇 2023年4月2日 下午5:48
下一篇 2023年4月2日 下午5:49

相关文章

  • Python 函数进阶-递归函数

    递归函数 什么是递归函数 如果一个函数,可以自己调用自己,那么这个函数就是一个递归函数。 递归,递就是去,归就是回,递归就是一去一回的过程。 递归函数的条件 一般来说,递归需要边界条件,整个递归的结构中要有递归前进段和递归返回段。当边界条件不满足,递归前进,反之递归返回。就是说递归函数一定需要有边界条件来控制递归函数的前进和返回。 定义一个简单的递归函数 #…

    Python开发 2023年4月2日
    00
  • python生成器

    生成器 我们学习完推导式之后发现,推导式就是在容器中使用一个for循环而已,为什么没有元组推导式? 原因就是“元组推导式”的名字不是这样的,而是叫做生成器表达式。 什么是生成器 生成器表达式本质上就是一个迭代器,是定义迭代器的一种方式,是允许自定义逻辑的迭代器。生成器使用generator表示。 迭代器和生成器的区别 迭代器本身是系统内置的, 无法重写内置的…

    Python开发 2023年4月2日
    00
  • python常用标准库(压缩包模块zipfile和tarfile)

    常用的标准库 在我们常用的系统windows和Linux系统中有很多支持的压缩包格式,包括但不限于以下种类:rar、zip、tar,以下的标准库的作用就是用于压缩解压缩其中一些格式的压缩包。 zip格式 import zipfile zipfile模块操作压缩包使用ZipFile类进行操作,使用方法和open的使用方法很相似,也是使用r、w、x、a四种操作模…

    Python开发 2023年4月2日
    00
  • Python 函数进阶-高阶函数

    高阶函数 什么是高阶函数 高阶函数就是能够把函数当成参数传递的函数就是高阶函数,换句话说如果一个函数的参数是函数,那么这个函数就是一个高阶函数。 高阶函数可以是你使用def关键字自定义的函数,也有Python系统自带的内置高阶函数。 自定义一个高阶函数 我们下面的例子中,函数 senior 的参数中有一个是函数,那么senior就是一个高阶函数;函数 ten…

    Python开发 2023年4月2日
    00
  • Python常用标准库(pickle序列化和JSON序列化)

    常用的标准库 序列化模块 import pickle 序列化和反序列化 把不能直接存储的数据变得可存储,这个过程叫做序列化。把文件中的数据拿出来,回复称原来的数据类型,这个过程叫做反序列化。 在文件中存储的数据只能是字符串,或者是字节流,不能是其它的数据类型,但是如果想要将其存储就需要序列化。 Python中的序列化模块叫做 pickle,PHP等其它的一些…

    Python开发 2023年4月2日
    00
  • Python 函数进阶-迭代器

    迭代器 什么是迭代器 能被 next 指针调用,并不断返回下一个值的对象,叫做迭代器。表示为Iterator,迭代器是一个对象类型数据。 概念 迭代器指的是迭代取值的工具,迭代是一个重复的过程,每次重复都是基于上一次的结果而继续的,单纯的重复并不是迭代。 特征 迭代器并不依赖索引,而通过 next 指针迭代所有数据,一次只取一个值,大大节省空间。 惰性序列 …

    Python开发 2023年4月2日
    00
  • Python推导式

    推导式 什么是推导式 推导式是 for 循环的简化使用方法,使用推导式,将一个可迭代对象中的数据遍历到某一个容器当中。简单的来说就是用一行for循环语句,遍历一个可迭代对象中的所有数据,然后将遍历出来的数据进行处理放入对应的容器中的一个过程和方式。 和推导类似作用的还有三元运算符,三元运算符是条件判断语句的简化使用方法。 语法 val for val in …

    Python开发 2023年4月2日
    00
  • Python 函数进阶-全局空间和局部空间

    全局空间和局部空间 命名空间 命名空间的概念的提出是为了划分和控制变量是否可见,以及生存周期的长短;命名空间的作用范围叫做作用域。 划分一块区域保存所有数据,以字典的方式存储(变量与值形成映射关系)。一共三种。 内建命名空间: 解释器启动时创建,直到解释器运行结束,生存周期最长; 全局命名空间: 文件运行时创建,直到解释器运行结束,生存周期较长; 局部命名空…

    Python开发 2023年4月2日
    00
合作推广
合作推广
分享本页
返回顶部