不恰当的 import 会导致的问题

1. 直接执行被导入模块的代码

在 Python 中,import 语句会被执行,也就是在导入某个模块的的类、函数等时候,会执行该模块,此时如果该模块中有实例化的对象或者可以执行的函数,那么就会执行。用一个工作中遇到的问题来解释:

(关于业务的描述可以忽略)在执行工装测试套时,正确填写好需要测试的工装IPC的信息后,发现实际执行的是默认的IP值是device_info.json文件中默认值,而不是当时需要测试的203.1.2.35。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WeCNl37A-1593916899751)(screenshot/import导致的问题0.png)]
在 pycharm 本地调试 main_tooling.py,发现是一个导包问题导致的:引入了一个类的实例化(业务:破解设备二层的模块)。

解决过程:

首先是复制console打印日志中的“查看update -v异常”,到Utest工程中全局查找,发现只有在破二层的地方出现,于是在这边添加断点:
在这里插入图片描述
通过debug可以回溯前面调用的地方:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LoJn7DRF-1593916899841)(screenshot/import导致的问题2.png)]
通过 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 位置的代码就没有问题。正常使用时要避免三种使用方法:

  1. from … import … (如果有循环导入的,考虑把这种形式的去掉)
  2. 直接执行的代码 (避免导入直接执行的代码)
  3. 类的继承(避免基类的模块去 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)

Reference


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建议重组代码,这样一开始就不需要递归导入(循环导入)。

以上这些解决方案不是互斥的。

Reference

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章