【python基础之命名空间与作用域】---命名空间与作用域

发布时间 2023-12-11 18:49:55作者: Unfool
title:  【python基础之命名空间与作用域】---命名空间与作用域
date:  2023-12-11  18:44:060
updated: 2023-12-11 18:44:00
description: 
cover: 
       https://home.cnblogs.com/u/dream-ze/

image

【一】名称空间与闭包

【1】什么是名称空间

  • 名称空间即存放名字与对象映射/绑定关系的地方。
    • 对于x=3
    • Python会申请内存空间存放对象3,然后将名字x与3的绑定关系存放于名称空间中
    • del x表示清除该绑定关系。
  • 在程序执行期间最多会存在三种名称空间
内置名称(build-in names):python语音内置的名称,比如函数名(abs,char)和异常名称(BaseException、Exception)等等。
全局变量(global names):模块中定义的名称,记录了模块的变量,包括函数、类、其他导入的模块,模块级的变量和常量
局部名称(local names): 函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量(类中定义的也是)

image

【2】内建名称空间

  • 伴随python解释器的启动/关闭而产生/回收
    • 因而是第一个被加载的名称空间,用来存放一些内置的名字,比如内建函数名
print(max)
# <built-in function max>

【3】全局名称空间

  • 伴随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

【4】局部名称空间

  • 伴随函数的调用/结束而临时产生/回收,函数的形参、函数内定义的名字都会被存放于该名称空间中
def foo(x):
    y=3 #调用函数时,才会执行函数代码,名字x和y都存放于该函数的局部名称空间中

【5】名称空间的加载顺序是

  • 内置名称空间->全局名称空间->局部名称空间,
  • 而查找一个名字,必须从三个名称空间之一找到,查找顺序为:
  • 局部名称空间->全局名称空间->内置名称空间。

【二】作用域

作用域是一个python程序可以直接访问命名空间的正文区域
在python程序中,如果直接访问一个变量,会从内到外的作用域去查找,找不到就会报错,变量未定义。
变量的访问权限取决于变量在哪赋值
变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称

【1】变量作用域

  • 变量的作用域
    • Python是静态作用域,也就是说Python中,变量的作用域源于它在代码中的位置
    • 在不同的位置,可能有不同的命名空间。命名空间是变量作用域的体现形式
  • python变量作用域一般有4种:
- Local(局部变量)			L:最内层,包含局部变量,比如一个函数/方法内部。
- Enclosed(嵌套)		 E: 包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套											函数,一个函数(或类)A里又包含了一个函数B,那么对于B中的名称来说A中的作用												域就叫做nonlocal
- Global(全局)      G: 当前脚步的最外层,比如当前模块的全局变量
- Built-in(内置)    B:包含了内建的变量/关键字等,最后被搜索出来


(1)Local(局部变量)

  • Local(局部变量):暂时的存在,依赖于创建该局部作用域的函数。函数存,则局部变量存,函数亡,则局部变量亡。

  • 作用范围:当前整个函数体范围

    # 定义一个函数,函数内部就是局部作用域
    def fun():
        # 只有在函数内部的变量
        b = 2
        print(b)  # 输出2
    
    
    fun()
    # 调用函数后,发现找不到变量 b 是因为在全局作用域中找不到 变量 b
    print(b)  # 报错
    
    '''
    Traceback (most recent call last):
      File "E:\PythonProjects\def_func.py", line 16, in <module>
        print(b)  # 报错
    NameError: name 'b' is not defined
    '''
    

image

(2)Enclosed(嵌套)

  • Enclosed(嵌套):一般是在函数中嵌套函数的时候,外层函数的变量作用域。

  • 作用范围:闭包函数

    # Enclosed(嵌套)作用域
    def fun1():
        b = 2
        print("这是fun1打印的:", b)
    
        def fun2():
            # 函数 func1 里面 嵌套的 函数 func2 里面的作用域就是 嵌套作用域
            print("这是fun2打印的:", b)
        
        # 将内部函数 func2 的内存地址返回
        return fun2
    
    
    # 调用 函数 fun1 , 函数 func1 的返回值是内部函数 fuc2的函数地址
    temp = fun1()
    
    # 调用 函数 fun1 的返回值(fuc2的函数地址) , 从而执行 函数 fuc2
    temp()
    # 这是fun1打印的: 2
    # 这是fun2打印的: 2
    
    • 在这里函数fun2里面并没有定义变量b,但是它能够引用外层函数fun1定义的b变量,此时变量b的作用域就是Enclosed

image

(3)Global(全局)

  • Global(全局):一般模块文件顶层声明的变量具有全局作用域,从外部来看,模
    块的全局变量就是一个模块对象的属性,仅限于单个模块文件中。
  • 作用范围:当前模块(文件)
# Global(全局)作用域

# 定义在全局的变量,在本文件任意位置可调用该变量
a = 2


def fun1():
    print("这是fun1打印的:", a)


fun1()
print(a)

# 这是fun1打印的: 2
# 2

image

(4)Built-in(内置)

  • Built-in(内置):解释器内置的变量,比如int, str等。

  • 作用范围:所有模块(文件)

  • Python中没有块级作用域。

  • 块级作用域:代码块中的变量,比如if、while、for后面的代码

【2】LEGB规则

  • LGEB规则:按照L--->E--->G--->B的顺序查找变量。
    • 也就是,当不同命名空间具有相同变量名称的变量时,我们首先查找局部变量,如果没有查到,再向全局变量查找。

(1)基于命名空间的常见变量类型

  • 局部变量:
    • 在一个函数体的内部定义的变量
    • 作用域为函数内部
    • 查看局部变量命令:locals()
  • 全局变量
    • 在函数体外部,文件最外层定义的变量
    • 作用域为整个文件内部
    • 查看全局变量命令:globals()
  • 注意:
    • 变量访问规则:从内到外
    • 全局变量和局部变量重名时,采用就近原则

(2)案例讲解

# 定义了一个全局变量G,并把"G全局"赋值给a
a = "G全局"


def myfunc():
    # 定义了一个局部变量a,并把"E局部1"赋值给a
    a = "E局部"

    def inner():
        # 定义了一个局部变量a,并把"L局部2"赋值给a
        a = "L局部2"
        print(f"inner打印的a:{a}")

    inner()
    print(f"myfunc打印的a:{a}")


myfunc()
print(f"__main__打印的a:{a}")
# inner打印的a:L局部2
# myfunc打印的a:E局部
# __main__打印的a:G全局
  • 结果如下
    image

  • 代码结果分析流程
    image

(3)变量的使用规则

  • 创建变量时从上往下创建,搜索变量时从下往上搜索。
  • 创建变量时,下层变量会覆盖上层同名变量,但不会改变上层变量的值,除非使用gloable和nonlocal关键字声明

image

【3】变量的修改

(1)global修改全局变量

  • 一般全局变量一经定义后几乎是不用改的,也不允许在局部修改全局变量,除非使用Global关键字声明。

    # 定义一个全局变量 a
    a = 1
    
    
    def fun1():
        # 修改全局变量
        a = a + 2
        print(a)
    
    
    fun1()
    print(a)
    '''
    Traceback (most recent call last):
      File "/Users/xxx/Downloads/笔记/pythonProject/py-days-test/test.py", line 11, in <module>
        fun1()
      File "/Users/xxx/Downloads/笔记/pythonProject/py-days-test/test.py", line 7, in fun1
        a = a + 2
    UnboundLocalError: local variable 'a' referenced before assignment
    '''
    

image

  • 可以看到,当我们试图在函数fun1创建的局部作用域内改变全局变量a就会报错

    • 但如果在修改之前使用global关键字声明时,就会正常修改外部的全局变量a

      # 定义一个全局变量 a
      a = 1
      
      
      def fun1():
          global a
      
          # 修改全局变量
          a = a + 2
          print(f"函数内部修改全局变量 :>>>> {a}")
      
      
      fun1()
      print(f"调用函数后全局变量 :>>>> {a}")
      # 函数内部修改全局变量 :>>>> 3
      # 调用函数后全局变量 :>>>> 3
      

image

(2)nonlocal修改外层函数变量

  • 在函数中嵌套函数时,嵌套在里面的函数创建的作用域内一般也是不允许改变外层函数变量的值的

  • 除非是nonlocal关键字声明

  • 如下

    # 不使用nonocal声明,修改外层函数变量值
    def fun1():
        a = 1
    
        def fun2():
            a += 2
            print(a)
    
        return fun2
    
    
    temp = fun1()  # 调用fun1
    temp()  # 调用fun2
    '''
    Traceback (most recent call last):
      File "/Users/xxx/Downloads/笔记/pythonProject/py-days-test/test.py", line 13, in <module>
        temp()  # 调用fun2
      File "/Users/xxx/Downloads/笔记/pythonProject/py-days-test/test.py", line 6, in fun2
        a += 2
    UnboundLocalError: local variable 'a' referenced before assignment
    '''
    

image

可以看到,报错和在函数内不使用global修改全局变量报的错是一样的

  • 当使用nonlocal声明后再修改就不会报错了

    # 不使用nonocal声明,修改外层函数变量值
    def fun1():
        a = 1
        print(f"我是func1的变量修改前:{a}")
    
        def fun2():
            nonlocal a  # 使用nonocal声明
            a += 2
            print(f"这是内层嵌套修改局部变量后 :>>>> {a}")  # 修改后
    
        print(f"我是func1的变量修改后:{a}")
        return fun2
    
    
    temp = fun1()  # 调用fun1
    temp()  # 调用fun2
    
    # 我是func1的变量修改前:1
    # 我是func1的变量修改后:1
    # 这是内层嵌套修改局部变量后 :>>>> 3
    

image
可以看到是正常修改的