本文介绍Python中跨文件夹调用时,产生的相对路径问题和模块搜索路径问题。
1. 相对路径问题
假定我们的文件结构如下:
myExperiments
-> test_1
-> test_1.py
-> img.jpg
-> test_2
-> test_2.py
并且test_1.py中有如下函数,即读取img.jpg文件并返回其是否为None(为None表示读取失败):
import cv2
def read_image():
img = cv2.imread("img.jpg")
return img is None # 返回False表示读取图片成功,True则为失败
我们使用下面的方法在test_2.py中调用该函数,并在PyCharm中运行得到如下结果:
from test_1.test_1 import read_image
print(read_image())
[Out]:
True
得到的结果为True,表示我们读取到的图片为None,即图片路径发生了错误。具体地,我们在运行test_2.py时,当前路径是“xxx\myExperiments\test_2”,再加上test_1.py中图片的相对路径“img.jpg",读取图片的路径为”xxx\myExperiments\test_2\img.jpg“,这显然是一个无效的地址。
我们可以在test_1.py和test_2.py中,分别加入下面的代码,来输出该文件夹所对应的路径:(也有人建议os.path.abspath(os.curdir)、os.getcwd()、sys.argv[0],但经过测试还是如下的代码更适用本问题)
import os
print(os.path.dirname(__file__))
加入上述代码后再次运行test_2.py程序,可以打印出两个.py文件的路径:
D:/CV_Project/myExperiments/test_2
D:\CV_Project\myExperiments\test_1
True
所以,解决办法也就有了,在test_1.py中利用上述代码将相对路径改为绝对路径即可:
test_1,py:
def read_image():
img = cv2.imread(os.path.dirname(__file__) + "/img.jpg")
return img is None
[Out]:
False
问题1解决!
2. 模块搜索路径问题
上述代码在PyCharm中运行得不错,但是当我们在控制台中运行时,会出现模块调用的错误:
问题产生的原因是,在模块搜索路径中没有找到test_1/test_1.py模块。我们在test_2.py中加入如下代码,并分别在PyCharm和Console中运行,得到如下结果:
test_2.py
import sys
pprint(sys.path)
from test_1.test_1 import read_image
print(read_image())
[PyCharm Out]:
['D:\\CV_Project\\myExperiments\\test_2',
'D:\\CV_Project\\myExperiments',
'E:\\Python\\PyCharm 2019.2.5\\helpers\\pycharm_display',
'E:\\Anaconda\\anaconda\\python37.zip',
'E:\\Anaconda\\anaconda\\DLLs',
'E:\\Anaconda\\anaconda\\lib',
'E:\\Anaconda\\anaconda',
'E:\\Anaconda\\anaconda\\lib\\site-packages',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\pdfparanoia-0.0.16-py3.7.egg',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\pycocotools-2.0-py3.7-win-amd64.egg',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\pyqt5_stylesheets-2.3.0-py3.7.egg',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\win32',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\win32\\lib',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\Pythonwin',
'E:\\Python\\PyCharm 2019.2.5\\helpers\\pycharm_matplotlib_backend']
False
[Console Out]:
['D:\\CV_Project\\myExperiments\\test_2',
'E:\\Anaconda\\anaconda\\python37.zip',
'E:\\Anaconda\\anaconda\\DLLs',
'E:\\Anaconda\\anaconda\\lib',
'E:\\Anaconda\\anaconda',
'E:\\Anaconda\\anaconda\\lib\\site-packages',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\pdfparanoia-0.0.16-py3.7.egg',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\pycocotools-2.0-py3.7-win-amd64.egg',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\pyqt5_stylesheets-2.3.0-py3.7.egg',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\win32',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\win32\\lib',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\Pythonwin']
Traceback (most recent call last):
File "test_2/test_2.py", line 16, in <module>
from test_1.test_1 import read_image
ModuleNotFoundError: No module named 'test_1'
这里我们输出的sys.path是指定用于模块搜索路径的字符串列表(module search path; path[0] is the script directory, else ‘’)。我们可以看到,在PyCharm中的sys.path比Console多个’D:\CV_Project\myExperiments’,而test_1/test_1.py模块正是位于该路径下。也正是因此,PyCharm中依据此路径可以成功加载test_1模块,而在Console中不行。
PyCharm这种设置是可以在 “File | Settings | Build, Execution, Deployment | Console | Python Console”中修改的:
所以,解决这种问题的最直观的方法就是在sys.path中手动添加所需路径,本问题中将test_2.py代码按如下修改:
import os, sys
sys.path.insert(0, os.path.abspath(
os.path.join(
os.path.dirname(__file__), "../"))
)
print(sys.path)
from test_1.test_1 import read_image
print(read_image())
[Console Out]:
['D:\\CV_Project\\myExperiments',
'D:\\CV_Project\\myExperiments\\test_2',
'E:\\Anaconda\\anaconda\\python37.zip',
'E:\\Anaconda\\anaconda\\DLLs',
'E:\\Anaconda\\anaconda\\lib',
'E:\\Anaconda\\anaconda',
'E:\\Anaconda\\anaconda\\lib\\site-packages',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\pdfparanoia-0.0.16-py3.7.egg',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\pycocotools-2.0-py3.7-win-amd64.egg',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\pyqt5_stylesheets-2.3.0-py3.7.egg',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\win32',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\win32\\lib',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\Pythonwin']
False
这里sys.path.insert(0, xxx)是将’D:\CV_Project\myExperiments’路径插入到sys.path最前面,当然也可以使用os.path.append(xxx)将其插入到最后面。可以看到,采用这种方法后,在Console运行时的sys.path中加入了所需路径,并成功运行。
当然,对于本场景,还有一种更简单有效的方法。“python -m 模块” 的方式是把模块直接当脚本来运行,此时自动把输入命令的目录加入到了sys.path中(而不把.py文件的路径加进去)。所以,我们保持test_2.py为如下内容,并在Console中用python -m 调用:
test_2.py
import sys
print(sys.path)
from test_1.test_1 import read_image
print(read_image())
[Console In]:
D:\CV_Project\myExperiments>python -m test_2.test_2
[Console Out]:
['D:\\CV_Project\\myExperiments',
'E:\\Anaconda\\anaconda\\python37.zip',
'E:\\Anaconda\\anaconda\\DLLs',
'E:\\Anaconda\\anaconda\\lib',
'E:\\Anaconda\\anaconda',
'E:\\Anaconda\\anaconda\\lib\\site-packages',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\pdfparanoia-0.0.16-py3.7.egg',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\pycocotools-2.0-py3.7-win-amd64.egg',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\pyqt5_stylesheets-2.3.0-py3.7.egg',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\win32',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\win32\\lib',
'E:\\Anaconda\\anaconda\\lib\\site-packages\\Pythonwin']
False
我们可以看到,这里sys.path中自动加入了’D:\CV_Project\myExperiments’(而非’D:\CV_Project\myExperiments\test_2’)。注意这个路径是输入命令的地址,当我们改变输入命令的地址时,这个也会发生改变。