python 使用argparse、pytest和sphinxdoc的`__main__.py`结构

3lxsmp7m  于 2023-05-27  发布在  Python
关注(0)|答案(1)|浏览(90)

对于一个小模块,我希望有一个__main__.py文件,通过Python的-m参数执行。根据基本文档,该文件看起来像

import argparse

from . import MyClass

def getparser():
    parser = argparse.ArgumentParser()
    # […]
    return parser

if __name__ == "__main__":
    parser = getparser()
    args = parser.parse_args()
    c = MyClass()
    # […]

我想用runpy测试一下:

import runpy

def test_main():
    runpy.run_module("mymodule")
    # […]

因为在这种情况下__name__不是__main__,所以这不起作用。原则上,可以省略if __name__ == "__main__"条件以使其工作。
但是,我还想用sphinxarg.ext记录这个例程。这需要从sphinx获得getparser()函数。删除if __name__ == "__main__"条件也会在sphinxdoc中运行模块,这不是我们想要的。

.. argparse::
   :module: mymodule.__main__
   :func: getparser
   :prog: myprog

我该如何构建它,以便所有用例都能很好地工作***并且***代码可读性良好(即``getparser()`函数和主代码不应该分布在不同的文件中)?

ufj5ltwl

ufj5ltwl1#

一种方法是保持__main__.py非常精简,并导入所需的内容并运行它。
因此,您可以将逻辑提取到另一个模块中,比如cli.py,并编写一个main函数,其功能与您在if __name__ == '__main__':下的代码块相同。

# cli.py
def get_parser():
   """docstring"""
   ...

def main() -> int:
    parser = get_parser()
    ...
    return exit_code

然后在__main__.py中,您可以简单地拥有类似于此的东西,它将与runpy一起工作(尽管现在可能没有必要!),并且不需要sphinx或测试模块来导入它:

from .cli import main
raise SystemExit(main()) # optionally use `if __name__ == ...`

这样,您仍然可以从cli.py(或您选择的任何地方)导入所有内容,测试它,自动记录它,或其他任何内容。使用这种方法,您还可以测试main函数,而不会受到使用runpy进行测试时出现的限制。

from mypackage.cli import main
def test_main():
    assert main() == 0

提取到cli.py这样的模块在技术上是可选的。您也可以在__main__.py中保留所有这些,但主要的教训是最大限度地减少if __name__ == '__main__':下的代码量。如果我有这个,它几乎总是看起来像这样:

if __name__ == '__main__':
    main() # or raise SystemExit(main())

您也可以简单地使用runpy.run_modulerun_name关键字参数设置__name__

def test_main():
    runny.run_module('mymodule', run_name='__main__')

但是我建议提取一个可测试的main函数是一种更好的测试方法。

相关问题