Python argparse按需导入类型、选项等

Python argparse按需导入类型、选项等,python,argparse,Python,Argparse,我有一个相当大的程序,它有一个基于argparse的CLI交互,有几个子解析器。subparser参数的支持选项列表是根据DB查询、解析不同的xml文件、进行不同的计算等确定的,因此它非常耗费IO和时间 问题是,当我运行脚本时,argparse似乎为所有子解析器获取选项,这增加了相当大且恼人的启动延迟 是否有一种方法可以使当前使用的子解析器的argparse仅获取和验证选项 一个解决方案可能是将所有验证逻辑移到代码内部的更深处,但这意味着需要做大量的工作,如果可能的话,我希望避免这些工作 谢谢要

我有一个相当大的程序,它有一个基于
argparse
的CLI交互,有几个子解析器。subparser参数的支持选项列表是根据DB查询、解析不同的xml文件、进行不同的计算等确定的,因此它非常耗费IO和时间

问题是,当我运行脚本时,argparse似乎为所有子解析器获取
选项
,这增加了相当大且恼人的启动延迟

是否有一种方法可以使当前使用的子解析器的
argparse
仅获取和验证
选项

一个解决方案可能是将所有验证逻辑移到代码内部的更深处,但这意味着需要做大量的工作,如果可能的话,我希望避免这些工作


谢谢

要延迟选择的获取,您可以分两个阶段分析命令行:在第一阶段,您只找到子parser,在第二阶段,子parser用于分析其余参数:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('subparser', choices=['foo','bar'])

def foo_parser():
    parser = argparse.ArgumentParser()
    parser.add_argument('fooval', choices='123')
    return parser

def bar_parser():
    parser = argparse.ArgumentParser()
    parser.add_argument('barval', choices='ABC')
    return parser

dispatch = {'foo':foo_parser, 'bar':bar_parser}
args, unknown = parser.parse_known_args()
args = dispatch[args.subparser]().parse_args(unknown)
print(args)
它可以这样使用:

% script.py foo 2
Namespace(fooval='2')

% script.py bar A
Namespace(barval='A')
请注意,顶级帮助消息将不那么友好,因为它只能告诉您有关子Parser选项的信息:

% script.py -h
usage: script.py [-h] {foo,bar}
...
要查找每个子Parser中的选项信息,用户必须选择子Parser并将
-h
传递给它:

% script.py bar -- -h
usage: script.py [-h] {A,B,C}

--
之后的所有参数都被视为非选项(to
script.py
),因此由
bar\u解析器进行解析。在这种情况下,选项是一系列整数。我认为需要昂贵的数据库查找的情况也可以以类似的方式实现

# argparse with lazy choices

class LazyChoice(object):
    # large range
    def __init__(self, argmax):
        self.argmax=argmax
    def __contains__(self, item):
        # a 'lazy' test that does not enumerate all choices
        return item<=self.argmax
    def __iter__(self):
        # iterable for display in error message
        # use is in:
        # tup = value, ', '.join(map(repr, action.choices))
        # metavar bypasses this when formatting help/usage
        return iter(['integers less than %s'%self.argmax])

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--regular','-r',choices=['one','two'])
larg = parser.add_argument('--lazy','-l', choices=LazyChoice(10))
larg.type = int
print parser.parse_args()
#使用惰性选项解析argparse
类LazyChoice(对象):
#大范围
def_uuuinit_uuu(self,argmax):
self.argmax=argmax
定义包含(自身,项目):
#不枚举所有选择的“惰性”测试

return item这是一个脚本,用于测试延迟创建子parser的想法,直到实际需要它为止。从理论上讲,它可能会节省启动时间,只需创建实际需要的次parser

我使用
nargs=argparse.PARSER
在主解析器中复制子解析器行为<代码>帮助
行为类似

# lazy subparsers test
# lazy behaves much like a regular subparser case, but only creates one subparser
# for N=5 time differences do not rise above the noise

import argparse

def regular(N):
    parser = argparse.ArgumentParser()
    sp = parser.add_subparsers(dest='cmd')
    for i in range(N):
        spp = sp.add_parser('cmd%s'%i)
        spp.set_defaults(func='cmd%s'%(10*i))
        spp.add_argument('-f','--foo')
        spp.add_argument('pos', nargs='*')
    return parser

def lazy(N):
    parser = argparse.ArgumentParser()
    sp = parser.add_argument('cmd', nargs=argparse.PARSER, choices=[])
    for i in range(N):
        sp.choices.append('cmd%s'%i)
    return parser

def subpar(cmd):
    cmd, argv = cmd[0], cmd[1:]
    parser = argparse.ArgumentParser(prog=cmd)
    parser.add_argument('-f','--foo')
    parser.add_argument('pos', nargs='*')
    parser.set_defaults(func=cmd)
    args = parser.parse_args(argv)
    return args

N = 5
mode = True #False
argv = 'cmd1 -f1 a b c'.split()
if mode:
    args = regular(N).parse_args(argv)
    print(args)
else:
    args = lazy(N).parse_args(argv)
    print(args)
    if isinstance(args.cmd, list):
        sargs = subpar(args.cmd)
        print(sargs)
使用不同的
模式值进行测试运行
(N=5)


N
必须大得多才能开始看到效果。

我通过创建一个简单的
ArgumentParser
子类解决了这个问题:

import argparse

class ArgumentParser(argparse.ArgumentParser):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.lazy_init = None

    def parse_known_args(self, args=None, namespace=None):
        if self.lazy_init is not None:
            self.lazy_init()
            self.lazy_init = None

        return super().parse_known_args(args, namespace)
然后我可以如下使用它:

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command', title='commands', parser_class=ArgumentParser)
subparsers.required = True

subparser = subparsers.add_parser(
    'do-something', help="do something",
    description="Do something great.",
)

def lazy_init():
    from my_database import data

    subparser.add_argument(
        '-o', '--option', choices=data.expensive_fetch(), action='save',
    )

subparser.lazy_init = lazy_init

只有当父解析器尝试解析子解析器的参数时,才会初始化子解析器。因此,如果执行
program-h
它将不会初始化子解析器,但是如果执行
program-do-h
它将初始化。如果选择是动态的(计算结果),用户如何知道选择是什么?用户是否必须运行程序两次——一次查看帮助消息,然后第二次实际运行程序?这是不好的,因为它需要生成两次选择。如果将
选择设置为DB表,则可以添加,以便在其他DB表更改时,可以运行相应的命令来更新
选择表。要处理更改的XML文件,可以使用在XML文件更改时更新
选项
DB表。。。如果您这样做,那么您的程序只需读取
选项
DB表即可准备
argparse
选项
仅在可用选项数量较大且帮助文本无法读取时用作验证。我正在使用
metavar
属性格式化帮助文本。这没关系,因为通常情况下,用户知道/猜测可用的选项,但他们可能会犯打字错误或类似的错误。关于db触发器和看门狗,这听起来是一个有趣的选择。我会调查的。感谢您的评论。如果您不需要帮助显示选项,则在
parse_args
之后进行自己的验证可能会更简单。您仍然可以使用语法分析器的错误机制(尽管使用子语法分析器可能更困难)。choices对象需要一个
\uuuuu包含的
(用于响应
中的
)进行测试。它还需要是可编辑的(以生成错误消息)<代码>列表
设置
dict
是常用的对象,但是您可以编写自己的类来提供相同类型的功能,但只能按需提供。我已经尝试过了,效果很好。一个缺点是,它基本上创建了一种自定义方式来处理子解析器,而不是使用
argparse
内置子解析器,但这对我的项目来说还可以。
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command', title='commands', parser_class=ArgumentParser)
subparsers.required = True

subparser = subparsers.add_parser(
    'do-something', help="do something",
    description="Do something great.",
)

def lazy_init():
    from my_database import data

    subparser.add_argument(
        '-o', '--option', choices=data.expensive_fetch(), action='save',
    )

subparser.lazy_init = lazy_init