Python提供了3种方式来引入外部模块:import
语句、from...import...
及__import__
函数。
其中较为常见的为前面两种,而 __import__
函数与 import
语句类似,
不同点在于前者显式地将模块的名称作为字符串传递并赋值给命名空间的变量。
使用 import
时的注意事项
在使用 import
的时候注意以下几点:
- 一般情况下尽量优先使用
import a
形式,如访问B
时需要使用a.B
的形式。 - 有节制地使用
from a import B
形式,可以直接访问。 - 尽量避免使用
from a import *
,因为这会污染命名空间,并且无法清晰地表示导入了哪些对象。
为什么在使用 import
的时候要注意以上几点呢?在回答这个问题之前先来简单了解一下 Python的导入机制。
Python在初始化运行环境的时候会预先加载一批内建模块到内存中, 这些模块相关的信息被存放在 sys.modules
中。
导入 sys
模块后,在Python解释器中输入 sys.modules.items()
便可显示所有预加载模块的相关信息。
当加载一个模块的时候,解释器实际上要完成以下动作:
- 在
sys.modudles
中进行搜索看看该模块是否已经存在,如果存在,则将其导入到当前局部命名空间,加载结束。 - 如果在
sys.modudles
中找不到对应模块的名称,则为需要导入的模块创建一个字典对象,并将该对象信息插入sys.modudles
中。 - 加载前确认是否需要对模块对应的文件进行编译,如果需要则先进行编译。
- 执行动态加载时,在当前模块的命名空间中执行编译后的字节码,并将其中所有的对象放入模块对应的字典中。
使用的导入方式 from ... import *
不建议这么做,使用这种方式会将目标模块里的所有内容都导入,除了以下划线开始的变量。 使用这种方式导入模块,会导入很多原本不需要的东西,比如函数,类,造成一些不必要的麻烦。
如果编写的模块可能会被其他人使用,
可以通过在模块里定义列表 __all__
来防止对方导入不需要的模块或者不希望被其他人导入的模块。
脚本 my_utils.py
with open('my_utils.py', 'w') as fo:
fo.write('''
def check_phone(phone):
print('check_phone')
def check_id_no(id_no):
print('check_phone')
__all__ = []
''')
定义 __all__
为空列表,如果其他模块在导入 my_utils
模块时使用 from ... import *
的方式,
那么任何内容都无法正常导入:
from my_utils import *
逼迫对方导入指定的函数:
from my_utils import check_phone
即便 __all__
是空列表,只要导入模块时指定要导入的函数就可以通过编译。
如果想稍稍放开限制,允许其他人是用 from ... import *
的方式,
但是限制可以导入的内容,那么只需要在 __all__
填写允许被 *
导入的内容即可,
比如允许导入 check_phone
函数,那么就可以这样来定义。
with open('my_utils_v2.py', 'w') as fo:
fo.write('''
def check_phone(phone):
print('check_phone')
def check_id_no(id_no):
print('check_phone')
__all__ = ['check_phone']
''')
这样,即便使用了 from ... import *
的方式,
也只能导入 check_phone
这一个函数。
from my_utils_v2 import *
check_phone('')
check_phone
使用其他函数都是不被允许的。
# check_id_no('')
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[7], line 1 ----> 1 check_id_no('') NameError: name 'check_id_no' is not defined
以用户自定义的模块为例来看看sys.modudles
和当前局部命名空间发生的变化。
在 Python 的安装目录下创建一个简单的模块 testmodule.py
:
a=1
b = 'a'
print( "testing module import")
用户模块未加载之前 .sys.modules
中并不存在相关信息。
那么逬行 import testmodule
操作会发生什么情况呢?
dir()
['In', 'Out', '_', '__', '___', '__builtin__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__session__', '__spec__', '_dh', '_i', '_i1', '_i2', '_i3', '_i4', '_i5', '_i6', '_i7', '_i8', '_ih', '_ii', '_iii', '_oh', 'check_phone', 'dt', 'exit', 'fo', 'get_ipython', 'open', 'pf', 'quit']
import testmodule
testing module import
dir()#①import* testmodule之后局部命名空间发生变化
['In', 'Out', '_', '_8', '__', '___', '__builtin__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__session__', '__spec__', '_dh', '_i', '_i1', '_i10', '_i2', '_i3', '_i4', '_i5', '_i6', '_i7', '_i8', '_i9', '_ih', '_ii', '_iii', '_oh', 'check_phone', 'dt', 'exit', 'fo', 'get_ipython', 'open', 'pf', 'quit', 'testmodule']
import sys
'testmodule' in sys.modules.keys()
True
id(testmodule)
139813803649328
id(sys.modules['testmodule'])
139813803649328
dir(testmodule)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b']
sys.modules['testmodule' ].__dict__.keys()
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__file__', '__cached__', '__builtins__', 'a', 'b'])
从输出结果可以看出,对于用户定义的模块,
import
机制会创建一个新的 module
将其加入当前的局部命名空间中,与此同时,
sys.modules
也加入了该模块的相关信息。但从它们的 id
输出结果可以看出,本质上是引用同一个对象。
同时会发现 testmodule.py
所在的目录下多了一个 .pyc
的文件,该文件为解释器生成的模块相对应的字节码,
从 import
之后的输出 "testing module import"
可以看出模块同时被执行,
而 a
和 b
被写入 testmodule
所对应的字典信息中。
需要注意的是,直接使用 import
和使用 from a import B
形式这两者之间存在一定的差异,
后者直接将 B
暴露于当前局部空间,而将 a
加载到 sys.modules
集合。
import sys
dir ()
['In', 'Out', '_', '_10', '_11', '_12', '_13', '_14', '_15', '_8', '__', '___', '__builtin__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__session__', '__spec__', '_dh', '_i', '_i1', '_i10', '_i11', '_i12', '_i13', '_i14', '_i15', '_i16', '_i17', '_i2', '_i3', '_i4', '_i5', '_i6', '_i7', '_i8', '_i9', '_ih', '_ii', '_iii', '_oh', 'check_phone', 'dt', 'exit', 'fo', 'get_ipython', 'open', 'pf', 'quit', 'sys', 'testmodule']
from testmodule import a
dir()#使用from...import...之后命名空f司发生的变化
['In', 'Out', '_', '_10', '_11', '_12', '_13', '_14', '_15', '_17', '_8', '__', '___', '__builtin__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__session__', '__spec__', '_dh', '_i', '_i1', '_i10', '_i11', '_i12', '_i13', '_i14', '_i15', '_i16', '_i17', '_i18', '_i19', '_i2', '_i3', '_i4', '_i5', '_i6', '_i7', '_i8', '_i9', '_ih', '_ii', '_iii', '_oh', 'a', 'check_phone', 'dt', 'exit', 'fo', 'get_ipython', 'open', 'pf', 'quit', 'sys', 'testmodule']
sys.modules['testmodule']
<module 'testmodule' from '/home/jovyan/work/jupylab_xuzp/pt03_thinking/ch05_模块机制/testmodule.py'>
id (sys.modules['testmodule'])
139813803649328
class a:
def add():
print("add in module A")
文件 b.py
如下:
class b:
def add():
print ("math in module B")
文件 importtest.py
如下:
from .a import add
from .b import add
if __name__ == '__main__':
math()
从程序的输出 “add in module B"
可以看出实际起作用的是最近导入的 add()
,
它完全覆盖了当前命名空间之前从 a
中导入的 add()
。在项目中,
特别是大型项目中频繁地使用 from...import
的形式会增加命名空间冲突的概率从而导致出现无法预料的问题。
因此需要有节制地使用 from...import
语句。一般来说在非常明确不会造成命名冲突的前提下,
以下几种情况下可以考虑使用 from...import
语句:
当只需要导入部分属性或方法时。
模块中的这些属性和方法访问频率较高导致使用“模块名.名称”的形式进行访问过于烦琐时。
模块明确说明需要使用
from...import
形式,导入的是一个包下面的子模块, 且使用from...import
形式能够更为简单和便利,如使用from io.drivers import zip
要比使用import io.drivers.zip
更方便。
循环嵌套导入的问题
先来看下面的例子:
cl.py:
from c2 import q
def x() :
Pass
c2.py:
from c1 import x
def g():
Pass
无论运行上面哪一个文件都会抛出 ImportError
异常。这是因为在执行 c1.py
的加载过程中,
需要创建新的模块对象 c1
,然后执行 c1.py
所对应的字节码。此时遇到语句from c2 import g
,
而 c2
在 sys.modules
也不存在,故此时创建与 c2
对应的模块对象并执行 c2.py
所对应的字节码。
当遇到 c2
中的语句from c1 import x
时,由于 c1
已经存在,于是便去其对应的字典中査找 g
,
但 c1
模块对象虽然创建但初始化的过程并未完成,因此其对应的字典中并不存在 g
对象,
此时便抛出 ImportError: cannot import name g
异常。
而解决循环嵌套导入问题的一个方法是直接使用 import
语句。