1、需求分析
运行Python程序的时候需要获取的资源有2种: 一种是固定的资源文件,你希望启动程序的时候可以调用的,比如数据库文件和图片资源文件;另一种是用于存储历史记录的文件,你希望每次打开都会改变其中的内容,存储记录信息,而不是每次运行获取到相同的状态。
运行Python的方式有3种: 一种是直接运行py脚本,一种是以多文件模式打包成exe文件运行,一种是以单文件模式打包成exe文件运行。单文件exe运行,实际上是将资源文件解压到某个临时文件夹,然后再启动运行。
启动Python程序时的工作路径有2种: 一种是在当前目录启动Python程序,一种是在非当前目录启动程序。在当前目录启动是更简单的场景,在程序目录双击打开就是。但是在非当前目录启动程序是更通用的场景,这个问题能够解决,前者自然能够解决。
启动Python程序时的程序路径有2种: 一种是使用绝对路径,一种是使用相对论路径。
所以,排列组合,总共有2×3×2×2=24种组合情况,我希望在这24种情况内都有符合预期的表现。
2、网上的做法
如果使用PyInstaller打包Python程序,需要包含资源文件,需要在打包命令中添加 --add-data
参数,例如:
pyinstaller main.py --add-data="resource.jpg;."
但为了在py脚本和exe程序中能够兼容运行代码,网上一般给出的解决方案是这样的:
import os
import sys
resource_path = 'resource.jpg'
if hasattr(sys, '_MEIPASS'):
resource_path = os.path.join(sys._MEIPASS, resource_path)
3、优雅的做法
最近我发现有一个更优雅的解决方案:
resource_path = __file__ + '/../resource.jpg'
这样可以解决在单文件打包和直接运行情况下静态资源路径不同的问题。
然而对于存储历史记录的资源文件,如果使用单文件打包模式,在窗口关闭时会清除临时文件,会将这个记录文件也一起删除掉,这自然是不符合预期的。
如果直接写一个路径,例如:
history_path = 'history.txt'
当你在程序文件夹内启动该程序时,临时文件会创建在程序文件夹的目录内,这是符合预期的。
但是在其他工作路径启动这个程序,例如在使用自启动功能启动程序、在“运行”中启动程序时,程序的“工作路径”不是exe文件的所在文件夹,临时文件就会产生到意想不到的地方。
但我发现一种优雅的写法可以是:
history_path = sys.argv[0] + '/../history.txt'
原理就是 sys.argv[0]
是从工作目录到程序目录之间的相对路径(或绝对路径),所以以这样的相对地址查找,能够准确定位到“程序位置”。
3.1 Mac的Bug
我在Mac上运行的时候,发现OSX竟然不支持 /../
这种相对路径的表达方法:
还得在外面套一层,解算出真实路径:
resource_path = os.path.realpath(__file__ + '/../resource.jpg')
其中 realpath
表示计算真实路径,可以将符号链接消除,找到文件实际存储的位置。
也可以替换为 relpath
(相对路径)、或 abspath
(绝对路径),这三种没有太大的区别。
附、Python中的“路径”
Python中有若干和“路径”有关的参数或者方法,它们的获取方式及其返回结果对比:
获取方法 | py脚本 | 多文件exe | 单文件exe |
---|---|---|---|
__file__ | <程序目录>\main.py 或相对路径 | <程序目录>\main.py | <临时目录>\main.py |
os.getcwd() | <工作目录> | <工作目录> | <工作目录> |
sys.argv[0] | <程序目录>\main.py 或相对路径 | <程序目录>\main.exe 或相对路径 | <程序目录>\main.exe 或相对路径 |
sys.path[0] | <程序目录> | <程序目录>\base_library.zip | <临时目录>\base_library.zip |
sys.executable | <Python安装目录>\python.exe | <程序目录>\main.exe | <程序目录>\main.exe |
注:当在程序文件夹内启动程序时, <工作目录>
和 <程序目录>
是同一位置。