Python 3 script using relative imports on standard input gives error: No module named '__main__.XXX'; '__main__' is not a package

Python 3 script using relative imports on standard input gives error: No module named '__main__.XXX'; '__main__' is not a package

Python 3 在标准输入上使用相对导入的脚本给出错误:没有名为 'main.XXX' 的模块; “main”不是包

如果我有一个 Python 3 脚本,它使用

形式的相对导入语句
from .subscript2 import subfunc2

还有一些其他脚本将上述脚本作为模块导入,它可以正常工作。但是,当我在 Python 解释器的标准输入上执行包含上述行的脚本时,我收到以下形式的错误:

ModuleNotFoundError: No module named '__main__.subscript2'; '__main__' is not a package

我需要使用 stdin 的原因是因为我需要使用 Elpy (https://elpy.readthedocs.io/en/latest/),它允许我评估该脚本(使用 C-c C-c 键绑定)然后添加额外的 Python 在该脚本中调用函数的代码,类似于 pdb 'interact' 命令。

下面是演示正在发生的事情的独立脚本:

#!/bin/bash

rm -rf /tmp/topdir
mkdir /tmp/topdir
mkdir /tmp/topdir/subdir

unset PYTHONPATH

cat > /tmp/topdir/topscript.py <<'EOF'
from subdir.subscript1 import subfunc1
EOF

touch /tmp/topdir/subdir/__init__.py

cat > /tmp/topdir/subdir/subscript1.py <<'EOF'
from .subscript2 import subfunc2

def subfunc1():
    subfunc2()

print('subscript1 loaded successfully')
EOF

cat > /tmp/topdir/subdir/subscript2.py <<'EOF'
import os
def subfunc2():
    print('subfunc2 called. Current working directory: {}'.format(os.getcwd()))

print('subscript2 loaded successfully')
subfunc2()
EOF

echo
echo ////////////////////////////////////////////////////////////////////////////////
echo Python version:
echo ////////////////////////////////////////////////////////////////////////////////
python --version

cd /tmp
echo
echo ////////////////////////////////////////////////////////////////////////////////
echo Direct execution of topscript.py from inside $(pwd)
echo ////////////////////////////////////////////////////////////////////////////////
python /tmp/topdir/topscript.py

cd /tmp/topdir
echo
echo ////////////////////////////////////////////////////////////////////////////////
echo Using stdin on topscript.py from inside $(pwd):
echo ////////////////////////////////////////////////////////////////////////////////
python < topscript.py

cd /tmp/topdir/subdir
echo
echo ////////////////////////////////////////////////////////////////////////////////
echo Using stdin on subscript1.py from inside $(pwd):
echo ////////////////////////////////////////////////////////////////////////////////
python < subscript1.py

cd /tmp/topdir/subdir
echo
echo ////////////////////////////////////////////////////////////////////////////////
echo Using -m subscript1 from inside $(pwd):
echo ////////////////////////////////////////////////////////////////////////////////
python -m subscript1

cd /tmp/topdir/subdir
echo
echo ////////////////////////////////////////////////////////////////////////////////
echo Using stdin on subscript2.py from inside $(pwd):
echo ////////////////////////////////////////////////////////////////////////////////
python < subscript2.py

# This one was added in response to 
cd /tmp/topdir
echo
echo ////////////////////////////////////////////////////////////////////////////////
echo Using -m on subdir.subscript1 from inside $(pwd):
echo ////////////////////////////////////////////////////////////////////////////////
python -m subdir.subscript1

# This one is a variation of  using symlinks:
cd /tmp/topdir/subdir
echo
echo ////////////////////////////////////////////////////////////////////////////////
echo "Using -m on selfdir.subscript1 from inside $(pwd) (where selfdir is a symlink):"
echo ////////////////////////////////////////////////////////////////////////////////
ln -s ../subdir selfdir
python -m selfdir.subscript1

这是上面的输出:

////////////////////////////////////////////////////////////////////////////////
Python version:
////////////////////////////////////////////////////////////////////////////////
Python 3.7.3

////////////////////////////////////////////////////////////////////////////////
Direct execution of topscript.py from inside /tmp
////////////////////////////////////////////////////////////////////////////////
subscript2 loaded successfully
subfunc2 called. Current working directory: /tmp
subscript1 loaded successfully

////////////////////////////////////////////////////////////////////////////////
Using stdin on topscript.py from inside /tmp/topdir:
////////////////////////////////////////////////////////////////////////////////
subscript2 loaded successfully
subfunc2 called. Current working directory: /tmp/topdir
subscript1 loaded successfully

////////////////////////////////////////////////////////////////////////////////
Using stdin on subscript1.py from inside /tmp/topdir/subdir:
////////////////////////////////////////////////////////////////////////////////
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named '__main__.subscript2'; '__main__' is not a package

////////////////////////////////////////////////////////////////////////////////
Using -m subscript1 from inside /tmp/topdir/subdir:
////////////////////////////////////////////////////////////////////////////////
Traceback (most recent call last):
  File "/home/drunkard/conda/Ubuntu.18.04.miniconda3/envs/envpython3/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/home/drunkard/conda/Ubuntu.18.04.miniconda3/envs/envpython3/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/tmp/topdir/subdir/subscript1.py", line 1, in <module>
    from .subscript2 import subfunc2
ImportError: attempted relative import with no known parent package

////////////////////////////////////////////////////////////////////////////////
Using stdin on subscript2.py from inside /tmp/topdir/subdir:
////////////////////////////////////////////////////////////////////////////////
subscript2 loaded successfully
subfunc2 called. Current working directory: /tmp/topdir/subdir

////////////////////////////////////////////////////////////////////////////////
Using -m on subdir.subscript1 from inside /tmp/topdir:
////////////////////////////////////////////////////////////////////////////////
subscript2 loaded successfully
subfunc2 called. Current working directory: /tmp/topdir
subscript1 loaded successfully

////////////////////////////////////////////////////////////////////////////////
Using -m on selfdir.subscript1 from inside /tmp/topdir/subdir (where selfdir is a symlink):
////////////////////////////////////////////////////////////////////////////////
subscript2 loaded successfully
subfunc2 called. Current working directory: /tmp/topdir/subdir
subscript1 loaded successfully

我尝试了各种方法,但无法找到解决方案。我能做的最好的就是删除最初的“。”像这样:

from subscript2 import subfunc2

但它破坏了正常执行:

////////////////////////////////////////////////////////////////////////////////
Python version:
////////////////////////////////////////////////////////////////////////////////
Python 3.7.3

////////////////////////////////////////////////////////////////////////////////
Direct execution of topscript.py from inside /tmp
////////////////////////////////////////////////////////////////////////////////
Traceback (most recent call last):
  File "/tmp/topdir/topscript.py", line 1, in <module>
    from subdir.subscript1 import subfunc1
  File "/tmp/topdir/subdir/subscript1.py", line 1, in <module>
    from subscript2 import subfunc2
ModuleNotFoundError: No module named 'subscript2'

////////////////////////////////////////////////////////////////////////////////
Using stdin on topscript.py from inside /tmp/topdir:
////////////////////////////////////////////////////////////////////////////////
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/topdir/subdir/subscript1.py", line 1, in <module>
    from subscript2 import subfunc2
ModuleNotFoundError: No module named 'subscript2'

////////////////////////////////////////////////////////////////////////////////
Using stdin on subscript1.py from inside /tmp/topdir/subdir:
////////////////////////////////////////////////////////////////////////////////
subscript2 loaded successfully
subfunc2 called. Current working directory: /tmp/topdir/subdir
subscript1 loaded successfully

////////////////////////////////////////////////////////////////////////////////
Using -m subscript1 from inside /tmp/topdir/subdir:
////////////////////////////////////////////////////////////////////////////////
subscript2 loaded successfully
subfunc2 called. Current working directory: /tmp/topdir/subdir
subscript1 loaded successfully

////////////////////////////////////////////////////////////////////////////////
Using stdin on subscript2.py from inside /tmp/topdir/subdir:
////////////////////////////////////////////////////////////////////////////////
subscript2 loaded successfully
subfunc2 called. Current working directory: /tmp/topdir/subdir

////////////////////////////////////////////////////////////////////////////////
Using -m on subdir.subscript1 from inside /tmp/topdir:
////////////////////////////////////////////////////////////////////////////////
Traceback (most recent call last):
  File "/home/drunkard/conda/Ubuntu.18.04.miniconda3/envs/envpython3/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/home/drunkard/conda/Ubuntu.18.04.miniconda3/envs/envpython3/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/tmp/topdir/subdir/subscript1.py", line 1, in <module>
    from subscript2 import subfunc2
ModuleNotFoundError: No module named 'subscript2'

////////////////////////////////////////////////////////////////////////////////
Using -m on selfdir.subscript1 from inside /tmp/topdir/subdir (where selfdir is a symlink):
////////////////////////////////////////////////////////////////////////////////
subscript2 loaded successfully
subfunc2 called. Current working directory: /tmp/topdir/subdir
subscript1 loaded successfully

为了完成上述工作,我必须在环境中将 PYTHONPATH 设置为冒号分隔的目录列表,其中包含我使用的所有脚本。对于我自己的脚本,这会起作用,但只要我想通过标准输入将其他已发布的脚本读入 python,它就会再次中断,除非我通过这些脚本并删除初始的“。”暂时(这是一个麻烦和容易出错的地方)。

那么,有没有办法让它适用于所有这些情况?我确实可以选择更改我正在使用的工具(将脚本提供给 python 的标准输入的工具)以向该标准输入流添加额外的代码,before实际的脚本,但如果那是解决方案,哪些语句会强制它工作?

有助于理解的有用参考 material,但未揭示解决方案:

您需要在 topdir 中使用 -m subdir.subscript1subdir文件夹是一个包,当你使用-m时你需要给你想要的模块一个完全限定的名称运行,否则它无法弄清楚它应该成为包的一部分而不是顶级模块。

试试这个:

echo
echo ////////////////////////////////////////////////////////////////////////////////
echo Using -m on subscript1.py:
echo ////////////////////////////////////////////////////////////////////////////////
cd /tmp/topdir                        # note, changed folder here
python -m subdir.subscript1           # add package name prefix here

我花了很多时间寻找聪明的替代方案来让我做我想做的事,而我能想到的所有解决方案都太复杂而无法付诸实践,所以:

我最终通过在 top-level 脚本所在的同一目录中使用符号 link 来避免这种情况。这不是很好,但比必须在工作环境中更改 PYTHONPATH 要好。因此,如果我的实用程序模块存储在我的 ~/bin/python 目录中,但我实际上正在处理一个脚本,比如说,~/some_project_dir/my_top_level.py,我创建一个符号 link 像这样:

ln -s ~/bin/python ~/some_project_dir/python

然后在 my_top_level.py 文件中,我使用导入语句,例如:

from python.file_utils import read_lines_from_file
lines = read_lines_from_file("/tmp/some_file")

因此,如果我将 ~/some_project_dir 重新定位到其他目录,则符号 link 也会随之移动。如果我曾经要移动 ~/bin/python 目录,那么我确实有更新符号 link 的苦差事,但这是一个小苦差事,并且不会发生那么多。