9

I have a project set up in Pycharm, with an existing conda environment. My scripts work when run from within the console.

I would like to be able to run python -m path_to_my_script/script.py from any location, but I need conda activated. Conda recommends I do conda init but I'm worried it may change settings someplace and break things.

What does conda init do?

GlaceCelery
  • 921
  • 1
  • 13
  • 30
  • Don't worry, just do it. See [conda install docs](https://docs.conda.io/projects/conda/en/latest/user-guide/install/macos.html?#installing-in-silent-mode) – Trock Sep 02 '21 at 02:14
  • "Don't worry, just do it" is neither helpful, nor should it be encouraged. I recommend readers to be skeptical of this. – xdavidliu Sep 04 '22 at 00:31
  • It might also be worth mentioning that the scenario stated is usually better solved by `conda run`. E.g., `conda run -n env_name python path_to_my_script/script.py`. This would confine the "activation" to the subprocess in which the script is executed. – merv Sep 27 '22 at 23:25

2 Answers2

14

Strategy for Answering

Exactly what the conda init command does and its consequences are shell-specific. Instead of trying to cover all cases, let's walk through a case, noting along the way that one can replicate this analysis by substituting their shell of interest.


Case Study: conda init zsh

Let's look at zsh as the shell. This is a common shell (default for macOS 10.15+) and very close to bash. Plus, I don't already have it configured.

Probing the Command: Dry Run

Many Conda commands include some form of dry run functionality via a --dry-run, -d flag, which - combined with verbosity flags - enables seeing what this would do without doing them. For the init command, dry run alone will only tell us what files it would modify:

$ 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. <==

Here we can see that it plans to target the user-level resources file for zsh, /Users/mfansler/.zshrc, but it doesn't tell us how it will modified it. Also, OMG! the UX here is awful, because it in no way reflects the fact that I used the -d flag. But don't worry: as long as the -d flag is there it won't actually change things.

Patch Preview

To see what exactly it will do, add a single verbosity flag (-v) to the command. This will give everything from the previous output, but will now shows us the diff it will use to patch (update) the .zshrc file.

$ 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

That is, the plan of action is to add these 16 lines to the .zshrc file. In this case, I don't have an existing .zshrc file, so it plans to add it at line 1. If the file had already existed, it would append these lines.


Interpreting the Shell Code

Let's overview this code, before focusing on the details. Essentially, this is a redundant sequence of attempts to set up some shell functionality. They are ordered from most to least functional.

What Conda Hopes To Do

The code

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

gets something from conda itself, storing the result to a string, and then evaluates that string if the command had a clean exit ($? -eq 0). The neat engineering here is that the subprocess (technically python -m conda) passes back a result that can be run within this current process (zsh), allowing it to define shell functions.

I'll dig deeper into what is going on here in a second.

Fallback 1: Hardcoded Shell Functions

If that strange internal command fails, the devs included a hardcoded version of some essential shell functions (specifically conda activate). This is in:

miniconda3/etc/profile.d/conda.sh

and they simply check the file exists and source it. Let's hit that last option, then we'll swing back to look at the functionality.

Fallback 2: The Last Resort

The absolute last resort is to literally violate the standing recommendation since Conda v4.4, which is to simply put the base environment's bin directory on PATH. In this case, there is no conda activate functionality; this only ensures that Conda is on your PATH.


Details: Shell Functionality

Coming back to the intended case, we can inspect exactly what it would evaluate by simply getting that string result:

$ 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

I'm not going to walk through all this, but the main part is that instead of directly putting bin on PATH, it defines a shell function called conda and this serves as a wrapper for the condabin/conda entrypoint. This also defines a new functionality conda activate, which uses a shell function, __conda_activate(), behind the scenes. At the final step, it then activates the base environment.

Why do it this way?

This is engineered like this in order to be responsive to the configuration settings. Configuration options like auto_activate_base and change_ps1 affect how Conda manipulates the shell, and so that changes what functionality Conda includes in its shell functions.


Does Conda "Pollute the Environment"?

Not really. The main behavioral things like auto-activation and prompt modification can be disabled through configuration settings, so that conda init ultimately just adds the conda activate function to the shell, enabling clean switching between environments without ever having to manually manipulate PATH.

merv
  • 67,214
  • 13
  • 180
  • 245
1

Your caution may be warranted. The conda init command adds Anaconda to the path on Linux or Mac (not recommended on Windows). Anaconda FAQ
You also need to prep for conda init by running source <path to conda>/bin/activate first.

You can run your script in your desired conda environment by specifying the python environment in the shebang statement in the first line of your code. You can get that value by activating your desired environment and executing which python

(base) -> conda activate py39
(py39) -> which python
/home/user/anaconda3/envs/py39/bin/python

The shebang would be:

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

On Mac and Linux, once you add the shebang you can chmod the file to be executable (e.g. chmod 700 myscript.py) and run from the command line directly. (I'm not a Windows user, so ymmv.)

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

(Which now runs in shebang virtual environment instead of base.)

garlic_rat
  • 99
  • 6
  • 1
    That's great. I will use this for linux. I also have the same problem in Windows...but I still would like to know, in words, what does conda init do? – GlaceCelery Sep 02 '21 at 04:38