Python argparse grupo mútuo exclusivo

88

O que eu preciso é:

pro [-a xxx | [-b yyy -c zzz]]

Eu tentei isso, mas não funcionou. Alguém poderia me ajudar?

group= parser.add_argument_group('Model 2')
group_ex = group.add_mutually_exclusive_group()
group_ex.add_argument("-a", type=str, action = "store", default = "", help="test")
group_ex_2 = group_ex.add_argument_group("option 2")
group_ex_2.add_argument("-b", type=str, action = "store", default = "", help="test")
group_ex_2.add_argument("-c", type=str, action = "store", default = "", help="test")

Obrigado!

Sean
fonte
Conectando, mas eu queria mencionar minha biblioteca Joffrey . Permite que você faça o que esta pergunta deseja, por exemplo, sem obrigá-lo a usar subcomandos (como na resposta aceita) ou validar tudo sozinho (como na segunda resposta mais votada).
olá

Respostas:

109

add_mutually_exclusive_groupnão torna um grupo inteiro mutuamente exclusivo. Isso torna as opções dentro do grupo mutuamente exclusivas.

O que você está procurando são subcomandos . Em vez de prog [-a xxxx | [-b yyy -c zzz]], você teria:

prog 
  command 1 
    -a: ...
  command 2
    -b: ...
    -c: ...

Para invocar com o primeiro conjunto de argumentos:

prog command_1 -a xxxx

Para invocar com o segundo conjunto de argumentos:

prog command_2 -b yyyy -c zzzz

Você também pode definir os argumentos do subcomando como posicionais.

prog command_1 xxxx

Mais ou menos como git ou svn:

git commit -am
git merge develop

Exemplo de Trabalho

# create the top-level parser
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo', action='store_true', help='help for foo arg.')
subparsers = parser.add_subparsers(help='help for subcommand')

# create the parser for the "command_1" command
parser_a = subparsers.add_parser('command_1', help='command_1 help')
parser_a.add_argument('a', type=str, help='help for bar, positional')

# create the parser for the "command_2" command
parser_b = subparsers.add_parser('command_2', help='help for command_2')
parser_b.add_argument('-b', type=str, help='help for b')
parser_b.add_argument('-c', type=str, action='store', default='', help='test')

Teste-o

>>> parser.print_help()
usage: PROG [-h] [--foo] {command_1,command_2} ...

positional arguments:
  {command_1,command_2}
                        help for subcommand
    command_1           command_1 help
    command_2           help for command_2

optional arguments:
  -h, --help            show this help message and exit
  --foo                 help for foo arg.
>>>

>>> parser.parse_args(['command_1', 'working'])
Namespace(a='working', foo=False)
>>> parser.parse_args(['command_1', 'wellness', '-b x'])
usage: PROG [-h] [--foo] {command_1,command_2} ...
PROG: error: unrecognized arguments: -b x

Boa sorte.

Jonathan
fonte
Eu já os coloquei em um grupo de argumento. Como posso adicionar um subcomando neste caso? Obrigado!
Sean
1
Atualizado com código de amostra. Você não usará grupos, mas subparsers.
Jonathan
6
Mas como você faria o que o OP pediu originalmente? Atualmente, tenho um conjunto de subcomandos, mas um desses subcomandos precisa da capacidade de escolher entre[[-a <val>] | [-b <val1> -c <val2>]]
code_dredd
2
Isso não responde à pergunta porque não permite que você faça comandos "noname" e alcance o que o OP pediu[-a xxx | [-b yyy -c zzz]]
The Godfather
34

Embora a resposta de Jonathan seja perfeitamente adequada para opções complexas, há uma solução muito simples que funcionará para os casos simples, por exemplo, 1 opção exclui 2 outras opções como em

command [- a xxx | [ -b yyy | -c zzz ]] 

ou mesmo como na pergunta original:

pro [-a xxx | [-b yyy -c zzz]]

Aqui está como eu faria:

parser = argparse.ArgumentParser()

# group 1 
parser.add_argument("-q", "--query", help="query", required=False)
parser.add_argument("-f", "--fields", help="field names", required=False)

# group 2 
parser.add_argument("-a", "--aggregation", help="aggregation",
                    required=False)

Estou usando aqui as opções fornecidas a um wrapper de linha de comando para consultar um mongodb. A collectioninstância pode chamar o método aggregateou o método findcom argumentos opcionais querye fields, portanto, você vê por que os dois primeiros argumentos são compatíveis e o último não.

Então, agora eu corro parser.parse_args()e verifico seu conteúdo:

args = parser().parse_args()

print args.aggregation
if args.aggregation and (args.query or args.fields):
    print "-a and -q|-f are mutually exclusive ..."
    sys.exit(2)

Claro, este pequeno hack só funciona para casos simples e seria um pesadelo verificar todas as opções possíveis se você tiver muitas opções e grupos mutuamente exclusivos. Nesse caso, você deve dividir suas opções em grupos de comando como Jonathan sugeriu.

Oz123
fonte
5
Eu não chamaria isso de 'hack' para este caso, já que parece mais legível e gerenciável - obrigado por apontar isso!
sábio
13
Uma forma ainda melhor seria usar parser.error("-a and -q ..."). Desta forma, a ajuda de uso completa será impressa automaticamente.
WGH
Observe que, neste caso, você também precisa validar os casos como: (1) ambos qe fsão obrigatórios no primeiro grupo é o usuário, (2) qualquer um dos grupos é obrigatório. E isso faz com que a solução "simples" não seja mais tão simples. Então, eu concordaria que este é mais um hack para script artesanal, mas não uma solução real
The Godfather
4

Existe um patch Python (em desenvolvimento) que permite que você faça isso.
http://bugs.python.org/issue10984

A ideia é permitir a sobreposição de grupos mutuamente exclusivos. Então, usagepode ser parecido com:

pro [-a xxx | -b yyy] [-a xxx | -c zzz]

Alterar o código argparse para que você possa criar dois grupos como este foi a parte fácil. Alterar o usagecódigo de formatação exigiu escrever um código personalizadoHelpFormatter .

Em argparse, os grupos de ação não afetam a análise. Eles são apenas uma helpferramenta de formatação. No help, grupos mutuamente exclusivos afetam apenas a usagelinha. Ao analisar, o parserusa os grupos mutuamente exclusivos para construir um dicionário de conflitos potenciais ( anão pode ocorrer com bou c, bnão pode ocorrer coma etc.) e, em seguida, gera um erro se surgir um conflito.

Sem esse patch argparse, acho que sua melhor escolha é testar o namespace produzido por parse_argsvocê (por exemplo, se ambos ae btiverem valores não padrão) e aumentar seu próprio erro. Você pode até usar o mecanismo de erro do próprio analisador.

parser.error('custom error message')
hpaulj
fonte
1
Problema do Python: bugs.python.org/issue11588 está explorando maneiras de permitir que você escreva testes personalizados exclusivos / inclusivos.
hpaulj