在Setuptools入口点中使用argparse
我正在写一个脚本,想用Setuptools来分发它。我已经把这个脚本添加到了我的setup.py
文件中的entry_points
部分。
根据Setuptools的文档:
你指定的函数会在没有参数的情况下被调用,它的返回值会传递给
sys.exit()
,所以你可以返回一个错误级别或要打印到标准错误的信息。
因为这个方法会返回而不是直接退出,所以它更容易进行测试。为了方便测试,我在方法中接受参数,默认使用sys.argv
。到目前为止,一切都很好。
问题出现在添加了argparse之后。当argparse无法解析参数时,它会调用sys.exit
。我其实更希望argparse不要这样做,因为这个退出是由Setuptools的包装器来处理的。我想到的第一个解决办法是重写argparse.ArgumentParser
,但后来我看到这个:
# ===============
# Exiting methods
# ===============
def exit(self, status=0, message=None):
if message:
self._print_message(message, _sys.stderr)
_sys.exit(status)
def error(self, message):
"""error(message: string)
Prints a usage message incorporating the message to stderr and
exits.
If you override this in a subclass, it should not return -- it
should either exit or raise an exception.
"""
self.print_usage(_sys.stderr)
self.exit(2, _('%s: error: %s\n') % (self.prog, message))
文档中提到我不应该返回,而是应该抛出一个异常。我该如何解决这个问题呢?
如果我没有解释得够清楚,下面是主要方法的代码:
def main(args=sys.argv):
parser = ArgumentParser(prog='spam')
# parser is configured here
parsed = parser.parse_args(args)
# Parsed args are used here
2 个回答
如果你正在开始一个新项目,或者有时间对现有代码进行一些改进,那么你可以考虑使用Click这个库。Click库有一些很不错的功能,比如与setuptools的集成和'可测试性'等。
下面是文档中的一个示例/测试代码片段,它不仅创建了一个简单的命令行界面,还立即对其进行了测试:
import click
from click.testing import CliRunner
def test_hello_world():
@click.command()
@click.argument('name')
def hello(name):
click.echo('Hello %s!' % name)
runner = CliRunner()
result = runner.invoke(hello, ['Peter'])
assert result.exit_code == 0
assert result.output == 'Hello Peter!\n'
你不想在出错的时候直接用 return
的原因是,解析器会继续进行解析。有些错误可能在最后才出现(比如未解析的字符串),而有些错误可能在一开始就会出现(比如第一个参数字符串类型不对)。如果你在错误处理的方法里用 return
,那么 parse_args
的行为就会变得不可预测。通常情况下,你希望解析器停止工作,然后把控制权交回给你的代码。
你应该把 parse_args()
的调用放在一个 try: except SystemExit:
的代码块里。我经常会用这样的测试脚本:
for test in ['-o FILE',
...
]:
print(test)
try:
print(parser.parse_args(test.split()))
except SystemExit:
pass
你可以使用 error
和/或 exit
来返回其他类型的异常。它们也可能会跳过使用信息。不过,不管怎样,你需要在你的包装器里捕获这个异常。