关于Python闭包的一切
任何把函数当做一等对象的语言,它的设计者都要面对一个问题:作为一等对象的函数在某个作用域中定义,但是可能会在其他作用域中调用,如何处理自由变量?
自由变量(free variable),未在局部作用域中绑定的变量。
为了解决这个问题,Python之父Guido Van Rossum设计了闭包,有如神来之笔,代码美学尽显。在讨论闭包之前,有必要先了解Python中的变量作用域。
变量作用域
先看一个全局变量和自由变量的示例:
>>> b = 6
>>> def f1(a):
... print(a)
... print(b)
...
>>> f1(3)
3
6
函数体外的b为全局变量,函数体内的b为自由变量。因为自由变量b绑定到了全局变量,所以在函数f1()中能正确print。
如果稍微改一下,那么函数体内的b就会从自由变量变成局部变量:
>>> b = 6
def f1(a):
... print(a)
... print(b)
... b = 9
...
>>> f1(3)
3
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 3, in f1
UnboundLocalError: local variable 'b' referenced before assignment
在函数f1()后面加上b = 9
报错:局部变量b在赋值前进行了引用。
这不是缺陷,而是Python设计:Python不要求声明变量,而是假定在函数定义体中赋值的变量是局部变量。
如果想让解释器把b当做全局变量,那么需要使用global声明:
>>> b = 6
>>> def f1(a):
... global b
... print(a)
... print(b)
... b = 9
...
>>> f1(3)
3
6
闭包
回到文章开头的自由变量问题,假如有个叫做avg的函数,它的作用是计算系列值的均值,用类实现:
class Averager():
def __init__(self):
self.series = []
def __call__(self, new_value):
self.series.append(new_value)
total = sum(self.series)
return totle / len(self.series)
avg = Averager()
avg(10) # 10.0
avg(11) # 10.5
avg(12) # 11.0
类实现不存在自由变量问题,因为self.series是类属性。但是函数实现,进行函数嵌套时,问题就出现了:
def make_averager():
series = []
def averager(new_value):
# series是自由变量
series.append(new_value)
total = sum(series)
return totle / len(series)
return averager
avg = make_averager()
avg(10) # 10.0
avg(11) # 10.5
avg(12) # 11.0
函数make_averager()在局部作用域中定义了series变量,它的内部函数averager()的自由变量series绑定了这个值。但是在调用avg(10)时,make_averager()函数已经return返回了,它的局部作用域也消失了。没有闭包的话,自由变量series一定会报错找不到定义。
那么闭包是怎么做的呢?闭包是一种函数,它会保留定义时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍然能使用那些绑定。
如下图所示:
闭包会保留自由变量series的绑定,在调用avg(10)时继续使用这个绑定,即使make_averager()函数的局部作用域已经消失。
nonlocal
把上面示例的需求稍微优化下,只存储目前的总值和元素个数:
def make_averager():
count = 0
total = 0
def averager(new_value):
count += 1
total += new_value
return total / count
return averager
运行后会报错:局部变量count在赋值前进行了引用。因为count +=1等同于count = count + 1,存在赋值,count就变成局部变量了。total也是如此。
这里如果把count和total通过global关键字声明为全局变量,显然是不合适的,它们作用域最多只扩展到make_averager()函数内。为了解决这个问题,Python3引入了nonlocal关键字声明:
def make_averager():
count = 0
total = 0
def averager(new_value):
nonlocal count, total
count += 1
total += new_value
return total / count
return averager
nonlocal的作用是把变量标记为自由变量,即使在函数中为变量赋值了,也仍然是自由变量。
注意,对于列表、字典等可变类型来说,添加元素不是赋值,不会隐式创建局部变量。对于数字、字符串、元组等不可变类型以及None来说,赋值会隐式创建局部变量。示例:
def make_averager():
# 可变类型
count = {}
def averager(new_value):
print(count) # 成功
count[new_value] = new_value
return count
return averager
可变对象添加元素不是赋值,不会隐式创建局部变量。
def make_averager():
# 不可变类型
count = 1
def averager(new_value):
print(count) # 报错
count = new_value
return count
return averager
count是不可变类型,赋值会隐式创建局部变量,报错:局部变量count在赋值前进行了引用。
def make_averager():
# None
count = None
def averager(new_value):
print(count) # 报错
count = new_value
return count
return averager
count是None,赋值会隐式创建局部变量,报错:局部变量count在赋值前进行了引用。
小结
本文先介绍了全局变量、自由变量、局部变量的概念,这是理解闭包的前提。闭包就是用来解决函数嵌套时,自由变量如何处理的问题,它会保留自由变量的绑定,即使局部作用域已经消失。对于不可变类型和None来说,赋值会隐式创建局部变量,把自由变量转换为局部变量,这可能会导致程序报错:局部变量在赋值前进行了引用。除了使用global声明为全局变量外,还可以使用nonlocal声明把局部变量强制变为自由变量,实现闭包。
参考资料:
《流畅的Python》
关于Python闭包的一切的更多相关文章
- Python 闭包
什么是闭包? 闭包(closure)是词法闭包(lexical closure)的简称.闭包不是新奇的概念,而是早在高级程序语言开始发展的年代就已产生. 对闭包的理解大致分为两类,将闭包视为函数或者是 ...
- Python闭包与函数对象
1. Python闭包是什么 在python中有函数闭包的概念,这个概念是什么意思呢,查看Wikipedia的说明如下: “ In programming languages, closures (a ...
- Python闭包及装饰器
Python闭包 先看一个例子: def outer(x): def inner(y): return x+y return innder add = outer(8) print add(6) 我们 ...
- Python闭包详解
Python闭包详解 1 快速预览 以下是一段简单的闭包代码示例: def foo(): m=3 n=5 def bar(): a=4 return m+n+a return bar >> ...
- Python闭包及其作用域
Python闭包及其作用域 关于Python作用域的知识在python作用域有相应的笔记,这个笔记是关于Python闭包及其作用域的详细的笔记 如果在一个内部函数里,对一个外部作用域(但不是全局作用域 ...
- Python 闭包小记
闭包就是能够读取其他函数内部变量的函数.例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“.在本质上,闭包是将函数内部和函数外部连接起来 ...
- 理解Python闭包概念
闭包并不只是一个python中的概念,在函数式编程语言中应用较为广泛.理解python中的闭包一方面是能够正确的使用闭包,另一方面可以好好体会和思考闭包的设计思想. 1.概念介绍 首先看一下维基上对闭 ...
- Python闭包举例
Python闭包的条件: 1.函数嵌套.在外部函数内,定义内部函数. 2.参数传递.外部函数的局部变量,作为内部函数参数. 3.返回函数.外部函数的返回值,为内部函数. 举例如下: def line_ ...
- python 闭包和装饰器
python 闭包和装饰器 一.闭包闭包:外部函数FunOut()里面包含一个内部函数FunIn(),并且外部函数返回内部函数的对象FunIn,内部函数存在对外部函数的变量的引用.那么这个内部函数Fu ...
- Python 闭包(Closure)
Python 闭包 (Closure) 这里介绍一下python 的闭包 基本概念 闭包(closure)是函数式编程的重要的语法结构. 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函 ...
随机推荐
- java7与java9中的try-finally关闭资源
1.java7中的try 在java7之前,对于一些需要使用finally关闭资源的操作,会显得很臃肿. try { // } catch(Exception e) { // } finally { ...
- Bug Hunt UVA - 1596
In this problem, we consider a simple programming language that has only declarations of onedimens ...
- Day01_11_Java方法
Java - 方法 什么是java中的方法? - 方法就是一段代码片段,并且这段代码可以完成某个特定的功能.动作.是可以被重复的使用. - 方法就是类的一个动作. - 方法在C语言中也叫做函数 或 f ...
- k8s helm 安装etcd
待续 helm install etcd bitnami/etcd \ --set statefulset.replicaCount=3 \ --set persistence.enabled=tru ...
- ambari介绍及安装
Ambari简介 Ambari概述 Apache Ambari是一种基于Web的工具,支持Apache Hadoop集群的创建.管理和监控.Ambari已支持大多数Hadoop组件,包括HDFS.Ma ...
- 经典论文系列| 实例分割中的新范式-SOLO
前言: 这是实例分割中的一篇经典论文,以往的实例分割模型都比较复杂,这篇论文提出了一个简单且直接的实例分割模型,如何设计这种简单直接的模型且要达到一定的精度往往会存在一些困难,论文中有很多思路或思想值 ...
- 03- HTML基本结构
初始HTML HTML(英文Hyper Text Markup Language的缩写)中文译为"超文本标签语言",主要是通过HTML标签对网页中的文本.图片.声音等内容进行描述. ...
- DVWA之XSS
XSS XSS,全称Cross Site Scripting,即跨站脚本攻击,某种意义上也是一种注入攻击,是指攻击者在页面中注入恶意的脚本代码,当受害者访问该页面时,恶意代码会在其浏览器上执行,需要强 ...
- IIS6目录解析漏洞的实验
Windows 打开IIS管理器的方式 这个是网站IIS网站目录 右击默认网站中的属性,可以查看网站的属性,比如网站根目录,或者是端口 这个是网站的目录C:\www,有三个文件分别 ...
- Java中的反射机制Reflection
目录 什么是反射? 获取.class字节码文件对象 获取该.class字节码文件对象的详细信息 通过反射机制执行函数 反射链 反射机制是java的一个非常重要的机制,一些著名的应用框架都使用了此机制, ...