以python为例讲解闭包机制
以python为例讲解闭包机制
缘起
在学习JS的过程中,总是无可避免的接触到闭包机制,尤其是接触到react后,其函数式的编程思想更是将闭包发扬光大,作为函数式编程的重要语法结构,python自然也拥有闭包这一语法结构。
在这篇文章中我会介绍如何产生一个闭包函数,闭包函数产生的原因和使用它的优点。
回顾python的函数作用域
闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,闭包是由函数和与其相关的引用环境组合而成的实体。
我们从最简单的嵌套函数来了解闭包
def transmit_to_space(message):
def data_transmitter():
"这是一个嵌套函数"
print(message)
data_transmitter()
print(transmit_to_space("Test message"))
我们可以发现,嵌套在外层函数transmit_to_space里的data_transmitter访问了外层函数提供的形参message。我们可以得知,局部作用域能够访问外部作用域。
def print_msg(number):
def printer():
"Here we are using the nonlocal keyword"
nonlocal number
number=3
print(number)
printer()
print(number)
print_msg(9)
在上面的代码中我们使用了nonlocal这个关键字,正如字面意思,它使变量变为非局部的。我们知道局部作用域可以访问外部作用域,但不能更改他,一旦进行了更改就会把其当成局部变量,如果在修改前进行了读取则会报错。
通常为了解决这个问题我们会使用global直接从全局进行修改,这显然很危险。因此python3引入了nonlocal这个关键字使解释器能够从外部作用域查找变量名。
假如我们不使用nonlocal则会打印3,9,在使用nonlocal后则会打印3,3。使用nonlocal是的局部嵌套函数对于外层函数的变量进行了修改。
一切皆对象
上面的例子除了介绍了nonlocal,无非老调重弹,介绍了作用域的问题,这就是闭包吗?
我们知道python中一切皆对象,函数也是对象的一种,因此就有了一个相当有趣的例子
def transmit_to_space(message):
def data_transmitter():
print(message)
return data_transmitter
func = transmit_to_space("hello")
func()
这真的是一个很suprise的事情,内部函数作为对象已经脱离了其作用域,但是我们在执行是竟然发现它打印出我们传进去的形参。
其实原因很简单:本质上是闭包使得变量的值始终保存在内存中。本来,在执行完transmit_to_space后message这个变量应该被销毁,但由于data_transmitter使用了闭包,且在调用完仍然存在,message的销毁被延后了,仍然存储在内存中。
像这种,transmit_to_space执行完毕message却保存下来的过程我们称之为闭包。
在python中,函数本身也是对象,在我们调用transmit_to_space("hello"),我们传进去的参数作为局部变量储存了下来。在这个对象存在的生命周期我们都能访问它其中的变量。
def transmit_to_space(message):
def data_transmitter():
print(message)
return data_transmitter
func = transmit_to_space("hello")
func2 = transmit_to_space("toto")
func()
func2()
"""
hello
toto
"""
从上面的例子可以看出message的存储与transmit_to_space是没有关系的
我们可以看下面这个例子加深理解
def transmit_to_space(message):
def data_transmitter():
print(message)
return data_transmitter
func = transmit_to_space("hello")
del transmit_to_space
func()
正因为我们在调用该函数时,创建了该函数对象的一个实例,因此即使我们删除原先的函数,func仍能执行。
我们知道函数调用过程中局域变量是栈的压入和推出所实现的,那么在执行func的时候,message存储在哪?这里我们得提到一个魔法属性__closure__
def transmit_to_space(message):
def data_transmitter():
print(message)
return data_transmitter
func = transmit_to_space("hello")
print(func.__closure__[0].cell_contents)
闭包函数相比普通函数会多出一个__closure__的属性,闭包的cells中引用了函数执行过程中来自外部作用域却需要使用的变量。
具体可以参考这篇文章
闭包闭包
从上面的代码中我们可以总结出来创建闭包的条件:
即闭包函数中存在执行完毕仍然存在的对象
闭包使变量隐藏起来,我们很难从外部改变它的状态(其实可以通过__closure__访问),这使得人们很容易追踪变量的改变。
当多个模块依赖一个共有数据时,且该共有数据可以被修改,那么一个数据的改变可能引起多个模块不可预料的结果,使用闭包来对外隐藏这些数据,通过固定的方法进行修改能够极大减少bug的发生,redux的设计就是一个很好的例子。
我们可以使用python实现一个简单的redux
createStore(state, stateChanger):
getState = lambda:state
dispatch = lambda action:stateChanger(state,action)
return {
"getState":getState,
"dispatch":dispatch
}
appState = {
"status":"good"
}
def stateChanger(state,action):
if action == "good":
state["status"] = "good"
elif action == "bad":
state["status"]="bad"
action = "bad"
store = createStore(appState,stateChanger)
print(store["getState"]())
store["dispatch"](action)
print(store["getState"]())
使用createstore我们创建了一个store对象,只能通过固定的stateChanger访问。
总结
函数式编程受到推崇的原因不得不说包含了其中所有数据不可变,解耦了依赖,而闭包这一函数式编程的重要语法结构更是体现了这一优点。
参考
Python Closures: How to use it and Why? - Programiz
以python为例讲解闭包机制的更多相关文章
- Python 变量作用域,闭包和装饰器
from dis import dis b = 6 def f1(a): print(a)print(b) b = 9 f1(3) print(dis(f1)) # dis模块可以查看python函数 ...
- JavaScript——闭包机制
闭包机制是JavaScript的重点和难点,本文希望能帮助大家轻松的学习闭包 一.什么是闭包? 闭包就是可以访问另一个函数作用域中变量的函数.下面列举出常见的闭包实现方式,以例子讲解闭包概念 func ...
- Python基础系列讲解—动态类型语言的特点
前言 在C语言中变量所分配到的地址是内存空间中一个固定的位置,当我们改变变量值时, 对应内存空间中的值也相应改变.在Python中变量存储的机制是完全不一样的,当给一个变量赋值时首先解释器会给这个值分 ...
- 常用正则表达式最强汇总(含Python代码举例讲解+爬虫实战)
大家好,我是辰哥~ 本文带大家学习正则表达式,并通过python代码举例讲解常用的正则表达式 最后实战爬取小说网页:重点在于爬取的网页通过正则表达式进行解析. 正则表达式语法 Python的re模块( ...
- python基础知识讲解——@classmethod和@staticmethod的作用
python基础知识讲解——@classmethod和@staticmethod的作用 在类的成员函数中,可以添加@classmethod和@staticmethod修饰符,这两者有一定的差异,简单来 ...
- C#编译器闭包机制
背景 C# 在编译器层面为我们提供了闭包机制(Java7 和 Go 也是这种思路),本文简单的做个解释. 背景知识 你必须了解:引用类型.值类型.引用.对象.值类型的值(简称值). 关于引用.对象和值 ...
- python ---split()函数讲解
python ---split()函数讲解 split中文翻译为分裂. 在python用于分割字符串使用. split()就是将一个字符串分裂成多个字符串组成的列表. split()可以传入参数,也可 ...
- python与java的内存机制不一样;java的方法会进入方法区直到对象消失 方法才会消失;python的方法是对象每次调用都会创建新的对象 内存地址都不i一样
python与java的内存机制不一样;java的方法会进入方法区直到对象消失 方法才会消失;python的方法是对象每次调用都会创建新的对象 内存地址都不i一样
- Python中的垃圾回收机制
Python的垃圾回收机制 引子: 我们定义变量会申请内存空间来存放变量的值,而内存的容量是有限的,当一个变量值没有用了(简称垃圾)就应该将其占用的内存给回收掉,而变量名是访问到变量值的唯一方式,所以 ...
随机推荐
- FORTRAN学习记录
WHERE statement http://scv.bu.edu/computation/bluegene/IBMdocs/compiler/xlf-10.1/html/xlflr/where.ht ...
- 【leetcode】891. Sum of Subsequence Widths
题目如下: 解题思路:题目定义的子序列宽度是最大值和最小值的差,因此可以忽略中间值.首先对数组排序,对于数组中任意一个元素,都可以成为子序列中的最大值和最小值而存在.例如数组[1,2,3,4,5,6] ...
- Git 中的一些其他常用命令
1.查看提交的历史版本(git log) 我们可以使用 git log 命令来查看提交的历史版本. 默认不用任何参数的话,git log 会按提交时间列出所有的更新,最近的更新排在最上面.每个版本都有 ...
- 【Java】定义Logger为什么要用static和final?
private static final Logger logger= LoggerFactory.getLogger(ShiroConfig.class); (1)出于资源利用的考虑,Logger的 ...
- VC++ 控件赋值取值
SetWindowText(SetWindowTextW)void SetWindowText( LPCTSTR lpszString );GetWindowText(GetWindowTextW ...
- [CSP-S模拟测试]:beauty(搜索)
题目描述 距离产生美.一棵包含$n$个点的树,有$2k$个不同的关键点,我们现在需要将这些点两两配对,对于一种形如:$$(u_1,v_1),(u_2,v_2),...,(u_k,v_k)$$的配对方案 ...
- 转载:VScode 好用插件集合
--------------------- 作者:JayveeWong 来源:CSDN 原文:https://blog.csdn.net/weixin_42776111/article/details ...
- 【已转移】【Java架构:基础技术】一篇文章搞掂:SVN
一个例子: 公司的SVN代码中,含有target等文件夹,每次生成运行后,有很多文件打扰签入 处理方案: 1.CheckOut时,点击ChooseItems选项,不要选择这些target文件夹(有点麻 ...
- css > 的写法 html
.userInfo-view .info .name::after { content: " "; display: inline-block; height: 12rpx; wi ...
- 【SpringBoot】 理解Spirng中的IOC原理
前言 前文已经介绍了Spring Bean的生命周期,在这个周期内有一个重要的概念就是: IOC容器 大家也知道IOC是Sping 的重要核心之一,那么如何理解它呢,它又是产生什么作用呢?本文就IOC ...