Python 闭包

Python v3.7.0

在函数嵌套的程序结构中,如果内层函数包含对外层函数局部变量的引用,同时外层函数的返回结果又是对内层函数的引用,这就构成了一个闭包。当外层函数在调用结束时,发现自己的局部变量在内层函数中有引用,就会把这个局部变量绑定到内部函数,然后自己再结束。

以一个实现可变参数求和的函数为例,函数体定义如下:

def calc_sum(*args):
    ax = 0
    for n in args:
        ax += n
    return ax

但是,如果不需要立刻返回求和结构,而是在后面的代码中,根据需要再计算怎么办?此时可以不返回求和的结果,而是返回求和的函数,修改后的代码如下:

def lazy_sum(*args):
    def act_sum():
        return sum(args)
    return act_sum

s = lazy_sum(1, 2, 3, 4, 5)
print(s, type(s),s(),sep='\n')
  
 
# Output>>>
<function lazy_sum.<locals>.sum at 0x10768ed90>
<class 'function'>
15

在上面的代码中,在外层函数lazy_sum中又嵌套了的内层函数act_sumact_sum引用了lazy_sum的参数,并且lazy_sumact_sum 作为返回结果。当内嵌的函数体内有引用外部作用域的变量时,将会把定义时涉及到的引用环境和函数体打包成一个整体返回,这中程序结构就是上面说的"闭包(Closure)"。所以说,闭包就是由函数及其相关的引用环境组合而成的实体,即:闭包=函数+引用环境。

从运行结果中可以看到,当调用lazy_sum(*args)时,返回的是内部求和函数的引用地址,当调用函数act_sum() 时,才真正计算求和的结果。

进一步理解Python中的闭包概念,还有两点需要特别注意,首先看下面的例子:

s1 = lazy_sum(1, 2, 3, 4, 5)
s2 = lazy_sum(1, 2, 3, 4, 5)
print(s1==s2)

# Output>>>
False

也就是说当我们调用lazy_sum()时,即使传入参数相同,每次调用也都会返回一个新的函数,彼此不会互相影响。

第二点,在闭包中修改外部作用域的局部变量时,需要使用关键字nonlocal,否则会报错。稍微修改一下上面求和的过程代码,如下:

# ax = 0  # UnboundLocalError
def lazy_sum(*args):
    ax = 0  # UnboundLocalError
    def sum():
        for n in args:
            ax += n
        return ax

    return sum
  
s = lazy_sum(1, 2, 3, 4, 5)
print(s, type(s),s(),sep='\n')

# Output>>>
UnboundLocalError: local variable 'ax' referenced before assignment

此时运行代码会抛出UnboundLocalError 的异常,这再看下面的代码:

def decorator():
    name = "Rethink"
    def wrapper():
        print(name)
    return wrapper

deco()()

# Output>>>
Rethink

这里看到,代码可以正常运行,这是因为在闭包中只是引用了外部作用域的局部变量,而没有修改它的值。

在闭包结构中,内层函数改变外层函数的局部变量需要用nonlocal 关键字, nonlocal不能定义新的外层函数变量,只能改变已有的外层函数变量,同时也不能改变全局变量,在看下面的例子:

# ax = 0  # no binding for nonlocal 'ax' found
def lazy_sum(*args):
    ax = 0
    def sum():
        nonlocal ax
        for n in args:
            ax += n
        return ax
    return sum

s = lazy_sum(1, 2, 3, 4, 5)
print(s, type(s), s(), sep='\n')

# Output>>>
<function lazy_sum.<locals>.sum at 0x000001B0D80612F0>
<class 'function'>
15

从运行结果中可以看到,在闭包函数中使用 nonlocal 声明外层函数中定义的ax变量后,程序可以正常运行,但是如果ax是定义在函数体外部的全局变量,则运行函数时,会报错:no binding for nonlocal 'ax' found .

使用闭包将单方法的类转换成函数

from urllib.request import urlopen

class UrlTemplate:
    def __init__(self, template):
        self.template = template

    def openr(self, **kwargs):
        return urlopen(self.template.format_map(kwargs))

# 上面的类可以被一个更简单的函数代替
def url_template(template):
    def openr(**kwargs):
        return urlopen(template.format_map(kwargs))
    return openr

jianshu = url_template('https://www.jianshu.com/search?q={name}&page={page}&type={type}')
for line in jianshu(name='Rethink', page='1', type='note'):
    print(line.decode('utf-8'))

大部分情况下,选择实现一个单方法类的原因是需要存储某些额外的状态来给方法使用,比如,定义UrlTemplate 类的唯一目的就是先在某个地方存储模板值,以便将来可以在open() 方法中使用。
使用闭包函数代替单方法类通常会更优雅一些,在任何情况下,只要碰到需要给某个函数增加额外的状态信息的问题,都可以使用闭包。

[To be continued...]

参考文档

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

推荐阅读更多精彩内容