代码之家  ›  专栏  ›  技术社区  ›  jpyams David Thompson

编写argparse解析器的最佳实践

  •  10
  • jpyams David Thompson  · 技术社区  · 7 年前

    是否有使用Python的最佳实践或风格指南 argparse 单元

    我与 argparse 在常规基础上,它很快就会占用相当多的行来处理所有配置。对于我发现的几乎所有事情 PEP 8 结果是干净、可读的代码,但这里不是。最终的结果总是一段难看的代码,读起来很痛苦。

    读起来很痛苦并不是一种恶作剧:

    Beautiful is better than ugly ... Readibilty counts

    丑陋的样本(主要遵循政治公众人物第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()
    
    3 回复  |  直到 7 年前
        1
  •  9
  •   Aldehir    7 年前

    argparse 单元我个人的偏好是将解析器的创建分解为函数。在这种情况下,可以为创建的每个子Parser创建一个函数。

    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()
    
        2
  •  3
  •   mkrieger1 djuarezg    7 年前

    正如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()
    
        3
  •  2
  •   hpaulj    7 年前

    最近的一个问题, How to design object oriented subparsers for argparse? 询问OOP子Parser定义。我上了他的第一堂课,并添加了一个方法:

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

    解析器测试类有如下方法:

        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