Qual é a melhor maneira de analisar argumentos de linha de comando?
-
09-06-2019 - |
Pergunta
Qual é o mais fácil, mais conciso, e a maioria flexível método ou biblioteca para analisar argumentos de linha de comando do Python?
Solução
Esta resposta sugere optparse
o que é apropriado para versões mais antigas do Python.Para Python 2.7 e superior, argparse
substitui optparse
.Ver esta resposta Para maiores informações.
Como outras pessoas apontaram, é melhor optar pelo optparse do que pelo getopt.getopt é praticamente um mapeamento um-para-um das funções padrão da biblioteca C getopt(3) e não é muito fácil de usar.
optparse, embora seja um pouco mais detalhado, é muito melhor estruturado e mais simples de estender posteriormente.
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")
Praticamente fala por si;no momento do 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 especificá-lo.Também é autodocumentado porque você declara o argumento de ajuda (que será usado quando executado com -h/--help) ali mesmo com a opção.
Normalmente 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 com 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 de meus projetos, criei uma classe Command que permite aninhar facilmente subcomandos em uma árvore de comando.Ele usa optparse fortemente para encadear comandos.Não é algo que eu possa explicar facilmente em poucas linhas, mas fique à vontade para navegue no meu repositório para a classe principal, bem como uma classe que o utiliza e o analisador de opções
Outras dicas
Outras respostas mencionam que argparse
é o caminho a seguir para o novo Python, mas não fornece exemplos de uso.Para completar, aqui está um breve resumo de como usar argparse:
1) Inicializar
import argparse
# Instantiate the parser
parser = argparse.ArgumentParser(description='Optional app description')
2) Adicione 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) Verifique os 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
Usando docopt
Desde 2012 Python tem uma ferramenta muito fácil, poderosa e realmente legal módulo para análise de argumentos chamado docopt.Funciona com Python 2.6 a 3.5 e não precisa de instalação (basta copiá-lo).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:2 linhas de código mais sua string de documento que é essencial e você terá seus argumentos analisados e disponíveis em seu objeto de argumentos.Eu te disse que é legal, não disse ;-)
Usando python-fire
Desde 2017 fogo python tem outro módulo legal que pode fornecer uma interface CLI ao seu código com você fazendo zero análise de argumentos.Aqui está um exemplo simples da documentação (este pequeno programa expõe a função double
para a 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
Incrível, não é?
O novo jeito moderno é argparse
para esses razões.argparse > optparse > getopt
atualizar: A partir de py2.7 argparse faz parte da biblioteca padrão e optar por analisar está obsoleto.
eu prefiro Clique.Ele abstrai as opções de gerenciamento e permite "(...) criar lindas interfaces de linha de comando de forma combiná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()
Ele 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.
Quase todo mundo está usando obteropt
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, é assim que 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** argv
em 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 os argumentos curtos: -ONELETTER
.O :
significa que -o
aceita um argumento.
Finalmente ["help", "output="]
descreve argumentos longos ( --MORETHANONELETTER
).O =
após a saída mais uma vez significa 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 --help
aqui o arg
parte é uma string vazia.Geralmente, você deseja fazer um loop nesta lista e testar o nome da opção como no exemplo.
Espero que isso tenha ajudado você.
Usar optparse
que vem com a biblioteca padrão.Por exemplo:
#!/usr/bin/env python
import optparse
def main():
p = optparse.OptionParser()
p.add_option('--person', '-p', default="world")
options, arguments = p.parse_args()
print 'Hello %s' % options.person
if __name__ == '__main__':
main()
Fonte: Usando Python para criar ferramentas de linha de comando UNIX
No entanto, a partir do Python 2.7, o optparse está obsoleto, consulte: Por que usar argparse em vez de optparse?
Caso você 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()
Padrões leves de argumentos de linha de comando
Embora argparse
é ótimo e é a resposta certa para opções de linha de comando totalmente documentadas e recursos avançados, você pode usar padrões de argumentos de função para lidar com argumentos posicionais diretos de maneira muito simples.
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 do teste é semelhante a esta:
> ./test.py
a 2
> ./test.py A
A 2
> ./test.py A 20
A 20
Para scripts simples onde desejo apenas 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 todos strings.
Acho que a melhor maneira para projetos maiores é optparse, mas se você está procurando uma maneira fácil, talvez http://werkzeug.pocoo.org/documentation/script é 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, todas as funções Action_* são expostas à 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
Eu prefiro optparse a getopt.É muito declarativo:você informa os nomes das opções e os efeitos que elas devem ter (por exemplo, definindo um campo booleano) e ele lhe devolve um dicionário preenchido de acordo com suas especificações.
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''
O código Argparse pode ser mais longo que o código de implementação real!
Esse é um problema que encontro nas opções de análise de argumentos mais populares: se seus parâmetros forem apenas modestos, o código para documentá-los se tornará desproporcionalmente grande em relação ao benefício que eles fornecem.
Um relativamente novo na cena de análise de argumentos (eu acho) é lugar.
Ele faz algumas compensações reconhecidas com o argparse, mas usa documentação in-line e envolve simplesmente main()
tipo função função:
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)
Aqui está um método, não uma biblioteca, que parece funcionar para mim.
Os objetivos aqui são 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), avisa sobre argumentos ausentes ou desconhecidos normalmente , use um loop for/range() simples e funcione em python 2.xe 3.x
São mostrados dois sinalizadores de alternância (-d, -v) e dois valores controlados por 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 de NextArg() é retornar o próximo argumento enquanto verifica dados ausentes, e 'skip' pula o loop quando NextArg() é usado, mantendo a análise do sinalizador em um liner.
Estendi a abordagem de Erco para permitir argumentos posicionais obrigatórios e argumentos opcionais.Estes devem preceder o -d, -v etc.argumentos.
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 pesquisa de opções (por exemplo-i) é movido 1 para frente 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)