Tutorial de argparse¶
- autor:
Tshepang Lekhonkhobe
Este tutorial pretende ser una leve introducción a argparse
, el módulo de análisis (parsing) de línea de comandos recomendado en la biblioteca estándar de Python.
Nota
There are two other modules that fulfill the same task, namely
getopt
(an equivalent for getopt()
from the C
language) and the deprecated optparse
.
Note also that argparse
is based on optparse
,
and therefore very similar in terms of usage.
Conceptos¶
Vamos a mostrar el tipo de funcionalidad que vamos a explorar en este tutorial introductorio haciendo uso del comando ls:
$ ls
cpython devguide prog.py pypy rm-unused-function.patch
$ ls pypy
ctypes_configure demo dotviewer include lib_pypy lib-python ...
$ ls -l
total 20
drwxr-xr-x 19 wena wena 4096 Feb 18 18:51 cpython
drwxr-xr-x 4 wena wena 4096 Feb 8 12:04 devguide
-rwxr-xr-x 1 wena wena 535 Feb 19 00:05 prog.py
drwxr-xr-x 14 wena wena 4096 Feb 7 00:59 pypy
-rw-r--r-- 1 wena wena 741 Feb 18 01:01 rm-unused-function.patch
$ ls --help
Usage: ls [OPTION]... [FILE]...
List information about the FILEs (the current directory by default).
Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.
...
Algunos conceptos que podemos aprender de los cuatro comandos:
El comando ls es útil cuando se ejecuta sin ninguna opción en absoluto. Por defecto muestra el contenido del directorio actual.
Si queremos hacer algo, mas allá de lo que provee por defecto, le contamos un poco mas. En este caso, queremos mostrar un directorio diferente,
pypy
. Lo que hicimos fue especificar lo que se conoce como argumento posicional. Se llama así porque el programa debe saber que hacer con ese valor, basado únicamente en función de donde aparece en la línea de comandos. Este concepto es mas relevante para un comando como cp, cuyo uso mas básico escp SRC DEST
. La primer posición es lo que quieres copiar, y la segunda posición es a donde lo quieres copiar.Ahora, digamos que queremos cambiar el comportamiento del programa. En nuestro ejemplo, mostramos mas información para cada archivo en lugar de solo mostrar los nombres de los archivos. El argumento
-l
en ese caso se conoce como argumento opcional.Este es un fragmento del texto de ayuda. Es muy útil porque puedes encontrar un programa que nunca has usado antes, y puedes darte cuenta de como funciona simplemente leyendo el texto de ayuda.
Las bases¶
Comencemos con un simple ejemplo, el cual no hace (casi) nada:
import argparse
parser = argparse.ArgumentParser()
parser.parse_args()
Lo siguiente es el resultado de ejecutar el código:
$ python3 prog.py
$ python3 prog.py --help
usage: prog.py [-h]
options:
-h, --help show this help message and exit
$ python3 prog.py --verbose
usage: prog.py [-h]
prog.py: error: unrecognized arguments: --verbose
$ python3 prog.py foo
usage: prog.py [-h]
prog.py: error: unrecognized arguments: foo
Esto es lo que está pasando:
Ejecutar el script sin ninguna opción da como resultado que no se muestra nada en stdout. No es tan útil.
El segundo comienza a mostrar la utilidad del módulo
argparse
. No hemos hecho casi nada, pero ya recibimos un buen mensaje de ayuda.La opción
--help
, que también puede ser abreviada como-h
, es la única opción que tenemos gratis (es decir, no necesitamos especificarla). Especificar cualquier otra cosa da como resultado un error. Pero aún así, recibimos un mensaje útil, también gratis.
Introducción a los argumentos posicionales¶
Un ejemplo:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo")
args = parser.parse_args()
print(args.echo)
Y ejecutando el código:
$ python3 prog.py
usage: prog.py [-h] echo
prog.py: error: the following arguments are required: echo
$ python3 prog.py --help
usage: prog.py [-h] echo
positional arguments:
echo
options:
-h, --help show this help message and exit
$ python3 prog.py foo
foo
Aquí está lo que está sucediendo:
We’ve added the
add_argument()
method, which is what we use to specify which command-line options the program is willing to accept. In this case, I’ve named itecho
so that it’s in line with its function.Llamar nuestro programa ahora requiere que especifiquemos una opción.
The
parse_args()
method actually returns some data from the options specified, in this case,echo
.La variable es una forma de “magia” que
argparse
se realiza de forma gratuita (es decir, no es necesario especificar en qué variable se almacena ese valor). También notará que su nombre coincide con el argumento de cadena dado al método,echo
.
Sin embargo, tenga en cuenta que, aunque la pantalla de ayuda luce bien y todo, en realidad no es tan útil como podría ser. Por ejemplo, vemos que tenemos echo
como un argumento posicional, pero no sabemos lo que hace, de otra manera que no sea adivinar o leer el código fuente. Entonces, vamos a hacerlo un poco mas útil:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo", help="echo the string you use here")
args = parser.parse_args()
print(args.echo)
Y la salida:
$ python3 prog.py -h
usage: prog.py [-h] echo
positional arguments:
echo echo the string you use here
options:
-h, --help show this help message and exit
Ahora, que tal si hacemos algo más útil:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", help="display a square of a given number")
args = parser.parse_args()
print(args.square**2)
Lo siguiente es el resultado de ejecutar el código:
$ python3 prog.py 4
Traceback (most recent call last):
File "prog.py", line 5, in <module>
print(args.square**2)
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
Eso no fue tan bien. Esto es porque argparse
trata las opciones que le damos como cadenas, a menos que le digamos otra cosa. Entonces, vamos a llamar a argparse
para tratar esa entrada como un entero:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", help="display a square of a given number",
type=int)
args = parser.parse_args()
print(args.square**2)
Lo siguiente es el resultado de ejecutar el código:
$ python3 prog.py 4
16
$ python3 prog.py four
usage: prog.py [-h] square
prog.py: error: argument square: invalid int value: 'four'
Eso fue bien. El programa ahora aún se cierra útilmente en caso de una entrada ilegal incorrecta antes de proceder.
Introducción a los argumentos opcionales¶
Hasta ahora hemos estado jugando con argumentos posicionales. Vamos a darle una mirada a como agregar los opcionales:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbosity", help="increase output verbosity")
args = parser.parse_args()
if args.verbosity:
print("verbosity turned on")
Y la salida:
$ python3 prog.py --verbosity 1
verbosity turned on
$ python3 prog.py
$ python3 prog.py --help
usage: prog.py [-h] [--verbosity VERBOSITY]
options:
-h, --help show this help message and exit
--verbosity VERBOSITY
increase output verbosity
$ python3 prog.py --verbosity
usage: prog.py [-h] [--verbosity VERBOSITY]
prog.py: error: argument --verbosity: expected one argument
Esto es lo que está pasando:
El programa está escrito para mostrar algo cuando
--verbosity
sea especificado y no mostrar nada cuando no.To show that the option is actually optional, there is no error when running the program without it. Note that by default, if an optional argument isn’t used, the relevant variable, in this case
args.verbosity
, is givenNone
as a value, which is the reason it fails the truth test of theif
statement.El mensaje de ayuda es un poco diferente.
Cuando usamos la opción
--verbosity
, también se debe especificar un valor, cualquier valor.
El ejemplo anterior acepta arbitrariamente valores enteros para --verbosity
, pero para nuestro simple programa, solo dos valores son realmente útiles, True
o False
. Modifiquemos el código de acuerdo a esto:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbose", help="increase output verbosity",
action="store_true")
args = parser.parse_args()
if args.verbose:
print("verbosity turned on")
Y la salida:
$ python3 prog.py --verbose
verbosity turned on
$ python3 prog.py --verbose 1
usage: prog.py [-h] [--verbose]
prog.py: error: unrecognized arguments: 1
$ python3 prog.py --help
usage: prog.py [-h] [--verbose]
options:
-h, --help show this help message and exit
--verbose increase output verbosity
Esto es lo que está pasando:
The option is now more of a flag than something that requires a value. We even changed the name of the option to match that idea. Note that we now specify a new keyword,
action
, and give it the value"store_true"
. This means that, if the option is specified, assign the valueTrue
toargs.verbose
. Not specifying it impliesFalse
.Se queja cuando se especifica un valor, en verdadero espíritu de lo que realmente son los flags.
Observe los diferentes textos de ayuda.
Opciones cortas¶
Si estas familiarizado con el uso de la línea de comandos, podrás observar que aún no he tocado el tema de las versiones cortas de las opciones. Es bastante simple:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-v", "--verbose", help="increase output verbosity",
action="store_true")
args = parser.parse_args()
if args.verbose:
print("verbosity turned on")
Y aquí va:
$ python3 prog.py -v
verbosity turned on
$ python3 prog.py --help
usage: prog.py [-h] [-v]
options:
-h, --help show this help message and exit
-v, --verbose increase output verbosity
Tenga en cuenta que la nueva habilidad es también reflejada en el texto de ayuda.
Combinar argumentos opcionales y posicionales¶
Nuestro programa sigue creciendo en complejidad:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
help="display a square of a given number")
parser.add_argument("-v", "--verbose", action="store_true",
help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbose:
print(f"the square of {args.square} equals {answer}")
else:
print(answer)
Y ahora la salida:
$ python3 prog.py
usage: prog.py [-h] [-v] square
prog.py: error: the following arguments are required: square
$ python3 prog.py 4
16
$ python3 prog.py 4 --verbose
the square of 4 equals 16
$ python3 prog.py --verbose 4
the square of 4 equals 16
Hemos traído de vuelta un argumento posicional, de ahí la queja.
Tenga en cuenta que el orden no importa.
Que tal si le retornamos a nuestro programa la capacidad de tener múltiples valores de verbosidad, y realmente usarlos:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
help="display a square of a given number")
parser.add_argument("-v", "--verbosity", type=int,
help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
print(f"the square of {args.square} equals {answer}")
elif args.verbosity == 1:
print(f"{args.square}^2 == {answer}")
else:
print(answer)
Y la salida:
$ python3 prog.py 4
16
$ python3 prog.py 4 -v
usage: prog.py [-h] [-v VERBOSITY] square
prog.py: error: argument -v/--verbosity: expected one argument
$ python3 prog.py 4 -v 1
4^2 == 16
$ python3 prog.py 4 -v 2
the square of 4 equals 16
$ python3 prog.py 4 -v 3
16
Todos estos se ven bien, excepto el último, que expone un error en nuestro programa. Corrijamos esto restringiendo los valores que la opción --verbosity
puede aceptar:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
help="display a square of a given number")
parser.add_argument("-v", "--verbosity", type=int, choices=[0, 1, 2],
help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
print(f"the square of {args.square} equals {answer}")
elif args.verbosity == 1:
print(f"{args.square}^2 == {answer}")
else:
print(answer)
Y la salida:
$ python3 prog.py 4 -v 3
usage: prog.py [-h] [-v {0,1,2}] square
prog.py: error: argument -v/--verbosity: invalid choice: 3 (choose from 0, 1, 2)
$ python3 prog.py 4 -h
usage: prog.py [-h] [-v {0,1,2}] square
positional arguments:
square display a square of a given number
options:
-h, --help show this help message and exit
-v {0,1,2}, --verbosity {0,1,2}
increase output verbosity
Tenga en cuenta que el cambio se refleja tanto en el mensaje de error como en la cadena de ayuda.
Ahora, usemos un enfoque diferente para jugar con la verbosidad, lo cual es bastante común. También coincide con la forma en que el ejecutable de CPython maneja su propio argumento de verbosidad (verifique el resultado de python --help
):
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
help="display the square of a given number")
parser.add_argument("-v", "--verbosity", action="count",
help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
print(f"the square of {args.square} equals {answer}")
elif args.verbosity == 1:
print(f"{args.square}^2 == {answer}")
else:
print(answer)
Hemos introducido otra acción, «count», para contar el número de apariciones de opciones específicas.
$ python3 prog.py 4
16
$ python3 prog.py 4 -v
4^2 == 16
$ python3 prog.py 4 -vv
the square of 4 equals 16
$ python3 prog.py 4 --verbosity --verbosity
the square of 4 equals 16
$ python3 prog.py 4 -v 1
usage: prog.py [-h] [-v] square
prog.py: error: unrecognized arguments: 1
$ python3 prog.py 4 -h
usage: prog.py [-h] [-v] square
positional arguments:
square display a square of a given number
options:
-h, --help show this help message and exit
-v, --verbosity increase output verbosity
$ python3 prog.py 4 -vvv
16
Si, ahora es mas una bandera (similar a
action="store_true"
) en la versión anterior de nuestro script. Esto debería explicar la queja.También se comporta de manera similar a la acción
"store_true"
.Ahora aquí una demostración de lo que la acción
"count"
da. Probablemente haya visto esta clase de uso antes.Y si no especificas la bandera
-v
, se considera que esa bandera tiene el valorNone
.Como debería esperarse, especificando la forma larga de la bandera, obtendríamos el mismo resultado.
Lamentablemente, nuestra salida de ayuda no es muy informativa sobre la nueva capacidad que ha adquirido nuestro script, pero eso siempre se puede solucionar mejorando la documentación de nuestro script (por ejemplo, a través del argumento de la palabra clave
help
).La última salida expone un error en nuestro programa.
Vamos a arreglarlo:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
help="display a square of a given number")
parser.add_argument("-v", "--verbosity", action="count",
help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
# bugfix: replace == with >=
if args.verbosity >= 2:
print(f"the square of {args.square} equals {answer}")
elif args.verbosity >= 1:
print(f"{args.square}^2 == {answer}")
else:
print(answer)
Y esto es lo que da:
$ python3 prog.py 4 -vvv
the square of 4 equals 16
$ python3 prog.py 4 -vvvv
the square of 4 equals 16
$ python3 prog.py 4
Traceback (most recent call last):
File "prog.py", line 11, in <module>
if args.verbosity >= 2:
TypeError: '>=' not supported between instances of 'NoneType' and 'int'
La primer salida fue correcta, y corrigió el error que teníamos antes. Es decir, queremos que cualquier valor >= 2 sea lo más detallado posible.
Tercer salida no tan buena.
Vamos a arreglar ese error:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
help="display a square of a given number")
parser.add_argument("-v", "--verbosity", action="count", default=0,
help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity >= 2:
print(f"the square of {args.square} equals {answer}")
elif args.verbosity >= 1:
print(f"{args.square}^2 == {answer}")
else:
print(answer)
Acabamos de introducir otra palabra clave, default
. Lo hemos configurado en 0
para que sea comparable con otros valores int. Recuerde que por defecto, si un argumento opcional no es especificado, obtiene el valor None
, y eso no puede ser comparado con un valor int (de ahí la excepción TypeError
).
Y:
$ python3 prog.py 4
16
Tu puedes llegar bastante lejos con lo que hemos aprendido hasta ahora, y solo arañado la superficie. El módulo argparse
es muy poderoso, y exploraremos un poco mas antes de finalizar este tutorial.
Un poco mas avanzado¶
Qué pasaría si quisiéramos expandir nuestro pequeño programa para que tenga otros poderes, no solo cuadrados:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
parser.add_argument("-v", "--verbosity", action="count", default=0)
args = parser.parse_args()
answer = args.x**args.y
if args.verbosity >= 2:
print(f"{args.x} to the power {args.y} equals {answer}")
elif args.verbosity >= 1:
print(f"{args.x}^{args.y} == {answer}")
else:
print(answer)
Salida:
$ python3 prog.py
usage: prog.py [-h] [-v] x y
prog.py: error: the following arguments are required: x, y
$ python3 prog.py -h
usage: prog.py [-h] [-v] x y
positional arguments:
x the base
y the exponent
options:
-h, --help show this help message and exit
-v, --verbosity
$ python3 prog.py 4 2 -v
4^2 == 16
Tenga en cuenta que hasta ahora hemos estado usando el nivel de verbosidad para cambiar el texto que se muestra. El siguiente ejemplo en lugar de usar nivel de verbosidad para mostrar mas texto en su lugar:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
parser.add_argument("-v", "--verbosity", action="count", default=0)
args = parser.parse_args()
answer = args.x**args.y
if args.verbosity >= 2:
print(f"Running '{__file__}'")
if args.verbosity >= 1:
print(f"{args.x}^{args.y} == ", end="")
print(answer)
Salida:
$ python3 prog.py 4 2
16
$ python3 prog.py 4 2 -v
4^2 == 16
$ python3 prog.py 4 2 -vv
Running 'prog.py'
4^2 == 16
Opciones conflictivas¶
So far, we have been working with two methods of an
argparse.ArgumentParser
instance. Let’s introduce a third one,
add_mutually_exclusive_group()
. It allows for us to specify options that
conflict with each other. Let’s also change the rest of the program so that
the new functionality makes more sense:
we’ll introduce the --quiet
option,
which will be the opposite of the --verbose
one:
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", action="store_true")
group.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
args = parser.parse_args()
answer = args.x**args.y
if args.quiet:
print(answer)
elif args.verbose:
print(f"{args.x} to the power {args.y} equals {answer}")
else:
print(f"{args.x}^{args.y} == {answer}")
Nuestro programa ahora es mas simple, y perdimos algunas funcionalidades en aras de la demostración. De todos modos, aquí esta el resultado:
$ python3 prog.py 4 2
4^2 == 16
$ python3 prog.py 4 2 -q
16
$ python3 prog.py 4 2 -v
4 to the power 2 equals 16
$ python3 prog.py 4 2 -vq
usage: prog.py [-h] [-v | -q] x y
prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose
$ python3 prog.py 4 2 -v --quiet
usage: prog.py [-h] [-v | -q] x y
prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose
Esto debería ser sencillo de seguir. He agregado esa última salida para que se pueda ver el tipo de flexibilidad que obtiene, es decir, mezclar opciones de forma larga con opciones de forma corta.
Antes de concluir, probablemente quiera contarle a sus usuarios el propósito principal de su programa, solo en caso de que no lo supieran:
import argparse
parser = argparse.ArgumentParser(description="calculate X to the power of Y")
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", action="store_true")
group.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
args = parser.parse_args()
answer = args.x**args.y
if args.quiet:
print(answer)
elif args.verbose:
print(f"{args.x} to the power {args.y} equals {answer}")
else:
print(f"{args.x}^{args.y} == {answer}")
Tenga en cuenta la ligera diferencia en el uso del texto. Tenga en cuenta [-v | -q]
, lo cual nos indica que podemos usar -v
o -q
, pero no ambos al mismo tiempo:
$ python3 prog.py --help
usage: prog.py [-h] [-v | -q] x y
calculate X to the power of Y
positional arguments:
x the base
y the exponent
options:
-h, --help show this help message and exit
-v, --verbose
-q, --quiet
How to translate the argparse output¶
The output of the argparse
module such as its help text and error
messages are all made translatable using the gettext
module. This
allows applications to easily localize messages produced by
argparse
. See also Internacionalizando sus programas y módulos.
For instance, in this argparse
output:
$ python prog.py --help
usage: prog.py [-h] [-v | -q] x y
calculate X to the power of Y
positional arguments:
x the base
y the exponent
options:
-h, --help show this help message and exit
-v, --verbose
-q, --quiet
The strings usage:
, positional arguments:
, options:
and
show this help message and exit
are all translatable.
In order to translate these strings, they must first be extracted
into a .po
file. For example, using Babel,
run this command:
$ pybabel extract -o messages.po /usr/lib/python3.12/argparse.py
This command will extract all translatable strings from the argparse
module and output them into a file named messages.po
. This command assumes
that your Python installation is in /usr/lib
.
You can find out the location of the argparse
module on your system
using this script:
import argparse
print(argparse.__file__)
Once the messages in the .po
file are translated and the translations are
installed using gettext
, argparse
will be able to display the
translated messages.
To translate your own strings in the argparse
output, use gettext
.
Conclusión¶
El módulo argparse
ofrece mucho más que solo lo mostrado aquí. Su documentación es bastante detallada y completa, y está llena de ejemplos. Habiendo seguido este tutorial, debe digerirlos fácilmente sin sentirse abrumado.