python高级之名称空间和作用域

发布时间 2023-12-15 21:57:11作者: Xiao0101

名称空间与作用域

image

补充知识

栈区 / 堆区

  • 栈区 : 存放的是变量名与变量值的内存地址映射关系

  • 堆区 : 存放的是值真正的位置

image

名称空间

引入:

1.什么是名称空间?
-名称:定义的名字 空间:存放名字的地方
-名称空间即存放名字与对象映射/绑定关系的地方。
-名称空间只是虚拟的概念。栈区才是真正存在的。

2。名称空间有包含关系吗?
-名称空间之间本质是没有包含关系的,是互相独立的。

3.为什么要有名称空间?
-有了名称空间之后,就可以在栈区中存放相同的名字,让所有的名称不冲突。

4.查找名称空间的优先级是:
-在局部作用域查找名字时,起始位置是局部作用域,所以先查找局部名称空间,如果没有找到,再去全局作用域查找,还没有找到就在内置名称空间中查找,都没有找到就会抛出异常。

5.查找顺序:局部名称空间>>全局名称空间>>内置名称空间

6.三种全称空间的个数:内置、全局1只有一个。局部可以多个。

名称空间的分类(三类)

内建名称空间(也叫内置名称空间)

存放的名字:存放的是python解释器内置的名字
存活周期:python解释器启动则产生,python解释器关闭则销毁。
示例:
#例如:我们常用的内置函数

>>> len
>>> <built-in function len>
>>> print
>>> <built-in function print>
>>> input
>>> <built-in function input>

全局名称空间

存放的是顶级名字(文件级别的名字)
存活周期:在执行文件执行时生效,文件运行完毕或文件执行期间被删除则失效

x = 1      #全局名称空间
def foo(): #全局名称空间
    y = 2
注意,"if"下面的定义的变量名都是全局的
if 1 > 0        
    z = 3        #全局
    if 3 > 0: 
        p = 555  #全局

局部名称空间

存放函数内定义的名字
存活周期: 在调用函数时临时生效,函数调用完毕失效
示例一:
def f()
    x = 222    #局部名称空间
    
f()   #函数调用结束失效

示例二:
def func(a, b):
    pass

下面?调用了4次func,虽然使用pass语句,但是在调用函数时,也会产生局部名称空间,且它们每调用一次,都会产生一个新的局部名称空间。

func(10, 1)
func(10, 1)
func(10, 1)
func(10, 1)

三种名称空间的之间的关系

1.三种名称空间的加载顺序:
	内置名称空间= > 全局名称空间= > 局部名称空间

2.三种名称空间中一定要有的名称空间:
	内置名称空间、全局名称空间

3.三种名称空间的销毁顺序:
	局部名称空间 =》 全局名称空间 =》 内置名称空间

4.三种名称空间的名字的查找优先级:在当前所在的位置,一层层向上查找
	如果当前在局部名称空间:
局部名称空间 ==> 全局名称空间 ==> 内置名称空间

## 三种名称空间的名字的查找优先级示例
如果当前在局部名称空间:局部名称空间 => 全局名称空间 => 内置名称空间

input = 100
def func():
    input = 10
    print(input)  # 10
func()
#ps: 取值顺序和加载顺序无关


如果当前在全局名称空间:全局名称空间 --> 内置名称空间

示范一:
input = 111 #1.在全局定义input = 111
def func(): #2.定义f1函数
    input = 222 #4.在func局部名称空间内定义input = 222 
func() #3.调用func,执行func函数体代码
print(input) #5.在当前名称空间内取值,此时取值为111
111

示范二:
x = 222 #
def func():
    print(x) # 111
x = 111  # 定义阶段print语句还没有执行,也就是说func中的名称空间还没有产生。当运行到这条代码,x与之前值222的内存地址的绑定关系解除。绑定了新的值111的内存地址。所以当调用func()时,func中的局部名称空间才产生,这个时候print在自己的名称空间中没有找到,跑到全局找到了x=111,所以上面返回111
func()

示范三(难点):名称空间的"嵌套"关系是以函数定义时为准的,与调用位置无关。
x = 111  # 1、在全局定义x = 111
def func():  # 2、定义func函数,此时已确定x取值先从func局部取值,局部未找到则向全局取值。
    print(x)  # 7、打印x,x此时从全局取值,结果为111

def foo():  # 3、定义foo函数
    x = 222  # 5、在foo的局部名称空间内定义x = 222,并不会干扰到全局的x
    func()  # 6、调用func

foo()  # 4、调用foo
111

示范四(难点):函数的嵌套定义
input = 111
def f1():
    def f2():
        print(input) # 222
    input = 222
    f2()
f1()

示范五(难点):逻辑错误

x = 111
def func():
    print(x)  # 报错(UnboundLocalError: local variable 'x' referenced before assignment)。
    x = 222
func()

名称空间总结与注意事项

三种名称空间必须要有的是 : 内置名称空间, 全局名称空间

	重要概念:名称空间的嵌套关系是在函数定义阶段(检测语法)时确定的,与函数调用的位置无关,与函数定义位置有关。函数的取值位置是个很绕的知识点,最好一步一步分析函数执行步骤,与惯性思维作斗争。
## 示例一
代码一:
x = 111  # 1、首先在全局定义x = 111
def func():  # 2、定义func,此时已确定x取值先从func局部取值,局部未找到则向全局取值。
    print(x)  # 5、x向全局取值,此时x = 222,所以最终结果为222。

x = 222  # 3、此时全局定义x = 222
func()  # 4、调用func
## 222

代码二:
x = 111  # 1、首先在全局定义x = 111
def func():  # 2、定义func,此时已确定x取值先从func局部取值,局部未找到则向全局取值。
    print(x)  # 4、x向全局取值,此时x = 111,所以最终结果为111。

func()  # 3、调用func。
x = 222  # 5、此时在全局定义x = 222,但func函数已经调用,函数内的x已经取值为111。
## 111
示例二
x = 1  # 1、在全局定义x = 1
def func():  # 2、定义func函数,此时已确定x取值先从func局部取值,局部未找到则向全局取值。
    print(x)  # 7、打印x,x此时从全局取值,结果为1

def foo():  # 3、定义foo函数
    x = 222  # 5、在foo的局部名称空间内定义x = 222,并不会干扰到全局的x
    func()  # 6、调用func

foo()  # 4、调用foo
# 1
示例三:
input = 111  # 1、在全局定义input = 111。
def f1():  # 2、定义f1函数。
    def f2():  # 4、在f1局部名称空间内定义f2。
        input = 333  # 7、在f2局部名称空间内定义input = 333。
        print(input) # 8、input先在当前所在名称空间内取值,此时input值为333。
    input = 222  # 5、在f1局部名称空间内定义。
    f2()  # 6、调用f2,执行函数体代码。

f1()  # 3、调用f1,执行f1函数体代码。
# 333
示例四
count = 1
def func():
    count = 100
    print(count)  # 直接在当前名称空间取值,count为100。
func()
# 100
示例五:
def f1():
    m=111
    return m

def f2():
    print(res)  # 此时已确定res取值先从f2局部取值,局部未找到则向全局取值。

def f3():
    print(res)  # 此时已确定res取值先从f3局部取值,局部未找到则向全局取值。

res=f1()  # f1() = m = 111
f2()
f3()
# 111
# 111
示例六:
x=111

def f1():
    print(x)  # 定义阶段已经确定先从局部取值,但是局部x是先取值再定义,所以保存
    x = 222

f1()
# UnboundLocalError: local variable 'x' referenced before assignment
示例七:
m = [111,]
def func(y):
    m.append(333)
    print(y)

func(m)
m.append(222)
print(m)

# [111, 333]
# [111, 333, 222]

作用域

什么是作用域?
-作用域就是根据名称空间的范围和特点的不同进一步做了归类。
-查看作用域:: globals( ), locals( )
-变量的生效范围,分为全局作用域和局部作用域。

全局作用域与局部作用域

全局作用域

-包含:全局名称空间、内置名称空间
-特点:全局存活,全局有效
x就属于全局名称空间,在全局名称空间和局部名称空间都能访问到。
x = 10
def f1():
    print(x)
print(x)
f1()
# 10
# 10

globals()  ## 返回包含当前范围的全局变量的字典。
x = 111
print(globals())
# {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000000002061940>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/desktop/code.py', '__cached__': None, 'x': 111}

局部作用域

-包含:局部名称空间
-特点:临时存活,局部有效
局部作用域可以引用全局作用域的变量,但不可以改变
x = 10
def f1():
    x = 20
f1()
print(x) # 10

locals()  ## 返回包含当前作用域的局部变量的字典。
x = 111
def func():
    a = 10
    b = 20
    print(locals()) # {'a': 10, 'b': 20}
func()

global与nonlocal关键字

global关键字

-使用方式:global x:声明这个x是全局的名字x(x也可以是其他变量)
-针对的是不可变类型的值
-只能在局部名称空间使用。在局部声明一个全局作用域的变量。在 global 语句中列出的名称不得在同一代码块内该 global 语句之前的位置中使用。
-global用处:如果想在局部修改全局的名字对于的值,且对应得一定要是不可变类的值,这个时候就使用global。(注意:global作用就是争对局部中修改全局中不可变类型的值)
代码一:
在全局空间定义一个变量"x",函数"foo",在函数内部修改"x"的值
x = 111
def foo():
    x = 222
   
foo()
print(x)   #111
#发现并无软用
代码二:
使用"global"关键字之后
x = 111
def foo():
    global x  # 声明下面的"x"属于全局
    x = 222
    
foo()
print(x)  #222
#发现成功了
	在 global 语句中列出的名称不得被定义为正式形参,不也得出现于 for 循环的控制目标、class 定义、函数定义、import 语句或变量标注之中。
局部名称空间不能修改全局名称空间的不可变数据类型的值,只能引用。
c = 1
def func():
    c += 1      # 不可更改,在更改时会从先局部名称空间取c的值,然后再赋值给c,造成先引用、后定义的保错
    print(c)
func()

## local variable 'count' referenced before assignment
对于可变类型,也不能使用先取值后修改再赋值给原变量名这种方式,而是使用内置方法来修改。
代码一:
c = [1,2,3]
def func():
    c += [3,4,5]  # 相当于 c = c + [3,4,5]
    print(c)
func()
## UnboundLocalError: local variable 'c' referenced before assignment

代码二:
c = [1,2,3]
def func():
    c.extend([3,4,5])
    print(c)
func()
## [1, 2, 3, 3, 4, 5]
使用global关键字,可以在局部修改一个全局变量。
c = 1
def func():
    global c #声明下面的c属于全局
    c += 1
    print(c)
func()
# 2

nonlocal关键字

-使用方法 : nonlocal x
-作用:修改该函数外层函数包含的变量名对应的值, 也是针对不可变类型(nonlocal起码在第二次嵌套函数内使用才有意义,能改变一个外层函数的非全局变量)
-注意 : 只能在局部名称空间中查找, 先从外部嵌套的函数找, 没找到再往上找, 如果都没找到则报错,不会去全局修改变量值
代码一:
不做任何处理,此函数应该是打印"f2"函数下的"x = 333"
x = 111
def f1():
    x = 222
    def f2():
        x = 333
        def f3():
            x = 444
        f3()
        print(x)  # 333
    f2()
f1()
print(x) # 111
代码二:
使用"nonlocal"关键字,nonlocal起码在第二次嵌套函数内使用才有意义,能改变一个外层函数的非全局变量
x = 111
def f1():
    x = 222
    def f2():
        x = 333
        def f3():
            nonlocal x  #将下面的"x = 444"变成了上一层的"x"的值(ps:逻辑上是x=333 被修改为x=444,)
            x = 444
        f3()
        print(x)   # 444
    f2()
f1() 
代码三:
如果外层函数都没有"x"这个值,报错
x = 111  #并不会去全局修改
def f1():
    def f2():
        def f3():
            nonlocal x
            x = 444
        f3()
        print(x)
    f2()
f1()  #报错,SyntaxError: no binding for nonlocal 'x' found
只能在函数嵌套定义的内层函数中使用,在第一层局部名称空间内使用会报错。用作函数嵌套定义中,在内层函数中声明一个外层局部名称空间的变量。
nonlocal 语句中列出的名称不得与之前存在于局部作用域中的绑定相冲突。
x = 111
def f1():
    x = 222
    def f2():
        nonlocal x
        x = 333 
    f2()
    print(x)  # 333
f1()

LEGB原则

-按照LEGB原则就近取值,取值顺序单向不可逆。
-LEGB代表名字查找顺序 :Local本地 --> Enclosed嵌套函数的外层函数内部 --> Global全局 -->Builtin内置
    
L ——Local(function); 函数内的名字空间

E —— Enclosing function locals;外部嵌套函数的名字空间(例如closure)

G—— Global(module); 函数定义所在模块(文件)的名字空间

B —— Builtin(Python); Python内置模块的名字空间

(从局部开始找时)局部名称空间 --> 全局名称空间 --> 内置名称空间

name = 'xiao'

def index():
    name = 'quan'
    age = 18
    print(name)
    print(f"这是局部的变量 :>>>> {locals()}")

index()
print(f"这是外部的变量 :>>>> {globals()}")