You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python相对导入问题:外部调用hug API子进程报错求助

问题描述

原本在demo/mypkg目录内直接执行hug -f api.py或python startserver.py都能正常运行Hug API,但为了实现从包外部启动并支持PyInstaller打包,修改为相对导入并添加__init__.py、__main__.py后,执行python -m mypkg时出现相对导入错误:

File "...\demo\mypkg\api.py", line 2, in <module>
    from .a import greeting
ImportError: attempted relative import with no known parent package

核心原因是子进程中hug -f apipath直接把api.py作为脚本运行,此时它不在包上下文里,相对导入规则失效。


初始正常场景

项目结构

├── demo
│   ├── mypkg
│   │   ├── a.py
│   │   ├── b.py
│   │   ├── api.py
│   │   └── startserver.py
│   └── readme.md

关键代码(可正常运行版本)

  • a.py
from b import name

def greeting():
    return name()
  • api.py
import hug
from a import greeting
@hug.get('/ping')
def ping():
    return {"response": "pong"}

@hug.get('/hello')
def hello():
    return {"response": greeting()}
  • startserver.py
import subprocess

with open('testapi.log', 'w') as fd:
    subprocess.run(['hug', '-f', 'api.py'], stdout=fd , stderr=subprocess.STDOUT, bufsize=0)

在demo/mypkg目录内用虚拟环境执行hug -f api.py或python startserver.py,API可正常访问/ping和/hello接口。


修改后出现问题的场景

修改后的项目结构

├── demo
│   ├── mypkg
│   │   ├── __init__.py
│   │   ├── __main__.py
│   │   ├── a.py
│   │   ├── b.py
│   │   ├── api.py
│   │   └── startserver.py
│   └── readme.md

修改后的关键代码

  • a.py(改为相对导入)
from .b import name

def greeting():
    return name()
  • api.py(改为相对导入)
import hug
from .a import greeting
@hug.get('/ping')
def ping():
    return {"response": "pong"}

@hug.get('/hello')
def hello():
    return {"response": greeting()}
  • main.py
print('inside __main__.py')
import traceback
from .startserver import start

def main():
    try:
        start()
    except Exception:
        print(traceback.format_exc())

if __name__ == "__main__":
    print('... inside name == main ...')
    main()
  • startserver.py(调整路径获取逻辑)
import subprocess
import traceback
import os
from pathlib import Path

def start():
    
  try:
    currentpath = Path(__file__)
    print(f'Currently executing from {currentpath}')
    apipath = os.path.join(currentpath.parent, 'api.py')
    print(f'parse api path is {apipath}')
    print('inside startserver start()')
    with open('testapi.log', 'w') as fd:
        subprocess.run(['hug', '-f', apipath], stdout=fd , stderr=subprocess.STDOUT, bufsize=0)
  except Exception:
     print(traceback.format_exc())

执行python -m mypkg时触发相对导入错误。


解决方案

方案1:让子进程在包上下文运行Hug

不要直接执行api.py脚本,而是通过模块方式启动Hug,保留包上下文以支持相对导入。修改startserver.py的start函数:

import sys  # 需新增导入
import subprocess
import traceback
from pathlib import Path

def start():
    try:
        # 获取demo目录路径(mypkg的父目录)
        current_dir = Path(__file__).parent
        demo_dir = current_dir.parent
        print(f'Working dir set to: {demo_dir}')
        
        with open('testapi.log', 'w') as fd:
            # 以模块方式启动Hug,指定mypkg.api作为目标模块
            subprocess.run(
                [sys.executable, '-m', 'hug', '-f', 'mypkg.api'],
                stdout=fd,
                stderr=subprocess.STDOUT,
                bufsize=0,
                cwd=demo_dir  # 切换到demo目录作为工作目录,确保能识别mypkg包
            )
    except Exception:
        print(traceback.format_exc())

方案2:修改代码兼容两种运行方式

调整api.py和a.py的导入逻辑,让它们既能作为包模块运行,也能作为独立脚本运行:

  • api.py
import hug
# 兼容相对导入与直接运行场景
try:
    from .a import greeting
except ImportError:
    from a import greeting

@hug.get('/ping')
def ping():
    return {"response": "pong"}

@hug.get('/hello')
def hello():
    return {"response": greeting()}
  • a.py
# 兼容相对导入与直接运行场景
try:
    from .b import name
except ImportError:
    from b import name

def greeting():
    return name()

这种方式无需修改启动逻辑,同时支持包内、包外两种运行场景,也能兼容PyInstaller打包。

方案3:PyInstaller打包适配

如果选择方案1,打包时需确保PyInstaller能正确识别mypkg的包结构,可通过以下方式处理:

  1. 直接用命令行指定隐式导入:
pyinstaller --hidden-import mypkg.a --hidden-import mypkg.b mypkg/__main__.py
  1. 编写spec文件(更可控):
# mypkg.spec
block_cipher = None

a = Analysis(
    ['mypkg/__main__.py'],
    pathex=['demo'],  # 指定demo目录为搜索路径
    binaries=[],
    datas=[],
    hiddenimports=['mypkg.a', 'mypkg.b'],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name='mypkg',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,
    disable_windowed_traceback=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)

执行pyinstaller mypkg.spec完成打包。


内容的提问来源于stack exchange,提问作者Impostor Syndrome

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 00:12:05