将Python包合并为单脚本时遇相对导入错误求助
解决PyBreeder生成脚本中的相对导入错误(ImportError: attempted relative import with no known parent package)
问题背景
尝试用PyBreeder将Python包与二进制文件合并为单脚本,该工具原针对Python2开发,虽有适配Python3的PR可生成脚本,但运行生成的脚本时触发错误:
ImportError: attempted relative import with no known parent package
复现步骤
- 创建
trying目录,包含__init__.py和app.py文件:__init__.py内容:from .app import *app.py内容:def hello(): print("nice")
- 执行命令生成脚本:
python breeder.py trying/ > generated.py - 运行生成的脚本
python generated.py,触发上述错误。
错误原因
生成的脚本存在两个核心问题:
- 仅创建了
trying模块,但未设置__package__属性,导致相对导入from .app import *无法识别父包上下文。 - 未加载
app.py的内容到内存模块中,即使修复包属性,也找不到trying.app模块。
修复方案
修改生成的脚本,补充模块初始化逻辑:
修改后的完整脚本
#!/usr/bin/python import base64, os, sys, zlib try: import io as StringIO except ImportError: import StringIO if sys.version_info >= (3, 4): # 修正错误的版本判断条件 from importlib.util import module_from_spec as new_module else: import warnings with warnings.catch_warnings(): warnings.filterwarnings("ignore",category=DeprecationWarning) from imp import new_module __FILES = {} __builtin_open = open __os_path_exists, __os_path_getsize = os.path.exists, os.path.getsize def __comb_open(filename, *args, **kwargs): if filename in __FILES: return StringIO.StringIO(__FILES[filename]) else: return __builtin_open(filename, *args, **kwargs) def __comb_exists(filename, *args, **kwargs): if filename in __FILES: return True else: return __os_path_exists(filename, *args, **kwargs) def __comb_getsize(filename, *args, **kwargs): if filename in __FILES: return len(__FILES[filename]) else: return __os_path_getsize(filename, *args, **kwargs) if 'b64decode' in dir(base64): __b64d = base64.b64decode else: __b64d = base64.decodestring open = __comb_open os.path.exists = __comb_exists os.path.getsize = __comb_getsize sys.path[0:0] = ['.SELF/'] ############################################################################### # 先加载并初始化trying.app模块 __FILES[".SELF/trying/app.py"] = """\ def hello(): print("nice") """ app_mod = sys.modules["trying.app"] = new_module("trying.app") app_mod.__file__ = "trying/app.py" app_mod.__package__ = "trying" app_mod.open = __comb_open exec(__FILES[".SELF/trying/app.py"], app_mod.__dict__) # 再初始化trying模块并设置__package__属性 __FILES[".SELF/trying/__init__.py"] = """\ from .app import * """ m = sys.modules["trying"] = new_module("trying") m.__file__ = "trying/__init__.py" m.__package__ = "trying" # 新增属性,支持相对导入 m.open = __comb_open exec(__FILES[".SELF/trying/__init__.py"], m.__dict__) ############################################################################### def hello(): print("nice") #EOF#
关键修改点
- 修正版本判断:将原错误的
(999, 3, 4)改为(3,4),确保Python3环境下正确导入模块工具。 - 预加载子模块:先创建
trying.app模块并初始化,保证父模块导入时能找到子模块。 - 设置
__package__属性:为trying和trying.app模块添加该属性,提供相对导入的上下文。
验证
运行修改后的脚本,执行以下代码可正常输出:
from trying import hello hello() # 输出: nice
内容的提问来源于stack exchange,提问作者Mhmd Admn
相关产品推荐
相关产品推荐

