zoukankan      html  css  js  c++  java
  • 【2020Python修炼记17】Python语法入门—名称空间和作用域

    【目录】

    一、名称空间

    1. 内置名称空间

    2. 全局名称空间

    3. 局部名称空间

    二、作用域

    1. 全局作用域与局部作用域

    2. 作用域与名字查找的优先级

    一、名称空间

    名称空间(namespacs) :存放名字的地方,是对栈区的划分

    有了名称空间之后,就可以在栈区中存放相同的名字,详细的名称空间分为三种:内置名称空间,全局名称空间,局部名称空间


     L —— Local(function);函数内的名字空间
    E —— Enclosing function locals;外部嵌套函数的名字空间(例如closure)
    G —— Global(module);函数定义所在模块(文件)的名字空间
    B —— Builtin(Python);Python内置模块的名字空间 

    # builtin  B —— Builtin(Python);Python内置模块的名字空间 
    # global   G —— Global(module);函数定义所在模块(文件)的名字空间
    def f1():
        # enclosing  E —— Enclosing function locals;外部嵌套函数的名字空间(例如closure)
        def f2():
            # enclosing  E —— Enclosing function locals;外部嵌套函数的名字空间(例如closure)
            def f3():
                # local  L —— Local(function);函数内的名字空间
                pass

    1、内置名称空间

     # 存放的名字:存放的是python解释器内置的名字

    # 存活周期:python解释器启动则产生,python解释器关闭则销毁

    # 在Python解释器内:
    '''
    >>> print
    <built-in function print>
    >>> input
    <built-in function input>
    '''

    2、全局名称空间

    # 存放的名字:只要不是函数内定义、也不是内置的剩下的都是全局名称空间的名字
    # 存活周期:python文件执行则产生,python文件运行完毕后销毁

     即 伴随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
    if c==0:
       x=2     #if 为顶级代码,且不是函数,x为全局命名空间的名字
       print(x)

    3、局部名称空间

    # 存放的名字:在调用函数时,运行函数体代码过程中产生的函数内的名字;

    函数的形参、函数内定义的名字都会被存放于局部名称空间

    # 存活周期:在调用函数时存活,函数调用完毕后则销毁

    def func(a,b):
        pass
    
    func(10,1)
    func(11,12)
    func(13,14)
    func(15,16)
    def foo(x):
        y=3 #调用函数时,才会执行函数代码,名字x和y都存放于该函数的局部名称空间中

    4、名称空间加载顺序

     名称空间的加载顺序是:内置名称空间->全局名称空间->局部名称空间

    5、名称空间销毁顺序

     名称空间销毁顺序名称空间的加载顺序相反,即 局部名称空间>全局名空间>内置名称空间

    6、名字的查找优先级

    名称空间只有优先级之分,本身并无嵌套关系,画嵌套关系图只是为了理解。

    名称空间的嵌套关系决定了名字的查找顺序,而名称空间的嵌套关系是以函数定义阶段为准的。

    函数的嵌套关系和名字查找顺序是在函数定义阶段就已经确定好了。

    查找一个名字(按照定义阶段的语法顺序去找),必须从三个名称空间之一找到,

    当前所在的位置 向上一层一层查找——

    内置名称空间
    全局名称空间
    局部名称空间

    (1)如果当前在局部名称空间

    查找顺序为:局部名称空间->全局名称空间->内置名称空间->最后都没有找到就会抛出异常

    # input=333 #全局作用域的名字input
    def func():
        input=444 # 局部名字input
        print(input) #在局部找input
    
    func()
    
    输出结果:
    444
    input=333  #全局作用域的名字input
    
    def func():
        # input=444  #局部没有名字input,则找 全局作用域的名字input
        print(input)
    
    func()

    输出结果: 333

    (2)如果当前在全局名称空间
    查找顺序为:全局名称空间->内置名称空间->最后都没有找到就会抛出异常

    input=333   #全局变量
    def func(): input=444  #在函数调用时产生局部作用域的名字input
     func()
    print(input)  # 该语句不属于函数体,属于全局命名空间;函数调用时,对该语句没有影响,所以直接执行打印功能,从全局命名空间开始找,即输出全局变量 input=333  输出结果: 333
    # input=333
    def func(): input=444 func()
    print(input) # 该语句不属于函数体,属于全局命名空间;函数调用时,对该语句没有影响,所以直接执行打印功能,从全局命名空间开始找,
    全局空间没有值,则在内置命名空间找,即 <built-in function input> 输出结果:
    <built-in function input>

    栗子1号:

    def func():
        print(x)
    x=111 # 全局变量
    
    func()

    栗子2号:名称空间的"嵌套"关系是以函数定义阶段为准,与调用位置无关

    x=1
    def func():
       print(x)
    
    
    def foo():
        x=222
        func()
    
    foo()

    栗子3号:函数嵌套定义——(函数嵌套,为了保证可读性,最多三层嵌套)

    python支持函数的嵌套定义,在内嵌的函数内查找名字时,会优先查找自己局部作用域的名字

    然后由内而外一层层查找外部嵌套函数定义的作用域,没有找到,则查找全局作用域

    先运行一个函数: 

    def f2():
        input = 333
        print(input)
    input = 222  # 是全局变量,函数f2调用时先在函数内的局部空间找值,有局部变量input = 333,故打印该值。
    
    f2()
    
    输出结果:
    333

    将上述函数作为子函数,函数的嵌套定义:

    input=111
    def f1():
        def f2():
            input=333
            print(input)
        input=222
    
        f2()
    
    f1()  # f1嵌套子函数f2,子函数f2即为f1的函数体,所以函数调用执行过程同上
    
    输出结果:
    333
    input=111
    def f1():
        def f2():
            # input=333 #将局部变量注释,则f2执行时,在函数的局部空间内找不到值,会在其上一层的函数空间找,存在全局变量 input=222,执行f2,输出结果 222
            print(input)
        input=222
    
        f2()
    
    f1()
    
    输出结果:
    222
    input=111
    def f1():
        def f2():
            # input=333
            print(input)
        # input=222  #若将f2的局部空间和最接近的全局空间的变量都去除,则函数f2执行时,会继续往下一个全局空间找值,存在input=111,则输出结果 111
    
        f2()
    
    f1() 
    
    输出结果:
    111

    栗子4号:先定义,后使用

    x=111
    def func():
        print(x)
        x=222
    
    func()
    
    # UnboundLocalError: local variable 'x' referenced before assignment 
    #    即 变量x在使用前并未定义

    修改一下:

    x=111
    def func():
        x = 222
        print(x)
    
    func()
    
    输出结果:
    222

    二、作用域

     作用域,即 作用范围。

    1. 全局作用域与局部作用域

     全局作用域:位于全局名称空间、内建名称空间中的名字属于全局范围
    1、全局存活(除非被删除,否则在整个文件执行过程中存活)
    2、全局有效: 被所有函数共享(在任意位置都可以使用)

    x=111 #全局变量,全局有效
    
    def foo():
        print(x,id(x))
    
    def bar():
        print(x,id(x))
    
    foo()
    bar()
    
    print(x,id(x))
    
    输出结果:
    111 140730714743904
    111 140730714743904
    111 140730714743904

    局部作用域: 位于局部名称空间中的名字属于局部范围
    1、临时存活:即在函数调用时临时生成,函数调用结束后就释放
    2、局部有效: 只能在函数内使用

    x=100 #全局作用域的名字x
    def foo():
        x=300 #局部作用域的名字x
        print(x) #在局部找x
    foo()#结果为300

    2. 作用域与名字查找的优先级

     (可先参照第一部分内容中的——6、名字查找的优先级)

    先确定哪里有‘调用’操作,以此为起点,先不进行函数调用执行,而是以此为分水岭,开始名字查找——

    确定当前位置,从当前位置开始查找,若无,则继续向上一层一层地往上查找。

    总结如下:

    (1)在局部作用域查找名字时,起始位置是局部作用域,所以先查找局部名称空间,没有找到,再去全局作用域查找:先查找全局名称空间,没有找到,再查找内置名称空间,最后都没有找到就会抛出异常

    (2)在全局作用域查找名字时,起始位置便是全局作用域,所以先查找全局名称空间,没有找到,再查找内置名称空间,最后都没有找到就会抛出异常

    (3)提示:可以调用内建函数locals()和globals()来分别查看局部作用域全局作用域的名字,查看的结果都是字典格式。

    在全局作用域查看到的locals()的结果等于globals()

    (4)python支持函数的嵌套定义,在内嵌的函数内查找名字时,会优先查找自己局部作用域的名字,然后由内而外一层层查找外部嵌套函数定义的作用域,没有找到,则查找全局作用域 (栗子4号)

    (5)在函数内,无论嵌套多少层,都可以查看到全局作用域的名字,若要在函数内修改全局名称空间中名字的值,当值为不可变类型时,则需要用到global关键字(栗子2号)

    (6)对于嵌套多层的函数,使用nonlocal关键字可以将名字声明为来自外部嵌套函数定义的作用域(非全局)(栗子5号)

    (7)当实参的值为可变类型时,函数体内对该值的修改将直接反应到原值(栗子3号)

    栗子1号:

    x=111
    
    def func():
        x=222
    
    func()
    print(x) # 处于全局空间,所以先从全局空间找,存在x=111,输出结果 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) #l为可变类型,只是增减容器内的值,则没有必要声明为global
    
    func()
    print(l)
    
    输出结果:
    [111, 222, 333]

     如果要将可变类型改为不可变类型,则要声明global

    l=[111,222]
    def func():
        global l
    l=123 #l为可变类型,要修改它的值,则一定要声明为global,才可修改 func() print(l) 输出结果: [111, 222, 333]

    栗子4号:函数嵌套定义

    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

    栗子5号:nonlocal(了解): 修改函数外层函数包含的名字对应的值(不可变类型)
                    对于嵌套多层的函数,使用nonlocal关键字可以将名字声明为来自外部嵌套函数定义的作用域(非全局)

                    nonlocal x会从当前函数的外层函数开始一层层去查找名字x,若是一直到最外层函数都找不到,则会抛出异常。

    x=0
    def f1():
        x=11
        def f2():
            x=22
        f2()
        print('f1内的x:',x)
    
    f1()  #执行函数f1,先在函数体内的局部空间找值,存在x=11,则输出;内置函数f2里的x=22,只对f2有用。
    
    输出结果:
    f1内的x: 11
    x=0
    def f1():
        x=11
        def f2():
            nonlocal x   #声明 内置函数f2里的x=22,不是内置函数f2里的局部变量,则归属于外层函数f2,x=22会覆盖掉x=11,作为最后的结果输出。
            x=22
        f2()       #调用f2(),修改f1作用域中名字x的值
        print('f1内的x:',x)  #在f1作用域查看x
    
    f1()
    
    输出结果:
    f1内的x: 22

     

    参考:

    https://zhuanlan.zhihu.com/p/108924801

    https://www.cnblogs.com/linhaifeng/articles/7532497.html

     

     

  • 相关阅读:
    [BZOJ5339] [TJOI2018]教科书般的亵渎
    拉格朗日插值法
    [LOJ#2340] [WC2018] 州区划分
    [洛谷4609] [FJOI2016]建筑师
    [CF960G] Bandit Blues
    [BZOJ2115] [WC2011] Xor
    [Codeforces 809E] Surprise me!
    无需AutoCAD,用C#生成DWG文件
    扩展方法的几点总结
    RedirectStandardOutput
  • 原文地址:https://www.cnblogs.com/bigorangecc/p/12522462.html
Copyright © 2011-2022 走看看