https://zhuanlan.zhihu.com/p/108924801
一 名称空间
名称空间是对栈的一种划分, 真正存在的是栈区, 名称空间只是一种虚拟的划分.
名称空间: 存放名字的地方, 是对栈区的划分. 有了名称空间之后, 就可以在栈区存放相同的名字. 名称空间分为三种:
1. 内置名称空间
存放的名字: 存放的是python解释器内置的名字, 例如: print, input
存活周期: python解释器启动则产生, python解释器关闭则销毁.
2. 全局名称空间
存放的名字: 只要不是函数内定义的也不是内置的, 剩下的都是全局名称空间.
存活周期: python文件执行则产生, python文件运行完毕后销毁
if 1==1:
x = 2
其中, x属于全局名称空间.
3. 局部名称空间
存放的名字: 在调用函数时, 运行函数体代码过程中产生的函数内的名字
存活周期: 在调用函数时存活, 函数调用完毕后则销毁
名称空间的加载顺序:
内置名称空间 > 全局名称空间 > 局部名称空间
运行python程序过程中肯定有的空间是: 内置名称空间和全局名称空间, 即便python文件是空的, 也会造一个空的全局名称空间.
销毁顺序:
局部名称空间 > 全局名称空间 > 内置名称空间
名字的查找优先级:
从当前所在的位置向上一层一层查找, 最上面的是内置名称空间, 其次是全局名称空间, 最下面的是局部名称空间.
如果当前在局部名称空间, 查找顺序为:
局部名称空间 > 全局名称空间 > 内置名称空间
如果当前在全局名称空间, 查找顺序为:
全局名称空间 > 内置名称空间
示例:
input = 111 def func(): input = 222 print(input) func() # 结果: 222
input = 111
def func():
#input = 222
print(input)
func()
# 结果: 111
# input = 111
def func():
#input = 222
print(input)
func()
# 结果: <built-in function input>
input = 111
def func():
input = 222
func()
print(input)
# 结果: 111
练习:
x = 111 def func(): print(x) func() # 结果: 111 def func(): print(x) x = 111 func() # 结果: 111 def func(): print(x) func() x = 111 # 结果: NameError: name 'x' is not defined
名称空间的'嵌套'关系以函数调用之前为准, 与调用位置无关.
x = 1 def func(): print(x) def foo(): x = 222 func() foo() # 结果: 1 ''' 解释: 在定义func时, 并没有定义局部变量x, 所以print(x)中的x应该往全局或内置名称空间找. 即便在调用foo时才调用func, x不会使用foo中定义的x的值. '''
函数嵌套定义:
函数嵌套查找顺序: 从所在层为起始, 一层一层往外找, 如果都没有就去全局的找, 全局也没有则取内置空间找.
input = 111 def f1(): input = 222 def f2(): input = 333 print(input) f2() def foo(): input = 444 f1() foo()
input = 111 def f1(): input = 222 def f2(): input = 333 print(input) f2() def foo(): input = 444 f1() foo() # 结果: 333 input = 111 def f1(): input = 222 def f2(): # input = 333 print(input) f2() def foo(): input = 444 f1() foo() # 结果: 222 input = 111 def f1(): # input = 222 def f2(): # input = 333 print(input) f2() def foo(): input = 444 f1() foo() # 结果: 111 # input = 111 def f1(): # input = 222 def f2(): # input = 333 print(input) f2() def foo(): input = 444 f1() foo() # 结果: <built-in function input> input = 111 def f1(): def f2(): # input = 333 print(input) input = 222 f2() def foo(): input = 444 f1() foo() # 结果: 222
x = 111
def func():
print(x)
x = 222
func()
# 结果: 报错 UnboundLocalError: local variable 'x' referenced before assignment
# 解释: 名称空间的'嵌套'关系在定义阶段就确定好了. 定义阶段, 检测语法时发现func自己的空间中有x, 此时就确定print(x)中的x是局部名称空间中的x.
# 但是在调用阶段执行print(x)时, 发现x还没有定义, 所以会报错.
注意: 名称空间是虚拟的概念, 不是真实存在的. 名称空间之间是相互独立的, 不是相互包含的.
名称空间的概念就是把栈区的名字(即名字与内存地址的绑定关系)进行划片, 其中, 一片叫做内置名称空间, 一片叫做全局名称空间, 还有一片叫做局部名称空间. 其实它们都存在于栈区中.
作用域是指在名称空间的基础之上, 把名称空间按照它们作用范围和特点的不同, 进行归类. 内置名称空间和全局名称空间属于全局范围(全局作用域), 即内置名称空间和全局名称空间中包含的名字的作用范围是全局的. 局部名称空间属于局部范围(局部作用域). 内置名称空间有一个, 全局名称空间有一个, 局部名称空间有n多个.
二 作用域
作用域就是作用范围.
作用域将名称空间分为两大类, 即全局作用域与局部作用域.
全局作用域: 内置名称空间, 全局名称空间
1. 全局存活, 即整个程序运行过程中都存活
2. 全局有效, 即被所有函数共享
局部作用域: 局部名称空间的名字
1. 临时存活, 即函数调用时存活, 调用结束后销毁
2. 局部有效, 即函数内有效
# 有些人将局部名称空间又分为两种, 分别为global和e # LEGB # built_in # global def f1(): # enclosing def f2(): # enclosing def f3(): # local ...
注意: 全局变量指的可能是全局名称空间中的变量或内置名称空间中的变量.
三 global与nonlocal
1. global
如果不想在局部产生新的变量, 而是想修改全局名字的值, 则需要引入global, 声明变量名是全局的, 以下修改是针对全局名称的.
注意: 如果值属于可变类型, 不进行赋值操作, 而是通过append, pop等方法进行修改, 并不会在局部产生新的变量, 所以也没必要使用global声明.
x = 111 def func(): x = 222 # 这里给x赋值并不会修改全局的x func() print(x) # 结果: 111 x = 111 def func(): global x # 声明x这个名字是全局的名字, 不要再造新的名字了, 如果全局没有x, 则会造一个x, x属于全局. x = 222 func() print(x) # 结果: 222
def func():
global x
x = 10
func()
print(x) # 结果: 10
如果在局部想要修改全局名字的值(可变类型), 则不需要global, 这里的修改不是赋值操作, 是append, pop等操作. 如果是赋值操作, 且不用global, 说明是造新的变量, 为了标识不是造新的变量, 而是对全局名称空间中变量值的修改, 则需要使用global标注一下.
l = [11, 22] def func(): l.append(33) # l与内存地址的绑定关系没有改变, 不用global func() print(l) # 结果: [11, 22, 33]
l = [11, 22] def func(): l = [3, 4] # 尽管l对应的值是列表, 但是这里进行了赋值操作, 就会改变l与内存地址的绑定关系, 如果想修改全局的l, 依然需要使用global func() print(l) # 结果: [11, 22]
l = [11, 22] def func(): global l l = [3, 4] func() print(l) # 结果: [3, 4]
2. nonlocal(了解): 修改函数外层函数包含的名字对应的值(不可变类型), 直到找到最外面的一层函数, 如果找不到则报错
x=0 def f1(): x=11 def f2(): global x x=22 f2() print('f1内的x:{}'.format(x)) f1() print(x) # 结果: # f1内的x:11 # 22
x=0 def f1(): x=11 def f2(): nonlocal x x=22 f2() print('f1内的x:{}'.format(x)) f1() print(x) # 结果: # f1内的x:22 # 0
注意: 如果数据是可变类型, 则无需用nonlocal进行声明. 这里说的更改不是赋值操作.
四 练习
# 题目1 input = 333 def func(): input = 444 func() print(input) # 结果: 333 # 题目2 def func(): print(x) x =111 func() # 结果: 111 # 题目3 x = 1 def func(): print(x) def foo(): x = 222 func() foo() # 结果: 1 x = 1 def func(): print(x) def foo(): x = 222 func() x = 333 foo() # 结果: 333 # 题目4 input = 111 def f1(): def f2(): print(input) input = 222 f2() input = 333 f1() # 结果: 222 input = 111 def f1(): def f2(): print(input) # input = 222 f2() input = 333 f1() # 结果: 333 # 题目5 x = 111 def func(): print(x) x = 222 func() # 结果: 报错
下附egon老师知乎文章:
目录:
一 名称空间
1.1 内建名称空间
1.2 全局名称空间
1.3 局部名称空间
二 作用域
2.1 全局作用域与局部作用域
2.2 作用域与名字查找的优先级
一 名称空间
名称空间即存放名字与对象映射/绑定关系的地方。对于x=3,Python会申请内存空间存放对象3,然后将名字x与3的绑定关系存放于名称空间中,del x表示清除该绑定关系。
在程序执行期间最多会存在三种名称空间
1.1 内建名称空间
伴随python解释器的启动/关闭而产生/回收,因而是第一个被加载的名称空间,用来存放一些内置的名字,比如内建函数名
>>> max <built-in function max> #built-in内建
1.2 全局名称空间
伴随python文件的开始执行/执行完毕而产生/回收,是第二个被加载的名称空间,文件执行过程中产生的名字都会存放于该名称空间中,如下名字
import sys #模块名sys x=1 #变量名x if x == 1: y=2 #变量名y def foo(x): #函数名foo y=1 def bar(): pass Class Bar: #类名Bar pass
1.3 局部名称空间
伴随函数的调用/结束而临时产生/回收,函数的形参、函数内定义的名字都会被存放于该名称空间中
def foo(x): y=3 #调用函数时,才会执行函数代码,名字x和y都存放于该函数的局部名称空间中
名称空间的加载顺序是:内置名称空间->全局名称空间->局部名称空间,而查找一个名字,必须从三个名称空间之一找到,查找顺序为:局部名称空间->全局名称空间->内置名称空间。
二 作用域
2.1 全局作用域与局部作用域
按照名字作用范围的不同可以将三个名称空间划分为两个区域:
全局作用域:位于全局名称空间、内建名称空间中的名字属于全局范围,该范围内的名字全局存活(除非被删除,否则在整个文件执行过程中存活)、全局有效(在任意位置都可以使用);
局部作用域:位于局部名称空间中的名字属于局部范围。该范围内的名字临时存活(即在函数调用时临时生成,函数调用结束后就释放)、局部有效(只能在函数内使用)。
2.2 作用域与名字查找的优先级
在局部作用域查找名字时,起始位置是局部作用域,所以先查找局部名称空间,没有找到,再去全局作用域查找:先查找全局名称空间,没有找到,再查找内置名称空间,最后都没有找到就会抛出异常
x=100 #全局作用域的名字x def foo(): x=300 #局部作用域的名字x print(x) #在局部找x foo()#结果为300
在全局作用域查找名字时,起始位置便是全局作用域,所以先查找全局名称空间,没有找到,再查找内置名称空间,最后都没有找到就会抛出异常
x=100 def foo(): x=300 #在函数调用时产生局部作用域的名字x foo() print(x) #在全局找x,结果为100
提示:可以调用内建函数locals()和globals()来分别查看局部作用域和全局作用域的名字,查看的结果都是字典格式。在全局作用域查看到的locals()的结果等于globals()
Python支持函数的嵌套定义,在内嵌的函数内查找名字时,会优先查找自己局部作用域的名字,然后由内而外一层层查找外部嵌套函数定义的作用域,没有找到,则查找全局作用域
x=1 def outer(): x=2 def inner(): # 函数名inner属于outer这一层作用域的名字 x=3 print('inner x:%s' %x) inner() print('outer x:%s' %x) outer() #结果为 inner x:3 outer x:2
在函数内,无论嵌套多少层,都可以查看到全局作用域的名字,若要在函数内修改全局名称空间中名字的值,当值为不可变类型时,则需要用到global关键字
x=1 def foo(): global x #声明x为全局名称空间的名字 x=2 foo() print(x) #结果为2
当实参的值为可变类型时,函数体内对该值的修改将直接反应到原值,
num_list=[1,2,3] def foo(nums): nums.append(5) foo(num_list) print(num_list) #结果为 [1, 2, 3, 5]
对于嵌套多层的函数,使用nonlocal关键字可以将名字声明为来自外部嵌套函数定义的作用域(非全局)
def f1(): x=2 def f2(): nonlocal x x=3 f2() #调用f2(),修改f1作用域中名字x的值 print(x) #在f1作用域查看x f1() #结果 3
nonlocal x会从当前函数的外层函数开始一层层去查找名字x,若是一直到最外层函数都找不到,则会抛出异常。