Qual é a melhor maneira de analisar argumentos de linha de comando? [fechadas]

251

Qual é o método ou a biblioteca mais fácil , concisa e flexível para analisar argumentos de linha de comando do Python?

Kamens
fonte

Respostas:

183

Esta resposta sugere o optparseque é apropriado para versões mais antigas do Python. Para Python 2.7 e superior, argparsesubstitui optparse. Veja esta resposta para mais informações.

Como outras pessoas apontaram, é melhor você optar pelo oposto do que pelo getopt. O getopt é praticamente um mapeamento individual das funções padrão da biblioteca getopt (3) C, e não é muito fácil de usar.

O optparse, embora seja um pouco mais detalhado, é muito melhor estruturado e mais simples de estender mais tarde.

Aqui está uma linha típica para adicionar uma opção ao seu analisador:

parser.add_option('-q', '--query',
            action="store", dest="query",
            help="query string", default="spam")

Ele praticamente fala por si; no tempo de processamento, ele aceitará -q ou --query como opções, armazenará o argumento em um atributo chamado query e terá um valor padrão se você não o especificar. Também é auto-documentado, na medida em que você declara o argumento de ajuda (que será usado quando executado com -h / - help) ali mesmo com a opção

Geralmente você analisa seus argumentos com:

options, args = parser.parse_args()

Por padrão, isso analisará os argumentos padrão passados ​​para o script (sys.argv [1:])

options.query será então definido como o valor que você passou para o script.

Você cria um analisador simplesmente fazendo

parser = optparse.OptionParser()

Estes são todos os princípios básicos que você precisa. Aqui está um script Python completo que mostra isso:

import optparse

parser = optparse.OptionParser()

parser.add_option('-q', '--query',
    action="store", dest="query",
    help="query string", default="spam")

options, args = parser.parse_args()

print 'Query string:', options.query

5 linhas de python que mostram o básico.

Salve-o em sample.py e execute-o uma vez com

python sample.py

e uma vez com

python sample.py --query myquery

Além disso, você descobrirá que o optparse é muito fácil de estender. Em um dos meus projetos, criei uma classe Command que permite aninhar subcomandos em uma árvore de comandos facilmente. Ele usa optparse fortemente para encadear comandos juntos. Não é algo que eu possa explicar facilmente em poucas linhas, mas fique à vontade para procurar no meu repositório a classe principal, bem como uma classe que o use e o analisador de opções

Thomas Vander Stichele
fonte
9
Esta resposta é maravilhosamente clara e fácil de seguir - para python 2.3 a 2.6. Para python 2.7+, não é a melhor resposta, já que o argparse agora faz parte da biblioteca padrão e o optparse é profanado.
28511 matt wilkie
No meu caso, quero criar um perfil do meu aplicativo para detectar a lentidão. Existe outra ferramenta chamada [atum] ( github.com/nschloe/tuna ) que permite o perfil de todo o aplicativo simplesmente adicionando agrs, -mcProfile -o program.profmas o agrparcer está capturando esses argumentos, como passar esses argumentos para python exe ???
Yogeshwar
231

argparseé o caminho a percorrer. Aqui está um breve resumo de como usá-lo:

1) Inicializar

import argparse

# Instantiate the parser
parser = argparse.ArgumentParser(description='Optional app description')

2) Adicionar argumentos

# Required positional argument
parser.add_argument('pos_arg', type=int,
                    help='A required integer positional argument')

# Optional positional argument
parser.add_argument('opt_pos_arg', type=int, nargs='?',
                    help='An optional integer positional argument')

# Optional argument
parser.add_argument('--opt_arg', type=int,
                    help='An optional integer argument')

# Switch
parser.add_argument('--switch', action='store_true',
                    help='A boolean switch')

3) Analisar

args = parser.parse_args()

4) Acesso

print("Argument values:")
print(args.pos_arg)
print(args.opt_pos_arg)
print(args.opt_arg)
print(args.switch)

5) Verificar valores

if args.pos_arg > 10:
    parser.error("pos_arg cannot be larger than 10")

Uso

Uso correto:

$ ./app 1 2 --opt_arg 3 --switch

Argument values:
1
2
3
True

Argumentos incorretos:

$ ./app foo 2 --opt_arg 3 --switch
usage: convert [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
app: error: argument pos_arg: invalid int value: 'foo'

$ ./app 11 2 --opt_arg 3
Argument values:
11
2
3
False
usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
convert: error: pos_arg cannot be larger than 10

Ajuda completa:

$ ./app -h

usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]

Optional app description

positional arguments:
  pos_arg            A required integer positional argument
  opt_pos_arg        An optional integer positional argument

optional arguments:
  -h, --help         show this help message and exit
  --opt_arg OPT_ARG  An optional integer argument
  --switch           A boolean switch
Andrzej Pronobis
fonte
10
Isto é muito concisa e útil e aqui está o doc oficial (por conveniência): docs.python.org/3/library/argparse.html
Christophe Roussy
1
Se você achar argparse muito detalhado, use plac .
Nimitz14 17/01
76

Usando docopt

Desde 2012, existe um módulo muito fácil, poderoso e muito interessante para análise de argumentos chamado docopt . Aqui está um exemplo retirado de sua documentação:

"""Naval Fate.

Usage:
  naval_fate.py ship new <name>...
  naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
  naval_fate.py ship shoot <x> <y>
  naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
  naval_fate.py (-h | --help)
  naval_fate.py --version

Options:
  -h --help     Show this screen.
  --version     Show version.
  --speed=<kn>  Speed in knots [default: 10].
  --moored      Moored (anchored) mine.
  --drifting    Drifting mine.

"""
from docopt import docopt


if __name__ == '__main__':
    arguments = docopt(__doc__, version='Naval Fate 2.0')
    print(arguments)

Então é isso: duas linhas de código, mais a sua sequência de documentos, que é essencial e você obtém seus argumentos analisados ​​e disponíveis no seu objeto de argumentos.

Usando python-fire

Desde 2017, há outro módulo interessante chamado python-fire . Ele pode gerar uma interface CLI para seu código, sem a análise de argumentos. Aqui está um exemplo simples da documentação (este pequeno programa expõe a função doubleà linha de comando):

import fire

class Calculator(object):

  def double(self, number):
    return 2 * number

if __name__ == '__main__':
  fire.Fire(Calculator)

Na linha de comando, você pode executar:

> calculator.py double 10
20
> calculator.py double --number=15
30
ndemou
fonte
4
como o docopt "não precisa de instalação"? é um módulo python, portanto, ele precisa ser instalado. 'ImportError: No módulo chamado docopt'
afiado
1
@keen não está incluído no python, com certeza, mas você não precisa instalá-lo: "você pode simplesmente soltar o arquivo docopt.py no seu projeto - ele é independente" - github.com/docopt/docopt
ndemou
9
nós apenas temos definições diferentes de instalação - e eu gostaria de salientar isso para futuros leitores.
afiado
1
@keen Eu adicionei uma nota sobre "nenhuma instalação" para as pessoas compartilhando sua definição :-)
ndemou
39

A nova maneira moderna é argparsepor esses motivos. argparse> optparse> getopt

update: A partir do py2.7, o argparse faz parte da biblioteca padrão e o optparse está obsoleto.

Silfheed
fonte
Seu link principal é 404, então substituí-o por um link para uma pergunta SO que aborda o mesmo tópico.
Joe Holloway
28

Eu prefiro o Click . Ele abstrai as opções de gerenciamento e permite "(...) criar belas interfaces de linha de comando de uma maneira composível, com o mínimo de código necessário".

Aqui está um exemplo de uso:

import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
              help='The person to greet.')
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for x in range(count):
        click.echo('Hello %s!' % name)

if __name__ == '__main__':
    hello()

Também gera automaticamente páginas de ajuda bem formatadas:

$ python hello.py --help
Usage: hello.py [OPTIONS]

  Simple program that greets NAME for a total of COUNT times.

Options:
  --count INTEGER  Number of greetings.
  --name TEXT      The person to greet.
  --help           Show this message and exit.
suda
fonte
14

Praticamente todo mundo está usando getopt

Aqui está o código de exemplo para o documento:

import getopt, sys

def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:], "ho:v", ["help", "output="])
    except getopt.GetoptError:
        # print help information and exit:
        usage()
        sys.exit(2)
    output = None
    verbose = False
    for o, a in opts:
        if o == "-v":
            verbose = True
        if o in ("-h", "--help"):
            usage()
            sys.exit()
        if o in ("-o", "--output"):
            output = a

Então, em uma palavra, aqui está como isso funciona.

Você tem dois tipos de opções. Aqueles que estão recebendo argumentos e aqueles que são como interruptores.

sys.argvé praticamente seu char** argvem C. Como em C, você pula o primeiro elemento, que é o nome do seu programa, e analisa apenas os argumentos:sys.argv[1:]

Getopt.getopt irá analisá-lo de acordo com a regra que você fornecer no argumento.

"ho:v"aqui descreve as curtas argumentos: -ONELETTER. O :meio que -oaceita um argumento.

Finalmente ["help", "output="]descreve argumentos longos ( --MORETHANONELETTER). A =saída posterior significa novamente que a saída aceita um argumento.

O resultado é uma lista de pares (opção, argumento)

Se uma opção não aceita nenhum argumento (como --helpaqui), a argparte é uma sequência vazia. Você geralmente deseja fazer um loop nesta lista e testar o nome da opção como no exemplo.

Espero que isso tenha ajudado.

fulmicoton
fonte
6
Com a suspensão de uso getoptnas versões mais recentes do Python, essa resposta ficou desatualizada.
shuttle87
1
@ shuttle87 No python3.7.2, ele getoptainda não está obsoleto… Mas sua documentação afirma que ele é principalmente fornecido para usuários familiarizados com a getopt()função C e reconhece que para outros usuários argparsepode ser uma solução melhor, permitindo "escrever menos código e obter melhor ajuda e mensagens de erro ".
Skippy le Grand Gourou
6

Caso precise, isso pode ajudar se você precisar pegar argumentos unicode no Win32 (2K, XP, etc.):


from ctypes import *

def wmain(argc, argv):
    print argc
    for i in argv:
        print i
    return 0

def startup():
    size = c_int()
    ptr = windll.shell32.CommandLineToArgvW(windll.kernel32.GetCommandLineW(), byref(size))
    ref = c_wchar_p * size.value
    raw = ref.from_address(ptr)
    args = [arg for arg in raw]
    windll.kernel32.LocalFree(ptr)
    exit(wmain(len(args), args))
startup()
Shadow2531
fonte
Obrigado. Esse script me ajudou a elaborar algumas citações realmente complicadas que eu precisava fazer ao passar comandos de inicialização para o GVim.
Telotortium
6

Padrões de argumento de linha de comando leve

Embora argparseseja ótimo e seja a resposta certa para opções de linha de comando totalmente documentadas e recursos avançados, você pode usar os padrões de argumento de função para manipular argumentos posicionais diretos com muita simplicidade.

import sys

def get_args(name='default', first='a', second=2):
    return first, int(second)

first, second = get_args(*sys.argv)
print first, second

O argumento 'name' captura o nome do script e não é usado. A saída de teste é assim:

> ./test.py
a 2
> ./test.py A
A 2
> ./test.py A 20
A 20

Para scripts simples em que eu apenas quero alguns valores padrão, acho isso suficiente. Você também pode querer incluir algum tipo de coerção nos valores de retorno ou os valores da linha de comando serão cadeias de caracteres.

Simon Hibbs
fonte
2
as aspas são incompatíveis na declaração de definição.
Historystamp #
3

Eu prefiro optar por obter. É muito declarativo: você diz os nomes das opções e os efeitos que elas devem ter (por exemplo, definindo um campo booleano) e fornece um dicionário preenchido de acordo com suas especificações.

http://docs.python.org/lib/module-optparse.html

Chris Conway
fonte
3

Acho que a melhor maneira para projetos maiores é a opção optparse, mas se você estiver procurando uma maneira fácil, talvez http://werkzeug.pocoo.org/documentation/script seja algo para você.

from werkzeug import script

# actions go here
def action_foo(name=""):
    """action foo does foo"""
    pass

def action_bar(id=0, title="default title"):
    """action bar does bar"""
    pass

if __name__ == '__main__':
    script.run()

Então, basicamente, toda função action_ * é exposta à linha de comando e uma boa mensagem de ajuda é gerada gratuitamente.

python foo.py 
usage: foo.py <action> [<options>]
       foo.py --help

actions:
  bar:
    action bar does bar

    --id                          integer   0
    --title                       string    default title

  foo:
    action foo does foo

    --name                        string
Peter Hoffmann
fonte
Eu desenvolvi um pequeno pacote utilizando criação argumentos automática: declarative_parser. Obviamente, se alguém estiver trabalhando com o werkzeug, talvez seja melhor manter o arquivo werkzung.script. De qualquer forma, sou um grande fã dessa abordagem.
Krassowski
3

O código Argparse pode ser maior que o código de implementação real!

Esse é um problema que acho que nas opções de análise de argumento mais populares é que, se seus parâmetros são modestos, o código para documentá-los se torna desproporcionalmente grande para o benefício que eles oferecem.

Um recém-chegado relativo à cena de análise de argumentos (eu acho) é plac .

Ele faz algumas trocas reconhecidas com argparse, mas usa a documentação embutida e envolve simplesmente a main()função de função type:

def main(excel_file_path: "Path to input training file.",
     excel_sheet_name:"Name of the excel sheet containing training data including columns 'Label' and 'Description'.",
     existing_model_path: "Path to an existing model to refine."=None,
     batch_size_start: "The smallest size of any minibatch."=10.,
     batch_size_stop:  "The largest size of any minibatch."=250.,
     batch_size_step:  "The step for increase in minibatch size."=1.002,
     batch_test_steps: "Flag.  If True, show minibatch steps."=False):
"Train a Spacy (http://spacy.io/) text classification model with gold document and label data until the model nears convergence (LOSS < 0.5)."

    pass # Implementation code goes here!

if __name__ == '__main__':
    import plac; plac.call(main)
Controle de qualidade coletivo
fonte
Ponto de informação: o uso mais limpo de plac (como mostrado no exemplo) é apenas para Python 3.x, porque ele usa anotações de função 3.x.
barny
1

consoleargs merece ser mencionado aqui. É muito fácil de usar. Confira:

from consoleargs import command

@command
def main(url, name=None):
  """
  :param url: Remote URL 
  :param name: File name
  """
  print """Downloading url '%r' into file '%r'""" % (url, name)

if __name__ == '__main__':
  main()

Agora no console:

% python demo.py --help
Usage: demo.py URL [OPTIONS]

URL:    Remote URL 

Options:
    --name -n   File name

% python demo.py http://www.google.com/
Downloading url ''http://www.google.com/'' into file 'None'

% python demo.py http://www.google.com/ --name=index.html
Downloading url ''http://www.google.com/'' into file ''index.html''
perseguir
fonte
Eu usei uma abordagem semelhante no declarativo-analisador , veja dedução de argumentos (digitação, docstrings, kwargs) em docs. Principais diferenças: python3, dicas de tipo, instalável por pip.
Krassowski
1
Última confirmação em 2012
Boris
0

Aqui está um método, não uma biblioteca, que parece funcionar para mim.

Os objetivos aqui devem ser concisos, cada argumento analisado por uma única linha, os argumentos alinhados para facilitar a leitura, o código é simples e não depende de nenhum módulo especial (apenas os + sys), alerta sobre argumentos ausentes ou desconhecidos normalmente , use um loop for / range () simples e funcione no python 2.xe 3.x

São mostrados dois sinalizadores de alternância (-d, -v) e dois valores controlados pelos argumentos (-i xxx e -o xxx).

import os,sys

def HelpAndExit():
    print("<<your help output goes here>>")
    sys.exit(1)

def Fatal(msg):
    sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), msg))
    sys.exit(1)

def NextArg(i):
    '''Return the next command line argument (if there is one)'''
    if ((i+1) >= len(sys.argv)):
        Fatal("'%s' expected an argument" % sys.argv[i])
    return(1, sys.argv[i+1])

### MAIN
if __name__=='__main__':

    verbose = 0
    debug   = 0
    infile  = "infile"
    outfile = "outfile"

    # Parse command line
    skip = 0
    for i in range(1, len(sys.argv)):
        if not skip:
            if   sys.argv[i][:2] == "-d": debug ^= 1
            elif sys.argv[i][:2] == "-v": verbose ^= 1
            elif sys.argv[i][:2] == "-i": (skip,infile)  = NextArg(i)
            elif sys.argv[i][:2] == "-o": (skip,outfile) = NextArg(i)
            elif sys.argv[i][:2] == "-h": HelpAndExit()
            elif sys.argv[i][:1] == "-":  Fatal("'%s' unknown argument" % sys.argv[i])
            else:                         Fatal("'%s' unexpected" % sys.argv[i])
        else: skip = 0

    print("%d,%d,%s,%s" % (debug,verbose,infile,outfile))

O objetivo do NextArg () é retornar o próximo argumento enquanto verifica a falta de dados, e 'skip' ignora o loop quando o NextArg () é usado, mantendo o sinalizador analisando em um só forro.

erco
fonte
0

Estendi a abordagem de Erco para permitir argumentos posicionais necessários e argumentos opcionais. Estes devem preceder os argumentos -d, -v etc.

Argumentos posicionais e opcionais podem ser recuperados com PosArg (i) e OptArg (i, padrão), respectivamente. Quando um argumento opcional é encontrado, a posição inicial da busca por opções (por exemplo, -i) é movida 1 adiante, para evitar causar um fatal 'inesperado'.

import os,sys


def HelpAndExit():
    print("<<your help output goes here>>")
    sys.exit(1)

def Fatal(msg):
    sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), msg))
    sys.exit(1)

def NextArg(i):
    '''Return the next command line argument (if there is one)'''
    if ((i+1) >= len(sys.argv)):
        Fatal("'%s' expected an argument" % sys.argv[i])
    return(1, sys.argv[i+1])

def PosArg(i):
    '''Return positional argument'''
    if i >= len(sys.argv):
        Fatal("'%s' expected an argument" % sys.argv[i])
    return sys.argv[i]

def OptArg(i, default):
    '''Return optional argument (if there is one)'''
    if i >= len(sys.argv):
        Fatal("'%s' expected an argument" % sys.argv[i])
    if sys.argv[i][:1] != '-':
        return True, sys.argv[i]
    else:
        return False, default


### MAIN
if __name__=='__main__':

    verbose = 0
    debug   = 0
    infile  = "infile"
    outfile = "outfile"
    options_start = 3

    # --- Parse two positional parameters ---
    n1 = int(PosArg(1))
    n2 = int(PosArg(2))

    # --- Parse an optional parameters ---
    present, a3 = OptArg(3,50)
    n3 = int(a3)
    options_start += int(present)

    # --- Parse rest of command line ---
    skip = 0
    for i in range(options_start, len(sys.argv)):
        if not skip:
            if   sys.argv[i][:2] == "-d": debug ^= 1
            elif sys.argv[i][:2] == "-v": verbose ^= 1
            elif sys.argv[i][:2] == "-i": (skip,infile)  = NextArg(i)
            elif sys.argv[i][:2] == "-o": (skip,outfile) = NextArg(i)
            elif sys.argv[i][:2] == "-h": HelpAndExit()
            elif sys.argv[i][:1] == "-":  Fatal("'%s' unknown argument" % sys.argv[i])
            else:                         Fatal("'%s' unexpected" % sys.argv[i])
        else: skip = 0

    print("Number 1 = %d" % n1)
    print("Number 2 = %d" % n2)
    print("Number 3 = %d" % n3)
    print("Debug    = %d" % debug)
    print("verbose  = %d" % verbose)
    print("infile   = %s" % infile)
    print("outfile  = %s" % outfile) 
Erik
fonte