Python 闭包

简述

闭包是支持函数式编程范式的一个重要特性,在很多编程语言中都可以找到,包括:JavaScript、Python 和 Ruby。闭包十分强大,也非常有用,但是也很棘手,因为难以理解和使用。

下面,我会尽可能的为闭包提供一个清晰的解释,并详细介绍 Python 中的闭包支持。熟悉闭包之后,你会发现其实它非常有趣。

  • 简述
  • 闭包
  • 产生闭包的条件
  • 闭包的好处
  • 词法作用域

版权所有:一去丶二三里,转载请注明出处:http://blog.csdn.net/liang19890820

闭包

关于闭包,来看维基百科上的词条:

在计算机科学中,闭包(Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

上面涉及一个关键点 - 自由变量,根据 Python 文档 描述:

If a variable is used in a code block but not defined there, it is a free variable.

也就是说:如果在一个代码块中使用了一个变量,而这个变量并没有被定义在该代码块中,那么该变量就被称为自由变量。

产生闭包的条件

要创建闭包,必须满足以下条件:

  • 必须包含一个嵌套函数
  • 嵌套函数必须引用封闭函数中定义的值(自由变量)
  • 封闭函数必须返回嵌套函数

无码不欢,来用一个简单的程序来说明:

>>> def outer(x):
...     def inner():
...         return x
...     return inner  # 返回的函数没有圆括号
...
>>> f = outer('Hello')
>>> f()
'Hello'

当外部函数 outer(x) 被调用时,一个闭包 inner() 就形成了,并且该闭包持有自由变量 - x。这也意味着,当函数 outer(x) 的生命周期结束之后,变量 x 的值依然会被记住。

>>> del outer
>>> f()
'Hello'
>>>
>>> outer('Hello')
Traceback (most recent call last):
...
NameError: name 'outer' is not defined

可以看到,即使 outer 从当前的命名空间中删除,x 的值('Hello')也会被记住。

闭包的好处

那么,闭包的好处是什么呢?

  • 取代硬编码中的常量
  • 避免使用全局值,并提供某种形式的数据隐藏。
  • 提供一致的函数签名
  • 实现面向对象

注意: 当类中几乎没有(大多数情况只有一种)方法时,闭包可以提供一种更优雅的替代方案。但是,当属性和方法的数量较多时,最好通过类来实现。

如果要创建一个由不同参数构成的一系列函数。例如,关于正方形和立方体的函数,仅指数不同(分别为:2 和 3)。

使用传统方式:

>>> def square(x):  # 正方形
...     return x ** 2
...
>>>
>>> def cube(x):  # 立方形
...     return x ** 3
...
>>>
>>> square(2)
4
>>>
>>> cube(2)
8

换用闭包来实现,仅需一个 fpower() 就可以构造这些函数:

>>> def fpower(exp):
...     def inner(x):
...         return x ** exp
...     return inner
...
>>> square = fpower(2)
>>> cube = fpower(3)
>>>
>>> square(2)
4
>>>
>>> cube(2)
8

这样做的好处是:fpower 可以用来构建任何一个指数(2、3、4、…)。

所有函数对象都有一个 __closure__ 属性,如果这个函数是一个闭包函数,那么会返回的是一个由 cell 对象组成的元组。cell 对象具有 cell_contents 属性,存储了闭包中的自由变量。

>>> fpower.__closure__
>>> cube.__closure__
(<cell at 0x7fbfaccc4fa8: int object at 0x8beac0>,)
>>>
>>> cube.__closure__[0].cell_contents
3

这也解释了为什么局部变量在脱离函数之后,还可以在函数之外被访问,因为它存储在了闭包的 cell_contents 中。

词法作用域

词法作用域(Lexical Scoping):变量的作用域在定义时决定,而不是在执行时决定。也就是说,词法作用域取决于源码,通过静态分析就能够确定,因此,词法作用域也叫做静态作用域。

从 2.x 开始,Python 通过词法作用域支持闭包。然而,Python 在特性的最初实现中犯了一些小错误。请原谅我的冒犯,之所以这么说,是因为在 2.x 中,闭包无法更改非本地(non-local)变量,这是 Python 的词法作用域规则中的一个固有问题,其名称只能绑定到本地范围或全局范围。

自 Python 3.x 起,这个问题已经被解决掉了,见 PEP-3104

考虑下面的例子,每当调用函数时,为计数器加 1。

>>> def outer():
...     count = 0
...     def inner():
...         count += 1
...         return count
...     return inner
...
>>> 

看起来好像没任何问题,但是很遗憾,执行时会引发错误:

>>> f = outer()
>>> f()
Traceback (most recent call last):
...
UnboundLocalError: local variable 'count' referenced before assignment

这是因为 count 是一个不可变类型,当在内部范围对其重新分配时,它会被看作是一个新变量,由于它还没有被定义,所以会发生错误。

幸运的是,Python 3.x 引入了 nonlocal 关键字,用于标识外部作用域的变量。

>>> def outer():
...     count = 0
...     def inner():
...         nonlocal count  # 使用 nonlocal
...         count += 1
...         return count
...     return inner
...
>>> 

再次运行,结果和预期一样 - perfect:

>>> f = outer()
>>> f()
1
>>> f()
2
>>> f()
3

在 Python 2.x 中,要解决此问题,需要借助一个可变数据类型(例如:list 或 dict)。

>>> def outer():
...     count = [0]  # 使用 list
...     def inner():
...         count[0] += 1
...         return count[0]
...     return inner
...
>>>
>>> f = outer()
>>> f()
1
>>> f()
2
>>> f()
3

显然,这不算完美,因为不得不改变数据类型(int -> list)。

时间: 2024-11-03 22:14:37

Python 闭包的相关文章

Python闭包详解

闭包(closure)是函数式编程的重要的语法结构.函数式编程是一种编程范式 (而面向过程编程和面向对象编程也都是编程范式).在面向过程编程中,我们见到过函数(function):在面向对象编程中,我们见过对象(object).函数和对象的根本目的是以某种逻辑方式组织代码,并提高代码的可重复使用性(reusability).闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性. 不同的语言实现闭包的方式不同.Python以函数对象为基础,为闭包这一语法结构提供支持的 (我们在特殊方法与多范

Python闭包实现计数器的方法

  这篇文章主要介绍了Python闭包实现计数器的方法,分析了闭包的概念及实现计数器的相关技巧,需要的朋友可以参考下 本文实例讲述了Python闭包实现计数器的方法.分享给大家供大家参考.具体实现方法如下: 先来看看专业的解释:闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数.这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外.所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体. 代码如下: ? 1 2

python闭包以及装饰器

通俗的定义:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).它只不过是个"内层"的函数,由一个名字(变量)来指代,而这个名字(变量)对于"外层"包含它的函数而言,是本地变量; 1 #示例一: 2 #!/usr/bin/python 3 #encoding=utf-8 4 5 def add_a(num1): 6 print "num1:%d" % num1 7 def add

Python闭包的使用示例

什么是闭包? 借用维基上解释:在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数.这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外.所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体.闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例. 好吧,看了这段定义的确不是还不能立即理解闭包到底是什么.不过,闭包并不是很难理解,往下看几个小例子就能明确它到底是什么了. 前提

Python 闭包的概念和实例教程

闭包,在wikipedia中的解释为:In programming languages, a closure (also lexical closure or function closure) is a function or reference to a function together with a referencing environment-a table storing a reference to each of the non-local variables (also ca

Python编程中运用闭包时所需要注意的一些地方

  这篇文章主要介绍了Python编程中运用闭包时所需要注意的一些地方,文章来自国内知名的Python开发者felinx的博客,需要的朋友可以参考下 写下这篇博客,起源于Tornado邮件群组的这个问题how to use outer variable in inner method,这里面老外的回答很有参考价值,关键点基本都说到了.我在这里用一些有趣的例子来做些解析,简要的阐述下Python的闭包规则,首先看一个经典的例子: ? 1 2 3 4 5 6 7 8 9 10 11 def foo(

详解Python中的装饰器、闭包和functools的教程_python

装饰器(Decorators) 装饰器是这样一种设计模式:如果一个类希望添加其他类的一些功能,而不希望通过继承或是直接修改源代码实现,那么可以使用装饰器模式.简单来说Python中的装饰器就是指某些函数或其他可调用对象,以函数或类作为可选输入参数,然后返回函数或类的形式.通过这个在Python2.6版本中被新加入的特性可以用来实现装饰器设计模式. 顺便提一句,在继续阅读之前,如果你对Python中的闭包(Closure)概念不清楚,请查看本文结尾后的附录,如果没有闭包的相关概念,很难恰当的理解P

闭包在python中的应用之translate和maketrans用法详解_python

相对来说python对字符串的处理是比较高效的,方法也有很多.其中maketrans和translate两个方法被应用的很多,本文就针对这两个方法的用法做一总结整理. 首先让我们先回顾下这两个方法: ① s.translate(table,str) 对字符串s移除str包含的字符,剩下的字符串按照table里的字符映射关系替换.table可以理解为转换表,比较'a' -> 'A', 'b'->'B'. ② tabel = string.maketrans('s1', 's2') s1 和 s2

简单谈谈Python中的闭包_python

Python中的闭包 1. 闭包的概念 首先还得从基本概念说起,什么是闭包呢?来看下维基上的解释: 复制代码 代码如下: 在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数.这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外.所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体.闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例. .... 上面提到了两个关键的地方: