Conda 初始化污染环境?

Conda init polluting environment?

我在 Pycharm 中设置了一个项目,其中包含现有的 conda 环境。我的脚本在 运行 来自控制台时有效。

我希望能够从任何位置 运行 python -m path_to_my_script/script.py,但我需要激活 conda。 Conda 建议我这样做 conda init 但我担心它可能会更改某处的设置并破坏某些东西。

conda init 是做什么的?

您的谨慎可能是有道理的。 conda init 命令将 Anaconda 添加到 Linux 或 Mac 上的路径(不推荐在 Windows 上)。 Anaconda FAQ
您还需要先 运行 宁 source <path to conda>/bin/activateconda init 做准备。

通过在代码第一行的 shebang 语句中指定 python 环境,您可以 运行 在所需的 conda 环境中使用脚本。 您可以通过激活所需的环境并执行 which python

来获得该值
(base) -> conda activate py39
(py39) -> which python
/home/user/anaconda3/envs/py39/bin/python

shebang 将是:

#!/home/user/anaconda3/env/py39/bin/python

在 Mac 和 Linux 上,一旦您添加了 shebang,您就可以将文件修改为可执行文件(例如 chmod 700 myscript.py)和直接从命令行 运行 . (我不是 Windows 用户,所以 ymmv。)

(base) -> <path-to-script>/myscript.py

(现在 运行 在 shebang 虚拟环境中而不是在基础环境中。)

回答策略

conda init 命令的确切作用及其结果是 shell 特定的。我们不是试图涵盖所有案例,而是通过一个案例,并注意可以通过替换他们感兴趣的 shell 来复制此分析。


案例研究:conda init zsh

让我们把zsh看成shell。这是一个常见的 shell(macOS 10.15+ 的默认值)并且非常接近 bash。另外,我还没有配置它。

探测命令:Dry 运行

许多 Conda 命令通过 --dry-run, -d 标志包含某种形式的枯燥 运行 功能,结合冗长标志,可以看到不执行它们会做什么。对于 init 命令,仅干 运行 只会告诉我们它 修改哪些文件:

$ conda init -d zsh
no change     /Users/mfansler/miniconda3/condabin/conda
no change     /Users/mfansler/miniconda3/bin/conda
no change     /Users/mfansler/miniconda3/bin/conda-env
no change     /Users/mfansler/miniconda3/bin/activate
no change     /Users/mfansler/miniconda3/bin/deactivate
no change     /Users/mfansler/miniconda3/etc/profile.d/conda.sh
no change     /Users/mfansler/miniconda3/etc/fish/conf.d/conda.fish
no change     /Users/mfansler/miniconda3/shell/condabin/Conda.psm1
no change     /Users/mfansler/miniconda3/shell/condabin/conda-hook.ps1
no change     /Users/mfansler/miniconda3/lib/python3.7/site-packages/xontrib/conda.xsh
no change     /Users/mfansler/miniconda3/etc/profile.d/conda.csh
modified      /Users/mfansler/.zshrc

==> For changes to take effect, close and re-open your current shell. <==

这里我们可以看到它计划针对zsh的用户级资源文件,/Users/mfansler/.zshrc,但是它没有告诉我们它将如何修改它。还有,天哪!这里的 UX 很糟糕,因为它丝毫没有反映出我使用了 -d 标志这一事实。但别担心:只要 -d 标志在那里,它就不会真正改变事情。

补丁预览

要查看它究竟会做什么,请向命令添加一个详细信息标志 (-v)。这将提供之前输出的所有内容,但现在将向我们展示它将用于修补(更新).zshrc 文件的差异。

$ conda init -dv zsh

/Users/mfansler/.zshrc
--- 

+++ 

@@ -0,0 +1,16 @@

+
+# >>> conda initialize >>>
+# !! Contents within this block are managed by 'conda init' !!
+__conda_setup="$('/Users/mfansler/miniconda3/bin/conda' 'shell.zsh' 'hook' 2> /dev/null)"
+if [ $? -eq 0 ]; then
+    eval "$__conda_setup"
+else
+    if [ -f "/Users/mfansler/miniconda3/etc/profile.d/conda.sh" ]; then
+        . "/Users/mfansler/miniconda3/etc/profile.d/conda.sh"
+    else
+        export PATH="/Users/mfansler/miniconda3/bin:$PATH"
+    fi
+fi
+unset __conda_setup
+# <<< conda initialize <<<
+

# ...the rest is exactly as above

即行动计划是将这16行添加到.zshrc文件中。在这种情况下,我没有现有的 .zshrc 文件,因此它计划将其添加到第 1 行。如果该文件已经存在,它将附加这些行。


解释 Shell 代码

在关注细节之前,让我们先概述一下这段代码。从本质上讲,这是设置某些 shell 功能的一系列冗余尝试。它们按功能从多到少排序。

康达希望做什么

密码

__conda_setup="$('/Users/mfansler/miniconda3/bin/conda' 'shell.zsh' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
    eval "$__conda_setup"

conda 本身获取一些东西,将结果存储到一个字符串中,然后如果命令有一个干净的退出 ($? -eq 0),则评估该字符串。这里巧妙的工程是子进程(技术上 python -m conda)在当前进程(zsh)中传回一个可以是 运行 的结果,允许它定义 shell 函数.

稍后我将深入了解这里发生的事情。

回退 1:硬编码 Shell 函数

如果那个奇怪的内部命令失败了,开发者包括了一些基本 shell 功能的硬编码版本(特别是 conda activate)。这是在:

miniconda3/etc/profile.d/conda.sh

他们只是检查文件是否存在并获取它。让我们点击最后一个选项,然后我们将回头看看功能。

后备 2:不得已

绝对不得已的办法是从字面上违反自 Conda v4.4 以来的长期建议,即简单地将 base 环境的 bin 目录放在 PATH.在这种情况下,没有 conda activate 功能;这只能确保 Conda 在您的 PATH 上。


详细信息:Shell 功能

回到预期的情况,我们可以通过简单地获取该字符串结果来准确检查它将评估的内容:

$ conda shell.zsh hook

__add_sys_prefix_to_path() {
    # In dev-mode CONDA_EXE is python.exe and on Windows
    # it is in a different relative location to condabin.
    if [ -n "${_CE_CONDA}" ] && [ -n "${WINDIR+x}" ]; then
        SYSP=$(\dirname "${CONDA_EXE}")
    else
        SYSP=$(\dirname "${CONDA_EXE}")
        SYSP=$(\dirname "${SYSP}")
    fi

    if [ -n "${WINDIR+x}" ]; then
        PATH="${SYSP}/bin:${PATH}"
        PATH="${SYSP}/Scripts:${PATH}"
        PATH="${SYSP}/Library/bin:${PATH}"
        PATH="${SYSP}/Library/usr/bin:${PATH}"
        PATH="${SYSP}/Library/mingw-w64/bin:${PATH}"
        PATH="${SYSP}:${PATH}"
    else
        PATH="${SYSP}/bin:${PATH}"
    fi
    \export PATH
}

__conda_exe() (
    __add_sys_prefix_to_path
    "$CONDA_EXE" $_CE_M $_CE_CONDA "$@"
)

__conda_hashr() {
    if [ -n "${ZSH_VERSION:+x}" ]; then
        \rehash
    elif [ -n "${POSH_VERSION:+x}" ]; then
        :  # pass
    else
        \hash -r
    fi
}

__conda_activate() {
    if [ -n "${CONDA_PS1_BACKUP:+x}" ]; then
        # Handle transition from shell activated with conda <= 4.3 to a subsequent activation
        # after conda updated to >= 4.4. See issue #6173.
        PS1="$CONDA_PS1_BACKUP"
        \unset CONDA_PS1_BACKUP
    fi
    \local ask_conda
    ask_conda="$(PS1="${PS1:-}" __conda_exe shell.posix "$@")" || \return
    \eval "$ask_conda"
    __conda_hashr
}

__conda_reactivate() {
    \local ask_conda
    ask_conda="$(PS1="${PS1:-}" __conda_exe shell.posix reactivate)" || \return
    \eval "$ask_conda"
    __conda_hashr
}

conda() {
    \local cmd="${1-__missing__}"
    case "$cmd" in
        activate|deactivate)
            __conda_activate "$@"
            ;;
        install|update|upgrade|remove|uninstall)
            __conda_exe "$@" || \return
            __conda_reactivate
            ;;
        *)
            __conda_exe "$@"
            ;;
    esac
}

if [ -z "${CONDA_SHLVL+x}" ]; then
    \export CONDA_SHLVL=0
    # In dev-mode CONDA_EXE is python.exe and on Windows
    # it is in a different relative location to condabin.
    if [ -n "${_CE_CONDA:+x}" ] && [ -n "${WINDIR+x}" ]; then
        PATH="$(\dirname "$CONDA_EXE")/condabin${PATH:+":${PATH}"}"
    else
        PATH="$(\dirname "$(\dirname "$CONDA_EXE")")/condabin${PATH:+":${PATH}"}"
    fi
    \export PATH

    # We're not allowing PS1 to be unbound. It must at least be set.
    # However, we're not exporting it, which can cause problems when starting a second shell
    # via a first shell (i.e. starting zsh from bash).
    if [ -z "${PS1+x}" ]; then
        PS1=
    fi
fi

conda activate base

我不打算遍历所有这些,但主要部分是它没有直接将 bin 放在 PATH 上,而是定义了一个名为 conda 的 shell 函数,并且这用作 condabin/conda 入口点的包装器。这也定义了一个新功能 conda activate,它在幕后使用 shell 函数 __conda_activate()。在最后一步,它会激活 base 环境。

为什么要这样做?

这是为了响应配置设置而设计的。 auto_activate_basechange_ps1 等配置选项会影响 Conda 操作 shell 的方式,从而改变 Conda 在其 shell 函数中包含的功能。


康达“污染环境”吗?

不是真的。可以通过配置设置禁用自动激活和提示修改等主要行为,因此 conda init 最终只是将 conda activate 功能添加到 shell,从而无需在环境之间进行干净的切换必须手动操作 PATH。