Skip to content

PythonPathSadness

Anthony Sottile edited this page May 18, 2022 · 4 revisions

also in video form!

python path/to/file.py puts path/to on the beginning of the PYTHONPATH (sys.path)

this has some somewhat odd consequences:

Here's a toy example of where this can go horrible:

$ tree
.
├── mod1
│   ├── submod1.py
│   ├── submod2.py
│   └── __init__.py
└── mod2.py
 
$ cat mod1/submod1.py
from __future__ import absolute_import
 
import sys
 
print('sys.argv[0]: {0!r}'.format(sys.argv[0]))
print('sys.path[0]: {0!r}'.format(sys.path[0]))
 
from mod2 import baz
 
print('got baz: {0}'.format(baz))
 
$ cat mod2.py
baz = 1

Running via file path

$ python mod1/submod1.py
sys.argv[0]: 'mod1/submod1.py'
sys.path[0]: '/tmp/foo/mod1'
Traceback (most recent call last):
  File "mod1/submod1.py", line 8, in <module>
    from mod2 import baz
ImportError: cannot import name baz

Running via -m (which works great!)

$ python -m mod1.submod1
sys.argv[0]: '/tmp/foo/mod1/submod1.py'
sys.path[0]: ''
got baz: 1

Still a problem in python3 too \o/

The above problem can be worked around with PYTHONPATH, here's a related problem which cannot:

$ tree .
.
├── mod1
│   ├── __init__.py
│   ├── mod2.py
│   └── run.py
└── mod2
    └── __init__.py

2 directories, 4 files
$ find -name '*.py' | xargs tail -n 999
==> ./mod1/run.py <==
from __future__ import absolute_import

from mod2 import IMPORT_TARGET

print(IMPORT_TARGET)

==> ./mod1/__init__.py <==

==> ./mod1/mod2.py <==
# no IMPORT_TARGET here!

==> ./mod2/__init__.py <==
IMPORT_TARGET = 'hello world!'
$ python -m mod1.run
hello world!
$ python mod1/run.py
Traceback (most recent call last):
  File "mod1/run.py", line 3, in <module>
    from mod2 import IMPORT_TARGET
ImportError: cannot import name IMPORT_TARGET