美文网首页
11. Python之名称空间与作用域

11. Python之名称空间与作用域

作者: 随便写写咯 | 来源:发表于2021-01-15 00:30 被阅读0次

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)

相关文章

  • 1.Python语言基础

    Python的名称空间与作用域 名称空间名称空间(Namespace)是从名称到对象的映射,大部分的命名空间都是通...

  • 15.Python之名称空间与作用域

    Python之名称空间与作用域 名称空间名称空间(Namespace)是从名称到对象的映射,大部分的命名空间都是通...

  • 11. Python之名称空间与作用域

    1 名称空间 1.1 内置名称空间 1.1.1 存放的名字 1.1.2 存活周期 1.2 全局名称空间 1.2.1...

  • python的名称空间与作用域

    一 名称空间 名称空间即存放名字与对象映射/绑定关系的地方。对于x=3,Python会申请内存空间存放对象3,然后...

  • python名称空间和作用域

    https://www.jianshu.com/p/555598495cc6 语句 1,a 引用了 1 这个对象或...

  • 函数02

    名称空间与作用域:内置、全局、局部 定义在开始的都是全局名称,全局作用域。先是找自己——》找全局——》找内置——报...

  • 名称空间 / 作用域

    一,名称空间 二,作用域 1) 作用域与名字查找的优先级 2) grobal / nolocal 易错补充

  • 2019-01-04高阶函数

    高阶函数 一、名称空间和作用域 可以简单理解为存放变量值之间邦定关系的地方。 1.名称空间 在 Python 中有...

  • python名称空间及作用域解析

    1.名称空间namespace 从名字到对象的一个映射(a mapping from name to object...

  • 十、函数(进阶)

    一、名称空间和作用域 可以简单理解为存放变量名和变量值之间绑定关系的地方。 1. 名称空间 在 Python 中有...

网友评论

      本文标题:11. Python之名称空间与作用域

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