Introducción a la programación en Python

clase 08b
Argumentos en la línea de comando II

La función sys.argv permite procesar argumentos en la línea de comando de manera sencilla, pero muy limitada. Python dispone del módulo argparse, que permite procesar la línea de comando de forma mucho más flexible y con funcionalidades adicionales.

Módulo argparse

El módulo argparse posee muchas más funciones que sys.argv, lo que permite programar interfaces con más posibilidades. Entre las múltiples funcionalidades que ofrece argparse, podemos mencionar:

  • sistema de ayuda
  • argumentos posicionales y por nombre
  • argumentos opcionales
  • manejo de argumentos de diversos tipos
  • argumentos con valores múltiples

Este módulo es parte de la biblioteca estándar de Python, y por tanto puede importarse directamente con la directiva import argparse.

Las funciones y métodos principales que provee el módulo argparse, son:

argparser.ArgumentParser() Es la primera función que se debe invocar. Crea un objeto de tipo parser (analizador), que es donde se van a agregar los argumentos que se vayan definiendo. Al objeto se le puede dar cualquier nombre, pero es convencional darle el nombre parser.
.add_argument() Con este método se van agregando argumentos al objeto parser creado anteriormente. Este método admite varios parámetros que definen las características del argumento que crea.
.parse_args() Este método va al final, y analiza y procesa todos los argumentos creados previamente, asignándolos a una variable. A esta varible se le puede dar cualquier nombre, pero es convencional darle el nombre args.

Por lo tanto, los cuatro pasos básicos para procesar los argumentos de la línea de comandos con argparse, son:

  1. importar el módulo: import argparse
  2. crear el objeto parser: parser = argparse.ArgumentParser()
  3. agregar uno o más argumentos: parser.add_argument("arg")
  4. procesar los argumentos, y asignarlos a una variable: args = parser.parse_args()

Ejemplos

Todos los programas de ejemplo se encuentran en el mismo directorio donde se está corriendo este entorno. La instrucción %load carga el programa del disco a una celda, para poder ser ejecutado con la instrucción !python.

El primer programa solamente solamente importa el módulo, crea el objeto parser mediante la función argparse.ArgumentParser(), y finalmente lo analiza con el método .parse_args().

In [ ]:
import argparse
parser = argparse.ArgumentParser()
parser.parse_args()
In [1]:
# %load argparse_1.py

Este programa no hace nada ni define ningún argumento de entrada. Por lo tanto, si se invoca sin argumentos adicionales, no devuelve nada.

In [2]:
!python argparse_1.py

Sin embargo, si se invoca con la opción -h o --help, imprime en la pantalla un mensaje de ayuda. Esta función es provista automáticamente por argparse.

In [3]:
!python argparse_1.py -h
usage: argparse_1.py [-h]

optional arguments:
  -h, --help  show this help message and exit
In [4]:
!python argparse_1.py --help
usage: argparse_1.py [-h]

optional arguments:
  -h, --help  show this help message and exit

.add_argument()

Los argumentos se agregan al objeto parser con el método .add_argument(). Este método tiene un parámetro obligatorio, que es el nombre del argumento a través del cual se va a poder acceder en el programa, y varios parámetros opcionales posibles, que determinan cómo se va a procesar el argumento.

El nombre del argumento se suele definir implícitamente poniéndolo como primer parámetro, aunque también se puede establecer explícitamente con el parámetro opcional dest. Alternativamente, dest se puede utilizar para definir un nombre diferente para acceder al argumento en el programa. Las dos primeras directivas son equivalentes, mientras que la tercera establece que se va a acceder al argumento mediante el nombre arg:

parser.add_argument("argumento")    
parser.add_argument(dest="argumento")
parser.add_argument("argumento", dest="arg")    

Consideremos el siguiente programa, que agrega un único argumento llamado simplemente "argumento". Una vez que el objeto parser es analizado por parser.parser_args() y asignado al objeto args, el argumento se puede llamar a través de args.argumento. El programa imprime en la pantalla la cadena pasada como argumento de entrada.

In [ ]:
# argparse_2a.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("argumento")
args = parser.parse_args()
print("El valor del argumento es %s." % (args.argumento))
In [5]:
# %load argparse_2a.py

Si el programa se invoca sin argumentos, imprime un mensaje con el uso, y devuelve un error diciendo que el programa requiere un argumento.

In [6]:
!python argparse_2a.py
usage: argparse_2a.py [-h] argumento
argparse_2a.py: error: the following arguments are required: argumento

Si el progrma se invoca con la opción -h (o --help), no devuelve error, e imprime un mensaje de uso más completo.

In [7]:
!python argparse_2a.py -h
usage: argparse_2a.py [-h] argumento

positional arguments:
  argumento

optional arguments:
  -h, --help  show this help message and exit

Si se invoca con un argumento, el programa se ejecuta correctamente, e imprime el argumento en la salida estándar.

In [8]:
!python argparse_2a.py cuatro
El valor del argumento es cuatro.
In [9]:
!python argparse_2a.py "Hola Python"
El valor del argumento es Hola Python.

argumentos posicionales

Por defecto, todos los argumentos se consideran obligatorios, y se procesan en la línea de comando en el orden en que se agregan al código mediante el método add_argument(). Por lo tanto, se denominan argumentos posicionales.

In [ ]:
# argparse_2b.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("argumento_1")
parser.add_argument("argumento_2")
args = parser.parse_args()
print("El valor del primer argumento es %s." % (args.argumento_1))
print("El valor del segundo argumento es %s." % (args.argumento_2))
In [10]:
# %load argparse_2b.py
In [11]:
!python argparse_2b.py hola Python
El valor del primer argumento es hola.
El valor del segundo argumento es Python.

Como todos los argumentos son obligatorios, el programa devuelve un error si no se invoca con la totalidad de los argumentos definidos.

In [12]:
!python argparse_2b.py hola
usage: argparse_2b.py [-h] argumento_1 argumento_2
argparse_2b.py: error: the following arguments are required: argumento_2

argumentos por nombre (opcionales)

Utilizando nombres que comiencen con guiones, se pueden definir argumentos que en la línea de comando se invoquen por su nombre, y no por su posición. Por defecto, este tipo de argumentos no son obligatorios, y por tanto se denominan argumentos opcionales.

In [ ]:
# argparse_2c.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--argumento_1")
parser.add_argument("--argumento_2")
args = parser.parse_args()
print("El valor del primer argumento es %s." % (args.argumento_1))
print("El valor del segundo argumento es %s." % (args.argumento_2))
In [13]:
# %load argparse_2c.py
In [14]:
!python argparse_2c.py --argumento_2 Python --argumento_1 hola
El valor del primer argumento es hola.
El valor del segundo argumento es Python.

Para los argumentos por nombre se puede definir un nombre largo y/o un nombre corto. Por convención, los nombres largos se definen con guión doble (--), y los nombres cortos con guión simple (-). Habitualmente (pero no necesariamente) para los nombres cortos se utiliza un solo carácter.

En caso de que para un argumento se defina tanto un nombre largo como uno corto, en el programa solo se puede acceder al argumento mediante el nombre largo. En la línea de comando se puede utilizar cualquiera de las dos opciones.

In [ ]:
# argparse_2d.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--argumento_1', '-a1')
parser.add_argument('-a2')
args = parser.parse_args()
print("El valor del primer argumento es %s." % (args.argumento_1))
print("El valor del segundo argumento es %s." % (args.a2))
In [15]:
# %load argparse_2d.py
In [16]:
!python argparse_2d.py -a2 Python -a1 hola
El valor del primer argumento es hola.
El valor del segundo argumento es Python.
In [17]:
!python argparse_2d.py --argumento_1 hola -a2 Python
El valor del primer argumento es hola.
El valor del segundo argumento es Python.

Opcionalmente, se puede utilizar adicionalmente el parámetro dest para especificar el nombre mediante el cual se va a acceder al argumento en el programa.

In [ ]:
# argparse_2e.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--argumento_1', '-a1', dest='arg1')
parser.add_argument('--argumento_2', '-a2', dest='arg2')
args = parser.parse_args()
print("El valor del primer argumento es %s." % (args.arg1))
print("El valor del segundo argumento es %s." % (args.arg2))
In [18]:
# %load argparse_2e.py
In [19]:
!python argparse_2e.py -a1 hola -a2 Python
El valor del primer argumento es hola.
El valor del segundo argumento es Python.

Como los argumentos que se pasan por nombre son opcionales, el programa no devuelve un error si no se invoca con todos los argumentos. Un argumento que no se incluya en la línea de comando adquiere por defecto el valor None.

In [20]:
!python argparse_2e.py -a2 Python
El valor del primer argumento es None.
El valor del segundo argumento es Python.

required=

En ocasiones se puede querer que un argumento pasado por nombre sea obligatorio, lo que puede establecerse con el parámetro opcional required. Este parámetro solo se puede usar en los argumentos opcionales que se pasan por nombre, y da un error si se utiliza en un argumento posicional.

Al parámetro required se le debe asignar un valor booleano, que puede ser True si se quiere que el argumento sea obligatorio, o False en caso contrario. Por defecto, el valor de required es False, y por lo tanto en ese caso no es necesario incluirlo explícitamente.

El siguiente programa acepta dos argumentos, ninguno de ellos obligatorios. Los argumentos que no se pasen al invocar el programa, adquieren el valor None.

In [ ]:
# argparse_3a.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--midi_number', '-n')
parser.add_argument('--duration', '-d')
args = parser.parse_args()
print("La nota tiene un número MIDI %s y una duración de %s beats."
      % (args.midi_number, args.duration))
In [21]:
# %load argparse_3a.py
In [22]:
!python argparse_3a.py -d 0.75
La nota tiene un número MIDI None y una duración de 0.75 beats.

El siguiente programa fue modificado para que ambos argumentos sean obligatorios. Si al invocar el programa no se incluye alguno de ellos en la línea de comando, el programa devuelve un error e imprime un mensaje de uso.

In [ ]:
# argparse_3b.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--midi_number', '-n', required=True)
parser.add_argument('--duration', '-d', required=True)
args = parser.parse_args()
print("La nota tiene un número MIDI %s y una duración de %s beats."
      % (args.midi_number, args.duration))
In [23]:
# %load argparse_3b.py
In [24]:
!python argparse_3b.py -d 0.75
usage: argparse_3b.py [-h] --midi_number MIDI_NUMBER --duration DURATION
argparse_3b.py: error: the following arguments are required: --midi_number/-n
In [25]:
!python argparse_3b.py -d 0.75 -n 60
La nota tiene un número MIDI 60 y una duración de 0.75 beats.

En la descripción de uso del programa incluida en el mensaje de ayuda, los argumentos opcionales se muestran entre paréntesis rectos.

In [26]:
!python argparse_3a.py -h
usage: argparse_3a.py [-h] [--midi_number MIDI_NUMBER] [--duration DURATION]

optional arguments:
  -h, --help            show this help message and exit
  --midi_number MIDI_NUMBER, -n MIDI_NUMBER
  --duration DURATION, -d DURATION
In [27]:
!python argparse_3b.py -h
usage: argparse_3b.py [-h] --midi_number MIDI_NUMBER --duration DURATION

optional arguments:
  -h, --help            show this help message and exit
  --midi_number MIDI_NUMBER, -n MIDI_NUMBER
  --duration DURATION, -d DURATION

help=''

Uno de los parámetros opcionales del método .add_argument() es help, que perminte agregar una descripción o ayuda de cada argumento que se define.

El siguiente programa agrega una descripción de los argumentos del programa anterior.

In [ ]:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--midi_number', '-n',
                    required=True,
                    help="número de nota MIDI")
parser.add_argument('--duration', '-d',
                    required=True,
                    help="duración en fracción de beat")
args = parser.parse_args()
print("La nota tiene un número MIDI %s y una duración de %s beats."
      % (args.midi_number, args.duration))
In [28]:
# %load argparse_4.py

De esta manera, al invocar el programa con la opción -h (o --help), la ayuda que se imprime en pantalla incluye una descripción del argumento.

In [29]:
!python argparse_4.py -h
usage: argparse_4.py [-h] --midi_number MIDI_NUMBER --duration DURATION

optional arguments:
  -h, --help            show this help message and exit
  --midi_number MIDI_NUMBER, -n MIDI_NUMBER
                        número de nota MIDI
  --duration DURATION, -d DURATION
                        duración en fracción de beat

type

El método .add_argument() también tiene el parámetro opcional type, que determina cómo se va a procesar el argumento de entrada. Si este parámetro no se incluye, los argumentos se procesan por defecto como cadenas de caracteres (tipo string).

Éstos son algunos de los principales tipos que se pueden especificar:

string cadena de caracteres (tipo por defecto, se puede omitir)
int número entero
float número decimal
argparse.FileType() tipo archivo: tipo provisto por el módulo, para facilitar pasar nombres de archivos en la línea de comando. Recibe como argumento el modo de acceso al archivo ('r', 'w', etc).

El siguiente programa imprime el argumento de entrada multiplicado por 2. Dado que el argumento se procesa como una cadena de caracteres, la operación de multiplicación (*) equivale a la repetición de la cadena.

In [ ]:
# argparse_5a.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--frecuencia', '-f',
                    dest='frec',
                    required=True)
args = parser.parse_args()
print("La octava de la frecuencia %s es %s" % (args.frec, args.frec*2))
In [30]:
# %load argparse_5a.py
In [31]:
!python argparse_5a.py -f 440
La octava de la frecuencia 440 es 440440

Definiendo el tipo del argumento como int, la operación se realiza correctamente.

In [ ]:
# argparse_5b.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--frecuencia', '-f',
                    dest='frec',
                    required=True,
                    type=int)
args = parser.parse_args()
print("La octava de la frecuencia %s es %s" % (args.frec, args.frec*2))
In [32]:
# %load argparse_5b.py
In [33]:
!python argparse_5b.py -f 440
La octava de la frecuencia 440 es 880

El tipo argparse.FileType() es un tipo especial provisto por el módulo argparse, que facilita pasar el nombre de un archivo como argumento al programa. Los archivos se pueden abrir tanto para lectura como para escritura, lo que se controla mediante el argumento que recibe este tipo.

Los archivos abiertos de esta manera no se cierran solos, por lo que conviene utilizarlos con la construcción with, tal como se vio oportunamente.

In [ ]:
# argparse_5c.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('infile', type=argparse.FileType('r'))
parser.add_argument('outfile', type=argparse.FileType('w'))
args = parser.parse_args()

with args.infile as infile:
    datos = infile.read()
print("Contenido del archivo de entrada:")
print(datos)

cadena = "Archivo de salida"
with args.outfile as outfile:
    outfile.write(cadena+'\n')

print("Se escribe el texto '%s' en el archivo de salida" % cadena)
In [34]:
# %load argparse_5c.py
In [35]:
!python argparse_5c.py infile.txt outfile.txt
Contenido del archivo de entrada:
Texto del archivo de entrada.

Se escribe el texto 'Archivo de salida' en el archivo de salida

default

Como se vio anteriormente, cuando en la línea de comando no se define el valor de un argumento opcional, por defecto adquiere el valor None. Mediante el parámetro opcional default se puede establecer qué valor adquiere por defecto un argumento.

In [ ]:
# argparse_6.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--arg1', default='argumento 1')
parser.add_argument('--arg2', default='argumento 2')
args = parser.parse_args()

print("El valor del primer argumento es '%s'" % args.arg1)
print("y el del segundo argumento es '%s'" % args.arg2)
In [36]:
# %load argparse_6.py
In [37]:
!python argparse_6.py --arg1 Hola --arg2 Python
El valor del primer argumento es 'Hola'
y el del segundo argumento es 'Python'
In [38]:
!python argparse_6.py --arg2 Python
El valor del primer argumento es 'argumento 1'
y el del segundo argumento es 'Python'
In [39]:
!python argparse_6.py
El valor del primer argumento es 'argumento 1'
y el del segundo argumento es 'argumento 2'

En los argumentos obligatorios, y que por tanto no pueden omitirse, también se puede definir un valor por defecto si se establece el número de valores con nargs='?', como se verá a continuación.

nargs

Con el parámetro opcional nargs, se pueden definir argumentos que reciban más de un valor. Esta opción puede recibir alguno de estos valores:

N (número entero) El argumento debe recibir exactamente N valores, de lo contrario el programa arroja un error. Los valores se guardan siempre como una lista, aunque N sea 1.
'*' El argumento admite cualquier número arbitrario de valores (cero o más). También se devuelve siempre una lista (si no se pasa ningún valor, se devuelve una lista vacía).
'+' Similar a '*', pero requiere al menos un valor.
'?' El argumento puede recibir un único valor (en cuyo caso se devuelve el valor individual, no como lista), o ningún valor (en cuyo caso adquiere el valor por defecto None, o el que se especifique con la opción default

El siguiente programa tiene dos argumentos que se pasan por nombre, pero obligatorios por el uso de required=True. Ambos argumentos pueden recibir un número arbitrario de valores, pero al menos uno ('+'). Este código se podría utilizar para pasar la talea y el color como argumentos en la línea de comando, al programa de melodía isorrítmica de la tarea 5.

In [ ]:
# argparse_7a.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--talea', '-t',
                    required=True,
                    type=float,
                    nargs='+')
parser.add_argument('--color', '-c',
                    required=True,
                    type=int,
                    nargs='+')

args = parser.parse_args()

print('talea:', args.talea)
print('color:', args.color)
In [41]:
# %load argparse_7a.py
In [42]:
!python argparse_7a.py -c 60 64 52 59 -t 0.75 0.5 0.5 0.25 0.75
talea: [0.75, 0.5, 0.5, 0.25, 0.75]
color: [60, 64, 52, 59]

El siguiente programa define dos argumentos posicionales, y por tanto, obligatorios. Estableciendo nargs='?', cualquiera de los dos argumentos puede omitirse, en cuyo caso adquiere un valor por defecto, en el primer caso definido por default, en el segundo caso None.

In [ ]:
# argparse_7b.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('arg1', nargs='?', default='argumento 1')
parser.add_argument('arg2', nargs='?')
args = parser.parse_args()

print("El valor del primer argumento es '%s'" % args.arg1)
print("y el del segundo argumento es '%s'" % args.arg2)
In [43]:
# %load argparse_7b.py
In [44]:
!python argparse_7b.py hola Python
El valor del primer argumento es 'hola'
y el del segundo argumento es 'Python'
In [45]:
!python argparse_7b.py hola
El valor del primer argumento es 'hola'
y el del segundo argumento es 'None'
In [46]:
!python argparse_7b.py
El valor del primer argumento es 'argumento 1'
y el del segundo argumento es 'None'

OBSERVACIÓN: Hay que tomar precuaciones cuando los argumentos opcionales de cantidad variable de valores se combinan con argumentos posicionales, porque pueden ser interpretados erróneamente. Se deben pasar primero los argumentos posicionales, y luego los opcionales con sus valores.

action='store_true'

Hay casos en que un argumento por nombre no requiere ningún valor, sino que funciona como una opción o "bandera" (flag), que controla el comportamiento del programa. Eso se puede obtener asignando el valor 'store_true' al parámetro opcional action.

El siguiente programa combina un argumento posicional obligatorio (un número entero que se va a elevar al cuadrado), y un argumento opcional de tipo flag, que aumenta la verbosidad de la salida.

Como se puede observar, el argumento opcional se puede utilizar en su forma corta o larga, y antes o después del argumento posicional.

In [ ]:
# argparse_8.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('input', type=int,
                    help='número entero para elevar al cuadrado')
parser.add_argument('--verbose', '-v', action='store_true',
                    help='imprime el resultado de manera verbosa')
args = parser.parse_args()

cuadrado = args.input ** 2
if args.verbose:
    print("El cuadrado de %s es %s" % (args.input, cuadrado))
else:
    print(cuadrado)
In [47]:
# %load argparse_8.py
In [48]:
!python argparse_8.py -h
usage: argparse_8.py [-h] [--verbose] input

positional arguments:
  input          número entero para elevar al cuadrado

optional arguments:
  -h, --help     show this help message and exit
  --verbose, -v  imprime el resultado de manera verbosa
In [49]:
!python argparse_8.py 4
16
In [50]:
!python argparse_8.py 4 -v
El cuadrado de 4 es 16
In [51]:
!python argparse_8.py --verbose 4
El cuadrado de 4 es 16