1. 直接执行被导入模块的代码
在 Python 中,import 语句会被执行,也就是在导入某个模块的的类、函数等时候,会执行该模块,此时如果该模块中有实例化的对象或者可以执行的函数,那么就会执行。用一个工作中遇到的问题来解释:
(关于业务的描述可以忽略)在执行工装测试套时,正确填写好需要测试的工装IPC的信息后,发现实际执行的是默认的IP值是device_info.json文件中默认值,而不是当时需要测试的203.1.2.35。
在 pycharm 本地调试 main_tooling.py,发现是一个导包问题导致的:引入了一个类的实例化(业务:破解设备二层的模块)。
解决过程:
首先是复制console打印日志中的“查看update -v异常”,到Utest工程中全局查找,发现只有在破二层的地方出现,于是在这边添加断点:
通过debug可以回溯前面调用的地方:
通过 git 历史提交记录发现是同事处理某个问题时引入:
通过回溯回顾,发现:
在最初执行的 main_tooling.py
中调用 liveoperator
模块中的类 :
from src.components.liveoperator import LiveOperator
在 liveoperator.py
中调用 videoaction
模块中的类 :
from src.components.videoaction import VideoActionLapi
在 videoaction.py
中调用 video_parameters
模块中的类:
from src.parameters.video_parameters import videoparameters
在 video_parameters.py
中实例化了 VideoParameters
类:
videoparameters = VideoParameters()
在实例化过程中,就出现了问题的原因所在,抛开业务来说就是在不合适的时机,实例化了我不想实例化的类,我还没做 xxx 呢,你现在就给我实例化,我不能让你这么做。
解决办法:
可以在将 main_tooling.py
中导入liveoperator 的代码放入需要执行的地方,而不是放在文件的首部,这样可以等到我做了某一业务操作后再执行导入就没有问题了,也就是一个先后问题。
2. import 循环
Python 是可以循环引用的,只要循环引用中的模块并不是在定义阶段就马上使用:
# module1.py
import module2
class ModuleDemo():
def module2_func(self):
print(module2.module2_func)
# module2.py
import module1
def module2_func():
print(module1.ModuleDemo)
由于示例中只有在函数内部使用,只要 import 阶段没有执行到相应的用到 import 位置的代码就没有问题。正常使用时要避免三种使用方法:
- from … import … (如果有循环导入的,考虑把这种形式的去掉)
- 直接执行的代码 (避免导入直接执行的代码)
- 类的继承(避免基类的模块去 import 派生类的模块)
还有其他方法:用到时再导入,而不是放在模块顶部。比如将 import 放到函数里面,可以解决问题,但治标不治本,治本的还是要重新划分模块,逻辑理顺了就不会出现循环 import 。
错误示范,出现 ImportError:
# module1.py
from module2 import module2_func
class ModuleDemo():
def module2_func(self):
print(module2_func)
# module2.py
from module1 import ModuleDemo
def module2_func():
print(ModuleDemo)
3. 如何拥有导入的模块
假设有以下模块:
foo.py:
from bar import bar_var
foo_var = 1
bar.py:
from foo import foo_var
bar_var = 2
问题在于解释器将执行以下步骤:
- python 的 main 主函数 import 导入 foo(执行 foo 模块)
- 为 foo 创建空的全局变量
- foo 被编译并开始执行
- foo 导入 bar (即 from bar import bar_var)
- 创建 bar 的空全局变量
- bar 被编译并开始执行
- 在 bar 模块中,导入foo(因为前面已经有一个名为foo的模块,所以它是空操作)
- bar.foo_var = foo.foo_var
最后一步失败了,因为 Python 尚未完成解释foo
,并且的全局符号字典foo
仍然为空。
同样的事情会发生:使用 import foo
,然后在全局代码中访问 foo.foo_var
。
foo.py:
import bar
foo_var = 1
bar.py:
import foo
bar_var = 2
print(foo.foo_var)
执行 bar.py 会出现 AttributeError: module 'foo' has no attribute 'foo_var'
。
Guido van Rossum 建议避免使用 from <module> import ...
的所有用法,并将所有代码放在函数中。全局变量和类变量的初始化应仅使用常量或内置函数。这意味着来自导入模块的所有内容都被引用为<module>.<name>
。
Jim Roskind建议在每个模块中按以下顺序执行步骤:
- 导出(不需要导入基类的全局变量,函数和类)
import
语句- 激活代码(包括从导入值初始化的全局变量)。
van Rossum 不太喜欢这种方法,因为这种导入语句会出现在一个奇怪的地方,但是也确实可行。
Matthias Urlichs建议重组代码,这样一开始就不需要递归导入(循环导入)。
以上这些解决方案不是互斥的。