【问题标题】:PYTHONPATH vs symbolic linkPYTHONPATH 与符号链接
【发布时间】:2026-02-16 14:20:09
【问题描述】:

昨天,我编辑了我的 virtualenv 的 bin/activate 脚本,以便它设置 PYTHONPATH 环境变量以包含某些外部包的开发版本。我必须这样做,因为包的setup.py 使用distutils 并且不支持develop 命令à la setuptools。就在终端中使用 Python 解释器而言,设置 PYTHONPATH 可以正常工作。

但是,刚才我在 PyCharm 中打开了项目设置,发现 PyCharm 不知道有问题的外部包——PyCharm 既没有列出外部包,也没有列出它的路径。当然,这是因为 PyCharm 不(也不能可靠地)解析或获取 bin/activate 脚本。我可以在 PyCharm 项目设置中手动添加路径,但这意味着我必须重复自己(一次在 bin/activate 中,然后再次在 PyCharm 项目设置中)。这不是 DRY,这很糟糕。

site-packages 中创建指向外部包的符号链接几乎完美。这样,至少 PyCharm 的源代码编辑器可以找到包,终端中的 Python 解释器也是如此。但是,不知何故,PyCharm 仍然没有在项目设置中列出包,我不确定是否可以这样。

那么我怎样才能以这样的方式将外部包添加到我的 virtualenv/项目中......

  1. 我不必重复自己;还有……
  2. Python 解释器和 PyCharm 都知道吗?

【问题讨论】:

  • 我认为您可能应该向 PyCharm 开发人员问这个问题,而不是 SO,他们会更好地了解 PyCharm 查找包/模块的方式和位置。

标签: python python-2.7 virtualenv pycharm pythonpath


【解决方案1】:

即使一个包没有使用setuptoolspipmonkeypatches setup.py 来强制它使用setuptools

也许您可以删除 PYTHONPATH hack 和 pip install -e /path/to/package

【讨论】:

  • 我忘记了这是针对哪个项目的,所以我不知道哪个包仍在其setup.py 中使用distutils,我无法验证这是否有效。但是这个解决方案看起来很合理,理论上应该可以使用 pip 和 setuptools 的最新版本。所以我将此标记为答案。
【解决方案2】:

一种选择是动态添加路径:

try:
    import foo
except ImportError:
    sys.path.insert(0. "/path/to/your/package/directory")
    import foo

但这不是最好的解决方案,因为该代码很可能不会进入应用程序的最终版本。另一种选择(更合适的恕我直言)是制作简单的 setup.py 文件进行打包,并使用 develop 参数或通过 pip 使用 -e 参数将其部署在 virtualenv 中:

python setup.py develop 

或:

pip install -e /path/to/your/package/directory

http://packages.python.org/distribute/setuptools.html#development-mode

【讨论】:

  • 差不多。有一些方法可以确保路径是正确的,无论它发生在哪里。
  • 这取决于文件所在的位置。这基本上从来都不是一件好事,尽管它可以在不需要迁移到另一台机器的一次性脚本中通过。
  • 我遵循了相对路径技术,假设 repo 的目录结构不变,或多或少地执行sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))) 之类的操作。此操作仅限于__main__。非常丑陋,但无论它在磁盘上的哪个位置克隆,它都会使 repo 立即运行。这更适用于 monorepo 上下文,以避免为每个克隆提供 python 环境。 python 环境的唯一要求是所有第三方包都安装在站点包中或任何有意义的地方。 ....(续)...
  • ...另一个约束是所有 python 模块都进行导入,假设 repo 中有一些公共父根文件夹,所有依赖模块都可以从中成功导入。所有子模块的导入路径可能都在增长,例如 import projRoot.packageA.packageB.moduleX as moduleX
【解决方案3】:

这是对 ndpu 答案的改进,无论真实文件在哪里都可以使用。

您可以取消引用符号链接,然后在导入本地导入之前设置 sys.path

import os.path
import sys

# Ensure this file is dereferenced if it is a symlink
if __name__ == '__main__' and os.path.islink(__file__):
    try:
        sys.path.remove(os.path.dirname(__file__))
    except ValueError:
        pass
    sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))

# local imports go here

【讨论】:

  • __file__ 变量包含 Python 当前正在导入的文件的路径!您正在尝试将当前模块目录添加到 sys.path?有什么意义?
  • __file__ 通常会指向 symlink 所在的位置,而不是文件的真实位置,因此这可以确保真实文件的目录位于路径中。调用 os.path.realpath 将确保取消引用符号链接。