资讯专栏INFORMATION COLUMN

PyTips 0x04 - Python 闭包与作用域

leejan97 / 2833人阅读

摘要:项目地址闭包在计算机科学中,闭包英语,又称词法闭包或函数闭包,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

项目地址:https://git.io/pytips

闭包(Closure)

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
[维基百科::闭包(计算机科学)]

0x02 Python 中的函数式编程 本来也应该包括闭包的概念,但是我觉得闭包更重要的是对作用域(Scope)的理解,因此把它多带带列出来,同时可以理顺一下 Python 的作用域规则。

闭包的概念最早出现在函数式编程语言中,后来被一些命令式编程语言所借鉴。尤其是在一些函数作为一等公民的语言中,例如JavaScript就经常用到(在JavaScript中函数几乎可以当做“特等公民”看待),我之前也写过一篇关于JavaScript闭包的文章(图解Javascript上下文与作用域),实际上闭包并不是太复杂的概念,但是可以借助闭包更好地理解不同语言的作用域规则。

命名空间与作用域

0x00 The Zen of Python的最后一句重点强调命名空间的概念,我们可以把命名空间看做一个大型的字典类型(Dict),里面包含了所有变量的名字和值的映射关系。在 Python 中,作用域实际上可以看做是“在当前上下文的位置,获取命名空间变量的规则”。在 Python 代码执行的任意位置,都至少存在三层嵌套的作用域:

最内层作用域,最早搜索,包含所有局部变量(Python 默认所有变量声明均为局部变量)

所有包含当前上下文的外层函数的作用域,由内而外依次搜索,这里包含的是非局部非全局的变量

一直向上搜索,直到当前模块的全局变量

最外层,最后搜索的,内置(built-in)变量

在任意执行位置,可以将作用域看成是对下面这样一个命名空间的搜索:

scopes = {
    "local": {"locals": None,
             "non-local": {"locals": None,
                          "global": {"locals": None,
                                    "built-in": ["built-ins"]}}},
}

除了默认的局部变量声明方式,Python 还有globalnonlocal两种类型的声明(nonlocal是Python 3.x之后才有,2.7没有),其中 global 指定的变量直接指向(3)当前模块的全局变量,而nonlocal则指向(2)最内层之外,global以内的变量。这里需要强调指向(references and assignments)的原因是,普通的局部变量对最内层局部作用域之外只有只读(read-only)的访问权限,比如下面的例子:

x = 100
def main():
    x += 1
    print(x)
main()
---------------------------------------------------------------------------

UnboundLocalError                         Traceback (most recent call last)

 in ()
      3     x += 1
      4     print(x)
----> 5 main()


 in main()
      1 x = 100
      2 def main():
----> 3     x += 1
      4     print(x)
      5 main()
UnboundLocalError: local variable "x" referenced before assignment

这里抛出UnboundLocalError,是因为main()函数内部的作用域对于全局变量x仅有只读权限,想要在main()中对x进行改变,不会影响全局变量,而是会创建一个新的局部变量,显然无法对还未创建的局部变量直接使用x += 1。如果想要获得全局变量的完全引用,则需要global声明:

x = 100
def main():
    global x
    x += 1
    print(x)
    
main()
print(x) # 全局变量已被改变
101
101
Python 闭包

到这里基本上已经了解了 Python 作用域的规则,那么我们来仿照 JavaScript 写一个计数器的闭包:

"""
/* JavaScript Closure example */
var inc = function(){  
  var x = 0;
  return function(){
    console.log(x++);
  };
};
var inc1 = inc()
var inc2 = inc()
"""

# Python 3.5
def inc():
    x = 0
    def inner():
        nonlocal x
        x += 1
        print(x)
    return inner
inc1 = inc()
inc2 = inc()

inc1()
inc1()
inc1()
inc2()
1
2
3
1

对于还没有nonlocal关键字的 Python 2.7,可以通过一点小技巧来规避局部作用域只读的限制:

# Python 2.7
def inc():
    x = [0]
    def inner():
        x[0] += 1
        print(x[0])
    return inner
inc1 = inc()
inc2 = inc()

inc1()
inc1()
inc1()
inc2()
1
2
3
1

上面的例子中,inc1()是在全局环境下执行的,虽然全局环境是不能向下获取到inc()中的局部变量x的,但是我们返回了一个inc()内部的函数inner(),而inner()inc()中的局部变量是有访问权限的。也就是说inner()inc()内的局部作用域打包送给了inc1inc2,从而使它们各自独立拥有了一块封闭起来的作用域,不受全局变量或者任何其它运行环境的影响,因此称为闭包。

闭包函数都有一个__closure__属性,其中包含了它所引用的上层作用域中的变量:

print(inc1.__closure__[0].cell_contents)
print(inc2.__closure__[0].cell_contents)
[3]
[1]
参考

9.2. Python Scopes and Namespaces

Visualize Python Execution

Wikipedia::Closure

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/37791.html

相关文章

  • PyTips 0x0c - Python 知之深浅

    摘要:不可变对象包括,,,,等,可变对象包括,,等。在中,赋值的过程仅仅是创建一个某个值的对象将变量名指向引用这个对象。这就像语言中指针的概念,只不过更灵活地是中的变量随时可以指向其它对象不分类型,其它变量也可以指向这一对象。 项目地址:https://git.io/pytips Python 中的对象分为两种:可变对象(mutable)和不可变对象(immutable)。不可变对象包括in...

    LoftySoul 评论0 收藏0
  • PyTips 0x0d - Python 上下文管理器

    摘要:项目地址引入了语句与上下文管理器类型,其主要作用包括保存重置各种全局状态,锁住或解锁资源,关闭打开的文件等。了解了语句的执行过程,我们可以编写自己的上下文管理器。生成器的写法更简洁,适合快速生成一个简单的上下文管理器。 项目地址:https://git.io/pytips Python 2.5 引入了 with 语句(PEP 343)与上下文管理器类型(Context Manager ...

    yuxue 评论0 收藏0
  • PyTips 0x11 - Python 时间日期

    摘要:项目地址时间和日期可能涉及到不同的时区格式,同时又经常需要作为时间戳保存,有时候还需要进行一些加减操作,因此处理起来通常会因为方法太多而无从下手。中与时间和日期相关的标准库有个和。 项目地址:https://git.io/pytips 时间和日期可能涉及到不同的时区、格式,同时又经常需要作为时间戳保存,有时候还需要进行一些加减操作,因此处理起来通常会因为方法太多而无从下手。Python...

    2501207950 评论0 收藏0
  • PyTips 0x07 - Python 字符串

    摘要:项目地址所有用过的人应该都看过下面两行错误信息这就是界的锟斤拷今天和接下来几期的内容将主要关注中的字符串字节及两者之间的相互转换。 项目地址:https://git.io/pytips 所有用过 Python (2&3)的人应该都看过下面两行错误信息: UnicodeEncodeError: ascii codec cant encode characters in position...

    go4it 评论0 收藏0
  • PyTips 0x05 - Python 函数参数解包

    摘要:这里的关键词函数必须明确指明,不能通过位置推断则代表任意数量的关键词参数添加的新特性,使得可以在函数参数之外使用这里的逗号不能漏掉所谓的解包实际上可以看做是去掉的元组或者是去掉的字典。 项目地址:https://git.io/pytips 函数调用的参数规则与解包 Python 的函数在声明参数时大概有下面 4 种形式: 不带默认值的:def func(a): pass 带有默认值的...

    pubdreamcc 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<