luffy最近在用python做一个项目. 一开始只针对各个功能进行实现, 没有什么问题. 后来出现某个模块需要调用另外一个模块的方法, 这时候就出现问题了, 频繁报错:
ValueError: attempted relative import beyond top-level package
假设项目的目录结构是这样的:
Fruit文件夹下有A文件夹, A文件夹下有B, C文件夹
Fruit|
f.py
A|
__init__.py
a.py
B|
__init__.py
b.py
C|
__init.py
c.py
原因分析
有三个主要原因, 导致Python比较容易出现导入包的错误, 不像java或者c/c++, 很少出现导入包或者库的问题;
-
py文件允许单独执行, 也就是说你可以单独以 f.py, a.py或者b.py作为入口来执行, 但是此文件会作为main模块, 就是top-level package.
-
在终端执行py文件的时候, 会将终端当前的路径
cur_path
作为参数传递给python程序, 在python程序中的所有相对路径代码./ ../ .../
都是基于cur_path
的(可以理解为进程所在路径为基准). -
python导入包的时候, 会从程序入口所在py文件的当前目录下进行查找, 如果找不到, 会从python环境变量列表里面找(实际上是遍历sys.path, 你可以print(sys.path)查看这些路径)
出现错误的情况千变万化, 这里不去做分析, 只提供靠谱的做法, 如下.
方式1 固定包名结构, 固定程序入口
此种方式结构最清晰, 容易理解. 有以下几点要求.
- 确定程序结构, 在任意模块中调用其他模块, 严格遵守包的层次结构;
- 执行py文件的时候
#F/A/a.py
def a():
pass
#F/A/B/b.py
def b():
pass
在c.py中导入b模块, 严格按照包层次命名: import A.B.b
#F/A/C/c.py
import A.B.b as b
def c():
print('This is {}, I imported {}'.format(__name__, b.__name__))
#F/f.py
#F/f.py
import A.a as a
import A.B.b as b
import A.C.c as c
print('This is {}, I imported {}, {} and {}'.format(__name__, a.__name__, b.__name__, c.__name__))
c.c()
在F下执行python f.py
, 得到输出结果
This is __main__, I imported A.a, A.B.b and A.C.c
This is A.C.c, I imported A.B.b
如果要单独执行包内的某个模块, 比如c.py, 在F目录下, python -m A.C.c
即可
方式2 设置环境变量
环境变量PYTHONPATH, 如果你把你需要调用的包所在路径'home/username/F/A'
写入PYTHONPATH, 那么python解释器会将其添加的’sys.path’的开头, 也就是说会首先从home/username/F/A
查找包.
设置PYTHONPATH:
那么, 如果你要在c.py中调用b.py, 你需要把包B所在路径添加到环境变量, 有永久和临时两种方式, 这里只介绍临时.
方法一:命令窗口添加路径
export PYTHONPATH=$PYTHONPATH:/home/username/F
注意:此方法只在当前命令窗口生效,即如果打开一个新的Terminal 窗口,定位到当前目录, 打印PYTHONPATH 是没有刚才加入的路径的.
方法二:在c.py 中添加:
#F/A/C/c.py
PROJECT_PATH = "/home/ubuntu/F/"
import sys
sys.path.append(PROJECT_PATH)
print(sys.path)
import A.B.b as b
def c():
print('This is {}, I imported {}'.format(__name__, b.__name__))
c()
OK, 在任意位置运行c.py文件, 都可以得到正确的结果,
This is __main__, I imported A.B.b
方式3 采用相对路径(强烈不推荐)
这种方式就是产生混乱的源泉, 所以建议不要使用.
有时候临时需要单独调试某个模块, 又不想切换到顶级目录, 便使用这种. 比如还是在c.py中调用b模块, 代码如下:
#F/A/C/c.py
import sys
sys.path.append('../')
import B.b as b
def c():
print('This is {}, I imported {}'.format(__name__, b.__name__))
c()
将终端cd到c.py所在路径, 然后执行python c.py
, OK, 可以得到正确的结果.!!!但是, 一旦在其他目录执行c.py, 或者在顶级目录执行程序入口, 用到了c模块, 就会出错. 比如在c的上一级目录A下执行python C/c.py
Traceback (most recent call last):
File "C/c.py", line 4, in <module>
import B.b as b
ModuleNotFoundError: No module named 'B'
参考博客
python引入模块报错ValueError: attempted relative import beyond top-level package
Python 相对导入
python3文档
Python中的PYTHONPATH环境变量