¿Cómo crear un argumento que es opcional?

En lugar de que el usuario tenga que usar script.py --file c:/stuff/file.txt ¿hay una manera de permitir que el usuario use opcionalmente el --file ? Entonces, en cambio, se vería como script.py c:/stuff/file.txt pero el analizador aún sabría que el usuario se refiere al argumento –file (porque está implícito).

Prueba esto

 import argparse class DoNotReplaceAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): if not getattr(namespace, self.dest): setattr(namespace, self.dest, values) parser = argparse.ArgumentParser(description="This is an example.") parser.add_argument('file', nargs='?', default='', help='specifies a file.', action=DoNotReplaceAction) parser.add_argument('--file', help='specifies a file.') args = parser.parse_args() # check for file argument if not args.file: raise Exception('Missing "file" argument') 

Mira el mensaje de ayuda. Todos los argumentos son opcionales.

 usage: test.py [-h] [--file FILE] [file] This is an example. positional arguments: file specifies a file. optional arguments: -h, --help show this help message and exit --file FILE specifies a file. 

Una cosa a tener en cuenta es que el file posicional anulará el file opcional y establecerá el args.file como predeterminado ”. Para superar esto usé action personalizada para el file posicional. Se prohíbe anular propiedades ya establecidas.

La otra cosa a tener en cuenta es que, en lugar de generar una Exception , puede especificar un valor predeterminado.

Si puedo reformular su pregunta en la respuesta, desea un script que, cuando se ejecuta como:

  • script blah trata a blah como un nombre de archivo que se abrirá
  • script --file blah trata a blah como un nombre de archivo para abrir
  • script --file blah eggs trata a blah como un nombre de archivo para abrir, y eggs … ¿cómo?
  • script blah eggs trata blah … diferentemente? ¿cómo?

En cualquier caso, todavía empezaría con argparse:

 #! /usr/bin/env python import argparse parser = argparse.ArgumentParser(description='script to morgle blahs') parser.add_argument('--file', help='specify file name to be opened') parser.add_argument('args', metavar='FILE', nargs='*') args = parser.parse_args() print args 

En este punto, ejecutar ./script.py -h produce:

 usage: script.py [-h] [--file FILE] [FILE [FILE ...]] script to morgle blahs positional arguments: FILE optional arguments: -h, --help show this help message and exit --file FILE specify file name to be opened 

Carreras adicionales:

 $ ./script.py Namespace(args=[], file=None) $ ./script.py blah Namespace(args=['blah'], file=None) $ ./script.py --file blah eggs Namespace(args=['eggs'], file='blah') $ ./script.py blah eggs Namespace(args=['blah', 'eggs'], file=None) 

Entonces, en lugar de simplemente print args , ahora puede probar si args.file es None (no --file ) y luego verificar args.args , y si args.file no es None , todavía puede revisar args.args .

Si, en algún momento, decide en su propio código que alguna combinación de argumentos es mala / no válida, puede llamar a parser.error , por ejemplo:

 if args.file is not None and len(args.args) > 0: parser.error('use [--file] , not --file  ') if args.file is None and len(args.args) != 1: parser.error('use [--file] ') 

exigiría exactamente un argumento, esté o no precedido por la cadena --file .

Para aceptar --file FILE o simplemente FILE , puede usar mutually_exclusive_group() :

 import argparse parser = argparse.ArgumentParser(prog='script', description="This is an example.", usage='%(prog)s [-h] (--file FILE | FILE)') group = parser.add_mutually_exclusive_group(required=True) group.add_argument('positional_file', nargs='?', help='specifies a file.') group.add_argument('--file', help='specifies a file.') args = parser.parse_args() print(args) filename = args.positional_file if args.file is None else args.file 

Ejemplos

 ['abc'] -> Namespace(file=None, positional_file='abc') ['--file', 'abc'] -> Namespace(file='abc', positional_file=None) ['--file', 'abc', 'def'] -> usage: script [-h] (--file FILE | FILE) script: error: argument positional_file: not allowed with argument --file [] -> usage: script [-h] (--file FILE | FILE) script: error: one of the arguments positional_file --file is required 

Podría usar la argparse required=True en argparse :

 import argparse parser = argparse.ArgumentParser(description="Describe stuff.") parser.add_argument('--foo', required=True, help='bar') 

Sin embargo, como se indica en esta documentación , se considera que es una mala forma realizar las opciones necesarias, ya que los usuarios esperan que las opciones sean opcionales.

En su lugar, podría definir su argumento requerido y luego tener el almacén de --foo opcional para este argumento requerido. Esto puede hacer que el analizador genere una excepción, ya que puede pensar que simplemente está ignorando el argumento requerido.

 import argparse parser = argparse.ArgumentParser(description="Will this work?") parser.add_argument('bar', help="required argument") parser.add_argument('--foo', required=False, help="kind of required argument", dest='bar') 

Creo que la mejor respuesta es simplemente no tener una marca de tipo de requisito. Simplemente haga la variable requerida y defina un valor predeterminado para ella en caso de que necesite algo en su progtwig para usar, pero de alguna manera se aplique un valor predeterminado:

 import argparse parser = argparse.ArgumentParser(description="Other option.") parser.add_argument('bar', default='value', help="required argument") 

En su diseño, hay una ambigüedad principal (eso ya es una explicación razonable de por qué no es implementado por argparse ):

  • Si hay más argumentos posicionales después de un “modo dual” (por ejemplo, foo/--foo y bar ), ¿a cuál se debe asignar un argumento posicional en una línea de cm como --foo=foo bar ? Esto se vuelve aún más confuso si hay múltiples argumentos de “modo dual”.

En mi guión que escribí hace dos años, que usaba argumentos de “modo dual”, prohibí tal entrada por completo, requiriendo que los argumentos de “modo posicional”, si los hubiera, fueran primero, seguidos de los de “modo nombrado”.

La secuencia de comandos estaba en Perl y usé la lógica personalizada para implementar esto después de analizar otras opciones con Getopt::Long Perl en modo “paso a través” (pasa a través de los argumentos que no se reconocen).

Así que te sugiero que hagas lo mismo, usando ArgumentParser.parse_known_args() .