6

If you launch a Python process either as a single command from the command line (with the -c flag) or by specifying a module path (with the -m flag), the working directory is prepended to sys.path. From the docs (emphases mine):

  • -c <command>

    [...]

    If this option is given, the first element of sys.argv will be "-c" and the current directory will be added to the start of sys.path (allowing modules in that directory to be imported as top level modules).

    [...]

  • -m <module-name>

    [...]

    If this option is given, the first element of sys.argv will be the full path to the module file (while the module file is being located, the first element will be set to "-m"). As with the -c option, the current directory will be added to the start of sys.path.

    [...]

But it seems the way this is done is different for each option:

$ python -c "import sys; print sys.path"
['', '/usr/lib/python38.zip', '/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload', '/home/plammens/.local/lib/python3.8/site-packages', '/usr/local/lib/python3.8/dist-packages', '/usr/lib/python3/dist-packages']

$ python -m script  # script.py does the same as the command above
['/mnt/c/Users/Paolo/Desktop/Code Sandbox/Python/clean', '/usr/lib/python38.zip', '/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload', '/home/plammens/.local/lib/python3.8/site-packages', '/usr/local/lib/python3.8/dist-packages', '/usr/lib/python3/dist-packages']

Notice how the first option uses an empty string to denote the current working directory while the second explicitly adds the path to the directory I ran the command in.

The empty string is always interpreted dynamically to denote the current working directory during execution, as per the docs on the import system:

The current working directory – denoted by an empty string – is handled slightly differently from other entries on sys.path. First, if the current working directory is found to not exist, no value is stored in sys.path_importer_cache. Second, the value for the current working directory is looked up fresh for each module lookup. Third, the path used for sys.path_importer_cache and returned by importlib.machinery.PathFinder.find_spec() will be the actual current working directory and not the empty string.

So this means that the -c version will always use the current working directory of the Python process, not the directory that it was originally launched in.

Indeed, we can make an example to show this. Consider the following file tree,

.
├── script.py
└── secret-folder
    └── findme.py

with findme.py just containing the one statement print("you found me!"). If we run the following

import os
os.chdir('secret-folder')
import findme

both as a command (-c) and as a script (in script.py), we get:

$ python -c "import os; os.chdir('secret-folder'); import findme"
you found me!

$ python -m script
Traceback (most recent call last):
  File "/usr/lib/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/.../script.py", line 5, in <module>
    import findme
ModuleNotFoundError: No module named 'findme'

This is because -m is using the "hardcoded" working directory while -c is using the "dynamically interpreted" current directory.

By how it's phrased in the documentation for -c and -m though, one would think these two should behave identically.

Is this intended? If so, it isn't documented.


Update - opened an issue on BPO.

Anakhand
  • 2,838
  • 1
  • 22
  • 50

0 Answers0