技术 · 实用指南
出现 attempted relative import with no known parent package?先证明执行上下文
前导点通常不是根本谜题。先记录 Python 如何启动文件、检查 __package__ 与 __spec__,再诚实选择包模块、安装命令或独立脚本中的一种入口契约。

先用完全相同的失败命令运行,并在修改导入前打印 __name__、__package__、__spec__、sys.argv、当前目录与 sys.path[0]。文件即使位于看起来像包的目录中,也可能仍作为顶层 __main__ 执行。Python 文档说明,直接运行源文件时 __main__.__spec__ 为 None,因此前导点没有已记录的父包可依附。
持久修复不是“多试几个点”。先决定代码应是包模块、安装后的命令,还是独立脚本。从包含顶层包的目录用 python -m package.module 启动包模块;用 [project.scripts] 暴露稳定命令;或把很薄的独立启动器放在包外并绝对导入应用代码。最后重复预期命令,记录模块来源和包元数据作为退出证明。
1. 捕获真实失败启动,而不是更干净的替代命令
复制产生回溯的精确命令、IDE 运行配置、服务命令或测试调用。在失败导入前临时打印 __name__、__package__、repr(__spec__)、sys.argv、os.getcwd() 与 sys.path[0]。不要用另一个终端命令来“确认”,启动边界本身就是证据。
若使用 python package/tool.py,通常会看到 __name__ 为 __main__、__spec__ 为 None。脚本目录可能进入 sys.path,但这不会给当前模块增加父包;错误既是身份问题,也是可搜索性问题。
2. 证明包根目录与模块全名
写下预期全名,例如 acme.tools.report,并找出包含顶层 acme 包的目录。在该目录运行 python -m acme.tools.report。若找不到 acme,应修复安装或工作目录,不要在模块内部把猜测的父目录塞入 sys.path。
检查 -m 启动下的 __package__ 与 __spec__.name。它们应对应预期包和模块。若指向另一份已安装副本,应先停止并打印导入模块的 __file__,因为 editable install、重复检出或启动器环境可能选错代码。
3. 只选择一种入口契约
包内工具可保留相对导入,并从包根上下文用 -m 启动。安装后使用的应用应把启动逻辑放进可导入的 main 函数,并声明 [project.scripts] 入口;生成命令会导入该函数,而不是假装内部文件是独立程序。
真正的独立脚本应绝对导入已安装应用代码,并把启动器留在包内部之外。一个文件不应既依赖前导点的包导入,又承诺支持任意直接文件执行。
4. 拒绝只会移动事故的修复
增加 __init__.py 可以定义常规包,但不会自动改变 python path/to/file.py 初始化 __main__ 的方式。把相对导入改成绝对名称也可能只是碰巧导入另一份检出。全局 PYTHONPATH 和无条件 sys.path.insert 会隐藏启动错误并让行为依赖调用方。
只有上游工具明确要求非标准启动时,带保护的兼容层才有理由存在。先记录契约并单独测试;不要复制包目录、用高权限全局安装或随意改名,除非身份证据证明边界确实在这里。
5. 删除诊断前先定义退出证明
重复精确的预期启动路径,记录 sys.executable、__package__、__spec__.name、sys.path[0] 和依赖模块的 __file__。包模块应显示预期包;安装命令应导入预期安装模块;独立脚本应按绝对且有文档的名称导入。
先跑最小导入单元测试,再从干净 shell 或服务环境运行正常命令。两条路径选择相同代码后再移除诊断。若仍失败,已捕获数据可以区分包身份、搜索路径、错误检出和框架启动问题。
