15.3-global、nonlocal和闭包

人生有两件事最难得:一是知足,二是感恩。知足,会让你看到别人的优点,拥有平和的心态,收获充实的生活;感恩,会让你懂得尊重和包容,增长人生的智慧,获得他人的认可。站得多高,看的就多远,你的目光所及就是你的世界;茫茫人海,匆匆旅途,重要的从来都不是结果。

一般不建议使用global(内层函数都可以看到全局变量),相当于常量,学习它是为了深入理解变量作用域;

函数体内部出现变量定义, 代表意义重大;

本章总结:

  1. global 优先放在前面;在内部不能先被赋值;函数定义的全局变量值会随着函数体内部变量的变化而改变;
  2. global 内部函数体变量的声明定义表示引用全局定义: 不在内外层之间有联系; 只影响当前作用域;内外层作用域是可以看见全局变量作用域;
  3. global 全局变量不建议经常使用;——global全局变量定义后,所有内部函数都可以看到,调用;传递在所有内部函数体之间;
  4. global 在不同作用域之间的声明,必须要有定义,可以在使用的时候再定义赋值;
  5. 在嵌套函数中,才谈闭包;指的是一个概念:内存函数引用了外层函数的自由变量; 闭包特性:内部定义的变量引用了外部变量; 被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
  6. 变量定义以后,全局变量与内部变量没有关系;
  7. nonlocal声明只在内外部变量之间起作用;用的是外部变量的定义,本地变量没有定义;
  8. 函数传形参 相当于 局部变量定义
  9. nonlocal 逐级向外 查找,绝对不会跑出最外层的封装,去全局作用域中查找

参考:Python函数式(九)——闭包

1. LEGB(Local, Enclosing, Global, Built-ins)

Python会按照LEGB(Local, Enclosing, Global, Built-ins)沿着内层局部命名空间->外层局部命名空间->全局命名空间->内建命名空间的顺序去寻找一个标识符的定义,也就是说,定义于局部命名空间的标识符会覆盖全局命名空间的同名标识符,定义于全局命名空间的标识符会覆盖同名的内建命名空间标识符:

Python中的作用域分4种情况:

L:local,局部作用域,即函数中定义的变量(局部变量);
E:enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域,但不是全局的;
G:globa,全局变量,就是模块级别定义的变量;
B:built-in,系统固定模块里面的变量,比如int, bytearray等

搜索变量的优先级顺序依次是作用域局部>外层作用域>当前模块中的全局>python内置作用域,也就是LEGB

1. 局部命名空间在函数调用结束后就会消失,因而,局部变量无法在全局命名空间中使用(上例中的x),但是全局变量可以在函数内访问得到。

a = 1
abs = 2
def local():
    b = 1
    a = 3
    print(a)
local()
# 3
print(a)
# 1
print(abs)
# 2

  1. Python解释器执行程序之前,Python已经预先将函数内的标识符a指定为局部变量,它会覆盖掉全局命名空间中的a;在执行时,第一次print时a还没有和对象3绑定(仅仅知道它是一个局部变量),所以会产生错误。
a = 1
def func():
    print(a)
    a = 3
    print(a)
func()
UnboundLocalError: local variable 'a' referenced before assignment

总结:
1.全局变量和内部、外部变量名不能相同(不能都赋值);容易冲突(这样好理解)

2.变量作用域的提升

如何避免上述问题呢?答案是将函数设计为无状态形式,将所有需要用到的外部变量全部作为参数传递给函数。不

python中的函数在运行前会经过预编译,函数内部的所有变量的声明都会被提到函数的开始处,此时并未开辟空间,只有真正赋值后才在内存中开辟空间,变量使用时,必须在内存中已经开辟了空间。

2.1 global

global不仅能绑定到已经存在的全局变量上,还能创造新的全局变量:
global的作用是将局部变量提升到变成全局变量,不过提升之前不能赋值。

格式如下: 优先放到前面;生产环境中,相当于常量,建议少使用!

总结:
1. 使用全局变量;优先放在前面;在内部不能先被赋值  ;
2.  global x   x=10   ; 等于在全局变量  x=10  ,不是外部变量 ;
3. 生产环境中,相当于常量,建议少使用

a = 1
def func():
    global a   ## 使用全局变量;优先放在前面;在内部不能先被赋值  ;SyntaxError: name 'a' is assigned to before global declaration
    print(a)
    a = 2    
    print(a)

func()
print(a)
--------------
1
2
2           # global全局变量在函数体内部的改变也是全局变量的改变;


def foo():
    z = 100
    def bar():
        global z 
        z = z + 1
    print(1,z)
    bar()
    
foo()
print(2,z)
--------------------------------
1 100
NameError: name 'z' is not defined


2.两层global 嵌套
def foo():
    global z   #  相当于外部的 z = 100
    z = 100
    def bar():
        global z 
        z = z + 1
    print(1,z)
    bar()
foo()
print(2,z)
------------------------------
1 100
2 201

3.多层内存嵌套全局变量,最后赋值变量;
def foo():
    global z   #相当于外部的 z = 100
    #z = 100
    def bar():
        global z 
        z = 200
        z = z + 1
    bar()
    print(1,z)

foo()
print(2,z)
--------------------------
1 201
2 201

4. 查看命名空间中存在哪些标识符
print(dir())            # 看命名空间中存在哪些标识符呢
---------------

总结:
1. 多层内存嵌套循坏中,声明全局变量后,程序是按照一步步运行的;
2. 尽量不要使用全局变量; global
3. global x   x=10   ; 等于在外部  x=10  ;
4. 

2.2 闭包(Closure) ( 嵌套函数中使用)

自由变量:未在本地作用域中定义的变量。例如定义在内层函数外的外层函数的作用域中的变量;
闭包(Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是内存函数引用了外部函数-自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

定义闭包格式
1.返回 return 函数对象,而不是调用函数。
2.实质就是函数体中, 内部变量 调用 外部变量;

让它直接返回函数对象,而不是调用嵌套函数:
 def outer(msg):
    def inner():
        print(msg)
    return inner  # 返回函数对象,而不是调用函数。
func = outer('Hello')
func()
Hello

当外部函数 outer(msg) 被调用时,一个闭包 inner() 就形成了,并且它持有自由变量 msg。这意味着,当函数 outer(msg) 的生命周期结束之后,变量 msg 的值依然会被记住,不妨来试试:

# 返回里面Inc函数;
def foo():
    z = 100
    def bar():
        z = z + 1
        return z
    return bar()

foo()    # 返回里面Inc函数;
z = 100
print(foo())
--------------------------------------------------------------------

可变类型的函数定义:   (与一般的有区别)
def counter():
    c = [0]
    def inc():
        c[0] += 1
        return c[0]
    return inc     # 复杂类型、变量内存地址、引用
foo = counter()    # 返回     函数inc   foo=>inc     inc()     foo()    
print(foo(),foo())
c=100        #   变量定义以后,全局变量与内部变量没有关系;
print(foo())
--------------------------------------
1 2
3

2.3 nonlocal

从 2.x 开始,Python 通过词法作用域支持闭包。然而,在特性的最初实现中“有一点小问题”。之所以这么说,是因为在 2.x 中,闭包无法更改 nonlocal 变量(只读的),但从 3.x 起,该问题已经被解决了,详见 PEP-3104(https://www.python.org/dev/peps/pep-3104/)。

词法作用域(Lexical Scoping):变量的作用域在定义时决定,而不是在执行时决定。也就是说,词法作用域取决于源码,通过静态分析就能够确定,因此,词法作用域也叫做静态作用域

因为函数内可以定义新的函数,因而在Python中,局部命名空间是可以嵌套的,即一个局部命名空间中包含另一个局部命名空间:

为了使内层函数能够使用到外层的局部变量,我们需要使用关键字nonlocal来声明一下,这样,内层的标识符就指向了外层的对象:

总结:
1. nonlocal声明只在内外部变量之间起作用;
2.  c 和函数的形参c 完全是两码事;

c=100
nonlocal c   
def outer():
    name = 'Z'
    print('outer: {}'.format(name))
    def inner():
        # 内部函数访问外部函数定义的变量
        nonlocal name
        print('inner: {}'.format(name))
        # 内部函数访问外部函数定义的变量的值
        name = 'X'
        print('inner: {}'.format(name))
    inner()
    print('outer: {}'.format(name))
outer()
--------------------------------------------
outer: Z
inner: Z
inner: X
outer: X


2. 内层的标识符就指向了外层的对象,可以看到,外层的局部变量也被修改了 a=3

def func():
    a = 1
    def inner():
        nonlocal a
        print(a)
        a = 3
        print(a)
    inner()
    print(a)
func()
# 1
# 3
# 3

传参数相当于定义:
def counter(c):   #传参数相当于定义:
    #c = 0
    def inc():
        nonlocal c
        c += 1
        return c
    return inc
foo = counter(100)
print(foo())
print(foo())
---------------------
101
102



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

推荐阅读更多精彩内容