编写 argparse 解析器的最佳实践

问题描述 投票:0回答:3

是否有使用 Python

argparse
模块的最佳实践或风格指南?

我定期使用

argparse
,它很快就会占用大量线路来处理所有配置。对于几乎所有事情,我发现坚持接近 PEP 8 会产生干净、可读的代码,但这里不是。最终结果总是丑陋的代码块,读起来很痛苦。

读起来很痛苦,这不是Python式的:

美丽胜于丑陋......可读性很重要

那么是否有 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()
python argparse code-formatting
3个回答
12
投票

您的代码没有任何问题,这只是使用

argparse
模块的结果。我个人的偏好是将解析器的创建分解为函数。在这种情况下,您可以为您创建的每个子解析器创建一个函数。

def parse_args(*args):
    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()

4
投票

正如 TemporalWolf 所评论的,我会更一致地使用换行符,并且使用更多的换行符。即使代码现在看起来更长,我发现它更容易阅读:

  • 各个函数调用之间有更多的垂直空间,因此更容易在视觉上区分
  • 每行一个参数,因此更容易查看使用了哪些参数
  • 参数更接近左边距,因此需要更少的水平眼球运动和更少的不需要的换行符(例如分割
    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()

1
投票

开发人员之间没有任何关于此特定模块的样式的讨论(我一直在密切关注相关的错误/问题)。

我更关心解决问题而不是样式和布局,但确实喜欢易于阅读和理解的代码。如果有大块重复模式,我喜欢使用实用函数、字典和列表。

最近的一个 SO 问题,如何为 argparse 设计面向对象的子解析器?询问 OOP 子解析器定义。我参加了他的初始课程并添加了一个方法:

  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)

这是一个简单的例子,不涉及参数。

单元测试文件,

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='*'),
]

解析器测试类具有以下方法:

    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
解析器。

© www.soinside.com 2019 - 2024. All rights reserved.