闭包(closure)

闭包

1)闭包定义

闭包:对于一个嵌套定义的函数(函数中定义函数),外部函数的返回值是内部函数,而在内部函数中又引用了外部函数的局部变量;在外部函数执行结束后,这些局部变量不会消失,会与被返回的内部函数一同存在,此时,内部函数被称为闭包(closure)

以上理解正确,但还不够一般性, 在计算机科学中 ,闭包(Closure)是词法闭包(Lexical Closure)的简称,是指引用了自由变量的函数, 这些被引用的自由变量将和这个函数共存,即使创造这些自变量的环境已经消亡了,他们也不会消亡,除非函数消亡,他们才消亡。所以,有另一种说法:闭包是由函数和与其相关的引用环境组合而成的实体。

当内部函数引用其外部范围中的值时,可以定义 Python 中的闭包。
闭包提供了某种形式的数据隐藏。闭包可以在一系列函数调用之间保持状态。

进一步说明闭包前,必须先明白python中“函数”的特性,这是实现闭包的前提
(1)函数是一个对象;
(2)函数中可以定义其他函数;
(3)函数能作为参数传递;
(4)函数能作为返回值;
还必须明白“函数”和“调用函数”的区别,下例中 func 称为函数, func(3) 称为函数调用,后者是对前者传入参数并求值的结果。函数也是一个对象,所以 func 指代一个函数对象,它的值是函数本身; func(3) 是对函数的调用,它的值是调用的结果,次例中其值为5。

def func(a):
    b = a + 2
    return b

print(func)
print(func(3))

#输出
<function func at 0x00000231340648B0>
5

创建闭包函数的必要条件★★★★★:
(1)必须有一个嵌套的函数;
(2)内部函数必须引用外部函数的变量或参数;
(3)外部函数返回值之一,必须是使用了外部变量或参数的内部函数。
只有同时满足上述三个条件时,内部函数才能叫闭包,否则内部函数只能叫内部函数。

例:

def outer_f(a): #(外部函数)
    b = 3
    def inner_f(c): #内部函数(闭包函数)
        return a + b + c
    return inner_f

my_f = outer_f(2)

print(my_f)
print(my_f(4))
print(my_f(5))
print(my_f(6))

#输出
<function outer_f.<locals>.inner_f at 0x0000023132A38C10>
9
10
11

上例中定义了一个嵌套函数(形成闭包的条件1),其中外部函数为outer_f,内部函数为inner_f;
变量a和参数b都属于外部函数,只有变量c属于内部函数,但内部函数用到了外部函数的a和b(形成闭包的条件2);
外部函数最后返回内部函数inner_f(形成闭包的条件3)(注意不是inner_(),前者表示函数本身,后者表示函数执行;
至此,内部函数inner_f就是一个完整的闭包函数了。

2)闭包核心作用

上例中,my_f = outer_f(2),表示将2赋值给a,再将outer_f(2)的返回值inner_f(这是一个函数)赋给my_f,即my_f实际上就是内部函数inner_f,从输出也可以看出这点。注意,到这里inner_f函数还没有被执行。
这一步执行完以后,外部函数outer_f已经被销毁了,意味着a和b也销毁了,但是我们发现后面执行my_f(4)、my_f(5)、my_f(6),结果是9、10、11,显然a和b的值被闭包保留下来了,这正是闭包的核心作用:外部变量保存!

3)闭包修改外部函数的变量

前面定义中明确指出,形成闭包的其中一个条件是内部函数必须使用了外部的变量或参数。但是否意味着可以在内部函数(闭包内)中修改外部变量或参数内?请看下例:

def outer_f(a):
    b = 3
    def inner_f(c):
        a += 4
        return a + b + c
    return inner_f

my_f = outer_f(2)
print(my_f(4))  #报错

QQ截图20210906192926.jpg

从结果可以看出,a += 4 这步出错,这里闭包inner_f企图对外部变量a进行修改,这是不行的,正常情况情况下,只允许闭包使用外部变量或参数,不允许修改外部变量或参数
如果要在闭包内部修改外部变量,则可以先在闭包内部使用nonlocal 变量名的方式做声明:

def outer_f(a):
    b = 3
    def inner_f(c):
        nonlocal a,b
        a += 4
        b += 4
        return a + b + c
    return inner_f

my_f = outer_f(2)
print(my_f(4))

#输出
17

事实上,内部函数(闭包)会判断其中的变量和参数属于外部还是内部:
(1)如果在闭包中没有重新定义与外部同名的变量,则可以在闭包中使用外部变量(注意不是修改),本文最前面的例子即是如此;
(2)如果在闭包内部没有重新定义外部变量,也没有声明是nolocal,而企图修改外部变量,则报错;
(3)如果在闭包内部重新定义与外部同名的变量,则外部变量会被屏蔽,即虽然同名,内外实际上是两个完全不同的变量;如下例:

def outer_f(a):
    b = 3
    def inner_f(c):
        a = 100
        return a + b + c
    return inner_f

my_f = outer_f(2)
print(my_f(4))

#输出:
107

上例中a = 100是在inner_f中新定义的变量,其作用范围只有inner_f内部,与外部的a完全没有关系。

注意: 容器式外部变量可在内部函数(闭包)中被修改

前面已经说明,在没有使用nolocal的情况下,不能在闭包中修改外部变量,但如果外部变量是容器式变量,如list等,则可以在闭包中增加或减少元素:

def outer_f():
    a = []
    def inner_f(b):
        a.append(b)
        return a
    return inner_f

my_f = outer_f()

print(my_f(4))
print(my_f(5))
print(my_f(6))

#输出
[4]
[4, 5]
[4, 5, 6]

上例中列表a是外部函数的变量,企图在没有声明nolocal的情况下,在闭包内部对a扩展元素,结果成功了。值的说明的是,上例中执行了三次my_f()函数,从输出结果可知,后面的结果是基于前面的结果的,这说明三次执行,共用了同一个a,其每执行一次my_f()后,a就变化,可见,闭包函数的一个对象(如这里的my_f)对应一套外部变量,如果是有多个对象,则每个对象之间是独立的,且会单独为每个对象分配一套外部变量,如下面的my_f1和my_f2他们是闭包的两个独立对象,即有两个独立的外部变量a分别与其对应:

(开头程序同上一个程序)
my_f1 = outer_f()
print(my_f1(4))
print(my_f1(5))
print(my_f1(6))

my_f2 = outer_f()
print(my_f2(100))
print(my_f2(101))
print(my_f2(102))

#输出:
[4]
[4, 5]
[4, 5, 6]
[100]
[100, 101]
[100, 101, 102]

4)闭包陷阱

def outer_f():
    fs = []
    for i in range(3):
        def inner_f():
            return i * i
        fs.append(inner_f)
    return fs
 
fs1, fs2, fs3 = outer_f()
print (fs1())
print (fs2())
print (fs3())

#输出:
4
4
4

上面代码是典型的错误使用闭包的例子,本意是想输出0、1、4,实际却是4、4、4。可见,闭包并没有把外部循环变量i=0、1、2分别记录下来,而是只用了最后的i=2。
这个例子中,外部函数返回的并不是一个闭包函数,而是包含三个闭包函数的一个list。而且三个闭包函数均引用外部函数中定义的同一个自由变量i。
但问题是为什么for循环中的变量变化会影响到所有的闭包函数?而且前面已经说过,同一闭包的不同对象是相互独立的。
其实问题的关键就在于在返回闭包列表fs之前for循环的变量的值已经发生改变了,而且这个改变会影响到所有引用它的内部定义的函数。因为在函数outer_f返回前其内部定义的函数并不是闭包函数,只是一个内部定义的函数。
上述代码跟下面代码一个意思,但更高理解:

def outer_f():
    fs = []
    j = 0
    for i in range(3):
        def inner_f():
            return j * j
        fs.append(inner_f)
    j = 2
    return fs
 
fs1, fs2, fs3 = outer_f()
print (fs1())
print (fs2())
print (fs3())

#输出:
4
4
4

5)闭包的作用

1、共享变量时避免使用不安全的全局变量。
2、允许将函数与某些数据关联起来。
3、延伸的作用域都彼此独立。
4、需要动态实现,同时又想保持接口的一致性。
5、较低的内存开销。
6、实现装饰器。

6)参考

https://www.cnblogs.com/yssjun/p/9887239.html
https://zhuanlan.zhihu.com/p/22229197
http://c.biancheng.net/view/5335.html
https://www.jb51.net/article/161402.htm
https://www.jb51.net/article/158236.htm
https://www.jb51.net/article/98604.htm
https://www.jb51.net/article/86383.htm
https://www.jb51.net/article/161754.htm
https://www.jb51.net/article/174564.htm
https://www.jb51.net/article/185802.htm
https://www.jb51.net/article/211926.htm
https://www.jb51.net/article/195088.htm
https://www.jb51.net/article/54498.htm

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • ● 闭包基础 ● 闭包作用 ● 闭包经典例子 ● 闭包应用 ● 闭包缺点 ● 参考资料 1、闭包基础 作用域和作...
    lzyuan阅读 916评论 0 0
  • 结论:闭包函数可以直接引用外层代码定义的变量,但是,注意,闭包函数里面引用的是变量的地址,当goroutine被调...
    qishuai阅读 2,475评论 0 0
  • [toc] 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现...
    Jason杰森阅读 334评论 0 0
  • 闭包:指有权访问另一个函数作用域中的变量的函数闭包实现条件:内部函数使用了外部函数的变量、外部函数已退出、内部函数...
    闪电西兰花阅读 281评论 0 0
  • 闭包指的是有权访问另一个函数作用域中变量的函数。 ###函数调用过程(第一次被调用时) 1. 创建执行环境(exe...
    huhu213阅读 173评论 0 0