pip install -e 不允许通过包目录导入

pip install -e doesn't allow to import via package dir

问题末尾重现错误的简单脚本

这也作为错误提交到 pip's github

对于结构如下的 pkg:

.
├── setup.py
└── src
    └── my_pkg
        ├── __init__.py
        └── main.py

和这样的 setup.py:

from setuptools import setup, find_packages

setup(
    name='test-pkg',
    packages=find_packages('src'),
    package_dir={
        'my_pkg': 'src/my_pkg',
    },
)

创建一个 wheel 并安装它允许我导入“my_pkg”,但使用 pip install -e . 安装包则不行。为什么?

通过 wheel 安装允许导入“my_pkg”,而通过“-e”安装只允许导入“src”,这违背了目的,因为“src”不是包 dir

python -c "import my_pkg"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'my_pkg'

pip版本:21.3.1(最新)


总结一下:

工作:

  1. pip install .
  2. python setup.py bdist_wheel && pip install dist/*whl

不工作:

  1. pip install -e .
  2. python setup.py develop(这是 pip install -e 运行的内容,但我想确保在转换过程中没有丢失任何内容)
  3. pip install -e . 然后编辑 /lib/python3.6/site-packages/test-pkg.egg-link 并将“src”添加到路径

糟糕...

来自 setuptools docs:

然而,对于 .egg-info 格式,基本位置是包含 .egg-info 的目录,因此它是目录必须将其添加到 sys.path 才能使 egg 可导入。 (请注意,这意味着将软件包“正常”安装到 sys.path 目录足以使其成为“蛋”,如果它旁边安装了 .egg-info 文件或目录。)

我不完全确定这是否意味着我被搞砸了,但有可能。有什么办法可以解决这个问题吗?


错误重现脚本:

#!/bin/bash
set -e

mkdir -p reproduce_pip_install_e_bug/src/my_pkg
cd reproduce_pip_install_e_bug
touch src/my_pkg/__init__.py

echo "
from setuptools import setup, find_packages

setup(
    name='test-pkg',
    packages=find_packages('src'),
    package_dir={
        'my_pkg': 'src/my_pkg',
    },
)
" > setup.py

virtualenv -p python3.6 good_venv > /dev/null && . good_venv/bin/activate && pip install . -vvv &> .good_installation.log && python -c "import my_pkg"
echo this was successful, however, next command will break
virtualenv -p python3.6 bad_venv > /dev/null && . bad_venv/bin/activate && pip install -e . -vvv &> .bad_installation.log && python -c "import my_pkg"

== 解决方法 ==

PYTHONPATH 救援!!!

运行 export PYTHONPATH=<abspath_to_src> 规避了这个问题。但是,在这种情况下,sys.pathpip install .sys.path 不同——它们都有 src 的路径,但 pip install -e . 也有根目录的路径- 允许导入它,这是不应该发生的。

为了使用 so-called src-layoutsetuptools setup.py应该看起来像这样:

from setuptools import setup, find_packages

setup(
    name='test-pkg',
    packages=find_packages('src'),
    package_dir={
        '': 'src',
    },
)

package_dir 应该得到一个空字符串作为键。

它在 setuptools 的“已弃用”文档中有解释:

The keys to this dictionary are package names, and an empty package name stands for the root package. The values are directory names relative to your distribution root. In this case, when you say packages = ['foo'], you are promising that the file lib/foo/__init__.py exists.

-- https://setuptools.pypa.io/en/latest/deprecated/distutils/setupscript.html?highlight=package_dir#listing-whole-packages

...在最新版本的文档中有点像,但在某种程度上可以将它们拼凑起来: