【Python】解决 can‘t find ‘__main__‘ module in ‘wm‘

解决 python: can’t find ‘main’ module in ‘wm’

author: jwensh
date: 2023.07.25

文章目录

  • 解决 python: can't find '__main__' module in 'wm'
    • 1. 这是个什么问题?
      • 我的报错是否与运行方式有关?
    • 2. 重新规划代码结构
      • python wm 直接运行
        • 运行结果 A
      • python -m wm 模块按脚本来执行
        • 运行结果 B
    • 3. 关于 `运行结果 A` 没有注释前报错的问题
      • 为什么相同的情况 python -m 方式不会报错
      • 解决当前这个问题

─$ tree ./wm 
wm
├── __init__.py
├── lib
│   └── string_util.py
└── worker.py

1. 这是个什么问题?

我在构建一个分布式任务监控框架库时,相对执行 python -m wm.worker 命令进简化,也就是 python wm, 执行后报错 miniconda/envs/owl/bin/python: can't find '__main__' module in 'wm'

why ?在 python 环境下想直接运行一个文件夹(有无 __init__.py 来区分普通文件夹,还是包),那么这个文件夹中必须包含一个名为 __main__.py 的文件,否则抛出异常 can't find '__main__' module in '***'; 当执行 python -m 文件夹名或者 python 文件夹名 的时候,这个文件中的代码都会被执行.

注: 早点的 python 版本,如果要想将一个文件夹作为包(package)来使用,当前文件夹下必须要有 __init__.py 文件,即使它是空文件。当 导入 这个包时,__init__.py 文件中的代码被执行; 像当前的 3 的版本,不用 __init__.py 也可以引用并使用;

我的报错是否与运行方式有关?

python 运行 py 文件的两种方式:

  • python worker.py # 直接运行
  • python -m worker # 不带 .py 相当于 import 然后执行, 即:将模块作为脚本执行

参考:

  • https://docs.python.org/zh-cn/3/using/cmdline.html#cmdoption-m
  • https://docs.python.org/zh-cn/3/tutorial/interpreter.html#argument-passing
  • https://docs.python.org/zh-cn/3/tutorial/modules.html#executing-modules-as-scripts

但是我的 python wmpython -m wm 都不行,可以说明,运行方式不影响我的预期;不同执行 py 文件的方式,会影响 sys.path 的内容.

2. 重新规划代码结构

wm
├── __init__.py
├── __main__.py
├── lib
│   └── string_util.py
└── worker.py
  • worker.py
from wm.lib.string_util import format_string
def run():
    format_string("not module for import")
    print('worker is running')

if __name__ == '__main__':
    print("worker.py is running")
    run()
  • init.py
from wm.worker import run
print('__init__ is called!!! , __init__.__name__ is: {}, __init__.__package__ is: {}'.format(__name__, __package__))

if __name__ == '__main__':
    print('__init__.py main is running')
    run()
  • main.py
from wm.worker import run

print('__main__ is called!!! , __main__.__name__ is: {}, __main__.__package__ is: {}'.format(__name__, __package__))

if __name__ == '__main__':
    print('__main__.py main is running')
    run()

python wm 直接运行

运行结果 A

Traceback (most recent call last):
  File "/miniconda/envs/owl/lib/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/miniconda/envs/owl/lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/qadev-py-scripts/wm/__main__.py", line 13, in 
    from wm.worker import run
ModuleNotFoundError: No module named 'wm'

将模块当作脚本来执行,main.py 会有冲突,我们来注释掉看看

─$ python wm 
__main__ is called!!! , __main__.__name__ is: __main__, __main__.__package__ is: 
__main__.py main is running

wm 当作模块直接执行的时侯,__init__.py 不会被执行,__main__.py 被执行。
其中,__main__.name 的名字是 main , __main__.__package__ 的包名为空,说明不是作为模块来运行的。

python -m wm 模块按脚本来执行

运行结果 B

─$ python -m wm
__init__ is called!!! , __init__.__name__ is: wm, __init__.__package__ is: wm
__main__ is called!!! , __main__.__name__ is: __main__, __main__.__package__ is: wm
__main__.py main is running
not module for import
worker is running

当 wm 模块作为脚本执行的时候,python 会先执行 __init__.py ,然后执行 __main__.py,
两者都有了 __package__ 的值,即模块名称 wm

  • init.py 中 name 为模块名称 wm; main.py 中 namemain
  • main__的__name__无论何时调用,它的值都是__main

3. 关于 运行结果 A 没有注释前报错的问题

为什么相同的情况 python -m 方式不会报错

两种不同的调用方式,第 1 种直接运行的方式没有调用 init.py 文件,第 2 种以脚本的方式运行调用了__init.py 文件。

对于 python -m wm 的调用方式来说,由于 init.py 被事先载入,此时 python 解释器已经知道了自己就是一个 package ,因此当前路径被包含在 sys.path 中。然后再调用 main.py ,这时 import wm 这个包就毫无压力了。

而对于 python wm 的调用方式来说,由于 init.py 没有被载入,python 解释器并不知道自己正在一个 package 下面工作。默认的,python 解释器将 main.py 的当前路径 wm 加入 sys.path 中,然后在这个路径下面寻找 wm 这个模块。显然, wm 文件夹下面并没有 wm 这个模块,因此出错。要明白 init.py 是 python 解释器将当前文件夹作为 package 处理的必要条件。如果没有读取到 init.py ,python 就不会认为当前的文件夹是一个 package,而只是把它当作普通文件夹来处理。
验证,分别在 init.py 和 main.py 文件中,打印 sys.path ,添加以下代码 :

import sys
print("sys.path====", sys.path)

python wm 执行完后,包名为空,说明当前不是作为一个包来运行。

解决当前这个问题

当直接运行包的时侯(python wm),wm 不是作为一包来运行,因此包的路径 wm 没有被加入 sys.path 路径中。解决办法,将 wm 包所在路径,加入到 sys.path 即可

  • 修改 main.py 文件:
import os
import sys
if not __package__:
    sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
print("sys.path :", sys.path)
from wm.worker import run

print('__main__ is called!!! , __main__.__name__ is: {}, __main__.__package__ is: {}'.format(__name__, __package__))


if __name__ == '__main__':
    print('__main__.py main is running')
    run()
  • python wm
sys.path: ['/Users/apple/Library/CloudStorage/OneDrive-个人/qadev-py-scripts/wm/..', 'wm', '/Users/apple/tools/miniconda/envs/owl/lib/pyth38.zip', '/Users/apple/tools/miniconda/envs/owl/lib/python3.8', '/Users/apple/tools/miniconda/envs/owl/lib/python3.8/lib-dynload', '/Users/apple/tools/miniconda/envs/owl/lib/python3.8/site-packages']
__init__ is called!!! , __init__.__name__ is: wm, __init__.__package__ is: wm
sys.path: ['/Users/apple/Library/CloudStorage/OneDrive-个人/qadev-py-scripts/wm/..', 'wm', '/Users/apple/tools/miniconda/envs/owl/lib/pyth38.zip', '/Users/apple/tools/miniconda/envs/owl/lib/python3.8', '/Users/apple/tools/miniconda/envs/owl/lib/python3.8/lib-dynload', '/Users/apple/tools/miniconda/envs/owl/lib/python3.8/site-packages']
__main__ is called!!! , __main__.__name__ is: __main__, __main__.__package__ is: 
__main__.py main is running
not module for import
worker is running

注释:直接运行的方式,本身应该只会执行 main.py,不会执行 init.py 文件。

但是由于我们 import 包,此时将执行 init.py 中的代码;在__ini__.py 中定义一个 main() 函数,在__main__.py 中调用它,实现入口,最后调用了 wm.main() 函数,此时执行了 init.py 文件中的 main() 函数。

你可能感兴趣的:(#,Python,研发,-,提升工程素养,python,开发语言)