Python内嵌作用域与函数闭包

要点抢先看

1.python中独特的嵌套函数

2.嵌套作用域与闭包现象

3.nonlocal关键字与内嵌作用域变量修改

前情回顾,上一节我们介绍了变量的LEGB索引机制:对一个变量,首先在本地(函数内)查找;之后查找嵌套函数的本地作用域,然后再是查找当前的全局作用域。

到目前为止,我们还有一个作用域没有介绍,就是嵌套作用域,即E,他是嵌套函数的本地作用域。

什么是嵌套函数?

python有一个很有意思的地方,就是def函数可以嵌套在另一个def函数之中。调用外层函数时,运行到的内层def语句仅仅是完成对内层函数的定义,而不会去调用内层函数,除非在嵌套函数之后又显式的对其进行调用。

x = 99

def f1():
    x = 88
    def f2():
        print(x)
    f2()

f1()

88

可以看出,f1中的嵌套变量x覆盖了全局变量x=99,然后f2中的本地变量按照引用规则,就引用了x=88。

下面我们来说说嵌套作用域的一个特殊之处:

本地作用域在函数结束后就立即失效,而嵌套作用域在嵌套的函数返回后却仍然有效。

def f1():
    x = 88
    def f2():
        print(x)
    return f2

action = f1()
action()

88

这个例子非常重要,也很有意思,函数f1中定义了函数f2,f2引用了f1嵌套作用域内的变量x,并且f1将函数f2作为返回对象进行返回。最值得注意的是我们通过变量action获取了返回的f2,虽然此时f1函数已经退出结束了,但是f2仍然记住了f1嵌套作用域内的变量名x。

上面这种语言现象称之为闭包:一个能记住嵌套作用域变量值的函数,尽管作用域已经不存在。

这里有一个应用就是工厂函数,工厂函数定义了一个外部的函数,这个函数简单的生成并返回一个内嵌的函数,仅仅是返回却不调用,因此通过调用这个工厂函数,可以得到内嵌函数的一个引用,内嵌函数就是通过调用工厂函数时,运行内部的def语句而创建的。

def maker(n):
    k = 8
    def action(x):
        return x ** n + k
    return action

f = maker(2)
print(f)

<function maker.<locals>.action at 0x00000000021C51E0>

再看一个例子:

def maker(n):
    k = 8
    def action(x):
        return x ** n + k
    return action

f = maker(2)
print(f(4))

24

这里我们可以看出,内嵌的函数action记住了嵌套作用域内得两个嵌套变量,一个是变量k,一个是参数n,即使后面maker返回并退出。我们通过调用外部的函数maker,得到内嵌的函数action的引用。这种函数嵌套的方法在后面要介绍的装饰器中会经常用到。这种嵌套作用域引用,就是python的函数能够保留状态信息的主要方法了。

这里接着说说另一个关键字nonlocal

本地函数通过global声明对全局变量进行引用修改,那么对应的,内嵌函数内部想对嵌套作用域中的变量进行修改,就要使用nonlocal进行声明。

def test(num):
    in_num = num
    def nested(label):
        nonlocal in_num
        in_num += 1
        print(label, in_num)
    return nested

F = test(0)
F('a')
F('b')
F('c')

a 1
b 2
c 3

这里我们可以看到几个点,我们在nested函数中通过nonlocal关键字引用了内嵌作用域中的变量in_num,那么我们就可以在nested函数中修改他,即使test函数已经退出调用,这个“记忆”依然有效。

再最后一个例子:

def test(num):
    in_num = num
    def nested(label):
        nonlocal in_num
        in_num += 1
        print(label, in_num)
    return nested

F = test(0)
F('a')
F('b')
F('c')

G = test(100)
G('mm')

a 1
b 2
c 3
mm 101

多次调用工厂函数返回的不同内嵌函数副本F和G,彼此间的内嵌变量in_num是彼此独立隔离的。

作者:酱油哥,清华程序猿、IT非主流

专栏地址:https://zhuanlan.zhihu.com/c_147297848

最近热门文章

用Python分析苹果公司股价数据

Nginx+uwsgi部署Django应用

用文本挖掘剖析近5万首《全唐诗》

Python自然语言处理分析倚天屠龙记

Python 3.6实现单博主微博文本、图片及热评爬取

原文发布于微信公众号 - Python中文社区(python-china)

原文发表时间:2018-04-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端那些事

强大的原型和原型链

前两次总结了JavaScript中的基本数据类型(值类型<引用类型>,引用类型<复杂值>)以及他们在内存中的存储,对内存空间有了一个简单的了解,以及第二次总结了...

20280
来自专栏工科狗和生物喵

【计算机本科补全计划】Java学习笔记(六) 循环+分支结构

正文之前 这两节太弱了。基本大一的C++程序设计课就足够对付理解了。所以还是水一波吧,实在没办法,整个教程还是全部抄全吧。免得到时候新人小白入门的时候还要去看别...

37190
来自专栏专注 Java 基础分享

Java 内部类的意义及应用

众所周知,我们的 C++ 程序语言是多继承制的,而多继承明显的好处就是,相对而言只需要写较少的代码即可完成一个类的定义,因为我们可以通过继承其它类来获取别人的实...

35440
来自专栏全沾开发(huā)

学习zepto.js(原型方法)[1]

学习zepto.js(原型方法)[1] 新的一周,新的开始,今天来学习一下zepto里边的原型方法,就是通过$.进行调用的方法,也是可...

40690
来自专栏向治洪

Java泛型和通配符那点事

泛型(Generic type 或者generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数...

25150
来自专栏程序员的知识天地

Python正则表达式的7个使用典范

作为一个概念而言,正则表达式对于Python来说并不是独有的。但是,Python中的正则表达式在实际使用过程中还是有一些细小的差别。

13310
来自专栏CodeSheep的技术分享

Java编程思想学习录(连载之:内部类)

227110
来自专栏mukekeheart的iOS之旅

final、finally、finalize的区别

final:修饰符,可以用于修饰成员、方法和类。 如果一个类被声明为final,意味着该类不能再派生出新的子类,不能作为父类被继承。因此一个类不能即被声明为ab...

25450
来自专栏架构师之旅

JavaScript 知识点梳理 | 从基础语法到高级用法

JavaScript是按照ECMAScript标准设计和实现的,后文说的JavaScript语法其实是ES5的标准的实现。 先说说有哪些基础语法? 01- 最...

28650
来自专栏LIN_ZONE

PHP 类型判断和NULL,空值检查

 PHP是一种宽松类型的编程语言,在函数中对传入的参数值的“类型”以及”值是否为空或者NULL“进行检查是不可缺少的步骤。

26020

扫码关注云+社区

领取腾讯云代金券