使用 python -m <模块名> 启动 Python 模块时,解释器首先定位并加载指定的模块或包,然后将其内容作为 __main__ 模块执行。
如果 <模块名> 对应的是一个包(即目录),解释器会先导入该包(运行其 __init__.py,若存在),然后查找并执行该包下的 __main__.py。因此,运行 python -m <包名> 时,若该包含有 __main__.py,则会依次执行 __init__.py(如果存在)和 __main__.py;若没有 __main__.py,则导入包后报错(**ImportError: No module named <pkg>.__main__; '<pkg>' is a package and cannot be directly executed**)。
对于命名空间包(PEP 420,即无 __init__.py 的包),行为类似:如果有 __main__.py 则执行它,否则报错。
本文详细比较了不同情况下 -m 的行为,说明了 __init__.py 在何时运行、何时不运行,以及常见陷阱,并通过示例代码加以演示和验证。
python -m 的官方文档
python -m <module-name> 会在 sys.path 上查找给定的模块,并将其内容作为 __main__ 模块执行。注意 <module-name> 必须是模块名(不带 .py 扩展),如果指定的是包名(包括命名空间包),解释器将执行 <pkg>.__main__ 子模块。
普通模块:如 python -m foo(假设 foo.py 存在并在 sys.path 上可见),则直接执行 foo.py,行为等同于直接运行脚本文件,只不过 __name__ 被设为 "__main__"。在这个过程中没有任何包被导入,也不会执行 __init__.py。
包:如 python -m pkg,则先 导入包本身(即执行 pkg/__init__.py,将包对象放入 sys.modules),再尝试执行 pkg/__main__.py。如果 __main__.py 不存在,会报错提示无法直接执行包(通常输出 No module named pkg.__main__; 'pkg' is a package and cannot be directly executed),但包的 __init__.py 已经执行。
模块文件 vs 包目录:如果使用脚本路径直接执行,如 python somepath/pkg(调用解释器时直接传入目录),则同样执行该目录下的 __main__.py。但 -m 不接受文件路径,只能接受模块名(包名)。
命名空间包:PEP 420 引入了无 __init__.py 的命名空间包。官方文档提到 -m 支持命名空间包,行为与普通包类似:如果命名空间包含有 __main__.py,则执行它;否则会报错。由于没有 __init__.py,仅执行 __main__.py 而无初始化代码。
runpy.run_module 与导入机制
-m 选项的实现依赖于标准库的 runpy 模块。其核心函数 run_module(mod_name, run_name="__main__", alter_sys=True) 会先通过 import 机制定位并加载模块或包,然后执行代码。当 mod_name 是包名时,runpy 会 先导入该包,然后执行其 __main__ 子模块。
为了可靠定位模块,-m 实现中需要 导入包含该模块的包。因此,执行 python -m pkg.submod 时,首先会导入 pkg(执行 pkg/__init__.py),然后找到 pkg.submod 并执行。如果目标模块本身就是包名,则如上执行其 __main__.py。这种导入行为会导致包初始化的副作用(例如修改 sys.path、注册钩子等)生效,并且包会被放入 sys.modules。比如:
包导入副作用:run_module 在找到 pkg.__main__ 之前,会 __import__("pkg")。这意味着 pkg/__init__.py 中的代码会执行一次,并在 sys.modules 中注册 pkg。任何注册的钩子、全局变量等都已生效,随后再执行 __main__.py。
__name__ 设置:被执行的模块会被当作顶级脚本运行,其 __name__ 被设为 "__main__"。例如,包的 __main__.py 中的 __name__ 为 "__main__"。这也意味着包的其余部分以正常导入模块的方式存在,只有 __main__.py 的代码在 __main__ 命名空间中执行。
线程安全注意:run_module(..., alter_sys=True) 会临时更改 sys.argv[0] 和 sys.modules["__main__"] 等,执行完毕后恢复原状。这使得运行时错误等栈信息看起来仿佛是在正常脚本中发生的。需要注意的是,这种操作在线程中可能不安全,应避免并行使用。
包 与 模块文件执行的差异
- 模块文件执行:直接运行模块文件(
python mod.py)或通过 python -m mod,如果 mod 是独立 .py 文件,则都会执行该文件。**此时不会执行任何 __init__.py**,因为没有关联包被导入。下面示例演示在当前目录存在 hello.py 时,两种方式结果相同:
$ cat hello.pyprint("Hello from module")$ python hello.pyHello from module$ python -m helloHello from module
“搜索 sys.path 找到模块并将其内容作为 __main__ 执行”。
包执行:如果以包名形式执行(-m pkg),则一定先执行该包的初始化(__init__.py),再执行其 __main__.py。若 __main__.py 不存在,则即使执行失败,__init__.py 已经运行。示例:
$ tree mypkgmypkg/├── __init__.py # 包初始化文件└── __main__.py # 主脚本$ cat mypkg/__init__.pyprint("mypkg __init__")$ cat mypkg/__main__.pyprint("mypkg __main__")$ python -m mypkgmypkg __init__mypkg __main__
如果删除 __main__.py 再执行:
$ rm mypkg/__main__.py$ python -m mypkgmypkg __init__Traceback (most recent call last): ...ImportError: No module named mypkg.__main__; 'mypkg' is a package and cannot be directly executed
可以看到,**mypkg/__init__.py 被执行了**(输出 “mypkg __init__”),然后由于缺少 __main__.py 导致错误。
子模块执行:若指定包中某个模块,如 python -m pkg.mod,解释器同样先导入 pkg(运行 pkg/__init__.py),然后执行 pkg/mod.py。例:
$ tree pkgpkg/├── __init__.py└── mod.py$ cat pkg/__init__.pyprint("pkg init")$ cat pkg/mod.pyprint("pkg.mod executed")$ python -m pkg.modpkg initpkg.mod executed
如上输出,pkg/__init__.py 先运行,然后模块 pkg/mod.py 的内容作为脚本执行。此时并未使用 __main__.py。
命名空间中包的行为(PEP 420)
PEP 420 允许目录缺少 __init__.py 也被视为包,此时为“命名空间包”。 -m 同样支持命名空间包:
如果命名空间包中有__main__.py,则可以执行它。示例:
$ tree ns_pkgns_pkg/└── __main__.py$ cat ns_pkg/__main__.pyprint("namespace package main")$ python -m ns_pkgnamespace package main
此时 ns_pkg 没有 __init__.py,所以没有包初始化行为,直接执行了 __main__.py。
如果没有 __main__.py,则 -m 会报错。由于该包没有 __init__.py,只会输出错误消息。例如:
$ tree ns_pkg2ns_pkg2/(empty directory, no __init__.py, no __main__.py)$ python -m ns_pkg2Traceback (most recent call last): ...ImportError: No module named ns_pkg2.__main__; 'ns_pkg2' is a package and cannot be directly executed
命名空间包没有 __file__ 属性。当 runpy 发现 spec.loader 为 None(即命名空间包),仍会尝试导入 __main__ 子模块。若找不到,就如常规包那样报错。
__init__.py 执行时机和 runpy 实现细节
首次导入时运行:和正常 import 语义一样,包的 __init__.py 只在第一次导入时执行。python -m pkg 在新进程中通常首次导入,故会执行一次 __init__.py。
已有导入时不会重复:如果包早已通过其他方式被导入(例如在 sitecustomize 中),-m 不会再次运行 __init__.py,因为 sys.modules 已缓存该包。runpy 在 get_module_details 中会警告此类情况,指出可能导致不可预测行为。因此避免在一个进程中多次以不同方式导入同一包,以免 __init__.py 执行时机不一致。
临时模块名和 sys.modules:run_module 会将执行的包或模块临时注册为 __main__,而原名称仍保留在 sys.modules 中。执行结束后,__main__ 的内容保留在当前进程中。例如执行 python -m foo 后,foo 包会在 sys.modules 中,同时 __main__ 也存在,指向同一个模块对象(或其一个副本)。这意味着程序内部若检查 __name__,会发现是 "__main__",而非原始模块名。
副作用:包初始化可能改变环境(注册入口点、修改路径等),并且这种改变会影响随后执行的模块。例如,某些应用在 __init__.py 中修改 sys.path,会影响查找 __main__.py。runpy 实现考虑到这一点,确保导入顺序正确。
runpy 实际实现:CPython 源码的 Lib/runpy.py 中,函数 _get_module_details(mod_name) 通过 importlib.util.find_spec 查找模块规范。如果发现目标是包(spec.submodule_search_locations 非空),它会尝试查找 <pkg>.__main__。若 spec.loader 为 None(即命名空间包),会报错:"%r is a namespace package and cannot be executed"。因此,runpy 强制要求目标包必须有可执行的 __main__.py 文件,且不能是内置模块。
示例代码
以下使用最新 Python 3 解释器运行示例代码:
# 1. 普通模块示例:module.py$ cat module.pyprint("module executed")$ python module.pymodule executed$ python -m modulemodule executed
两种方式等效地执行了 module.py,输出相同。无任何 init.py 执行。
# 2. 包的 __main__.py 示例:mypkg 包同时含 __init__.py 和 __main__.py$ tree mypkgmypkg/├── __init__.py└── __main__.py$ cat mypkg/__init__.pyprint("init of mypkg")$ cat mypkg/__main__.pyprint("main of mypkg")$ python -m mypkginit of mypkgmain of mypkg
输出显示先运行了 __init__.py(打印 “init of mypkg”),再执行了 __main__.py(打印 “main of mypkg”)。
# 3. 包无 __main__.py 示例:mypkg2 只有 __init__.py 没有 __main__.py$ tree mypkg2mypkg2/└── __init__.py$ cat mypkg2/__init__.pyprint("init of mypkg2")$ python -m mypkg2init of mypkg2Traceback (most recent call last): ...ImportError: No module named mypkg2.__main__; 'mypkg2' is a package and cannot be directly executed
可以看到,__init__.py 仍被执行(打印 “init of mypkg2”),随后因缺少 __main__.py 报错。
# 4. 子模块示例:pkg3 包内含子模块 mod.py,无 __main__.py$ tree pkg3pkg3/├── __init__.py└── mod.py$ cat pkg3/__init__.pyprint("init of pkg3")$ cat pkg3/mod.pyprint("pkg3.mod executed")$ python -m pkg3.modinit of pkg3pkg3.mod executed
先执行了 pkg3/__init__.py(“init of pkg3”),然后执行了子模块 pkg3/mod.py(“pkg3.mod executed”),未使用 __main__.py。
# 5. 命名空间包示例:ns_pkg 包没有 __init__.py,只有 __main__.py$ mkdir ns_pkg; # 无 __init__.py$ cat > ns_pkg/__main__.py <<EOFprint("ns_pkg main executed")EOF$ python -m ns_pkgns_pkg main executed
命名空间包的 __main__.py 被执行(打印 “ns_pkg main executed”),没有任何初始化输出,因为无 __init__.py。
常见误区与注意事项
误区:-m 包 不执行 __init__.py? 事实相反:运行 python -m 包 时,如果包存在,解释器会先导入包本身,执行 __init__.py,然后再执行 __main__.py。上述示例(如 mypkg)验证了这一点。只有在 包中没有 __main__.py 时最终会出错,但 __init__.py 已执行。
相对导入陷阱:在 -m 执行的主模块中使用相对导入会失败,因为其 __name__ 为 "__main__"。应使用绝对导入,或避免在 __main__.py 中使用相对导入,否则可能导致导入失败或导入不同模块副本。
模块名称重复:如果模块或包在执行前已被以不同路径导入,可能产生两个独立的模块对象。runpy 会在这种情况下发出警告。例如,在 sys.path 中有两个同名包目录,可能加载出多个命名空间包。应确保模块名唯一或使用绝对路径。
内置与扩展模块:-m 不能用于原生 C 扩展模块或内置模块,因为它们没有对应的 .py 文件。只适用于纯 Python 模块或包含 __main__.py 的包。
sys.argv[0] 的差异:使用 -m 启动时,sys.argv[0] 被设为被执行模块的路径,而直接运行脚本时也是脚本路径。差别在于,-m 在定位过程中将当前目录加入 sys.path,这可能影响相对路径的查找。
对比表格
| __init__.py | __main__.py | |
|---|
python module.py | | | 直接执行脚本文件,等同于 -m。无包概念,不执行 __init__.py。 |
python -m module | | | 执行模块 module.py。无包涉及,不使用 __main__.py。 |
python -m pkg | | | 首先导入包 pkg(执行 pkg/__init__.py),再执行 pkg/__main__.py。 |
python -m pkg | | | 导入包 pkg(执行 __init__.py),因无 __main__.py 导致报错:“is a package and cannot be directly executed”。 |
python -m pkg.mod | | | 导入 pkg(执行 __init__.py),然后执行 pkg/mod.py。 |
python -m pkg.subpkg | | | 依次导入 pkg、pkg/subpkg(执行各自的 __init__.py),再执行 pkg/subpkg/__main__.py。 |
python -m pkg.subpkg | | | 导入 pkg、pkg/subpkg(执行各自 __init__.py),因子包无 __main__.py 报错。 |
python -m ns_pkg | | | 无 __init__.py(命名空间包),直接执行 ns_pkg/__main__.py。 |
python -m ns_pkg | | | 命名空间包无 __main__.py,runpy 报错(无法执行命名空间包)。 |
参考资料
- Python 官方文档:“命令行选项”中对
-m <module-name> 的说明。 - PEP 338《Executing modules as scripts》。
- PEP 420《Implicit Namespace Packages》。
- CPython 源码(
Lib/runpy.py)实现细节。