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?

musashiXXX
fonte

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.

unutbu
fonte
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.

vacri
fonte
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)
cgseller
fonte
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.

pd321
fonte
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.

AManOfScience
fonte
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.

Ievgen Popovych
fonte
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 ...

pauricthelodger
fonte
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])
Nuno André
fonte
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
ThorSummoner
fonte
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

zerocog
fonte
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!

Radtek
fonte
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

Tim Bray
fonte
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.

Maxim Temny
fonte