背景
Python模块导入可能算是一个让人比较迷惑的知识点,经常出现找不到模块,让人不知所措,希望此篇文章能帮到你。
Python版本: 3.7.4
_init_.py
这份文件有三个作用:
- 1.将一个目录当成包(package)使用(Python3.3之前)
- 2.设置被导入的模块
- 3.缩短导入的路径
在Python3.3之前,如果想要将一个目录当成包使用,则该目录必须包含一份__init__文件,但在Python3.3之后,就算缺少该文件仍然能导入包,具体测试如下。
# 目录结构
├── main.py
└── module1
└── test.py
# test.py
def hello():
print('test_name:', __name__)
# main.py
import module1.test
if __name__ == '__main__':
module1.test.hello()
# 执行结果
# 3.7 正常执行
$ python3 main.py
test_name: module1.test
# 2.7 报错
/usr/bin/python main.py
Traceback (most recent call last):
File "main.py", line 7, in <module>
import module1.test
ImportError: No module named module1.test
关于设置被导入的模块,这个应该经常被提到,需要通过设置__all__变量来实现
# 目录结构
├── main.py
├── module2
│ ├── __init__.py
│ ├── test1.py
│ ├── test2.py
│ └── test3.py
# module2/__init__.py
__all__ = ['test1', 'test2']
# main.py
# 虽然这里是导入全部包,但由于__all__的限制,只能导入test1和test2
from module2 import *
if __name__ == '__main__':
test1()
test2()
关于缩短导入路径 (适用于模块名特别长的模块),这个需要借助__init__.py文件,具体测试如下:
# 目录结构
├── main.py
└── module3
├── __init__.py
└── ma1
├── __init__.py
└── mb1
├── __init__.py
└── test.py
# test.py的内容与上文一致
# module1/__init__.py
from .ma1.mb1 import test
# main.py: 路径缩短了
from module1 import test
if __name__ == '__main__':
test.hello()
# 运行结果
$ python3 main.py
test_name: module1.ma1.mb1.test
注:如果你在Python解释器多次导入同一模块,比如import module1.test,你可能会发现模块的打印信息只会执行一次,这是出于效率的考虑,每个模块在每个解释器会话中只被导入一次。
_name_
__name__ 是一个全局变量,代表模块的名字,即命名空间。
Python有一个经典的判断语句 if __name__ == __main__:,当Python文件被当做脚本直接执行时,__name__变量的值会变为__main__,从而使该判断语句成立,并执行该语句下的其他代码; 而当Python文件被当做模块/包导入时,如上文的三个例子,它的值表示的是模块名,比如module1.test,或者module1.ma1.mb1.test。
import关键字
import关键字是Python中用来导包的,常见的import语句有四种:
1.import module: 这种形式会导入module下所有文件,如果module下有很多模块,而你并没有全部用到,显然会浪费很多内存(加载代码)。
2.from module import sub_module/func:这种形式是导入module下的某个子模块,或者定义的某个函数,因为只是导入我们需要的模块,所以并不会像第一种方式很浪费内存,也推荐这种方式。
3.from module import sub_module/func as new_module:这种形式与第二种形式差不多,但是可以使用我们自定义的名称,看个人需求。
4.from module import *:这种形式也是导入module下所有文件,不同于第一种,这种不需要加上module前缀。举个例子,比如module下有个test函数,第一种方式是这样的module.test();而第四种方式是这样的test()。
sys.path
sys.path 是一个列表,它的作用是存储Python模块的搜索路径。一般情况下,该变量存储的路径有当前路径,内置模块路径等。
我们经常报模块找不到,很大程度可以通过设置sys.path变量来解决。
1.子模块执行报错
# 目录结构
import_lab
└── module4
├── __init__.py
├── utils
│ ├── __init__.py
│ └── redis_client.py
└── utils_test
├── __init__.py
└── redis_test.py
# redis_client.py
import redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, db=1)
redis_client = redis.StrictRedis(connection_pool=pool)
# redis_test.py
from module4.utils.redis_client import redis_client
def redis_test():
redis_client.set('redis_key', 'test')
if __name__ == '__main__':
redis_test()
# 执行
$ python3 module4/utils_test/redis_test.py
Traceback (most recent call last):
File "module4/utils_test/redis_test.py", line 11, in <module>
from module4.utils.redis_client import redis_client
ModuleNotFoundError: No module named 'module4'
通过上面的例子我们可以发现,如果直接执行redis_test.py(python redis_test.py),会报module4模块找不到的问题,这是因为直接执行python3 module4/utils_test/redis_test.py,sys.path会变为/xxx/import_lab/module4/utils_test,即模块搜索路径被限制该utils_test文件下,自然就找不到module4。
解决办法有两种:一是在sys.path加入我们项目根路径即可
# redis_test.py
import sys
# sys路径的添加必须定义在最前面,且注意这里替换成自己的路径
sys.path.append('/xxx/import_lab')
from module4.utils.redis_client import redis_client
def redis_test():
redis_client.set('redis_key', 'test')
if __name__ == '__main__':
redis_test()
这时有人可能就会问了,为啥我在Pycharm内直接Run redis_test.py不会报错,这是因为Pycharm默认将根路径加入到sys.path,所以模块自然能够找的到了。
第二种是加上 -m 参数,即 python3 -m module4/utils_test/redis_test.py
注:
- 除了通过设置sys.path,还可以通过设置
PYTHONPATH环境变量来修改模块的搜索路径。 - 有一种避免模块找不到的技巧,就是运行的文件(入口文件)定义在根路径下,这样运行的时候
sys.path就包含项目根路径,也就能搜索到相关模块了。
命令行参数
在Python的模块导入中,还有两个命令行参数也被多次提到,分别是 -c 和 -m。
python -c:传递一个字符串形式的Python程序,并执行
python -c "import sys; print(sys.path)"
python -m:将一个Python模块当成脚本使用,具体可以参见这篇文章。
源码
关于上文提到的源码,可在本人的Github上找到,觉得有用的话请点个赞哦!
官方文档
Python Import系统给粗心人设下的“陷阱”
构建一个模块的层级包
Python 3.x可能是史上最详解的【导入(import)】