Exibir mensagem de ajuda com python argparse quando o script é chamado sem argumentos


226

Isso pode ser simples. Suponha que eu tenho um programa que usa argparse para processar argumentos / opções de linha de comando. A seguir, será impressa a mensagem 'ajuda':

./myprogram -h

ou:

./myprogram --help

Mas, se eu executar o script sem nenhum argumento, ele não fará nada. O que eu quero fazer é exibir a mensagem de uso quando for chamada sem argumentos. Como isso é feito?

Respostas:


273

Esta resposta vem de Steven Bethard nos grupos do Google . Estou publicando aqui para facilitar o acesso de pessoas sem uma Conta do Google.

Você pode substituir o comportamento padrão do errormétodo:

import argparse
import sys

class MyParser(argparse.ArgumentParser):
    def error(self, message):
        sys.stderr.write('error: %s\n' % message)
        self.print_help()
        sys.exit(2)

parser = MyParser()
parser.add_argument('foo', nargs='+')
args = parser.parse_args()

Observe que a solução acima imprimirá a mensagem de ajuda sempre que o error método for acionado. Por exemplo, test.py --blahtambém imprimirá a mensagem de ajuda se --blahnão for uma opção válida.

Se você deseja imprimir a mensagem de ajuda apenas se nenhum argumento for fornecido na linha de comando, talvez essa ainda seja a maneira mais fácil:

import argparse
import sys

parser=argparse.ArgumentParser()
parser.add_argument('foo', nargs='+')
if len(sys.argv)==1:
    parser.print_help(sys.stderr)
    sys.exit(1)
args=parser.parse_args()

Observe que, parser.print_help()por padrão, imprime em stdout. Como init_js sugere , use parser.print_help(sys.stderr)para imprimir em stderr.


Sim ... era sobre isso que eu estava pensando, se havia uma maneira de o argparse lidar com esse cenário. Obrigado!
— musashiXXX

6
Na segunda solução que eu uso parser.print_usage()no lugar de parser.print_help()- a mensagem de ajuda inclui o uso, mas é mais detalhada.
— user2314737

5
Eu teria votado na segunda parte da resposta, mas substituir me error()parece uma péssima idéia. Ele serve a um propósito diferente, não foi projetado para imprimir uma ajuda ou uso amigável.
— Peterino

@ Peterino - a substituição está ocorrendo em uma classe filho, portanto isso não deve ser um problema. É explícito.
— Marcel Wilson #

1
@unutbu WONDERFUL! Exatamente o que eu precisava. Uma pergunta: isso também pode ser aplicado aos subcomandos? Normalmente eu apenas recebo `` Namespace (output = None) `. Como posso disparar um erro facilmente em TODOS os subcomandos? Eu gostaria de acionar um erro lá.
— Jonathan Komar

56

Em vez de escrever uma classe, uma tentativa / exceção pode ser usada

try:
    options = parser.parse_args()
except:
    parser.print_help()
    sys.exit(0)

A vantagem é que o fluxo de trabalho é mais claro e você não precisa de uma classe de stub. A desvantagem é que a primeira linha de 'uso' é impressa duas vezes.

Isso precisará de pelo menos um argumento obrigatório. Sem argumentos obrigatórios, é válido fornecer zero args na linha de comando.


eu também prefiro isso à resposta aceita. A adição de uma classe é exagerada para obter ajuda na impressão quando os argumentos são inesperados. Deixe o excelente módulo argparse lidar com casos de erro para você.
— 22416 Nicole Finnie

7
Esse código imprime ajuda 2 vezes se o -hsinalizador for usado e impressões desnecessárias ajudam se o --versionsinalizador for usado. Para mitigar esses problemas, você pode verificar o tipo de erro como este:except SystemExit as err: if err.code == 2: parser.print_help()
— pkowalczyk

25

Com argparse você pode fazer:

parser.argparse.ArgumentParser()
#parser.add_args here

#sys.argv includes a list of elements starting with the program
if len(sys.argv) < 2:
    parser.print_usage()
    sys.exit(1)

5
Isto deve vir antes a chamada paraparser.parse_args()
— Bob Stein

18

Se você tiver argumentos que devem ser especificados para a execução do script - use o parâmetro necessário para ArgumentParser, como mostrado abaixo: -

parser.add_argument('--foo', required=True)

parse_args () relatará um erro se o script for executado sem argumentos.


2
Essa é a solução mais simples e funcionará com opções inválidas especificadas também.
— Steve Scherer

1
Acordado. Eu acho que é sempre melhor aproveitar as habilidades internas do analisador de argumentos do que escrever um manipulador adicional de algum tipo.
— 22618 Christopher Hunter

18

Se você associar funções padrão para (sub) analisadores, conforme mencionado em add_subparsers, você pode simplesmente adicioná-lo como a ação padrão:

parser = argparse.ArgumentParser()
parser.set_defaults(func=lambda x: parser.print_usage())
args = parser.parse_args()
args.func(args)

Adicione a tentativa, exceto se você criar exceções devido à falta de argumentos posicionais.


1
Esta resposta é tão subestimada. Simples e funciona muito bem com sub-analisadores.
— orodbhen

Ótima resposta! A única alteração que fiz foi usar um lambda sem parâmetro.
— boh717

12

A solução mais limpa será passar manualmente o argumento padrão se nenhum foi fornecido na linha de comando:

parser.parse_args(args=None if sys.argv[1:] else ['--help'])

Exemplo completo:

import argparse, sys

parser = argparse.ArgumentParser()
parser.add_argument('--host', default='localhost', help='Host to connect to')
# parse arguments
args = parser.parse_args(args=None if sys.argv[1:] else ['--help'])

# use your args
print("connecting to {}".format(args.host))

Isso imprimirá a ajuda completa (não de uso curto) se for chamado sem argumentos.


2
sys.argv[1:]é um idioma muito comum. Eu vejo parser.parse_args(None if sys.argv[1:] else ['-h'])mais idiomática e mais limpa.
— Nuno André

1
@ NunoAndré obrigado - atualizou a resposta. Parece realmente mais pitônico.
— Ievgen Popovych

10

Jogando minha versão na pilha aqui:

import argparse

parser = argparse.ArgumentParser()
args = parser.parse_args()
if not vars(args):
    parser.print_help()
    parser.exit(1)

Você pode notar o parser.exit- eu faço isso principalmente porque salva uma linha de importação, se esse foi o único motivo para syso arquivo ...


parser.exit (1) é legal! Boa adição.
— cgseller

4
Infelizmente, parser.parse_args () será encerrado se um argumento posicional estiver ausente. Portanto, isso funciona apenas ao usar argumentos opcionais.
— Marcel Wilson

1
@MarcelWilson, de fato - boa captura! Vou pensar em como mudar isso.
— pauricthelodger

not vars(args)pode não funcionar quando os argumentos têm defaultmétodo.
— Funkid 22/11/19

5

Há um par de one-liners com sys.argv[1:](o idioma do Python muito comum para referenciar os argumentos da linha de comando, sendo sys.argv[0]o nome do script) que podem fazer o trabalho.

O primeiro é auto-explicativo, limpo e pitônico:

args = parser.parse_args(None if sys.argv[1:] else ['-h'])

O segundo é um pouco mais hackier. Combinando o fato avaliado anteriormente de que uma lista vazia está Falsecom oTrue == 1False == 0 equivalências e, você obtém o seguinte:

args = parser.parse_args([None, ['-h']][not sys.argv[1:]])

Talvez muitos colchetes, mas muito claro se uma seleção de argumento anterior foi feita.

_, *av = sys.argv
args = parser.parse_args([None, ['-h']][not av])

1
parser.print_help()
parser.exit()

O parser.exitmétodo também aceita um status(código de retorno) e um messagevalor (inclua uma nova linha à direita!).

um exemplo opinativo :)

#!/usr/bin/env python3

""" Example argparser based python file
"""

import argparse

ARGP = argparse.ArgumentParser(
    description=__doc__,
    formatter_class=argparse.RawTextHelpFormatter,
)
ARGP.add_argument('--example', action='store_true', help='Example Argument')


def main(argp=None):
    if argp is None:
        argp = ARGP.parse_args()  # pragma: no cover

    if 'soemthing_went_wrong' and not argp.example:
        ARGP.print_help()
        ARGP.exit(status=128, message="\nI just don't know what went wrong, maybe missing --example condition?\n")


if __name__ == '__main__':
    main()  # pragma: no cover

Chamadas de exemplo:

$ python3 ~ / helloworld.py; eco $?
uso: helloworld.py [-h] [--exemplo]

 Exemplo de arquivo python baseado em argparser

argumentos opcionais:
  -h, --help mostra esta mensagem de ajuda e sai
  Exemplo de argumento

Só não sei o que deu errado, talvez em falta - exemplo de condição?
128
$ python3 ~ / helloworld.py --exemplo; eco $?
0 0

0

Defina seus argumentos posicionais com nargs e verifique se os argumentos posicionais estão vazios.

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('file', nargs='?')
args = parser.parse_args()
if not args.file:
    parser.print_help()

Referência Python nargs


0

Aqui está outra maneira de fazer isso, se você precisar de algo flexível, onde deseja exibir ajuda se parâmetros específicos forem passados, nenhum ou mais que um argumento conflitante:

import argparse
import sys

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-d', '--days', required=False,  help="Check mapped inventory that is x days old", default=None)
    parser.add_argument('-e', '--event', required=False, action="store", dest="event_id",
                        help="Check mapped inventory for a specific event", default=None)
    parser.add_argument('-b', '--broker', required=False, action="store", dest="broker_id",
                        help="Check mapped inventory for a broker", default=None)
    parser.add_argument('-k', '--keyword', required=False, action="store", dest="event_keyword",
                        help="Check mapped inventory for a specific event keyword", default=None)
    parser.add_argument('-p', '--product', required=False, action="store", dest="product_id",
                        help="Check mapped inventory for a specific product", default=None)
    parser.add_argument('-m', '--metadata', required=False, action="store", dest="metadata",
                        help="Check mapped inventory for specific metadata, good for debugging past tix", default=None)
    parser.add_argument('-u', '--update', required=False, action="store_true", dest="make_updates",
                        help="Update the event for a product if there is a difference, default No", default=False)
    args = parser.parse_args()

    days = args.days
    event_id = args.event_id
    broker_id = args.broker_id
    event_keyword = args.event_keyword
    product_id = args.product_id
    metadata = args.metadata
    make_updates = args.make_updates

    no_change_counter = 0
    change_counter = 0

    req_arg = bool(days) + bool(event_id) + bool(broker_id) + bool(product_id) + bool(event_keyword) + bool(metadata)
    if not req_arg:
        print("Need to specify days, broker id, event id, event keyword or past tickets full metadata")
        parser.print_help()
        sys.exit()
    elif req_arg != 1:
        print("More than one option specified. Need to specify only one required option")
        parser.print_help()
        sys.exit()

    # Processing logic here ...

Felicidades!


Eu acho que você seria subparsers uso muito mais fácil ou mutually_exclusive_group
— Tim Bray

0

Se seu comando for algo em que um usuário precise escolher alguma ação, use um grupo mutuamente exclusivo com required = True .

Isso é uma extensão da resposta dada pelo pd321.

import argparse

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--batch", action='store', type=int,  metavar='pay_id')
group.add_argument("--list", action='store_true')
group.add_argument("--all", action='store_true', help='check all payments')

args=parser.parse_args()

if args.batch:
    print('batch {}'.format(args.batch))

if args.list:
    print('list')

if args.all:
    print('all')

Resultado:

$ python3 a_test.py
uso: a_test.py [-h] (--batch pay_id | --list | --all)
a_test.py: error: um dos argumentos --batch --list --all é obrigatório

Isso apenas dá a ajuda básica. E algumas das outras respostas lhe darão a ajuda completa. Mas pelo menos seus usuários sabem que podem fazer -h


0

Isso não é bom (também porque intercepta todos os erros), mas:

def _error(parser):
    def wrapper(interceptor):
        parser.print_help()

        sys.exit(-1)

    return wrapper

def _args_get(args=sys.argv[1:]):
    parser = argparser.ArgumentParser()

    parser.error = _error(parser)

    parser.add_argument(...)
    ...

Aqui está a definição da errorfunção da ArgumentParserclasse:

https://github.com/python/cpython/blob/276eb67c29d05a93fbc22eea5470282e73700d20/Lib/argparse.py#L2374

. Como você vê, após a assinatura, são necessários dois argumentos. No entanto, funções fora da classe nada sabem sobre o primeiro argumento: selfporque, grosso modo, esse é um parâmetro para a classe. (Eu sei, que você sabe ...) Assim, basta passar própria selfe messageem _error(...)não pode (

def _error(self, message):
    self.print_help()

    sys.exit(-1)

def _args_get(args=sys.argv[1:]):
    parser = argparser.ArgumentParser()

    parser.error = _error
    ...
...

irá produzir:

...
"AttributeError: 'str' object has no attribute 'print_help'"

) Você pode passar parser( self) na _errorfunção, chamando-a:

def _error(self, message):
    self.print_help()

    sys.exit(-1)

def _args_get(args=sys.argv[1:]):
    parser = argparser.ArgumentParser()

    parser.error = _error(parser)
    ...
...

, mas você não deseja sair do programa no momento. Depois devolva:

def _error(parser):
    def wrapper():
        parser.print_help()

        sys.exit(-1)

    return wrapper
...

. No entanto, parsernão sabe, que ele foi modificado; assim, quando ocorrer um erro, ele será responsável (a propósito, sua tradução localizada). Bem, então intercepte:

def _error(parser):
    def wrapper(interceptor):
        parser.print_help()

        sys.exit(-1)

    return wrapper
...

. Agora, quando ocorrer um erro e parserenviar sua causa, você o interceptará, examinará isso e ... jogará fora.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.