一、问题描述
当我们在构建python项目时,难以避免的会遇到在一个子目录中导入另一个子目录中文件的需要。但此时会遇到以下两种报错:
ModuleNotFoundError: No module named 'XXX'
ImportError: attempted relative import with no known parent package
第一种报错,多数发生在你在一个子目录中使用项目主目录相对路径导入另一个子目录中的py文件所致。这话有点绕,不懂没关系,下面会举例。
第二种报错,多数发生在你在一个子目录中使用相对于该子目录的相对路径导入另一个子目录中的py文件所致。这话有点绕,不懂没关系,下面会举例。
举个例子,我们有以下的项目结构:
./test
├── module_one
│ └── file_one.py
└── module_two
└── file_two.py
我们想要在file_two.py文件中导入file_one.py文件中的函数。假设我们的file_one.py内容如下:
#file_one.py
def file_one_print():
print(">>>Import successfully!")
项目主目录相对路径导入的file_two.py内容如下:
#file_two.py
from module_one.file_one import file_one_print
if __name__ == "__main__":
file_one_print()
此时,我们在module_two目录下执行file_two.py文件,将会报错:
ModuleNotFoundError: No module named 'module_one'
其实,如果file_two.py文件在test目录下,这样的导入将会是正确的。但是,由于该文件在test的子目录module_two下,python的默认检索路径将会是module_two而不是test,它并不知道test是你的项目根目录,所以找不到module_one。这个时候我们可能会想,如果我们使用子目录的相对路径导入,指明module_one的相对于当前执行文件的路径,是不是可以正确导入呢?
使用子目录的相对路径导入的file_two.py内容如下:
#file_two.py
from ..module_one.file_one import file_one_print
if __name__ == "__main__":
file_one_print()
此时,我们在module_two目录下执行file_two.py文件,将会报错:
ImportError: attempted relative import with no known parent package
为什么会出现这种错误,网上给的原因很多, 但是大多数人都是分析了一通,最后不知道如何解决,换成了其他方法添加库索引路径(例如在每一个需要引用其他子目录文件的python程序中加入os.path.append())。我并不认同他们的做法,我觉得一个大型的python项目,应该有一个能够在总体上管理整个项目内部文件依赖关系的方法,而不是把这个问题细化到每一个python文件中,这样的细化并不利于项目的管理。而这种管理项目内部文件依赖的方法就是setuptools。
二、问题解决——如何python管理项目内文件的相互依赖
1.我曾经尝试的办法和遇到的坑
如果你像我一样遇到这个问题并且去网上搜索,他们大概会叫你这么做:在每一个子目录下加入__init__.py文件,让他们看起来像一个包。然后呢?然后就是依旧报错;还有的人给出了进一步的方法,他们也使用了setuptools,他们先在每一个子目录下创建__init__.py,然后通过将你的工程当做python包安装到python中,这样你就可以像导入sys包一样导入你的依赖。这种方法我并没有尝试,我认为它很容易造成包的混乱,而且当你完成你这次的项目而去开发其他项目时,该项目所造成的包的残留是不容易清理的。综上,我并不喜欢他们的方法,或有用,或没用,都不太好用。
2.我的解决办法
在查找解决办法的时候,我看到了这样一种方法,他也使用setuptools,但是不将你的项目安装到python中,而是以一种软连接的方式为你的引用提供依据,这种方法仅会在你的项目主目录生成一个包含软连接和版本信息的文件夹,具有生成文件少,清理容易的优点。
你不需要在子文件夹中创建__init__.py文件,只需要在工程主目录创建setup.py文件。对于我们的示例文件结构来说,setup.py的文件位置和内容如下:
./test
├── module_one
│ └── file_one.py
├── module_two
│ └── file_two.py
└── setup.py
from setuptools import setup
setup(
name = "test",
version = "0.0.1",
packages = ["module_one"]
)
我们将需要添加进索引路径的子文件夹加入package中,然后执行以下命令,以开发模式构建包。
python setup.py develop
此时你的工程目录下将生成一个新的文件夹,里面包含包的版本信息和软连接
./test/
├── module_one
│ └── file_one.py
├── module_two
│ └── file_two.py
├── setup.py
└── test.egg-info
├── dependency_links.txt
├── PKG-INFO
├── SOURCES.txt
└── top_level.txt
此时,module_one文件夹已经加入了库索引路径。你可以使用pip list命令查看,它像包一样被python记住了。
最后,修改file_two.py文件,以工程目录的相对路径导入文件,如下:
#file_two.py
from module_one.file_one import file_one_print
if __name__ == "__main__":
file_one_print()
进入到module_two中执行file_two.py,结果如下:
>>>Import successfully!
至此,已经解决上述问题。
3.补充
当你完成该项目后,你可能希望清理这些软连接,你可以使用pip uninstall的方式将其卸载。
文章介绍了在Python项目中处理子目录间模块导入的问题,包括ModuleNotFoundError和ImportError。作者提出使用setuptools的开发模式创建软链接来管理内部依赖,而无需在每个子目录下创建__init__.py文件。通过编写setup.py并执行pythonsetup.pydevelop,可以将模块添加到库路径,从而解决相对导入问题。
156

被折叠的 条评论
为什么被折叠?



