美文网首页
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)
    

    相关文章

      网友评论

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

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