编写 argparse 解析器的最佳实践
Best practices for writing argparse parsers
是否有使用 Python 的 argparse
模块的最佳实践或风格指南?
我定期使用 argparse
,它很快就占用了大量的代码来处理所有配置。对于几乎所有我发现接近 PEP 8 的代码都会产生干净、可读的代码,但这里不是。最终结果总是难看的难看的代码块。
读起来不痛Pythonic:
那么是否有 PEP 或其他一些资源提供有关如何更好地格式化此代码的指南?
一个丑陋的例子(主要遵循 PEP 8):
parser = argparse.ArgumentParser(description='A nontrivial modular command')
subparsers = parser.add_subparsers(help='sub-command help')
parser_load = subparsers.add_parser('load', help='Load something somewhere')
parser_load.add_argument('--config',
help='Path to configuration file for special settings')
parser_load.add_argument('--dir', default=os.getcwd(),
help='The directory to load')
parser_load.add_argument('book', help='The book to load into this big thing')
parser_load.add_argument('chapter', nargs='?', default='',
help='Optionally specify a chapter')
parser_load.add_argument('verse', nargs='*',
help='Optionally pick as many verses as you want to'
' load')
parser_load.set_defaults(command='load')
parser_write = subparsers.add_parser(
'write', help='Execute commands defined in a config file')
parser_write.add_argument('config', help='The path to the config file')
parser_write.set_defaults(command='write')
parser_save = subparsers.add_parser(
'save',
help='Save this big thing for use somewhere later')
parser_save.add_argument('-n', '--name', default=None,
help='The name of the component to save')
parser_save.add_argument('path', help="The way out of Plato's cave")
parser_save.set_defaults(command='save')
...
args = parser.parse_args()
正如 TemporalWolf 评论的那样,我会更一致地使用换行符,并且使用更多换行符。即使代码现在看起来更长,但我发现它更容易阅读:
- 各个函数调用之间更垂直 space,因此更容易在视觉上区分
- 每行一个参数,因此更容易看出使用了哪些参数
- 参数更靠近左边距,因此水平眼球移动更少,需要的不需要的换行符(比如拆分
help
字符串的换行符)
此外,通过重命名 parser_X
/parser_Y
→ X_parser
/Y_parser
可以更容易区分 X
/Y
.
parser = argparse.ArgumentParser(
description='A nontrivial modular command'
)
subparsers = parser.add_subparsers(
help='sub-command help'
)
load_parser = subparsers.add_parser(
'load',
help='Load something somewhere'
)
load_parser.add_argument(
'--config',
help='Path to configuration file for special settings'
)
load_parser.add_argument(
'--dir',
default=os.getcwd(),
help='The directory to load'
)
load_parser.add_argument(
'book',
help='The book to load into this big thing'
)
load_parser.add_argument(
'chapter',
nargs='?',
default='',
help='Optionally specify a chapter'
)
load_parser.add_argument(
'verse',
nargs='*',
help='Optionally pick as many verses as you want to load'
)
load_parser.set_defaults(
command='load'
)
write_parser = subparsers.add_parser(
'write',
help='Execute commands defined in a config file'
)
write_parser.add_argument(
'config',
help='The path to the config file'
)
write_parser.set_defaults(
command='write'
)
save_parser = subparsers.add_parser(
'save',
help='Save this big thing for use somewhere later'
)
save_parser.add_argument(
'-n', '--name',
default=None,
help='The name of the component to save'
)
save_parser.add_argument(
'path',
help="The way out of Plato's cave"
)
save_parser.set_defaults(
command='save'
)
...
args = parser.parse_args()
您的代码没有任何问题,这只是使用 argparse
模块的结果。我个人的偏好是将解析器的创建分解为函数。在这种情况下,您可以为您创建的每个子解析器创建一个函数。
def parse_args(args=sys.argv[1:]):
parser = argparse.ArgumentParser(description='A nontrivial modular command')
subparsers = parser.add_subparsers(help='sub-command help')
add_load_subparser(subparsers)
add_write_subparser(subparsers)
add_save_subparser(subparsers)
return parser.parse_args(args)
def add_load_subparser(subparsers):
parser = subparsers.add_parser('load', help='Load something somewhere')
parser.add_argument('--config',
help='Path to configuration file for special settings')
parser.add_argument('--dir', default=os.getcwd(),
help='The directory to load')
parser.add_argument('book', help='The book to load into this big thing')
parser.add_argument('chapter', nargs='?', default='',
help='Optionally specify a chapter')
parser.add_argument('verse', nargs='*',
help='Optionally pick as many verses as you want to'
' load')
parser.set_defaults(command='load')
def add_write_subparser(subparsers):
parser = subparsers.add_parser(
'write', help='Execute commands defined in a config file')
parser.add_argument('config', help='The path to the config file')
parser.set_defaults(command='write')
def add_save_subparser(subparsers):
parser = subparsers.add_parser(
'save',
help='Save this big thing for use somewhere later')
parser.add_argument('-n', '--name', default=None,
help='The name of the component to save')
parser.add_argument('path', help="The way out of Plato's cave")
parser.set_defaults(command='save')
args = parse_args()
开发人员中没有关于此特定模块的样式的任何讨论(我一直在密切关注相关的 bug/issues)。
与样式和布局相比,我更关心解决问题,但确实喜欢易于阅读和理解的代码。如果有大块重复模式,我喜欢使用实用函数、字典和列表。
最近的一个 SO 问题, 询问了关于 OOP 子解析器定义的问题。我取了他的首字母class,加了个方法:
def make_sup(self,sp):
self.parser = sp.add_parser(self.name)
self.parser.add_argument('--foo')
self.parser.set_defaults(action=self)
因此可以定义一组对象
cmds = []
cmds.append(Cmd('list'))
cmds.append(Cmd('foo'))
cmds.append(Cmd('bar'))
甚至
cmds = [Cmd('list'), Cmd('foo'),...]
然后用于填充解析器:
parser = argparse.ArgumentParser()
sp = parser.add_subparsers(dest='cmd')
for cmd in cmds:
cmd.make_sup(sp)
这是不涉及参数的简单示例。
unittest 文件,test_argparse.py
有一个相当复杂的系统来简化解析器定义。
class Sig(object):
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
测试用例创建这些 Sig
对象的列表:
argument_signatures = [Sig('--foo-bar'), Sig('--baz', dest='zabbaz')]
argument_signatures = [
Sig('-x', type=float),
Sig('-3', type=float, dest='y'),
Sig('z', nargs='*'),
]
解析器测试class有如下方法:
def no_groups(parser, argument_signatures):
"""Add all arguments directly to the parser"""
for sig in argument_signatures:
parser.add_argument(*sig.args, **sig.kwargs)
Ipython
有(或者至少有几个版本)创建大型 argparse
解析器的代码,使用 config
文件条目来定义参数。
是否有使用 Python 的 argparse
模块的最佳实践或风格指南?
我定期使用 argparse
,它很快就占用了大量的代码来处理所有配置。对于几乎所有我发现接近 PEP 8 的代码都会产生干净、可读的代码,但这里不是。最终结果总是难看的难看的代码块。
读起来不痛Pythonic:
那么是否有 PEP 或其他一些资源提供有关如何更好地格式化此代码的指南?
一个丑陋的例子(主要遵循 PEP 8):
parser = argparse.ArgumentParser(description='A nontrivial modular command')
subparsers = parser.add_subparsers(help='sub-command help')
parser_load = subparsers.add_parser('load', help='Load something somewhere')
parser_load.add_argument('--config',
help='Path to configuration file for special settings')
parser_load.add_argument('--dir', default=os.getcwd(),
help='The directory to load')
parser_load.add_argument('book', help='The book to load into this big thing')
parser_load.add_argument('chapter', nargs='?', default='',
help='Optionally specify a chapter')
parser_load.add_argument('verse', nargs='*',
help='Optionally pick as many verses as you want to'
' load')
parser_load.set_defaults(command='load')
parser_write = subparsers.add_parser(
'write', help='Execute commands defined in a config file')
parser_write.add_argument('config', help='The path to the config file')
parser_write.set_defaults(command='write')
parser_save = subparsers.add_parser(
'save',
help='Save this big thing for use somewhere later')
parser_save.add_argument('-n', '--name', default=None,
help='The name of the component to save')
parser_save.add_argument('path', help="The way out of Plato's cave")
parser_save.set_defaults(command='save')
...
args = parser.parse_args()
正如 TemporalWolf 评论的那样,我会更一致地使用换行符,并且使用更多换行符。即使代码现在看起来更长,但我发现它更容易阅读:
- 各个函数调用之间更垂直 space,因此更容易在视觉上区分
- 每行一个参数,因此更容易看出使用了哪些参数
- 参数更靠近左边距,因此水平眼球移动更少,需要的不需要的换行符(比如拆分
help
字符串的换行符)
此外,通过重命名 parser_X
/parser_Y
→ X_parser
/Y_parser
可以更容易区分 X
/Y
.
parser = argparse.ArgumentParser(
description='A nontrivial modular command'
)
subparsers = parser.add_subparsers(
help='sub-command help'
)
load_parser = subparsers.add_parser(
'load',
help='Load something somewhere'
)
load_parser.add_argument(
'--config',
help='Path to configuration file for special settings'
)
load_parser.add_argument(
'--dir',
default=os.getcwd(),
help='The directory to load'
)
load_parser.add_argument(
'book',
help='The book to load into this big thing'
)
load_parser.add_argument(
'chapter',
nargs='?',
default='',
help='Optionally specify a chapter'
)
load_parser.add_argument(
'verse',
nargs='*',
help='Optionally pick as many verses as you want to load'
)
load_parser.set_defaults(
command='load'
)
write_parser = subparsers.add_parser(
'write',
help='Execute commands defined in a config file'
)
write_parser.add_argument(
'config',
help='The path to the config file'
)
write_parser.set_defaults(
command='write'
)
save_parser = subparsers.add_parser(
'save',
help='Save this big thing for use somewhere later'
)
save_parser.add_argument(
'-n', '--name',
default=None,
help='The name of the component to save'
)
save_parser.add_argument(
'path',
help="The way out of Plato's cave"
)
save_parser.set_defaults(
command='save'
)
...
args = parser.parse_args()
您的代码没有任何问题,这只是使用 argparse
模块的结果。我个人的偏好是将解析器的创建分解为函数。在这种情况下,您可以为您创建的每个子解析器创建一个函数。
def parse_args(args=sys.argv[1:]):
parser = argparse.ArgumentParser(description='A nontrivial modular command')
subparsers = parser.add_subparsers(help='sub-command help')
add_load_subparser(subparsers)
add_write_subparser(subparsers)
add_save_subparser(subparsers)
return parser.parse_args(args)
def add_load_subparser(subparsers):
parser = subparsers.add_parser('load', help='Load something somewhere')
parser.add_argument('--config',
help='Path to configuration file for special settings')
parser.add_argument('--dir', default=os.getcwd(),
help='The directory to load')
parser.add_argument('book', help='The book to load into this big thing')
parser.add_argument('chapter', nargs='?', default='',
help='Optionally specify a chapter')
parser.add_argument('verse', nargs='*',
help='Optionally pick as many verses as you want to'
' load')
parser.set_defaults(command='load')
def add_write_subparser(subparsers):
parser = subparsers.add_parser(
'write', help='Execute commands defined in a config file')
parser.add_argument('config', help='The path to the config file')
parser.set_defaults(command='write')
def add_save_subparser(subparsers):
parser = subparsers.add_parser(
'save',
help='Save this big thing for use somewhere later')
parser.add_argument('-n', '--name', default=None,
help='The name of the component to save')
parser.add_argument('path', help="The way out of Plato's cave")
parser.set_defaults(command='save')
args = parse_args()
开发人员中没有关于此特定模块的样式的任何讨论(我一直在密切关注相关的 bug/issues)。
与样式和布局相比,我更关心解决问题,但确实喜欢易于阅读和理解的代码。如果有大块重复模式,我喜欢使用实用函数、字典和列表。
最近的一个 SO 问题,
def make_sup(self,sp):
self.parser = sp.add_parser(self.name)
self.parser.add_argument('--foo')
self.parser.set_defaults(action=self)
因此可以定义一组对象
cmds = []
cmds.append(Cmd('list'))
cmds.append(Cmd('foo'))
cmds.append(Cmd('bar'))
甚至
cmds = [Cmd('list'), Cmd('foo'),...]
然后用于填充解析器:
parser = argparse.ArgumentParser()
sp = parser.add_subparsers(dest='cmd')
for cmd in cmds:
cmd.make_sup(sp)
这是不涉及参数的简单示例。
unittest 文件,test_argparse.py
有一个相当复杂的系统来简化解析器定义。
class Sig(object):
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
测试用例创建这些 Sig
对象的列表:
argument_signatures = [Sig('--foo-bar'), Sig('--baz', dest='zabbaz')]
argument_signatures = [
Sig('-x', type=float),
Sig('-3', type=float, dest='y'),
Sig('z', nargs='*'),
]
解析器测试class有如下方法:
def no_groups(parser, argument_signatures):
"""Add all arguments directly to the parser"""
for sig in argument_signatures:
parser.add_argument(*sig.args, **sig.kwargs)
Ipython
有(或者至少有几个版本)创建大型 argparse
解析器的代码,使用 config
文件条目来定义参数。