使用importlib与cx-freeze打包后出现FileNotFound问题
我完全懂你这种困惑——明明文件已经乖乖待在build目录的lib对应包路径下,程序却死活找不到,只有把文件扔到exe根目录才能正常运行。这其实是cx-freeze打包后,Python资源查找逻辑发生变化导致的,咱们一步步来搞定它:
问题根源
在源码环境下,importlib.resources.read_text会顺着Python的包结构精准找到资源文件,但cx-freeze打包后,运行环境和源码环境完全不一样:
- 打包后的Python模块被放到
lib目录,但程序运行时,importlib会优先从可执行文件所在的根目录解析包资源,而非lib下的子路径; - cx-freeze默认不会自动处理包内的非代码资源文件,哪怕你手动把文件复制到lib的对应路径,运行时的资源查找逻辑也没指向那里。
解决方案
方案1:适配打包环境的读取逻辑(简单直接)
通过判断程序是否处于打包状态,切换资源读取路径,同时调整setup.py的文件复制规则:
第一步:修改代码中的读取逻辑
import sys import os from importlib.resources import read_text def read_my_text_file(): # 判断是否是cx-freeze打包后的运行环境 if getattr(sys, 'frozen', False): # 打包后,读取exe所在根目录的文件 exe_dir = os.path.dirname(sys.executable) file_path = os.path.join(exe_dir, 'mytextfile.txt') with open(file_path, 'r', encoding='ISO-8859-1') as f: return f.read() else: # 源码环境下,正常读取包内资源 return read_text('spation.w2n.utils.data', 'mytextfile.txt', encoding='ISO-8859-1')
第二步:调整setup.py的include_files配置
把资源文件直接复制到可执行文件的根目录:
setup( # ...其他配置项 options={ "build_exe": { "include_files": [ ("spation\\w2n\\utils\\data\\mytextfile.txt", "mytextfile.txt") ] } } )
这样不管是源码环境还是打包后的exe,都能精准读取到文件。
方案2:让cx-freeze正确处理包资源(更优雅)
如果不想破坏原有的包结构,希望资源文件跟着包走,可以通过include_package_data让cx-freeze自动管理资源文件:
第一步:添加MANIFEST.in文件
在项目根目录创建MANIFEST.in,指定要包含的资源文件:
include spation/w2n/utils/data/*.txt
第二步:修改setup.py配置
from setuptools import find_packages setup( name="your_app_name", packages=find_packages(), include_package_data=True, # 自动识别MANIFEST.in里的资源文件 options={ "build_exe": { "packages": find_packages(), # 无需手动指定include_files,include_package_data会自动处理 } } )
第三步:使用更稳定的资源读取API(Python 3.9+)
改用importlib.resources.files来获取资源路径,它在打包环境下的兼容性更好:
from importlib.resources import files def read_my_text_file(): file_path = files('spation.w2n.utils.data').joinpath('mytextfile.txt') with open(file_path, 'r', encoding='ISO-8859-1') as f: return f.read()
配置完成后,cx-freeze会把资源文件和包一起打包到lib目录,importlib也能正确找到它们。
为什么你之前的配置无效?
你尝试把文件复制到lib/spation/w2n/utils/data/目录,但cx-freeze运行时,Python的资源查找路径并没有指向这个目录下的包结构,而是默认从exe根目录解析,所以即使文件存在,程序也找不到。上面的方案要么适配了这个路径变化,要么让cx-freeze正确配置了资源的查找路径。
内容的提问来源于stack exchange,提问作者Alejandro Alcalde

