Python-闭包

0. 函数相关知识

1)Python中“一切皆对象”,函数也不例外

先定义一个函数:

def func():

    print('我是函数:{}'.format(func.__name__))  # __name__属性返回函数的名称

打印函数名:

>>> print(func)

<function func at 0x00000209EA722E18>

返回的是函数对象func,并指出了它的内存地址。

对函数使用type():

>>> type(func)

<class 'function'>

以上可以看出 函数 func 是 function类型的一个对象,属于可调用对象(具有__call__魔法方法)。

2)函数可以赋值给一个变量

函数作为一个对象,它可以作为其他函数的参数,也可以作为函数的返回值,还能够赋值给一个变量:

>>> f = func     

>>> f()          # 可调用对象后加()即可执行调用

我是函数:func      # func赋值给变量f,f可以作为函数被调用,但函数名仍为func

将函数赋值给变量,是将函数的引用传递给变量。至此,函数名和变量共同指向 function对象func。

3)函数作为函数的返回值

定义一个可变参数的求和函数:

def sum(*args):

    result = 0

    for n in args:

        result += n

    return result

如果此时调用函数,将会立即求和。

再定义一个函数,它能够延迟求和:

def lazy_sum(*args):

    def sum():

      result = 0

      for n in args:

          result += n

      return result

    return sum                # 函数sum作为返回值

调用函数lazy_sum()时,返回的是求和函数sum,而不是求和结果:

>>> f = lazy_sum(1, 2, 3)

>>> f

# f = 函数lazy_sum局部空间中的sum函数

<function lazy_sum.<locals>.sum at 0x000001ECB6C19840>

当调用函数f时,才会真正计算求和的结果:

>>> f()

6

这种嵌套定义函数,具有两个特征:

1)内部定义的函数使用了外部函数的参数或局部变量;

2)外部函数的返回值为内部函数(注意不是返回内部函数的调用);

最后有一点需要注意,每次调用lazy_sum(),都会返回一个新的函数,即使传入相同的参数:

>>> f1 = lazy_sum(1, 2, 3)

>>> f1 = lazy_sum(1, 2, 3)

>>> f1 == f2

False    # f1和f2指向的是不同内存地址的sum函数

1.闭包

1)概念:

函数嵌套定义的前提下,如果内部函数使用了外部函数的变量,并且外部函数返回值为内部函数,此时我们就把这个内部函数称为“闭包”。

2)特征:

也即闭包的构成条件为:

(1)函数嵌套定义为前提;

(2)内部函数使用了外部函数的变量;

(3)外部函数的返回值为内部函数;

3)原理:

在上面的lazy_sum()函数中:

def lazy_sum(*args):

    def sum():

      result = 0

      for n in args:

          result += n

      return result

    return sum                # 函数sum作为返回值

lazy_sum是外部函数,sum是内部函数,此时的sum就是闭包。

闭包(内部函数)sum引用了外部函数lazy_sum的变量,当lazy_sum返回sum时,其相关参数和变量就保存在了闭包sum中。其中的过程:定义外部函数 --> 操作系统为外部函数分配局部空间 --> 局部空间中定义变量和内部函数 --> 为内部函数开辟嵌套的局部空间 --> 返回内部函数。

过程中存在嵌套的局部空间(Enclosing),对于这个嵌套的局部空间来说,外部函数所在的局部空间就相当于其全局空间,其中的变量也相当于全局变量,因此内部函数可以像调用全局变量一样调用外部函数的变量。而也是由于这个嵌套的局部空间,使得内部函数保存了外部函数的变量。

闭包原理复杂,应用的时候牢记三点就可以:

1)闭包(内部函数)使用了外部函数的变量,并且能够保存外部函数的相关变量;

2)外部函数返回的是闭包函数,不是一个计算结果,该函数不会立即执行;

3)调用外部函数并赋值给变量,则该变量就等于保存了外部函数相关变量的内部函数;

4)应用:两个小伙伴聊天

# 定义闭包

def person(a):

    def inner(b):

      print('{}:{}'.format(a, b))

    return inner


david = person('David')    # 建立一个闭包对象david

jane = person('Jane')      # 建立闭包对象jane

david('天王盖地虎')

jane('宝塔镇河妖')

>>> 运行结果:

David:天王盖地虎

Jane:宝塔镇河妖

可以看到两种现象:1)调用person()时,inner延迟执行;2)inner保存了person的变量a;

5)关键字nonlocal

关键字nonlocal能够实现对闭包外部函数变量的修改操作。nonlocal --> 声明内部函数的变量是非本地的。类似于定义普通函数时的声明全局变量的关键字global。

# 定义闭包:不使用nonlocal

def outer():

    a = 10

    def inner():

        a = 20

        result = a + 1

        print(result)

    inner()

    print(a) 

    return inner


test1 = outer()  # 调用外层函数

test1()      # 调用内层函数

>>> 运行结果:

21

10

21   

#  a的值仍为10,说明内部函数没有修改掉外部函数变量a的值

#  此种情况下内部函数并没有使用外部函数的变量,而是自建了一个新的变量a

# 定义闭包:使用nonlocal

def outer():

    a = 10

    def inner():

        nonlocal a

        a = 20

        result = a + 1

        print(result)

    inner()

    print(a)

    return inner


test1 = outer()  # 调用外层函数

test1()      # 调用内层函数

>>> 运行结果

21

20

21   

# a的值被修改为20 

2.总结

闭包是Python中函数式编程的重要语法结构,属于函数的高阶应用。通过它的原理也能够实现“装饰器”的定义。

3.Enclosing 补充

当定义闭包函数时,外部函数的局部命名空间中会存在“Enclosing作用域”。闭包函数会存在于这个Enclosing作用域中,可以通过闭包函数的__closure__属性调出。Enclosing除了存放闭包函数外,还会自动添加闭包函数用到的外部函数的对象,但仅限自己所用的。

上面闭包应用的例子:

# 定义闭包

def person(a):

    a1 = 10

    def inner(b):

        print(inner.__closure__)      # 打印闭包函数的__closure__属性

        print('{}:{}'.format(a, b))

    return inner

inner.__closure__ = (<cell at 0x000001DC0A6D7E88: str object at 0x000001DC0A768E30>, <cell at 0x000001DC0A6D76A8: function object at 0x000001DC0A799620>)

str object 即是inner用到的参数a;function  object为它自己;而变量a1由于inner不会使用,所以不在其中。

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