-
安全产品
-
-
-
技能get:关于Python模块引入,你真的hold住吗?
发布时间:
2020-07-14
来源:
作者:
访问量:
27
Python是一门优美简单、功能强大的动态语言。在刚刚接触这门语言时,常常会被其优美的格式、简洁的语法和无穷无尽的类库所震撼。在真正地将python应用到实际项目中,总会遇到一些无法避免的问题。最让人困惑不解的问题有二类,一个是编码问题,另一个则是模块引入问题。接下来,针对“模块引入”问题本文进行了细致分析。
一、问题表象
在某个python项目中,在两个不同的python模块A、B中引入了同一个模块C中的一个类D,该类中存在类变量E。简化的项目结构如下图所示。其中A模块中会设置D中的类变量E的值,模块B会使用类D中的类变量E。理论上模块A和模块B中所见的类属性E的值是一致的。然而在运行时出现了非常奇怪的现象,模块A中所见的类变量E是设置后的值,而模块B中所见的类变量E则是设置前的值。
在python程序运行的过程中,一个类实际上会在程序中的堆内存中体现,也就是说类的数据存储在一块堆内存中,其中就包含了该类变量。要出现之前同一个类变量的值不一致,则访问的是不同的类在堆中的内存。
难道同一个类可以在堆内存中存在于两块不同的内存中吗?
二、问题调试分析
带着以上疑问,分别在模块A和模块B中增加相关的调试打印信息,利用id()方法来获取各自引入的类在内存中的地址值。两个模块打印出来的信息显示出两个模块各自引入的类在内存中的地址值确实不一样。因为两个模块访问的类在堆内存中地址不一样,那么之前模块A设置类变量E的操作是作用在模块A的类D所在的堆内存,而模块B所访问的是模块B的类D所在的堆内存。
继续增加打印,观察模块A和模块B所引入的类D的名称会发现,在模块A中类D的名称为pkg1.pkg2.C.D,而在模块B中类D的名称为pkg2.C.D,这两个名称明显不同。原来在模块A中类D的引入语句是“from pkg1.pkg2.C import D”,而模块B中引入语句是“from pkg2.C import D”。之所以模块A和模块B可以有不同的引入方式,是因为之前已经将目录pkg1的路径加入sys.path。
三、问题原理解析
想弄清楚上述问题,就得弄清楚import原理。python在处理import时做了2件事情——查找模块和加载模块。
查找模块的流程如下所示:
1. 查找sys.modules是否有该模块,如果有,直接导入;
2. 查找sys.meta_path。meta_path是一个list,⾥面保存着一些finder对象,如果找到该module,则返回一个finder对象;
3. 检查⼀些隐式的finder对象,不同的python实现有不同的隐式finder,但都会有 sys.path_hooks、sys.path_importer_cache及sys.path;
4. 抛出ImportError。
加载模块的动作如下所示:
如果请求的模块已存在 sys.modules,应使用并重新加载该模块。否则加载器应是创建一个新的模块并在任何过程开始前将该新模块插入到 sys.modules 中。
为了更深入分析之前的问题,做了相关测试来验证sys.modules中保存的模块信息,看是否对于同一个类,由于引入方式不同,在sys.modules会存在两个不同模块。
项目工程如下图所示:
测试代码如下所示:
测试结果如下,可以看出针对同一个模块不同的引入方式,有可能会导致该模块被多次加载。
对于之前遇到的问题,模块A引入类模块D的方式会在sys.modules中加载pkg1.pkg2.C.D,而对于模块B,会在sys.modules中加载pkg2.C.D。而对于sys.modules来说,这是两个不同模块,也就开辟了两块堆内存空间,直接导致当模块A对类D的类变量E做出修改后,模块A和模块B中所见的类属性E的值不一致。
四、结论
通过上述问题的原因分析可知,在python程序中必须要注意模块的引入方式,对同一个模块的引用尽量要保证引入方式的一致性。这类问题有比较强的隐蔽性,因此从一开始就应该使用代码规范的角度来规避此类问题。
天地和兴,Python,模块引入
上一条:
下一条:
相关资讯

关注我们