python 模块导入详解
在写python代码时经常会导入一些内置模块、第三方模块或者自己目录下写的模块。模块可以通过绝对路径或相对路径导入,既可以导入一整个包,也可以导入某个模块,还可以导入模块中的某个特定对象(类,函数或变量等)。而在一些大型的工程中,如果不通过一定的方式管理好包的导入,则各文件之间的导入非常混乱,极易出错。对于该工程包的外部调用者来说,如果调用的路径太深或者太乱,都很不友好。
下面构造一个package目录,后续所有例子都根据该结构来进行解释和说明。
项目根目录下有两个包,package_1和package_2;package_1下有两个包package_1_1和package_1_2;package_2下有一个包package_2_1。
在module_1_1中有一个类Cls1_1;在module_1_1_1中有一个类Cls1_1_1;其它所有module同理;所有的init文件当前为空。
import运行机制
当import XX的时候
- Python会首先从 sys.modules 路径中找是否有该模块。python 中所有加载到内存的模块都放在 sys.modules缓存 。如果加载了则只是将模块的名字加入到正在调用 import 的模块的 Local 名字空间中。
- 如果sys.modules缓存中没找到,则搜索python的内置模块
- 如果仍没有找到,则在sys.path列表定义的路径中找该模块,如果找到则加载到当前空间。sys.path下主要就是python设置的三方包安装的路径以及当前工作的路径。
>>>sys.path
['D:\\pycharm\\PyCharm Community Edition 2021.1.1\\plugins\\python-ce\\helpers\\pydev', 'D:\\pycharm\\PyCharm Community Edition 2021.1.1\\plugins\\python-ce\\helpers\\third_party\\thriftpy', 'D:\\pycharm\\PyCharm Community Edition 2021.1.1\\plugins\\python-ce\\helpers\\pydev', 'C:\\Users\\Administrator\\anaconda\\envs\\blog\\python38.zip', 'C:\\Users\\Administrator\\anaconda\\envs\\blog\\DLLs', 'C:\\Users\\Administrator\\anaconda\\envs\\blog\\lib', 'C:\\Users\\Administrator\\anaconda\\envs\\blog', 'C:\\Users\\Administrator\\anaconda\\envs\\blog\\lib\\site-packages', #第三方包安装路径
'D:\\pycharmprojects',
'D:/pycharmprojects'##当前工作路径]
这里有一个值得注意的地方,就是pycharm 等部分IDE会自动添加当前的项目工作目录到sys.path中,如上面这个例子就是在pycharm中运行。而比如在控制台或者服务器上运行代码时,则不会自动添加,如:
(tensorflow) D:\>cd pycharmprojects\
(tensorflow) D:\pycharmprojects>python
>>> import sys
>>> import os
>>> os.getcwd()
'D:\\pycharmprojects'
>>> import sys
>>> sys.path
['', 'C:\\Users\\Administrator\\anaconda\\envs\\tensorflow\\python36.zip', 'C:\\Users\\Administrator\\anaconda\\envs\\tensorflow\\DLLs', 'C:\\Users\\Administrator\\anaconda\\envs\\tensorflow\\lib', 'C:\\Users\\Administrator\\anaconda\\envs\\tensorflow', 'C:\\Users\\Administrator\\anaconda\\envs\\tensorflow\\lib\\site-packages',#第三方包路径
'C:\\Users\\Administrator\\anaconda\\envs\\tensorflow\\lib\\site-packages\\win32', 'C:\\Users\\Administrator\\anaconda\\envs\\tensorflow\\lib\\site-packages\\win32\\lib', 'C:\\Users\\Administrator\\anaconda\\envs\\tensorflow\\lib\\site-packages\\Pythonwin']
可以看到,在控制台中运行sys.path就没有当前的工作目录D:/pycharmprojects了;因此经常会出现在本地IDE运行代码时没有问题,推到服务器上运行时经常报错没有XX包,可能原因之一就是在服务器上没有添加当前项目根目录到sys.path中。
绝对导入
绝对导入和相对导入都有一个参考物,绝对导入的参照基准就是最顶层包的根目录;如module_2的顶层包为package_2;module_2_1虽然在package_2_1下面,但package_2_1是在package_2下面,因此module_2_1的顶层包仍为module_2_1。有了顶层包根目录的参考,则绝对导入的方式如下:
from XXX import yyy
#XXX为基于该包的根目录的路径,yyy是该路径下要导入的包、模块或者对象
举例:
例子1:在module_2.py中导入module_2_1或者module_2_1中的Cls2_1
from package_2.package_2_1 import module_2_1#module_2_1的根目录为package_2
#更进一步,导入模块中的类
from package_2.package_2_1.module_2_1 import Cls2_1
注意一般说的相对导入、绝对导入都是指包内导入。但这里的包一定是指最顶层的包,比如这里应为package_2而不是package_2_1;如果使用from package_2_1 import module_2_1
导入,也能成功,但这里就是后面要说的相对导入(隐式)了,因为module_2.py和package_2_1在同一目录下。
例子2:在module_1_2中导入module_1_1
#绝对导入,module_1_1的顶层包根目录为package_1
from package_1.package_1_1 import module_1_1
例子3:在module_1_1中导入module_1_1_1
#module_1_1_1的顶层包根目录为package_1
>>>from package_1.package_1_1 import module_1_1#该方式没有问题
#该绝对导入的路径错误
>>>from package_1_1 import module_1_1
ModuleNotFoundError: No module named 'package_1_1'
注意和前面提到的同一个问题,虽然module_1_1和module_1_1_1都在包package_1_1下,但顶层包根目录是package_1,如果要使用绝对导入,必须从package_1开始。
相对导入
相对导入的参照位置为当前文件所在的目录。分为隐式相对导入和显示相对导入。
例子1:在module_1_1_1中导入module_1_1
import module1_1 # 隐式相对导入
from module_1_1 import Cls1_1# 隐式相对导入
from . import module1_1#显示相对导入,但这个在现在的python版本中会报错ImportError: attempted relative import with no known parent package;或许已经不支持.和..这类相对导入
例子2: 在module_1_1_1中导入module_1_2
from ..package_1_2 import module_1_2#..代表上级目录Package_1,但是该方式仍旧报错ImportError: attempted relative import with no known parent package;具体原因不确定是已经不支持还是怎么,查了很多都未解决。
因此,同一个包目录下可以使用隐式相对引用,跨目录最好直接都用绝对引用。
导入规范
一般在一个文件中,先导入内置模块,再导入第三方包模块,最后导入工程内自己写的模块。
举例:
#导入内置模块
import os
import datetime
#导入第三方包
import keras
import pandas as pd
#导入自己的模块
import package_1
总结
在 Python3.x 中,绝对导入是默认的导入形式,也是 PEP 8 推荐的导入形式,它相当直观,可以很明确的知道要导入的包或模块在哪里。且包的名称变化需要在导入的时候对应的修改。之后工程内部的导入尽量都是用绝对导入。
这里有一个问题,如果包的结构非常复杂,各类引用又要绝对引用,会让包导入的过程非常臃肿且难记,
举例:在package_1的外部想从package_1中的module_1_1中导入Cls1_1对象
from package_1.package_1_1.module_1_1 import Cls1_1
这种时候可以通过组织各package中的__init__文件来使导入更为方便,在python中当调用某包时,首先会运行其下面的init文件,如果在init中将该包下面的一些对象导入出来,在外部调用时就不用一层层调用了。
举例:
在package_1下的init文件中添加:
from package_1.package_1_1.module_1_1 import Cls1_1
那么在外部某模块中,如果想调用module_1_1中导入Cls1_1对象,则只需要
from package_1 import Cls1_1
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。