Python函数——命名空间与闭包

前言

执行以下代码

def my_test():
    x = 1
    y = x+1

print(x)

》》
Traceback (most recent call last):
  File "C:/Users/jingjing/PycharmProjects/py3Project/路飞/第二模块/函数练习/斐波那契.py", line 36, in <module>
    print(x)
NameError: name 'x' is not defined

x在函数里面定义了,为什么会报错?

python解释器如何执行

"""
从python解释器开始执行之后,就在内存中开辟了一个空间

每当遇到一个变量的时候,就把变量名和值之间的对应关系记录下来。

但是当遇到函数定义的时候解释器只是象征性的将函数名读入内存,表示知道这个函数的存在了,至于函数内部的变量和逻辑解释器根本不关心。

等执行到函数调用的时候,python解释器会再开辟一块内存来存储这个函数里的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量会存储在新开辟出来的内存中。函数中的变量只能在函数的内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。
"""

结论:“存放变量与值的关系”的空间——叫做命名空间

代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间在函数的运行中开辟的临时的空间叫做局部命名空间

命名空间与作用域

命名空间的本质:存放变量与值的绑定关系

  •    全局命名空间:创建的存储“变量名与值的关系”的空间叫做全局命名空间
  •    局部命名空间:在函数的运行中开辟的临时的空间叫做局部命名空间
  •    内置命名空间:内置命名空间中存放了python解释器为我们提供的名字:input,print,str,list,tuple...它们都是我们熟悉的,拿过来就可以用的方法。

三种命名空间之间的加载与取值顺序:

加载顺序:内置命名空间(程序运行前加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载)

使用:全局不能使用局部的,局部的可以使用全局的。
  

作用域

作用域就是作用范围,按照生效范围可以分为全局作用域和局部作用域。

全局作用域:包含内置名称空间、全局名称空间,在整个文件的任意位置都能被引用、全局有效

局部作用域:局部名称空间,只能在局部范围生效

globals方法:查看全局作用域的名字【print(globals())】

locals方法:查看局部作用域的名字【print(locals())】

def func():
    a = 12
    b = 20
    print(locals())
    print(globals())

func()

global关键字,在函数内部修改全局变量的值(在多人协作开发时慎用)

a = 10
print('原始值',a)
def func():
    global a
    a = 20
    print('函数修改值', a)

func()
print('最终值',a)

"""
原始值 10
函数修改值 20
最终值 20  全局变量被修改
"""



a = 10
print('原始值',a)
def func():
    a = 20
    print('函数修改值', a)

func()
print('最终值',a)

"""
原始值 10
函数修改值 20 
最终值 10 全局变量没有修改
"""

nonlocal关键字:在嵌套函数中,让内部函数中的变量在上一层函数中生效,外部必须有变量定义

a = 1
print('原始值',a)
def f1():
    a = 2
    print('进入f1内的打印', a)
    def f2():
        a = 5
        def f3():
            nonlocal a  # 修改局部的(当用nonlocal时),global a  修改全局的
            a = 100
            print('f3内的打印',a)
        f3()
        print('离开f2内的打印', a)  # nonlocal修改的值只对上一层函数(存在变量a被修改)的范围内有影响 ,此处只对f2有效
    f2()
    print('离开f1内的打印', a)  
f1()
print('最终值', a)  # nonlocal修改的值不影响全局的值

"""
原始值 1
进入f1内的打印 2
f3内的打印 100
离开f2内的打印 100
离开f1内的打印 2
最终值 1
"""


a = 1
print('原始值',a)
def f1():
    a = 2
    print('进入f1内的打印', a)
    def f2():
        # a = 5  取消修改,nonlocal会对f1有影响
        def f3():
            nonlocal a  # 修改局部的(当用nonlocal时),global a  修改全局的
            a = 100
            print('f3内的打印',a)
        f3()
        print('离开f2内的打印', a)  # nonlocal修改的值只对上一层函数(存在变量a被修改)的范围内有影响,此处对f2和f1有效,因为a在f1中被修改,在f2中没有变化
    f2()
    print('离开f1内的打印', a)  
f1()
print('最终值', a)  # nonlocal修改的值不影响全局的值

"""
原始值 1
进入f1内的打印 2
f3内的打印 100
离开f2内的打印 100
离开f1内的打印 100
最终值 1

"""

函数作用域查找顺序

LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__

  • locals 是函数内的名字空间,包括局部变量和形参
  • enclosing 外部嵌套函数的名字空间
  • globals 全局变量,函数定义所在模块的名字空间
  • builtins 内置模块的名字空间
level = 'L0'
n = 22
def func():
    level = 'L1'
    n = 33
    print(locals())

    def outer():
        n = 44
        level = 'L2'
        print(locals(), n)

        def inner():
            level = 'L3'
            print(locals(),n)  # 此外打印的n是多少?
        inner()
    outer()

"""
问题:在inner()里的打印的n的值是多少?44 取邻近函数outer中n=44
{'n': 33, 'level': 'L1'}
{'level': 'L2', 'n': 44} 44
{'level': 'L3', 'n': 44} 44
"""
func()

闭包

函数名的本质:就是函数的内存地址

def func():
    print('func')
a = func
print(a)
"""
<function func at 0x0000000002357B70> # a指向了函数的内存地址
"""

函数名可以作为函数的返回值

def func():
    print("func")
    def func2():
        print('func2')
        return '返回func2'
    return func2   # 函数名作为返回值

func2 = func()  # 返回func2地址
print(func2)
print(func2())
"""
func
<function func.<locals>.func2 at 0x0000000002567840>
func2
返回func2
"""

闭包的定义:内部函数包含对外部作用域而非全局作用域变量的引用,该内部函数称为闭包函数

# 闭包的常用形式:
def func():
    name = 'eva'
    def inner():
        """闭包函数"""
        print(name)
    print(inner.__closure__)  # 输出的__closure__有cell元素 :是闭包函数
    return inner

f = func()
f()
"""
(<cell at 0x0000000000510C48: str object at 0x0000000002558B90>,) 
eva
"""

判断闭包函数的方法:__closure__

#输出的__closure__有cell元素 :是闭包函数
def func():
    name = 'eva'
    def inner():
        print(name)
    print(inner.__closure__)
    return inner

f = func()
f()

#输出的__closure__为None :不是闭包函数,因为name为全局变量
name = 'egon'
def func2():
    def inner():
        print(name)
    print(inner.__closure__)
    return inner

f2 = func2()
f2()

闭包获取网络应用

from urllib.request import urlopen

def outer():
    url = "http://www.xiaohua100.cn/index.html"
    def inner():
        return urlopen(url).read()
    return inner

a = outer()
content = a()
print(content)

总结

作用域:小范围的可以用大范围的,但是大范围的不能用小范围的
范围从大到小(图)

如果在小范围内,如果要用一个变量,是当前这个小范围有的,就用自己的
如果在小范围内没有,就用上一级的,上一级没有的,就用上上级的,以此类推
如果都没有,报错

函数作用域查找顺序:

LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> builtins

函数名的本质:

  就是一个变量,保存了函数所在的内存地址

闭包:

  内部函数包含对外部作用域而非全局作用域名字的引用,该内部函数称为闭包函数

 


   

原文地址:https://www.cnblogs.com/xiao-apple36/p/8711876.html