对python中闭包概念的深入理解,很容易理解哟!不学白不学 :)

news/2024/7/19 13:40:37 标签: 1024程序员节, python, js

在这里插入图片描述

python是一门很常用的解释型语言,在日常使用中十分容易上手,但是实际上python中也有一些很高级很复杂的使用方式,学习这些方法对于理解整个python语言的构造、运行方式、以及提升学习者对python语言整体性的理解是很有必要的(实际上是因为我发现有的时候我写的代码不按照我想的方式来运行,可能这些和python的某些特殊设计有关,因此我觉得有必要把python学得全面一点)。

本文主要从程序的动态作用域和静态作用域入手介绍一下python函数中会出现的闭包现象的使用方式。或许在当下可能用不太上,但是我觉得这些功能还是挺牛的,总会有用的上的时候(其实是因为不熟,不知道怎么方便地用到自己的工作中)。

文章目录

    • 动态作用域和静态作用域
    • python函数闭包


动态作用域和静态作用域

要解释闭包现象,我觉得首先可以理解一下静态作用域动态作用域两个概念。

作用域,是程序设计中的概念,作用域简单解释实际上就是某个被定义的函数或者变量能够在整个脚本中被访问的范围。为什么要说是能够呢,是因为某些被定义的函数或者变量是局限性的,只有在某些特定的代码片中才能访问,比如说在函数中定义的变量只有在调用函数的时候才能被访问到。

理解了作用域后,动态作用域和静态作用域就比较好理解了。其实动态作用域和静态作用域是相对的概念,静态作用域与其名相称,也就是函数的作用域在定义的时候就已经被决定了;而对于动态作用域来说,在不同的地方对某个函数进行调用会产生不同的作用域,也即函数调用时作用域才被决定。

只说的话实际上有点抽象,看个例子就知道啦!(我暂时只会写python,就用python举例子啦,但是要知道python只支持静态作用域!!!)

抬上例子:

# 例1

# python函数,此处仅作为例子(python只支持静态作用域)

a = 'nihao'

def test1():
	print(a)
	
def test2(func):
	a = 'hello'
	func()
	
test2(test1)

上面这段代码在静态作用域和动态作用域中执行的结果是不同的。

上面这段代码在静态作用域中的执行结果是什么呢?结果是’nihao’,可能还有点懵是吧,我开始也有点懵,但是实际上这就是静态作用域的效果。怎么理解呢?实际上是因为 test1() 这个函数被定义后产生了其自身的作用域,test1()能访问到的只有全局变量和test1()内部的变量,也就是说在test2()函数中,被调用的test1()是访问不到test2()中定义的a变量的。也正是因此test2(test1)执行时test1()只能访问到全局的变量a,也就打印了"nihao"。

那么上面的例子如果在动态作用域中是怎样的呢?可能你已经猜到啦,应该是打印出"hello",这是因为在支持动态作用域的编程语言中,作用域随着我们使用函数的不同方式会发生变化 在这个例子中,要在test2()中调用test1函数,这时test1要执行的话能够访问到的作用域是test2()中定义的所有变量和所有全局变量,当然如果test1自身有定义变量,那么也能够访问到,这么多作用域它要获取哪里的a变量呢,就是调用test1时最接近test1函数的a变量,在这个例子中也就是test2中定义的a=“hello”。访问的顺序也就是从test1向调用test1的函数的外层一层一层进行访问,直到能得到一个a变量的值就停止。(这里我也验证不了,毕竟python使用的是静态作用域:(

当前大部分的编程语言都是只采用静态作用域的,比如C/C++、C#、Python、Java等等,支持动态作用域的编程语言只有极少数如Lisp、perl(两种作用域兼具)等。

python_46">python函数闭包

了解了动态作用域和静态作用域后,要理解闭包概念就比较容易了(参见例2的简易闭包)。实际上闭包现象存在于很多编程语言中,网上能查到最多的就是关于JavaScript的闭包的解释了。JavaScript MDN学习文档中关于闭包的解释是“一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域”。其实最重要的就是最后一句,闭包能让你在一个内层函数中访问到其外层函数的作用域,因为在正常情况下一个函数的作用域是不能被访问到的,函数的作用域只在函数内部。实际上闭包的本质可以归于静态作用域和万物皆对象的理论,为什么这么说呢,先看一下例子。

抬上例子:

# 例2

def outer():   # 设置外函数
	f = 5
	def inner():   # 设置内函数
		print(f)   
	return inner    # 返回内函数给outer()

a = outer()   # 将outer()的运行结果赋值给一个变量,此时的a是一个函数即inner
a()    # 实际上是在执行inner函数,同outer()()结果

或许你还不知道return inner是什么意思。首先,万物皆对象,python中的任何东西都是对象,也正是因此,对象才能够被以变量形式存储,也即我们可以任意的将函数或者变量进行分发给不同的变量名,但是实际上这些变量名背后都是最初的那个函数或者变量。也就是说这些函数或者变量名的在内存中的储存地址不会改变。我们调用这些不同的变量名实际上是调用的最初设置的对象(函数或者变量)。

现在进一步来理解上面的代码,return inner实际上就是将一个函数即inner返回给了outer()函数。之后将outer()函数的运行结果赋值给了a,注意a代表的是outer()的运行结果。而outer()的运行结果是什么呢,是inner这个函数名,也就是a = inner,那最后一步a()实际上就代表了inner()函数的运行结果,也正是因此上述代码的运行结果是5。可以看出在整个代码的运行中,不论函数如何调用,inner函数和outer函数的作用域都是确定的,也正是因此,静态作用域在此处发挥了功能。那么静态作用域是怎么发挥功能的呢,实际上就是内函数能够获取到外函数中的变量,这些被获取到的变量可以通过内函数被获取到(这里的例子就是打印出了外函数的f变量),这样的功能在获取局部变量时应该十分好用(毕竟我没用过,只能说应该)。

开始的时候我搞混了一个地方,先上例子:

# 例3

# 和第一个例子相似,但对test1的调用方式不同。此处是把test1作为参数返回出来
a = 'nihao'

def test1():
	print(a)
	
def test2(func):
	a = 'hello'
	return func
	
test2(test1)()

此段代码运行的结果还是"nihao",也就是test1部分被调用时并没有获取到test2里面的a变量。思考之后认为上面的例2之所以打印hello,应该是因为在外函数中定义内函数时才会使内函数获取到外函数的变量,也就是说定义的外函数outer建立起了自己的作用域,直到return语句结束后才结束,inner函数由于是在外函数内部定义,因此也属于outer的作用域。但是在例3中,test1函数处于外部,是被test2所调用的,也就是说test1函数并不在test2的作用域中,也正是因此test1获取不到test2中的a变量值

同时也能看出这里是静态作用域,因为test1和test2函数写好之后我们就能知道两个函数所具有的作用域了,要是这里是动态作用域的效果的话,test1直接访问离test1最近的a变量即test2提供的变量,那么打印的就是“hello”了(这里也侧面说明了代码中的test2(test1)()的运行对test1的调用并没有改变test1的作用域)。

现在应该大概理解了闭包的意思了吧。

除了上面获取函数变量的功能外,闭包还有一个作用就是内部函数会储存变量,也就是我们使用内函数访问到的外函数中的变量是不会从内存中清除的。正常来说函数在调用后其中的变量就不再被储存了,但是内函数所返回的变量值会继续保存在内存中直到外函数被清除。这里其实可以和之前的内容结合起来,即a = outer()其实是inner函数,再次执行a()是获取inner函数执行的结果,因此其执行并不经过外函数,从而能够储存之前获取到的变量。从整体上来看可以把内函数当做子函数,外函数为父函数,子函数能够访问父函数中的所有变量,同时子函数在父函数运行后还能够储存父函数的变量并使此变量变为全局变量。

上例子:

def outer():   # 设置外函数(父函数)
    f = 5
    w = 10
    l = [w]
    def inner():   # 设置内函数(子函数)
        nonlocal f   # 令f为非局部变量,即其改变会影响到全局
        l[0] += 1   # 使用列表等可变格式亦可设置非局部变量
        f += 1
        print("f的值",f)
        print("w的值",l[0])
    return inner    # 返回内函数给outer()
    
a = outer()   # 将outer()的运行结果赋值给一个变量,此时的a是一个函数即inner
a()
a()
del a
a()

运行结果(这里不再解释啦):

在这里插入图片描述

总结一下,在python中构成闭包的两个条件:

  • 闭包含有内函数和外函数,外函数中必须返回一个函数对象(其实就是内函数)
  • 内函数中必须引用到外函数中的变量,否则若是外函数和内函数执行不相关的功能的话闭包就没意义了

参考了很多网上的资料才算大致理解,花了很多时间,感觉内容也是很丰富呀,希望以后能用上吧!哈哈。其实在python中闭包还是有用武之地的,比如装饰器的使用,下一篇文章想写一下装饰器的使用。

参考:谈谈自己的理解:python中闭包,闭包的实质
参考:https://www.zhihu.com/question/283708101
参考:https://www.zhihu.com/question/34210214
参考:静态作用域与动态作用域的区别
参考:闭包


http://www.niftyadmin.cn/n/1203450.html

相关文章

CefSharp For WPF基本使用

原文:CefSharp For WPF基本使用Nuget引用 CefSharp.Wpf CefSharp.Common cef.redist.x64 cef.redist.x86 直接搜索安装CefSharp.Wpf即可,因为CefSharp.Wpf依赖于其他三个包,所以会自动安装四个包 先更改设置 指定特定的平台,x64或者x86&…

坑人的小米净水器: 漏水, 废水堵塞, 费用陷阱

出于对小米的好感, 当时看这个净水器外观也不错, 在无桶的产品里面也算便宜的(其他400g无桶的基本在2500左右), 虽说滤芯不便宜(各级滤芯都是30~80元不等, RO膜499), 但是也还可以接受, 就选了小米. 买的是厨下式的, 2017年9月装修好之后才开始用, 刚开始用时还比较正常, 废水:…

abi:c++11问题

2019独角兽企业重金招聘Python工程师标准>>> Dual ABI GCC 5 以后的版本&#xff0c;将std::string 与 std::list 从新实现了&#xff0c;对于c03 与 c11 来说&#xff0c; list<int> 从原有的 std::list<int> 变为了std::__cxx11::list<int> 所以…

python中装饰器的使用方式,非常简单易上手哦

上一篇文章写了python中闭包的基础使用方式&#xff0c;而闭包在python中较为常见的用法就是在装饰器中出现&#xff0c;由于前段时间看了一些装饰器的文章&#xff0c;因此本文想要简要介绍一下python中装饰器。需要注意的是最好先了解闭包的使用方式再来看装饰器的使用方式哟…

初识Spring Boot

​ 1、Spring Boot简介 Spring Boot是由Pivotal团队提供的全新框架&#xff0c;用于简化基于Spring的搭建与开发过程&#xff0c;通过少量的代码创建Spring应用。 2、Spring Boot诞生 由于Spring每集成一个开源软件&#xff0c;就要增加配置信息&#xff0c;这样项目越大&#…

Linux系统中根目录下各子目录分别是 干啥儿的!

由于觉得linux基础还很不足&#xff0c;而实际上linux在工作中十分常用&#xff0c;若是基础知识薄弱&#xff0c;那么之后必定会遇上麻烦&#xff0c;因此最近开始重新学linux基础啦。现在看的书是鸟哥写的鸟哥的LINUX私房菜之基础学习篇。根据自己学习到的知识会整理一些知识…

python 推导式的使用方式(代码整洁性、高级感必备!)

前不久碰上了一个同学&#xff0c;和他交流了一下写代码的感想&#xff0c;正好他也是使用python比较多&#xff0c;然后他给我提了一些我可以改善的地方&#xff0c;比如说多使用类、使用argparse、使用推导式等&#xff0c;我觉得这些都是挺好用的东西&#xff0c;本次就简单…

Microsoft正式发布Azure IoT Hub与Event Grid的集成

在经历为期六个月的公开预览后&#xff0c;微软宣布正式发布IoT Hub与Azure Event Grid的集成。组合使用两者可以提高对客户设备事件的支持&#xff0c;实现数据库更新、工单&#xff08;ticket&#xff09;创建和定价管理等操作的自动化。\\正式发布的IoT Hub与Event Grid的集…