192

The python interpreter has -m module option that "Runs library module module as a script".

With this python code a.py:

if __name__ == "__main__":
    print __package__
    print __name__

I tested python -m a to get

"" <-- Empty String
__main__

whereas python a.py returns

None <-- None
__main__

To me, those two invocation seems to be the same except __package__ is not None when invoked with -m option.

Interestingly, with python -m runpy a, I get the same as python -m a with python module compiled to get a.pyc.

What's the (practical) difference between these invocations? Any pros and cons between them?

Also, David Beazley's Python Essential Reference explains it as "The -m option runs a library module as a script which executes inside the __main__ module prior to the execution of the main script". What does it mean?

Rabarberski
  • 23,854
  • 21
  • 74
  • 96
prosseek
  • 182,215
  • 215
  • 566
  • 871

3 Answers3

284

When you use the -m command-line flag, Python will import a module or package for you, then run it as a script. When you don't use the -m flag, the file you named is run as just a script.

The distinction is important when you try to run a package. There is a big difference between:

python foo/bar/baz.py

and

python -m foo.bar.baz

as in the latter case, foo.bar is imported and relative imports will work correctly with foo.bar as the starting point.

Demo:

$ mkdir -p test/foo/bar
$ touch test/foo/__init__.py
$ touch test/foo/bar/__init__.py
$ cat << EOF > test/foo/bar/baz.py 
> if __name__ == "__main__":
>     print __package__
>     print __name__
> 
> EOF
$ PYTHONPATH=test python test/foo/bar/baz.py 
None
__main__
$ PYTHONPATH=test python -m foo.bar.baz 
foo.bar
__main__

As a result, Python has to actually care about packages when using the -m switch. A normal script can never be a package, so __package__ is set to None.

But run a package or module inside a package with -m and now there is at least the possibility of a package, so the __package__ variable is set to a string value; in the above demonstration it is set to 'foo.bar', for plain modules not inside a package it is set to an empty string.

As for the __main__ module, Python imports scripts being run as it would import regular modules. A new module object is created to hold the global namespace and is stored in sys.modules['__main__']. This is what the __name__ variable refers to, it is a key in that structure.

For packages, you can create a __main__.py module inside and have that run when running python -m package_name; in fact that is the only way you can run a package as a script:

$ PYTHONPATH=test python -m foo.bar
python: No module named foo.bar.__main__; 'foo.bar' is a package and cannot be directly executed
$ cp test/foo/bar/baz.py test/foo/bar/__main__.py
$ PYTHONPATH=test python -m foo.bar
foo.bar
__main__

So, when naming a package for running with -m, Python looks for a __main__ module contained in that package and executes that as a script. Its name is then still set to '__main__' and the module object is still stored in sys.modules['__main__'].

Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • When a module inside a package that does not import other modules also has __package__ == None: http://stackoverflow.com/questions/4437394/package-is-none-when-importing-a-python-module – prosseek Mar 07 '14 at 16:20
  • 3
    What actually does command `PYTHONPATH=test python -m foo.bar` mean? Could you explain it in details, please? – Andriy Apr 28 '17 at 16:47
  • 6
    @Andriy: `PYTHONPATH` sets an environment variable; it expands the series of directories where Python will look for modules when importing; here it adds the `test` directory to that series. By putting it on the same command line, it applies *only* to that single `python` command. `-m` tells Python to import a specific module, as if you ran `import foo.bar`. However, Python will automatically run a `__main__` module inside a package as a script when you use that switch. – Martijn Pieters Apr 28 '17 at 16:56
  • Now that we can use `-m` for a single module or a module in package, why don't we only use `-m` for running python modules. Why there is entrance like Django `manage.py`? – Cloud Aug 22 '18 at 06:18
  • @SiminJie because `-m` requires that the target module is findable on the module search path, which limits your options (you can’t use an ad hoc file in a random subdirectory somewhere, while a script file would work just fine). `manage.py` Is a different case, as it can be custom generated for each Django project. Besides, having to use `-m` **always** is not that user-.friendly. – Martijn Pieters Aug 23 '18 at 17:17
  • 2
    `having to use -m always is not that user-.friendly.` I think mix using and not using `-m` is lesser user friendly. – Cloud Aug 23 '18 at 23:36
  • `-m requires that the target module is findable on the module search path`. Current working directory will always in search path. – Cloud Aug 23 '18 at 23:37
  • 1
    @SiminJie: scripts can be opened in *any arbitrary path* and then their parent directory is added to the module search path. `-m` only works for the current directory or directories already registered on the search path. That was my point. `-m` is not something you give to end-users for that very usability issue. – Martijn Pieters Aug 24 '18 at 18:26
  • @MartijnPieters To clarify, the absolute path of `foo` is `/home/flow2k/foo`, which is the Python project root. There are other files and directories, like `/home/flowk/Java/` and `/home/flow2k/Photos`. So to execute `python -m foo.bar.baz`, I have to `cd /home/flow2k/` first; this command puts `/home/flow2k/` on `sys.path`, which is undesirable. Would you have a solution for this? – flow2k Sep 13 '19 at 20:36
  • @flow2k: My question is: *why is it undesirable*? Only Python modules and packages will be loaded, other subdirectories don't matter. You can otherwise introduce an extra directory (e.g. `/home/flow2k/python`). – Martijn Pieters Sep 13 '19 at 21:31
  • @MartijnPieters About it being undesirable, I guess I want to have some kind "controlled environment" or "isolation" when I run my Python scripts. Had I written `import Photos` (a pointless statement that's probably a bug), the interpreter would not complain because the Photos directory exists, but if I deleted the Photos directory someday while cleaning up my home directory, my program would immediately fail. This kind of dependence on external factors is what I'd like to avoid. – flow2k Sep 14 '19 at 00:49
  • Yes, introducing the extra directory, i.e. making `/home/flow2k/python/foo`, would solve the problem for me. – flow2k Sep 14 '19 at 00:49
  • @flow2k If this is Python 3.3 or newer it would only complain if you used a plain `import Photos` with no use of the module, as it is treated as an [implicit namespace package](https://docs.python.org/3/whatsnew/3.3.html#pep-420-implicit-namespace-packages); in older Python versions that would still be an import error. And code quality issues like unused imports can trivially be caught by running a linter like `flake8` on your code, you should do so for any published project. – Martijn Pieters Sep 14 '19 at 07:40
  • @MartijnPieters About your first point " it would only complain if you used a plain import Photos with no use of the module" - I tried `import Photos` with no subsequent use of `Photos`, and it did not complain. Did I misunderstand you? Thanks for pointing to `flake8` by the way. – flow2k Sep 15 '19 at 04:48
  • 1
    @flow2k: I mean that `from Photos import ...` will complain. So would `import Photos.`. `import Photos` only works because Python supports namespaced packages (where two separate distributions provide `Photos.foo` and `Photos.bar` separately and they can independently be managed). – Martijn Pieters Sep 15 '19 at 11:31
  • "When you use the `-m` command-line flag, Python will import a module or package for you, then run it as a script. When you don't use the `-m` flag, the file you named is run as just a script." Two questions: 1. What do you mean by *import*? I thought it meant running a module’s code in the current environment. 2. What do you mean by *run as a script*? I am wondering if a module can *not* be run as a script. – Géry Ogam Feb 08 '20 at 15:39
  • 1
    @Maggyero: all scripts are modules, but they are imported as the `__main__` module. They loose their package context, however. Importing is a two-step process: loading if the name doesn't yet exist in `sys.modules`, and binding (assigning to a name), so `import foo.bar as spam` finds *something* to be stored in `sys.modules['foo.bar']`, and then binds the name `spam`. For scripts and `-m` there is no visible binding, however, but the module needs loading in the first place. – Martijn Pieters Feb 08 '20 at 20:43
  • 1
    @Maggyero: Any `.py` file can be run as a script as well as being imported. What differs (between running as a script and using `-m`) is the *context*, how `sys.path` is affected and if the loaded module is part of a package, whether or not the package is also imported and so have a module that can use package-relative imports. – Martijn Pieters Feb 08 '20 at 20:45
  • "`import foo.bar as spam` finds *something* to be stored in `sys.modules['foo.bar']`" Does it only *find* the module, or does it also *execute* its code before storage in `sys.modules['foo.bar']` (in other words, load = find + execute + store)? – Géry Ogam Feb 08 '20 at 22:09
  • @Maggyero: sorry, comments are not really suitable for a full expose of how the Python import system works. In short, if `sys.modules` doesn't have it, then yes, something needs to be loaded. Usually that's finding a module file and executing it. – Martijn Pieters Feb 09 '20 at 11:17
51

Execution of Python code with -m option or not

Use the -m flag.

The results are pretty much the same when you have a script, but when you develop a package, without the -m flag, there's no way to get the imports to work correctly if you want to run a subpackage or module in the package as the main entry point to your program (and believe me, I've tried.)

The docs

Like the docs on the -m flag say:

Search sys.path for the named module and execute its contents as the __main__ module.

and

As with the -c option, the current directory will be added to the start of sys.path.

so

python -m pdb

is roughly equivalent to

python /usr/lib/python3.5/pdb.py

(assuming you don't have a package or script in your current directory called pdb.py)

Explanation:

Behavior is made "deliberately similar to" scripts.

Many standard library modules contain code that is invoked on their execution as a script. An example is the timeit module:

Some python code is intended to be run as a module: (I think this example is better than the commandline option doc example)

$ python -m timeit '"-".join(str(n) for n in range(100))'
10000 loops, best of 3: 40.3 usec per loop
$ python -m timeit '"-".join([str(n) for n in range(100)])'
10000 loops, best of 3: 33.4 usec per loop
$ python -m timeit '"-".join(map(str, range(100)))'
10000 loops, best of 3: 25.2 usec per loop

And from the release note highlights for Python 2.4:

The -m command line option - python -m modulename will find a module in the standard library, and invoke it. For example, python -m pdb is equivalent to python /usr/lib/python2.4/pdb.py

Follow-up Question

Also, David Beazley's Python Essential Reference explains it as "The -m option runs a library module as a script which executes inside the __main__ module prior to the execution of the main script".

It means any module you can lookup with an import statement can be run as the entry point of the program - if it has a code block, usually near the end, with if __name__ == '__main__':.

-m without adding the current directory to the path:

A comment here elsewhere says:

That the -m option also adds the current directory to sys.path, is obviously a security issue (see: preload attack). This behavior is similar to library search order in Windows (before it had been hardened recently). It's a pity that Python does not follow the trend and does not offer a simple way to disable adding . to sys.path

Well, this demonstrates the possible issue - (in windows remove the quotes):

echo "import sys; print(sys.version)" > pdb.py

python -m pdb
3.5.2 |Anaconda 4.1.1 (64-bit)| (default, Jul  5 2016, 11:41:13) [MSC v.1900 64 bit (AMD64)]

Use the -I flag to lock this down for production environments (new in version 3.4):

python -Im pdb
usage: pdb.py [-c command] ... pyfile [arg] ...
etc...

from the docs:

-I

Run Python in isolated mode. This also implies -E and -s. In isolated mode sys.path contains neither the script’s directory nor the user’s site-packages directory. All PYTHON* environment variables are ignored, too. Further restrictions may be imposed to prevent the user from injecting malicious code.

What does __package__ do?

It enables explicit relative imports, not particularly germane to this question, though - see this answer here: What's the purpose of the "__package__" attribute in Python?

Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
8

The main reason to run a module (or package) as a script with -m is to simplify deployment, especially on Windows. You can install scripts in the same place in the Python library where modules normally go - instead of polluting PATH or global executable directories such as ~/.local (the per-user scripts directory is ridiculously hard to find in Windows).

Then you just type -m and Python finds the script automagically. For example, python -m pip will find the correct pip for the same instance of Python interpreter which executes it. Without -m, if user has several Python versions installed, which one would be the "global" pip?

If user prefers "classic" entry points for command-line scripts, these can be easily added as small scripts somewhere in PATH, or pip can create these at install time with entry_points parameter in setup.py.

So just check for __name__ == '__main__' and ignore other non-reliable implementation details.

ddbug
  • 1,392
  • 1
  • 11
  • 25
  • 1
    That the -m option also adds the current directory to sys.path, is obviously a security issue (see: **preload attack**). This behavior is similar to library search order in Windows (before it had been hardened recently). It's a pity that Python does not follow the trend and does not offer a simple way to disable adding . to sys.path. – ddbug Sep 08 '16 at 11:00
  • ddbug: In Python 3.4+ there is `-I` flag to run the script in isolated mode, which will not add current directory to sys.path. – Niko Föhr Feb 05 '23 at 17:11