美文网首页Python全栈工程师
15.3-global、nonlocal和闭包

15.3-global、nonlocal和闭包

作者: BeautifulSoulpy | 来源:发表于2019-09-16 17:30 被阅读0次

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

一般不建议使用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



相关文章

  • 15.3-global、nonlocal和闭包

    人生有两件事最难得:一是知足,二是感恩。知足,会让你看到别人的优点,拥有平和的心态,收获充实的生活;感恩,会让你懂...

  • 闭包 global 和 nonlocal

    闭包 一个函数在调用的时候, 内部的自由变量, 要到这个函数被定义的地方去找, 而不是在这个函数当前被调用的地方去...

  • python必知必会8

    什么是闭包? 实现闭包的要素是哪些?闭包中的自由变量的绑定?nonlocal 的使用? 在一个内部函数中,对外部作...

  • Python闭包与nonlocal

    在廖雪峰的官网上看到一个很有意思题目。关于闭包的,有兴趣的朋友可以看一下这里, 做一下这个题目,当然需要一点闭包的...

  • 闭包中的nonlocal和global以及不使用nonlocal

    effective-python中,在阐述作用域变量时提到了nonlocal和global,当提到python2没...

  • 装饰器

    闭包 nonlocal这个就是修改外部参数的值 装饰器 装饰器有结论 两个装饰器装饰一个函数 装饰器传参

  • python中怎样使用装饰器

    闭包 nonlocal这个就是修改外部参数的值 装饰器 装饰器有结论 两个装饰器装饰一个函数 装饰器传参

  • rust 闭包与同步

    rust 闭包与同步 rust 闭包 rust闭包中主要包括两个部分,闭包参数和闭包环境变量。闭包函数和函数参数使...

  • 闭包(closure)

    ● 闭包基础 ● 闭包作用 ● 闭包经典例子 ● 闭包应用 ● 闭包缺点 ● 参考资料 1、闭包基础 作用域和作...

  • iOS swift 逃逸闭包(@escaping)和非逃逸闭

    iOS swift 逃逸闭包(@escaping)和非逃逸闭包 (@noescaping) 逃逸闭包: 逃逸闭包...

网友评论

    本文标题:15.3-global、nonlocal和闭包

    本文链接:https://www.haomeiwen.com/subject/uxzkyctx.html