12、Python名称空间与作用域

Python函数 / 2020-11-12

一、名称空间

1.1、名称空间是什么?

先来一段官方中文文档的原话:

namespace(命名空间)是一个从名字到对象的映射。 大部分命名空间当前都由 Python 字典实现

命名空间听起来不太顺口,还是名称空间顺口些!

名称空间顾名思义,存放着一堆名称!函数名、变量名、类名等等都是名称,Python把栈区划分为三大名称空间,分别是内置名称空间、全局名称空间、局部名称空间!wait!本来就有栈区了,为什么还要搞什么名称空间???

少年莫慌!为了便于理解,我会经常在文章中运用大量例子来解释,不是非常准确,但是便于理解!

栈区堆区属于Python底层机制,名称空间和它们关系就好比操作系统和应用程序的关系,比如Windows系统和office文档!你在office文档中进行增删查改的操作,其实都不是office干的,它哪有这本事可以直接操控硬件设备,最终都是通过发起系统调用,由底层的操作系统来完成的!

1.2、为何要有名称空间?

如果没有名称空间来划分栈区,那么栈区就相当于是大锅饭,大杂烩,所有名称都一股脑堆在里面,也不加以区分!还是以Windows为例吧,应该大家都试过在同一个文件夹中,不能存在相同名字的文件吧,但是不同文件夹就没事!Python同理,有了名称空间划分三大区域后,就可以使用重名的名称了!

1.3、如何使用它们?

呃......其实没什么特别语法,只是记住它们的查找顺序即可!下面开始逐个解释三大名称空间会提到顺序!

1.4、内置名称空间

1.4.1、存放的名字

即Python解释器内置的名字,如print,input等我们常用的内嵌函数名!你要是不爽,可以自己写一个Python解释器,想加什么函数就加什么函数!像linus大神,看unix商业化收费不爽,自己开发Linux内核(21岁时干的)!看bitkeeper不爽,两周时间开发了个git,至今仍火爆全球!大神的定义是什么?大家可以自己体会一下!

1.4.2、存活周期

随Python解释器启动而生,关闭则销毁!无毒无残留无公害绿色健康食品,你值得拥有!

# 如下所示,built-in即内嵌函数
>>> print
<built-in function print>

1.5、全局名称空间

1.5.1、存放的名字

模块中定义的名称,记录了模块的变量、函数名、类、模块级的变量等称之为全局名称!而模块又是个什么东西?其实我们一直都在用,只是不了解它的定义而已!下面解释摘取自菜鸟教程!

以下内容来自菜鸟教程,版权归菜鸟教程所有!

在前面的几个章节中我们脚本上是用 python 解释器来编程,如果你从 Python 解释器退出再进入,那么你定义的所有的方法和变量就都消失了。

为此 Python 提供了一个办法,把这些定义存放在文件中,为一些脚本或者交互式的解释器实例使用,这个文件被称为模块。

模块是一个包含所有你定义的函数和变量的文件,其后缀名是.py。模块可以被别的程序引入,以使用该模块中的函数等功能。这也是使用 python 标准库的方法。

# snaxi就是全局变量
>>> sanxi=666
1.5.2、存活周期

执行代码产生,运行完毕后销毁!也就是执行这个模块文件里的代码时产生,py文件执行完毕后销毁!如上所示的是在交互式窗口,则当关闭此窗口后销毁!

1.6、局部名称空间

1.6.1、存放的名字

内置函数和类内定义的名称,记录了函数、类的变量、参数、局部定义的变量!

调用时,运行函数体代码过程中产生的函数内的名字

1.6.2、存活周期

调用时生,调用完死。即运行函数体代码时产生,调用结束销毁!

摘自《再别康桥·徐志摩》

悄悄地我走了,

正如我悄悄地来!

我挥一挥衣袖,

不带走一片云彩!

1.7、名称空间加载顺序

内置 --> 全局 --> 局部

1.8、销毁顺序

局部 --> 全局 --> 内置

1.9、名称查找优先级

名称查找的优先级跟加载顺序一样;它根据执行代码时所处位置往别的空间查找!如下所示,因为局部空间已经存在str这个变量,所以以此为准!

>>> str = 666
>>> def print_str():
...     str = 777
...     print(str)
... 
>>> print_str()
777

当我注释掉它时,局部找不着就往全局找,发现全局有,就以全局为准!

str = 666

def print_str():
    # str=777
    print(str)

print_str()
# 执行得到结果
666

如全局也不存在,则往内置空间找!如下所示,str是Python的内置数据类型!如果内置也没有,就报错了!

# str = 666

def print_str():
    # str=777
    print(str)

print_str()

# 执行得到结果
<class 'str'>

1.10、名称空间关系图

这样,为了便于理解,我还是画一张图概括一下,虽然并不是很精确,但是便于理解! 值得一提的是,查找顺序仅与函数定义阶段有关,与调用位置无关!好像人出生就决定了身世一样无法改变,无论身处何方,无论如何转变国籍、说什么鸟语也改变不了我们体内流淌着的滚烫的是中华民族的血液的事实!

二、作用域

域,空间;Python作用域指的是一个Python程序可以访问到的名称空间范围!在Python上面,有人称Python作用域四大才子之称的LEGB!来,都认识一下!

四大作用域其实可以套用上面的图,只需要在里面再加块区域即可!

2.1、L:local

local,本地,局部;不用想了,封闭派系,作用范围最小,仅限于函数内部!

>>> def count_number():
...     a = 666
...     b = 777
...     print(a + b)
... 
>>> count_number()
1443

2.2、E:enclosing

enclosing,包围的意思;对,就是农村包围城市的包围,既然是农村包围城市,那么它一开始的范围就仅仅是几个农村而已,范围也不大!而这些农村就是一个又一个嵌套在一起的local了!就好像美国联邦政府和各大洲之间关系一样,政府代表全美国所有洲,但各大洲同时权力又非常大,甚至能公然抵抗政府法令!

如下所示,像这种一层套一层的,就叫enclosing!对于test2而言,print_str就是它的非本地(non-local)作用域;反过来,对于print_str来说,test2就是它的非全局作用域(non-global),因为print_str是函数最外层,再往外就是城市global了,既然不是global那就叫non-global!

>>> def print_str():
...     x = 6
...     def test2():
...             y = 7
...

2.3、G:global

global,全局;比如当前Python程序的全局变量,被当前Python程序中所有函数共享!

2.4、B:built-in

内嵌;包含Python解释器内置的变量等,按照上述的查找顺序,它是最后才会被搜索!被所有函数共享!

2.5、global与nonlocal

上面有提到non-local与non-global,走农村包围城市的道路!局部正常来说是闭关锁国的,不与外界交流的!那么是否存在一种手段,能够强行打开大门呢?当然有了!GKD!

2.5.1、global

在函数内声明,以此将global名称引入函数内,实现局部想干涉全局名称的欲望,不过仅限于不可变数据类型,因为可变数据类型是个挂比,一向都可以。

如下所示,如果没有global引入全局;那么无论sanxi_info里面定义了什么调用后都不会影响到全局的sanxi!

>>> sanxi = 666
>>> def sanxi_info():
...     global sanxi
...     sanxi = 777
... 
>>> sanxi_info()
>>> sanxi
777
2.5.2、nonlocal

当我们只是改嵌套函数外层函数的变量时,global就不能用了,因为它是引入全局;这时候需要用nonlocal,它能修改函数外层函数包含的名字对应的值又不至于出界到全局,当然了,依然是针对不可变类型;它是从当前函数往外层函数找,直至最外层,都没有则报错!

如下所示,是没有引入nonlocal时,无论test里的sanxi怎么改都无法影响上面的sanxi

def print_str():
    sanxi = 666
    def test():
        # nonlocal sanxi
        sanxi = 777
        print('this is test:', sanxi)
    test()
    print('this is 666:', sanxi)


print_str()
# 执行得到结果
this is test: 777
this is 666: 666

当引入nonlocal后,结果就不一样了!

def print_str():
    sanxi = 666
    def test():
        nonlocal sanxi
        sanxi = 777
        print('this is test:', sanxi)
    test()
    print('this is 666:', sanxi)


print_str()
# 执行得到结果
this is test: 777
this is 666: 777
世间微尘里 独爱茶酒中