Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does PYTHONPATH with trailing colon add current directory to sys.path?

Consider a Python project like this:

foo/
    __init__.py
scripts/
    run.py
demo.sh

Under normal circumstances, attempting to import from the foo package will fail if you run the script from the root of the project, because default Python behavior is to add the directory of script invoking the Python interpreter (and not necessarily the current directory) to sys.path. (docs):

python scripts/run.py

However, I recently noticed that such imports were working on my box, and I tracked it down to some surprising behavior related to PYTHONPATH. In my Bash profile I had added a directory to PYTHONPATH:

export PYTHONPATH="/some/path:$PYTHONPATH"

If PYTHONPATH is initially empty, that (somewhat sloppy, but commonly seen) form of the command will leave a trailing colon:

echo $PYTHONPATH
/some/path:

I had always assumed that this trailing punctuation had no impact, but it appears that the trailing colon was the cause of the mysteriously successful import. Leading or trailing colons (or even a defined but empty PYTHONPATH) will result in sys.path containing an empty string before the site module loads, which in turn leads to the current working directory being added to sys.path.

Here are a Python script and a Bash script to demonstrate. I got the same behavior using both Python 2.7 and Python 3.3.

Python script: run.py:

import sys, os

pp = os.environ.get('PYTHONPATH')

try:
    import foo
    print 'OK'
    assert os.getcwd() in sys.path
    assert pp == '' or pp.startswith(':') or pp.endswith(':')

except Exception:
    print 'FAIL'
    assert os.getcwd() not in sys.path
    assert pp is None

Bash script: demo.sh:

# Import fails.
unset PYTHONPATH;  python scripts/run.py

# Import succeeds -- to my surprise.
PYTHONPATH=''      python scripts/run.py
PYTHONPATH='/tmp:' python scripts/run.py
PYTHONPATH=':/tmp' python scripts/run.py

My questions:

  • Am I understanding the situation correctly, or have I somehow gone astray?

  • Is this documented anywhere? I did not find anything. At a minimum, I am posting this information here in case it will help others.

  • Am I alone in finding this behavior unexpected?

like image 515
FMc Avatar asked Sep 27 '22 06:09

FMc


1 Answers

When modifying colon-delimited environment variables such as PYTHONPATH, PATH, CPATH, MANPATH, LD_LIBRARY_PATH, PKG_CONFIG_PATH, etc... Some of these variables place special meaning to trailing colons, while others do not.

For PYTHONPATH and PATH, I would recommend prepending (or appending) new directories in a manner that does not accidentally introduce trailing (or leading) colons if the variable was previously unset:

export PYTHONPATH="/some/path${PYTHONPATH+":"}${PYTHONPATH-}"

(In the case of MANPATH and INFOPATH, you do want to introduce a trailing colon so that man and info will include their default search directories.)

Explanation:

  • ${PYTHONPATH+":"} expands to a : if PYTHONPATH is set, regardless of if PYTHONPATH is empty.
  • ${PYTHONPATH-} will expand to the contents of PYTHONPATH if it is set, but if PYTHONPATH is unset, then ${PYTHONPATH-} expands to nothing --- just like the usual ${PYTHONPATH}.

    • ${PYTHONPATH-} is the same as ${PYTHONPATH-""} meaning to substitute "" (nothing) when PYTHONPATH is unset.
    • The reason I recommend ${PYTHONPATH-} over ${PYTHONPATH} here is that ${PYTHONPATH-} will not create an error when PYTHONPATH is unset and your script has executed set -u to raise errors on unset variables.

For details on the ${parameter+[word]} and ${parameter-[word]} mechanisms, see "Parameter Expansion" at http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02

For details on set -u, see the description of "set" at http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#set

like image 118
David Avatar answered Oct 11 '22 18:10

David