Argparse: Argumentos necessários listados em "argumentos opcionais"?

229

Eu uso o seguinte código simples para analisar alguns argumentos; note que um deles é necessário. Infelizmente, quando o usuário executa o script sem fornecer o argumento, o texto de uso / ajuda exibido não indica que existe um argumento não opcional, que eu acho muito confuso. Como posso obter python para indicar que um argumento não é opcional?

Aqui está o código:

import argparse
if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='Foo')
    parser.add_argument('-i','--input', help='Input file name', required=True)
    parser.add_argument('-o','--output', help='Output file name', default="stdout")
    args = parser.parse_args()
    print ("Input file: %s" % args.input )
    print ("Output file: %s" % args.output )

Ao executar o código acima sem fornecer o argumento necessário, obtenho a seguinte saída:

usage: foo.py [-h] -i INPUT [-o OUTPUT]

Foo

optional arguments:
    -h, --help            show this help message and exit
    -i INPUT, --input INPUT
                          Input file name
    -o OUTPUT, --output OUTPUT
                          Output file name
mort
fonte
5
Na linha de uso, a -i INPUTpeça não é cercada por colchetes, que sutileza indica que é realmente necessário. Além disso, você pode explicar manualmente que através do helpparam
Jaime RGP
7
@JaimeRGP Sim, mas isso não é suficiente, é claro, e também é menos do que proeminente. O nome do grupo designado optional argumentspara os argumentos necessários ainda é enganoso.
Acumenus 5/10

Respostas:

316

Os parâmetros que começam com -ou --são geralmente considerados opcionais. Todos os outros parâmetros são parâmetros posicionais e, como tal, exigidos pelo design (como argumentos da função posicional). É possível exigir argumentos opcionais, mas isso é um pouco contra o design deles. Como eles ainda fazem parte dos argumentos não posicionais, eles ainda serão listados no cabeçalho confuso "argumentos opcionais", mesmo que sejam necessários. Os colchetes ausentes na parte de uso, no entanto, mostram que eles são realmente necessários.

Veja também a documentação :

Em geral, o módulo argparse assume que sinalizadores como -f e --bar indicam argumentos opcionais, que sempre podem ser omitidos na linha de comando.

Nota: As opções necessárias geralmente são consideradas más formas porque os usuários esperam que as opções sejam opcionais e, portanto, devem ser evitadas quando possível.

Dito isto, os cabeçalhos “argumentos posicionais” e “argumentos opcionais” na ajuda são gerados por dois grupos de argumentos nos quais os argumentos são automaticamente separados. Agora, você pode "invadir" e alterar o nome dos opcionais, mas uma solução muito mais elegante seria criar outro grupo para "argumentos nomeados obrigatórios" (ou o que você quiser chamá-los):

parser = argparse.ArgumentParser(description='Foo')
parser.add_argument('-o', '--output', help='Output file name', default='stdout')
requiredNamed = parser.add_argument_group('required named arguments')
requiredNamed.add_argument('-i', '--input', help='Input file name', required=True)
parser.parse_args(['-h'])
usage: [-h] [-o OUTPUT] -i INPUT

Foo

optional arguments:
  -h, --help            show this help message and exit
  -o OUTPUT, --output OUTPUT
                        Output file name

required named arguments:
  -i INPUT, --input INPUT
                        Input file name
cutucar
fonte
Eu tenho tido o mesmo problema. Eu tentei sua solução. Ele adiciona os argumentos ao novo grupo, mas meu código não parece funcionar depois disso. Todas as soluções seriam apreciadas. Link para o meu código - pastebin.com/PvC2aujz
Zarar Mahmud 13/04/19
1
@ZararMahmud: Você está passando argumentos vazios na linha 24 do seu código: em parser.parse_args([])vez disso, use parser.parse_args()sem argumentos para capturar o conteúdo de sys.argv. Por argparse
Devin
@ puxão: Boa solução! Mas isso não ajuda no caso de você precisar de grupos exclusivos mútuos ou estou perdendo alguma coisa?
Juiz
@ Juiz eu recomendaria ler este pymotw.com/3/argparse/#mutually-exclusive-options
Peter Moore
79

Como eu prefiro listar os argumentos obrigatórios antes do opcional, eu os pego através de:

    parser = argparse.ArgumentParser()
    parser._action_groups.pop()
    required = parser.add_argument_group('required arguments')
    optional = parser.add_argument_group('optional arguments')
    required.add_argument('--required_arg', required=True)
    optional.add_argument('--optional_arg')
    return parser.parse_args()

e isso gera:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
               [--optional_arg OPTIONAL_ARG]

required arguments:
  --required_arg REQUIRED_ARG

optional arguments:
  --optional_arg OPTIONAL_ARG

Eu posso viver sem 'ajuda' aparecendo no grupo de argumentos opcionais.

Karl Rosaen
fonte
3
Isso realmente força o argparse a tratar qualquer um dos argumentos conforme necessário?
Anthony
6
Eu acho que o argumento 'obrigatório' ainda precisa ser definido ao adicionar um argumento.
Karl Rosaen 13/10
Isso é muito legal.
Paul Cezanne
7
@ Anthony - não, você precisa do 'required = True' em add_argument para isso. A resposta acima ilustra apenas o agrupamento de argumentos.
user2275693
47

Construindo fora de @Karl Rosaen

parser = argparse.ArgumentParser()
optional = parser._action_groups.pop() # Edited this line
required = parser.add_argument_group('required arguments')
# remove this line: optional = parser...
required.add_argument('--required_arg', required=True)
optional.add_argument('--optional_arg')
parser._action_groups.append(optional) # added this line
return parser.parse_args()

e isso gera:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
           [--optional_arg OPTIONAL_ARG]

required arguments:
  --required_arg REQUIRED_ARG

optional arguments:
  -h, --help                    show this help message and exit
  --optional_arg OPTIONAL_ARG
RalphyZ
fonte
1
BTW, existem formas (métodos) de obter acesso _action_groupsem acessar o membro protegido? No meu caso, preciso adicionar algum argumento ao grupo (personalizado) já existente.
Machin
Isso é ótimo. Resolve o item --help exibido em uma segunda lista opcional.
Jeremy
Nota : esta resposta quebra a API exposta, verifique a resposta por Bryan_D abaixo.
lol
18

Mais uma vez, aproveitando o @RalphyZ

Este não quebra a API exposta.

from argparse import ArgumentParser, SUPPRESS
# Disable default help
parser = ArgumentParser(add_help=False)
required = parser.add_argument_group('required arguments')
optional = parser.add_argument_group('optional arguments')

# Add back help 
optional.add_argument(
    '-h',
    '--help',
    action='help',
    default=SUPPRESS,
    help='show this help message and exit'
)
required.add_argument('--required_arg', required=True)
optional.add_argument('--optional_arg')

Que mostrará o mesmo que acima e deve sobreviver a versões futuras:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
           [--optional_arg OPTIONAL_ARG]

required arguments:
  --required_arg REQUIRED_ARG

optional arguments:
  -h, --help                    show this help message and exit
  --optional_arg OPTIONAL_ARG
Bryan_D
fonte
Você pode explicar como a resposta de RalphyZ quebra a API exposta?
Jeremysprofile 19/09/19
5
_action_groupsdestina-se apenas a uso interno. Portanto, não há garantia de compatibilidade entre as versões.
Bryan_D