百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

Python之模块和包:模块导入对命名空间的影响

off999 2024-11-26 07:19 13 浏览 0 评论

引言

前面文章中,关于Python解释器在模块导入行为背后所执行的操作,已经做了深入的介绍。本文打算在此基础上,结合实际代码案例,进行进一步的补充说明。同时,比较看似只是微小的导入方式的改变,可能会导致的难以理解的异常问题。

本文的主要内容有:

1、关于命名空间

2、模块导入对命名空间的影响


关于命名空间

有关于命名空间的介绍,前面已经很详细了,对此仍然不太熟悉的,可以翻看一下上一篇文章。这里,主要想就在Python中如何查看、操作命名空间做进一步的说明。

命名空间的作用,主要是用于进行代码中“名字”与对象之间绑定关系的存储。

所谓执行模型,更像是代码在命名空间上执行,从而实现对Python中对象模型的访问及修改。

所以,代码执行过程中,对名称的解析、对象的定位,都是基于命名空间来实现的。

在Python中我们可以通过几个内置函数查看命名空间的相关内容,前面已经简单提及,这里再回顾一下:

1、globals():查看当前全局命名空间的字典

2、locals():查看当前局部命名空间的字典

3、dir():返回当前代码所在范围的所有名字列表

可以先看下这几个内置函数的文档说明:

接下来,我们通过实际代码来看下,这几个内置函数的使用。

首先看代码执行前后全局命名空间的变化,直接看代码:

from rich.pretty import pprint
print("初始的全局命名空间:")
pprint(locals())
a = 10
b = 20


def add(n1, n2):
    return n1 + n2


print("定义了变量和函数后的全局命名空间:")
pprint(locals())

执行结果如下:

从执行结果可以看到,我们在代码中定义的变量和函数,都会把名称和对应的对象存储到全局命名空间的字典中。

需要说明的是,当我们在顶级代码块中,使用locals()函数时,返回的命名空间与globals()返回的是一样的,也就是都是该模块的全局命名空间。

感兴趣的,可以自行把代码中的globals()切换为locals()。


当locals()函数在函数体内进行调用时,则能看到真实的函数内部的局部命名空间中的名称及对象的绑定关系:

from rich.pretty import pprint

a = 10


def add(n1, n2):
    pprint(locals())
    return a + n1 + n2


pprint(globals())
add(10, 20)
c = add(5, 10)
pprint(globals())

执行结果:

从执行结果中可以看出,虽然add()函数的局部命名空间中只有n1和n2两个名称,但是执行到return a + n1 + n2时,并不会报错,且成功返回了我们期望的10 + 5 + 10 = 25的结果。

所以,当函数调用时,会创建局部命名空间。函数体中的代码会在局部命名空间上执行,如果在名称解析的过程中,如果遇到局部命名空间中不存在的名称,则会去更高层级的命名空间去查找,查找顺序遵循LEGB规则。


如果我们稍微调整一下add()函数体的代码,尝试修改全局变量a的取值:

from rich.pretty import pprint

a = 10


def add(n1, n2):
    a = 1000
    pprint(locals())
    return a + n1 + n2


pprint(globals())
add(10, 20)
add(5, 10)
pprint(globals())

执行结果:

从代码中可以看出,我在顶级代码块中定义了一个全局变量a,在函数add中尝试修改全局变量a,其实并没有成功。

因为add函数体中,a = 1000这行代码,默认是在局部命名空间中执行,所以会在局部命名空间中创建一个新的名称对象绑定关系。所以,对比前面一个程序的运行结果,局部命名空间中多了一个a局部变量的名称对象绑定。

虽然全局命名空间中也有同名的变量a,但是在函数的区局作用域中,会优先查找局部命名空间来进行名称解析。

现在陷入了一个尴尬的点,我们想要修改全局变量,却导致了在局部命名空间中新增了一个局部变量。

其实有两种做法可以实现在局部作用域(也就是函数体中),对全局变量的修改。

先来看比较粗暴的做法,既然我们的意图是修改全局变量,而全局变量的名称对象绑定是存储在全局命名空间中,那么我们直接尝试修改全局命名空间中的字典,是否就实现了对全局变量的修改呢?

可以通过如下代码来验证:

from rich.pretty import pprint

a = 10


def add(n1, n2):
    globals()['a'] = 1000
    pprint(locals())
    return a + n1 + n2


pprint(globals())
add(10, 20)
c = add(5, 10)
pprint(globals())

我们将add()函数体中,a = 1000的代码,变换为:globals()['a'] = 1000。

看下执行结果:

从执行结果看出,这种做法,确实直接修改了全局命名空间中a变量,而且没有在局部命名空间中,引入新的局部变量,从而实现了我们的目的。

我们也可以不通过定义变量,直接通过这种操作全局命名空间字典的方式,引入新的变量,感兴趣的同学,可以自行尝试。

之所以说,这种做法比较粗鲁,一方面是因为有点繁琐,另外一方面也不太安全,稍有不慎,会影响后续代码中的命名解析。

其实,Python中有更简单的做法,就是通过global关键字,来声明变量为全局变量,代码如下:

from rich.pretty import pprint

a = 10


def add(n1, n2):
    print(dir())
    global a
    a = 1000
    pprint(locals())
    return a + n1 + n2


pprint(globals())
add(10, 20)
c = add(5, 10)
pprint(globals())

执行结果,跟粗鲁的做法是完全相同的,这里就不再贴出来了。

此外,dir()函数,不传参时,能直接获取当前作用域中可见的名称列表,这里就不再演示了。


模块导入对命名空间的影响

接下来看一下,模块导入会对命名空间产生怎样的影响。

首先,我们定义一个m1.py的模块,代码如下:

a = 100


def add(n1, n2):
    print("在m1的add函数中")
    print(locals())
    return a + n1 + n2


print("在m1模块中")
print(dir())

然后,我们在入口文件中导入该模块,代码如下:

from rich.pretty import pprint

a = 10


def add(n1, n2):
    print("在入口文件的add函数中")
    pprint(locals())
    return a + n1 + n2


pprint(globals())
import m1

c = add(5, 10)
d = m1.add(20, 10)
m1.a = 1000
e = m1.add(20, 10)
pprint(globals())



执行结果如下:

可以看到,通过import m1的方式导入模块,在全局命名空间中只增加了一个m1的模块名与模块对象的绑定关系。虽然我们在模块m1和入口文件中,有同名的全局变量a和add()函数,但是模块中的是通过m1.的前缀访问的。所以,可以各自访问,并不会有冲突或者覆盖的情况。


但是,如果导入方式调整为 from m1 import a, add

代码如下:

from rich.pretty import pprint

a = 10


def add(n1, n2):
    print("在入口文件的add函数中")
    pprint(locals())
    return a + n1 + n2


pprint(globals())
from m1 import a, add

d = add(20, 10)
a = 1000
e = add(20, 10)
pprint(globals())


执行结果:

从执行结果中,可以看出,通过from m1 import a, add的方式进行导入时,全局命名空间中原有的名称a和add的绑定关系发生了变化。也就是入口文件中定义的变量a和函数add()已经被覆盖。

同时会发现,不同于通过m1.a = 1000进行模块m1中全局变量的修改,这里m1中的变量a直接被加入到了入口文件的全局命名空间,所以,a = 1000,并没有影响函数add()的计算结果,因为没有真正修改add()函数中引用的m1.a变量的取值。这点,是需要格外注意的。


总结

本文首先介绍了命名空间相关的补充内容,然后演示了模块导入对命名空间的影响。需要注意的有这几点:

1、当在顶级代码块或者全局作用域中,globals()和locals()获取的命名空间字典都是相同的,都是该模块的全局命名空间。

2、在函数体内或者局部作用域中,globals()是全局命名空间,locals()则是真正的局部命名空间,只有函数的形参及函数体内定义的变量。

3、要在局部作用域中修改全局变量,可以通过globals()['变量名'] 的方式进行修改,也可以通过global关键字的方式进行修改,推进使用后一种方法。

4、import 模块名的方式,只会在全局命名空间中添加模块名与模块对象的名称绑定关系,所以,不会导致命名冲突或者相互覆盖的情况。

5、from 模块名 import xxx的导入方式,会导致模块中具体的变量名、函数名等添加到全局命名空间中,如果已经存在相同的名称,则会覆盖。最终的结果是,最后一次导入的真正生效,之前的都会被覆盖掉。此外,当模块中的函数需要访问模块中的全局变量时,此种导入方式,会导致无法修改模块全局变量的情况,需要特别注意!


感谢您的拨冗阅读,如果对您学习Python有所帮助,欢迎点赞、关注。

相关推荐

python入门到脱坑经典案例—清空列表

在Python中,清空列表是一个基础但重要的操作。clear()方法是最直接的方式,但还有其他方法也可以实现相同效果。以下是详细说明:1.使用clear()方法(Python3.3+推荐)...

python中元组,列表,字典,集合删除项目方式的归纳

九三,君子终日乾乾,夕惕若,厉无咎。在使用python过程中会经常遇到这四种集合数据类型,今天就对这四种集合数据类型中删除项目的操作做个总结性的归纳。列表(List)是一种有序和可更改的集合。允许重复...

Linux 下海量文件删除方法效率对比,最慢的竟然是 rm

Linux下海量文件删除方法效率对比,本次参赛选手一共6位,分别是:rm、find、findwithdelete、rsync、Python、Perl.首先建立50万个文件$testfor...

数据结构与算法——链式存储(链表)的插入及删除,

持续分享嵌入式技术,操作系统,算法,c语言/python等,欢迎小友关注支持上篇文章我们讲述了链表的基本概念及一些查找遍历的方法,本篇我们主要将一下链表的插入删除操作,以及采用堆栈方式如何创建链表。链...

Python自动化:openpyxl写入数据,插入删除行列等基础操作

importopenpyxlwb=openpyxl.load_workbook("example1.xlsx")sh=wb['Sheet1']写入数据#...

在Linux下软件的安装与卸载(linux里的程序的安装与卸载命令)

通过apt安装/协助软件apt是AdvancedPackagingTool,是Linux下的一款安装包管理工具可以在终端中方便的安装/卸载/更新软件包命令使用格式:安装软件:sudoapt...

Python 批量卸载关联包 pip-autoremove

pip工具在安装扩展包的时候会自动安装依赖的关联包,但是卸载时只删除单个包,无法卸载关联的包。pip-autoremove就是为了解决卸载关联包的问题。安装方法通过下面的命令安装:pipinsta...

用Python在Word文档中插入和删除文本框

在当今自动化办公需求日益增长的背景下,通过编程手段动态管理Word文档中的文本框元素已成为提升工作效率的关键技术路径。文本框作为文档排版中灵活的内容容器,既能承载多模态信息(如文字、图像),又可实现独...

Python 从列表中删除值的多种实用方法详解

#Python从列表中删除值的多种实用方法详解在Python编程中,列表(List)是一种常用的数据结构,具有动态可变的特性。当我们需要从列表中删除元素时,根据不同的场景(如按值删除、按索引删除、...

Python 中的前缀删除操作全指南(python删除前导0)

1.字符串前缀删除1.1使用内置方法Python提供了几种内置方法来处理字符串前缀的删除:#1.使用removeprefix()方法(Python3.9+)text="...

每天学点Python知识:如何删除空白

在Python中,删除空白可以分为几种不同的情况,常见的是针对字符串或列表中空白字符的处理。一、删除字符串中的空白1.删除字符串两端的空白(空格、\t、\n等)使用.strip()方法:s...

Linux系统自带Python2&yum的卸载及重装

写在前面事情的起因是我昨天在测试Linux安装Python3的shell脚本时,需要卸载Python3重新安装一遍。但是通过如下命令卸载python3时,少写了个3,不小心将系统自带的python2也...

如何使用Python将多个excel文件数据快速汇总?

在数据分析和处理的过程中,Excel文件是我们经常会遇到的数据格式之一。本文将通过一个具体的示例,展示如何使用Python和Pandas库来读取、合并和处理多个Excel文件的数据,并最终生成一个包含...

【第三弹】用Python实现Excel的vlookup功能

今天继续用pandas实现Excel的vlookup功能,假设我们的2个表长成这样:我们希望把Sheet2的部门匹在Sheet1的最后一列。话不多说,先上代码:importpandasaspd...

python中pandas读取excel单列及连续多列数据

案例:想获取test.xls中C列、H列以后(当H列后列数未知时)的所有数据。importpandasaspdfile_name=r'D:\test.xls'#表格绝对...

取消回复欢迎 发表评论: