¿Cuál es la mejor manera de analizar los argumentos de la línea de comandos? [cerrado]

Respuestas:

183

Esta respuesta sugiere optparsecuál es apropiado para versiones anteriores de Python. Para Python 2.7 y superior, argparsereemplaza optparse. Vea esta respuesta para más información.

Como otras personas señalaron, es mejor optar por optparse sobre getopt. getopt es más o menos un mapeo uno a uno de las funciones estándar de la biblioteca getopt (3) C, y no es muy fácil de usar.

optparse, aunque es un poco más detallado, está mucho mejor estructurado y es más fácil de extender más adelante.

Aquí hay una línea típica para agregar una opción a su analizador:

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

Más o menos habla por sí mismo; en el momento del procesamiento, aceptará -q o --query como opciones, almacenará el argumento en un atributo llamado query y tiene un valor predeterminado si no lo especifica. También se documenta automáticamente al declarar el argumento de ayuda (que se usará cuando se ejecute con -h / - help) allí mismo con la opción.

Por lo general, analiza sus argumentos con:

options, args = parser.parse_args()

Esto, de forma predeterminada, analizará los argumentos estándar pasados ​​al script (sys.argv [1:])

options.query se establecerá en el valor que le pasó al script.

Usted crea un analizador simplemente haciendo

parser = optparse.OptionParser()

Estos son todos los elementos básicos que necesita. Aquí hay un script completo de Python que muestra esto:

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 líneas de python que te muestran los conceptos básicos.

Guárdelo en sample.py y ejecútelo una vez con

python sample.py

y una vez con

python sample.py --query myquery

Más allá de eso, encontrará que optparse es muy fácil de extender. En uno de mis proyectos, creé una clase de Comando que le permite anidar subcomandos en un árbol de comandos fácilmente. Utiliza optparse en gran medida para encadenar comandos juntos. No es algo que pueda explicar fácilmente en unas pocas líneas, pero siéntase libre de navegar en mi repositorio para la clase principal, así como una clase que lo use y el analizador de opciones

Thomas Vander Stichele
fuente
99
Esta respuesta es maravillosamente clara y fácil de seguir, para Python 2.3 a 2.6. Para python 2.7+ no es la mejor respuesta ya que argparse ahora es parte de la biblioteca estándar y optparse en desuso.
Matt Wilkie
En mi caso, quiero perfilar mi aplicación para detectar la lentitud. Hay otra herramienta llamada [atún] ( github.com/nschloe/tuna ) que me permite perfilar toda la aplicación simplemente agregando agrs -mcProfile -o program.profpero agrparcer está capturando estos args, ¿cómo paso estos args a python exe?
Yogeshwar
231

argparsees el camino a seguir Aquí hay un breve resumen de cómo usarlo:

1) Inicializar

import argparse

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

2) Agregar 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) Parse

args = parser.parse_args()

4) Acceso

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

5) Comprobar valores

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

Uso

Uso correcto:

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

Argument values:
1
2
3
True

Argumentos incorrectos:

$ ./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

Ayuda 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
fuente
10
Esto es muy conciso y útil y aquí está el documento oficial (por conveniencia): docs.python.org/3/library/argparse.html
Christophe Roussy
1
Si encuentra argparse demasiado detallado, use plac en su lugar.
Nimitz14
76

Usando docopt

Desde 2012 hay un módulo muy fácil, poderoso y realmente genial para analizar argumentos llamado docopt . Aquí hay un ejemplo tomado de su documentación:

"""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)

Así que esto es todo: 2 líneas de código más su cadena de documentación, que es esencial y obtiene sus argumentos analizados y disponibles en su objeto de argumentos.

Usando fuego de pitón

Desde 2017 hay otro módulo genial llamado python-fire . Puede generar una interfaz CLI para su código con un análisis cero de argumentos. Aquí hay un ejemplo simple de la documentación (este pequeño programa expone la función doublea la línea de comando):

import fire

class Calculator(object):

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

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

Desde la línea de comandos, puede ejecutar:

> calculator.py double 10
20
> calculator.py double --number=15
30
ndemou
fuente
44
¿Cómo docopt "no necesita instalación"? Es un módulo de Python, por lo que debe instalarse. 'ImportError: ningún módulo llamado docopt'
interesado el
1
@keen no está incluido en Python con seguridad, pero no es necesario instalarlo: "simplemente puede colocar el archivo docopt.py en su proyecto, es autónomo" - github.com/docopt/docopt
ndemou
99
solo tenemos diferentes definiciones de instalación, y quería señalarlo para futuros lectores.
entusiasta
1
@keen He agregado una nota sobre "sin instalación" para las personas que comparten su definición :-)
ndemou
39

La nueva forma moderna es argparsepor estas razones. argparse> optparse> getopt

actualización: a partir de py2.7 argparse es parte de la biblioteca estándar y optparse está en desuso.

Silfheed
fuente
Su enlace principal es 404, así que lo reemplacé con un enlace a una pregunta SO que aborda el mismo tema.
Joe Holloway
28

Prefiero Click . Resume las opciones de gestión y permite "(...) crear hermosas interfaces de línea de comandos de una manera componible con el mínimo código necesario".

Aquí hay un ejemplo 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()

También genera automáticamente páginas de ayuda bien formateadas:

$ 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
fuente
14

Casi todo el mundo está usando getopt

Aquí está el código de ejemplo para el 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

En una palabra, así es como funciona.

Tienes dos tipos de opciones. Aquellos que reciben argumentos y aquellos que son como interruptores.

sys.argves más o menos tu char** argven C. Al igual que en C omites el primer elemento que es el nombre de tu programa y solo analizas los argumentos:sys.argv[1:]

Getopt.getopt lo analizará de acuerdo con la regla que proporcione en el argumento.

"ho:v"Aquí se describen los argumentos cortos: -ONELETTER. El :medio que -oacepta un argumento.

Finalmente ["help", "output="]describe argumentos largos ( --MORETHANONELETTER). La =salida posterior una vez más significa que la salida acepta uno de los argumentos.

El resultado es una lista de pareja (opción, argumento)

Si una opción no acepta ningún argumento (como --helpaquí), la argparte es una cadena vacía. Por lo general, desea hacer un bucle en esta lista y probar el nombre de la opción como en el ejemplo.

espero que esto te ayude.

fulmicoton
fuente
66
Con la desaprobación de getoptlas versiones más recientes de Python, esta respuesta se ha quedado obsoleta.
shuttle87
1
@ shuttle87 A partir de python3.7.2, getopttodavía no está en desuso ... Pero su documentación indica que se proporciona principalmente a usuarios familiarizados con la getopt()función C , y reconoce que para otros usuarios argparsepodría ser una mejor solución, permitiendo "escribir menos código y obtener mejor ayuda y mensajes de error ".
Skippy le Grand Gourou
14

Uso optparseque viene con la biblioteca estándar. Por ejemplo:

#!/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()

Fuente: uso de Python para crear herramientas de línea de comandos de UNIX

Sin embargo, a partir de Python 2.7, optparse está en desuso, vea: ¿Por qué usar argparse en lugar de optparse?

Corey
fuente
6

En caso de que necesite hacerlo, esto puede ayudar si necesita obtener argumentos unicode en 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()
Sombra2531
fuente
Gracias. Este script me ayudó a resolver algunas citas realmente complicadas que tenía que hacer al pasar comandos de inicio a GVim.
telotortio
6

Valores predeterminados de argumento de línea de comando ligero

Aunque argparsees excelente y es la respuesta correcta para los cambios de línea de comandos completamente documentados y las características avanzadas, puede usar los valores predeterminados de los argumentos de funciones para manejar argumentos de posición directos de manera muy simple.

import sys

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

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

El argumento 'nombre' captura el nombre del script y no se usa. La salida de prueba se ve así:

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

Para scripts simples donde solo quiero algunos valores predeterminados, esto me parece bastante suficiente. También es posible que desee incluir algún tipo de coerción en los valores de retorno o los valores de la línea de comandos serán todos cadenas.

Simon Hibbs
fuente
2
las comillas no coinciden en la declaración def.
sello histórico
3

Prefiero optparse a getopt. Es muy declarativo: le dices los nombres de las opciones y los efectos que deberían tener (por ejemplo, establecer un campo booleano), y te devuelve un diccionario poblado de acuerdo con tus especificaciones.

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

Chris Conway
fuente
3

Creo que la mejor manera para proyectos más grandes es optparse, pero si está buscando una manera fácil, tal vez http://werkzeug.pocoo.org/documentation/script sea ​​algo para usted.

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()

Entonces, básicamente, cada función action_ * está expuesta a la línea de comando y se genera un buen mensaje de ayuda de forma gratuita.

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
fuente
He desarrollado un pequeño paquete de la utilización de creación automática de argumentos: declarative_parser. Por supuesto, si uno está trabajando con werkzeug, puede ser mejor mantener el werkzung.script. De todos modos, soy un gran admirador de ese enfoque.
krassowski
3

¡El código Argparse puede ser más largo que el código de implementación real!

Ese es un problema que encuentro con las opciones de análisis de argumentos más populares es que si sus parámetros son solo modestos, el código para documentarlos se vuelve desproporcionadamente grande para el beneficio que proporcionan.

Un relativo nuevo en la escena de análisis de argumentos (creo) es plac .

Hace algunas compensaciones reconocidas con argparse, pero usa documentación en línea y se ajusta simplemente alrededor de la main()función de función de tipo:

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)
QA Collective
fuente
Punto de información: el uso más simple de plac (como se muestra en el ejemplo) es solo para Python 3.x, porque usa anotaciones de función 3.x.
cebada
1

Consoleargs merece ser mencionado aquí. Es muy fácil de usar. Echale un vistazo:

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()

Ahora en la consola:

% 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''
tallo
fuente
He usado un enfoque similar en el analizador declarativo , vea la deducción de argumentos (mecanografía, docstrings, kwargs) en los documentos. Principales diferencias: python3, sugerencias de tipo, pip-installable.
krassowski
1
Última confirmación en 2012
Boris el
0

Aquí hay un método, no una biblioteca, que parece funcionar para mí.

Los objetivos aquí deben ser concisos, cada argumento analizado por una sola línea, los argumentos se alinean para facilitar la lectura, el código es simple y no depende de ningún módulo especial (solo os + sys), advierte sobre argumentos faltantes o desconocidos con gracia , utiliza un bucle simple para / range () y funciona en python 2.xy 3.x

Se muestran dos indicadores de alternancia (-d, -v) y dos valores controlados por argumentos (-i xxx y -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))

El objetivo de NextArg () es devolver el siguiente argumento mientras se comprueban los datos faltantes, y 'omitir' omite el bucle cuando se utiliza NextArg (), manteniendo el análisis de la bandera en uno.

erco
fuente
0

Extendí el enfoque de Erco para permitir los argumentos posicionales requeridos y los argumentos opcionales. Deben preceder a los argumentos -d, -v, etc.

Los argumentos posicionales y opcionales se pueden recuperar con PosArg (i) y OptArg (i, predeterminado) respectivamente. Cuando se encuentra un argumento opcional, la posición de inicio de búsqueda de opciones (por ejemplo, -i) se mueve 1 hacia adelante para evitar causar una muerte 'inesperada'.

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
fuente