以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的垃圾回收机制 引子: 我们定义变量会申请内存空间来存放变量的值,而内存的容量是有限的,当一个变量值没有用了(简称垃圾)就应该将其占用的内存给回收掉,而变量名是访问到变量值的唯一方式,所以 ...
随机推荐
- Ubuntu菜单栏的位置可以调 到左侧 或者底部
hyx@hyx:/mnt/hgfs/Linux$ gsettings set com.canonical.Unity.Launcher launcher-position Bottom
- 关于touch-action
在项目中发现 ,Android下列表页的滚动加载失效. 原因: css中设定了html{ touch:none } 解决方法:移除该样式. touch:none // 当触控事件发生在元素上是时,不进 ...
- 人生苦短_我用Python_pymysql库对Mysql数据库操作_009
# coding=utf-8 import pymysql ''' 数据库的登录信息: config={ 'host':'118.126.108.xxx', # :主机 'user':'python' ...
- 51nod 1661: 黑板上的游戏(sg函数 找规律)
题目链接 先打一个sg函数的表,找找规律,发现sg函数可以递归求解 打表代码如下 #include<bits/stdc++.h> using namespace std; typedef ...
- 【leetcode】592. Fraction Addition and Subtraction
题目如下: 解题思路:本题考察的是分数的加减法.小学时候就学过,分数的加减法是先求两个分母的最小公倍数,然后分子分别乘以最小公倍数与自己分母的商,相加后约分即可.所以,本题只要按+,-两个符号分割输入 ...
- gitHub那些优秀的库和想要实现的效果
1. 轮播库SDCycleScrollView 2. 自动布局库SDAutoLayout 3. 类似支付宝福卡滑动切换的效果 https://github.com/huangxuan518/HXCar ...
- PHP curl_multi_add_handle函数
curl_multi_add_handle — 向curl批处理会话中添加单独的curl句柄 说明 int curl_multi_add_handle ( resource $mh , resourc ...
- Android中插件开发篇总结和概述
刚刚终于写完了插件开发的最后一篇文章,下面就来总结一下,关于Android中插件篇从去年的11月份就开始规划了,主要从三个方面去解读Android中插件开发原理.说白了,插件开发的原理就是:动态加载技 ...
- Divideing Jewels
Divideing Jewels 时间限制: 1 Sec 内存限制: 128 MB提交: 63 解决: 17[提交][状态] 题目描述 Mary and Rose own a collection ...
- AngularJS ng-model 指令
AngularJS ng-model 指令 ng-model 指令用于绑定应用程序数据到 HTML 控制器(input, select, textarea)的值. 代码示例如下: <!DOCTY ...