cómo obtener argparse para leer argumentos de un archivo con una opción en lugar de prefijo

Me gustaría saber cómo usar el módulo argparse de python para leer argumentos tanto desde la línea de comandos como posiblemente desde archivos de texto. Sé de argparse’s fromfile_prefix_chars pero eso no es exactamente lo que quiero. Quiero el comportamiento, pero no quiero la syntax. Quiero una interfaz que se vea así:

 $ python myprogram.py --foo 1 -A somefile.txt --bar 2 

Cuando argparse ve -A, debe dejar de leer desde sys.argv o lo que sea que yo le dé, y llamar a una función que escribo que leerá somefile.text y devolverá una lista de argumentos. Cuando el archivo se agote, debe reanudar el análisis de sys.argv o lo que sea. Es importante que el procesamiento de los argumentos en el archivo suceda en orden (es decir: -foo debe procesarse, luego los argumentos en el archivo, luego -bar, de modo que los argumentos en el archivo pueden anular –foo, y – la barra podría anular lo que está en el archivo).

¿Es posible tal cosa? ¿Puedo escribir una función personalizada que introduzca nuevos argumentos en la stack de argparse, o algo parecido?

Puede resolver esto utilizando un argparse.Action personalizado que abre el archivo, analiza el contenido del archivo y luego agrega los argumentos.

Por ejemplo, esta sería una acción muy simple:

 class LoadFromFile (argparse.Action): def __call__ (self, parser, namespace, values, option_string = None): with values as f: parser.parse_args(f.read().split(), namespace) 

Que se puede utilizar de esta manera:

 parser = argparse.ArgumentParser() # other arguments parser.add_argument('--file', type=open, action=LoadFromFile) args = parser.parse_args() 

El espacio de nombres resultante en args también contendrá cualquier configuración que también se haya cargado desde el archivo.

Si necesita un análisis más sofisticado, también puede analizar primero la configuración en el archivo por separado y luego elegir selectivamente qué valores deben tomarse. Por ejemplo, podría tener sentido no permitir especificar otro archivo en el archivo de configuración:

 def __call__ (self, parser, namespace, values, option_string=None): with values as f: contents = f.read() data = parser.parse_args(contents.split(), namespace=namespace) for k, v in vars(data).items(): if v and k != option_string.lstrip('-'): setattr(namespace, k, v) 

Por supuesto, también podría hacer que la lectura del archivo sea un poco más complicada, por ejemplo, lea primero de JSON.

Usted comentó que

Necesito poder escribir mi propia función para leer ese archivo y devolver los argumentos (no está en un formato de un argumento por línea) –

Hay una disposición en el controlador de archivos de prefijo existente para cambiar la forma en que se lee el archivo. El archivo se lee mediante un método ‘privado’, parser._read_args_from_files , pero llama a un método público simple que convierte una línea en cadenas, acción predeterminada de un argumento por línea:

 def convert_arg_line_to_args(self, arg_line): return [arg_line] 

Fue escrito de esta manera para que pueda personalizarlo fácilmente. https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.convert_arg_line_to_args

Una anulación útil de este método es una que trata a cada palabra separada por espacios como un argumento:

 def convert_arg_line_to_args(self, arg_line): for arg in arg_line.split(): if not arg.strip(): continue yield arg 

En el archivo test_argparse.py unittesting hay un caso de prueba para esta alternativa.


Pero si aún desea activar esta lectura con una opción de argumento, en lugar de un carácter de prefijo, entonces el enfoque de acción personalizado es bueno.

Sin embargo, podría escribir su propia función que procesa argv antes de argv al parser . Podría ser modelado en parser._read_args_from_files .

Así que podrías escribir una función como:

 def read_my_file(argv): # if there is a '-A' string in argv, replace it, and the following filename # with the contents of the file (as strings) # you can adapt code from _read_args_from_files new_argv = [] for a in argv: .... # details left to user return new_argv 

Luego invoca tu analizador con:

 parser.parse_args(read_my_file(sys.argv[1:])) 

Y sí, esto podría estar envuelto en una subclase ArgumentParser .

Una Action , cuando se llama, obtiene un parser y namespace entre sus argumentos.

Así que puedes poner tu archivo a través del primero para actualizar el último:

 class ArgfileAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): extra_args = (values) #`namespace' is updated in-place when specified parser.parse_args(extra_args,namespace)