Python最好减少使用 from...import,这里告诉你为什么
在 Python 中我们可以通过三种方式来引入外部的模块:
import
from...import
__import__
其中 __import__
比较少使用,而它本身和 import
比较相似,区别在于前者显示的将模块用字符串的形式传递到命名空间。
1 | __import__(name[, globals[, locals[, fromlist[, level]]]]) |
而在使用 import
时要注意以下几点:
- 一般情况下尽量优先使用
import a
形式,如访问B
时需要使用a.B
的形式。 - 有节制地使用
from a import B
形式,可以直接访问B
。 - 尽量避免使用
from a import *
,因为这会污染命名空间,并且无法清晰地表示导入了哪些对象。
那么为什么要注意 i 这些呢?
这就不得不说说 Python 中的 import
机制。Python 在初始化运行环境的时候会预先加载一批内建模块到内存中,这些模块相关的信息被存放在 sys.modules
中。导入 sys
模块后在 Python 解释器中输入 sys.modules.items()
便可显示所有预加载模块的相关信息。当加载一个模块的时候,解释器实际上要完成以下动作:
- 在
sys.modules
中进行搜索看看该模块是否已经存在,如果存在,则将其导入到当前局部命名空间,加载结束。 - 如果在
sys.modules
中找不到对应模块的名称,则为需要导入的模块创建一个字典对象,并将该对象信息插入sys.modules
中。 - 加载前确认是否需要对模块对应的文件进行编译,如果需要则先进行编译。
- 执行动态加载,在当前模块的命名空间中执行编译后的字节码,并将其中所有的对象放入模块对应的字典中。
从输出结果可以看出,对于用户定义的模块,import
机制会创建一个新的 module 将其加入当前的局部命名空间中,与此同时,sys.modules
也加入了该模块的相关信息。
但从它们的 id 输出结果可以看出,本质上是引用同一个对象。
需要注意的是,直接使用 import
和使用 from a import B
形式这两者之间存在一定的差异,后者直接将 B
暴露于当前局部空间,而将 a
加载到 sys.modules
集合。
那么 from ... import
有什么不好的地方呢?
命名空间冲突
假设我们现在有两个模块 a
和 b
,内容如下:
1
2
3
# a.py
def C():
print("This is a's C.")
1 | # a.py |
1 | # b.py |
我们在某一个文件中要同时导入这两个模块
1 | from a import C |
运行后会得到 This is b's C.
, 从结果中可以看到后导入的包中的 add()
覆盖了先导入的。特别是在一个较大的项目中如果频繁的使用 from ... import ...
就会大概率的出现命名空间冲突的问题。 所以一般最好不使用该方式,若在非常明确不会导致冲突的前提在,以下情况可以考虑:
- 只导入少数方法或属性时;
- 使用
模块名.名称
的方式过于繁琐的时候; - 模块文档明确要求要使用该方法, 如:
from urllib import request
。
循环嵌套导入问题
现在有两个文件 c1
和 c2
, 内容如下:
1
2
3
4
5
# c1.py
from c2 import y
def x():
pass
1 | # c1.py |
1 | # c2.py |
运行结果:
这时无论运行哪一个都会抛出 ImportError
异常。
这是因为在执行 c1.py
的加载过程中,需要创建新的模块对象 c1
然后执行 c1.py
所对应的字节码。在这过程中先遇到语句from c2 import y
。
而 c2
在 sys.modules
也不存在,故此时会去创建与 c2
对应的模块对象并执行 c2.py
所对应的字节码。
而在创建 c2
中遇到 from c1 import x
时,由于 c1
已经存在,于是便去其对应的字典中查找 y
,但 c1
模块对象虽然创建但初始化的过程并未完成,因此其对应的字典中并不存在 y
对象,此时便抛出 ImportError: cannot import name 'y' from 'c2'
异常。而解决循环嵌套导入问题的一个方法是直接使用 import
语句。
说到这里就差不多聊完啦。