1 名称空间
名称空间namespacs:存放名字的地方,是对栈区的划分, 只是一个虚拟的概念, 并不是在内存中实际存在的
有了名称空间之后,就可以在栈区中存放相同的名字
名称空间分为三种, 内置名称空间, 全局名称空间,局部名称空间
1.1 内置名称空间
1.1.1 存放的名字
存放Python解释器内置的名字
>>> print
<built-in function print>
>>> input
<built-in function input>
1.1.2 存活周期
Python解释器启动则产生,Python解释器关闭则销毁
1.2 全局名称空间
1.2.1 存放的名字
只要不是函数内定义、也不是内置的,剩下的都是全局名称空间的名字
每个py文件, 只有一个全局名称空间
1.2.2 存活周期
Python文件执行则产生,Python文件运行完毕后销毁
1.3 局部名称空间
1.3.1 存放的名字
在调用函数时,运行函数体代码过程中产生的函数内的名字
1.3.2 存活周期
在调用函数时存活,函数调用完毕后则销毁
同一个函数, 每被调用一次, 会产生一个局部名称空间, 即使空函数也会局部名称空间
不同函数, 每被调用一次, 也会产生一个局部名称空间
1.4 名称空间的加载顺序
内置名称空间>全局名称空间>局部名称空间
运行一个py程序, 内置名称空间和全局名称空间必须是有的, 即使没有代码, 也会创建全局名称空间, 这样之后定义变量就可以直接放到全局名称空间
一旦运行到函数调用的代码, 才会产生局部名称空间
因此, 运行一个python文件, 即使是空文件, 也会产生内置和全局名称空间
交互式使用python, 启动解释器后也会产生内置和全局名称空间
1.5 名称空间的销毁顺序
局部名称空间>全局名称空间>内置名称空间
1.6 名字的查找优先级
先在当前所在的名称空间查找, 如果找不到, 则向上一层一层查找
如果当前在局部名称空间:
局部名称空间—>全局名称空间->内置名称空间
input=333
def func():
input=444 # 此时print(input)运行在局部空间, 从局部空间找input
print(input) # 局部空间定义了input, 因此, 从局部找, 值为444
func()
>> 444
如果当前在全局名称空间
全局名称空间->内置名称空间
input=333 # 全局定义了input, 并且运行在了函数调用前, 因此, 可以被找到
def func():
input=444 # 这个input定义在局部, 因此不会采用
func()
print(input) # print(input)运行在全局空间, 从全局开始找
>> 333
1.7 名称空间举例
示范1:
def func():
print(x) # 当前局部名称空间找不到x定义, 就去全局找
x=111 # 全局名称空间定义了x
func() # 调用函数是在定义x=111后, 因此, 此时函数局部名称空间还存在, 就能从全局找到x=111了
>> 111
***********************************************************************************************************
def func():
print(x)
func()
x=111
>> NameError: name 'x' is not defined # 此时会报错, 是因为, 全局的定义是在调用函数后, 此时函数的局部空间已经销毁, 因此, 函数调用期间是找不到x的
***********************************************************************************************************
示范2:名称空间的"嵌套"关系是以函数定义阶段为准,与调用位置无关, 在定义阶段, 一旦确定了去哪个空间找名字, 调用阶段就不会改变.
如果调用阶段, 在确定的空间没找到名字, 那么就会报错
x=1
def func():
print(x) # 此时print(x)是在局部空间, 局部空间内没有x, 就去全局找, 全局定义了x=1,那么这里的print(x) 就是print(1)
def foo():
x=222
func() # 之后, 无论func()在哪个局部, 也就是函数内调用, 或者全局空间, 其调用的x都是在定义func()阶段的x, 不会改变
foo()
>> 1
***********************************************************************************************************
x=1
def func():
print(x)
x=2
func()
>> 2
这里x=1和x=2都是全局空间的定义, 而全局空间是不能重复给变量赋值的, 后赋值的会覆盖先赋值
因此, 执行到了x=2时, 全局中的x已经为2了, 那么定义func()时, 由于局部没有定义x, 那么就是去全局找, 但是并没有具体要找哪一个, 具体找哪个x是在调用阶段看的.
更证明了, 函数的嵌套关系和名字的查找是在函数定义阶段确定好的, 定义阶段会确定局部内定义的变量需要去哪里查找名字, 如果定义阶段发现局部内有, 就在局部内找, 没有就去全局
但是不会具体指定, 从局部内对应找全局哪个变量. 具体就看调用阶段, 局部或者全局里的变量情况
***********************************************************************************************************
示范3:函数嵌套定义: 名称的查找也是取决于定义阶段,在定义阶段确定好的关系, 在调用阶段不会改变
input=111
def f1():
def f2():
input = 222
print(input) # print(input)作用在f2定义阶段, 因此, 会先在f2的局部查找input. 定义阶段, f2的局部内有input, 因此, 调用阶段就会在f2的局部查找, 查找到则返回, 找不到则报错, 而不会在调用阶段再去全局找
# input = 333
input = 333
f2()
f1()
>>
222
input=111
def f1():
def f2():
# input = 222
print(input) # 函数定义阶段, print(input)作用在f2的局部, 因此, 会先在f2的局部查找input.
input = 333 # 该input定义在了f2的局部, 因此, 调用阶段, print(input)会在f2的局部查找input. 但是, 一旦到了调用阶段, print(input)在定义input前面执行, 因此, 是找不到input的, 所以报错.
input = 333
f2()
f1()
>>
UnboundLocalError: local variable 'input' referenced before assignment
input=111
def f1():
def f2():
# input = 222
print(input) # 函数定义阶段, f2的局部是没有input的, 因此, 会去上一层f1的局部查找.
# input = 333
input = 333 # 在f1的局部查找到了input定义, 因此, 调用阶段, print(input)就会在f1的局部查找input. 因为f1局部的input定义在了f2调用前, 所以可以找到
f2() # 如果f2的调用运行在f1局部定义的input=333前, 那么调用就会报错
f1()
>>
333
input=111 # 在全局, input的定义运行在函数执行前, 因此, 可以找到
def f1():
def f2():
# input = 222
print(input) # print定义在f2的局部, 局部内, 在定义阶段没有input, 就去f1的局部找
# input = 333
# input = 333 # f1的局部也没有input, 就去全局找
f2()
# input=111 此案例, 如果input运行在这行, 也是可以找到的, 只要运行在f1调用前即可
f1()
>>
111
# input=111
def f1():
def f2():
# input = 222
print(input)
# input = 333
# input = 333
f2()
f1() # 此时, 无论是f1,f2的局部, 还是全局都是没有input定义的, 因此, 只会调用内置的input
>>
<built-in function input>
***********************************************************************************************************
示范4:
x=111
def func():
print(x)
x=222
# 局部空间内, 变量的定义一定要在调用前面. 其原理是, 在函数定义期间, print(x)和x=222会被Python解释器检查语法, 语法通过, 那么解释器就认为在func()的局部空间是定义了x的, 那之后调用它就会在func()局部内部找x; 但是调用期间, 执行到print(x)时发现, 变量并没有定义,
func()
>> UnboundLocalError: local variable 'x' referenced before assignment
2 全局作用域与局部作用域
作用域指的就是作用范围
2.1 全局作用域
内置名称空间、全局名称空间
1、全局存活: 内置和全局都是伴随整个程序运行的周期
2、全局有效: 被所有函数共享, 不同的函数, 可以共享内置和全局的名称空间
没有嵌套关系的不同函数的名称空间是彼此独立的, 不能从foo()去bar()的名称空间去查找
但是全局的和内置的是每个局部空间共享的, 在局部空间都有效
x=111
def foo():
print(x,id(x))
def bar():
print(x,id(x))
foo()
bar()
print(x,id(x))
>>
111 140710317532256
111 140710317532256
111 140710317532256
2.2 局部作用域
局部名称空间的名字
1、临时存活, 函数调用时存活, 调用结束销毁
2、局部有效: 函数内有效
LEGB: 对于名称空间作用域的另一种定义方法
builtin
global
def f1():
enclosing
def f2():
enclosing
def f3():
local
pass
2.3 global与non-local
示范1:
x=111
def func():
x=222
func()
print(x)
>> 111
示范2:如果在局部想要修改全局的名字对应的值(不可变类型,每一次赋值, 都是产生新的内存地址, 把值放进去, 然后绑定变量名),需要用global
x=111
def func():
global x # 声明x这个名字是全局的名字,不要再造新的名字了
x=222
func()
print(x)
>> 222
示范3:
l=[111,222]
def func():
l.append(333) # 对于可变类型, 当局部没有名字, 需要去全局查找时, 可以在局部修改, append会修改原值.
func()
print(l)
>> [111,222,333]
global: 用于在局部修改全局的不可变类型名字
***********************************************************************************************************
nonlocal: 修改当前函数外层函数包含的名字对应的值(不可变类型), 如果在最外层函数也没找到名字对应的值, 那就报错
x=0
def f1():
x=11
def f2():
nonlocal x
x=22
f2()
print('f1内的x:',x)
f1()
>> 22 nonlocal x, x =22会去f2()的外层函数找x并且做修改, 所以就把f1()定义的x修改了, 至于全局的x=0, 和non-local没有关系, non-local只改外层函数
def f1():
x=[]
def f2():
x.append(1111) # 针对可变类型, 无需声明, 直接修改就行了
f2()
print('f1内的x:',x)
f1()
对于LEGB来说, 修改local, 就直接在函数内修改, 修改e, 需要在函数内使用non-local, 修改global, 需要在函数内用global
总结: global表面上是在局部修改全局变量, 更深一层是修改了名称查找的优先级, 把局部变量提升为了全局变量, 之后以全局为基础的查找, 改为查找这个局部的变量
3 局部变量与全局变量
3.1 局部变量
函数体内生效
def change_name(name):
print('before change', name) # >> admin. 这里的name, 就是函数传的实参
name = 'ADMIN' # 3 函数内定义的变量就是局部变量, 只在函数内部生效
print('after change', name) # >> ADMIN, 这里的name, 取的是局部定义的name
name = 'admin' # 1 定义name变量
change_name(name) # 2 把定义的name变量的值通过实参传给函数的形参
print(name) # 4 局部变量只在函数内生效, 所以外面的变量还是name='admin'
>>
before change admin
after change ADMIN
admin
3.2 全局变量
整个程序内生效
company = 'Tencent' # 定义全局变量
def change_name(name):
print('before change', name)
name = 'ADMIN'
company = 'alibaba' # 定义局部变量
print(company)
print('after change', name)
name = 'admin'
change_name(name)
print(name)
print(company)
>>
before change admin
alibaba # 局部变量只在函数体内生效
after change ADMIN
admin
Tencent # 全局变量在整个程序内生效
3.3 将局部变量提升为全局变量
需要在函数体内使用global指令
company = 'Tencent'
def change_name(name):
print('before change', name)
name = 'ADMIN'
global company
company = 'alibaba'
print(company)
print('after change', name)
name = 'admin'
change_name(name)
print(name)
print(company)
>>
before change admin
alibaba
after change ADMIN
admin
alibaba # 将company提升为全局变量后, 全局生效
也可以在函数体内, 把局部变量提升为全局变量, 即使这个全局变量本身不存在
注意: 由于函数会在程序的多个部分进行调用, 所以不要把局部变量提升为全局变量, 也不要在函数体内定义全局变量
全局变量一定要定义在程序开始
3.4 可变类型的全局变量, 是可以在函数体内被修改的, 并且会影响全局
name = ['admin','manager']
def change_name():
name[0] = 'staff'
change_name()
print(name)
>>
print(name)
几个容易出错的小案例
根据不同情况, 结果不同
# 在有参函数中, 局部的变量, 会优先使用传进来的参数. 如果在打印之前, 局部没有修改对应的参数值, 那么就用传进来的实参, 如果修改了那么就按照修改的值. 具体就看局部变量的修改是在打印前还是后
def func(x,age=20,**kwargs): #第一步: 函数定义阶段, age = 20
age = 40 #第三步, 函数体执行, age 又被赋值了40, 赋值都是创建新的内存空间, 所以age又被赋值了40
print(x)
print(age)
print(kwargs)
func(1,2,b=2) #第二步: 函数调用阶段, 1 给了 x, 2 给了 age, b=2 给了 kwargs
>>
1
40
{'b': 2}
def func(x,age=20,**kwargs):
print(x)
print(age)
print(kwargs)
age = 40 # 与上一题不同的是, 这里age重新赋值是在print(age)后运行的, 因此还没生效
print(age) # 如果在这里再次打印age, 那么就是40了
func(1,2,b=2)
>>
1
2
{'b': 2}
40
x= 10
def func(name,age,gender=x): # 这里是把一个变量名对应变量值的内存地址赋值给了另一个变量, 因此, 即使修改了源变量的绑定关系也不会影响新的变量的变量值
pass
print(name,age,gender)
x= 20
func('david',18,40)
网友评论