2012-02-10 7 views
26

Sto scrivendo un programma in cui mi piacerebbe avere argomenti come questo:In Python argparse, è possibile avere accoppiato --no-qualcosa/- qualcosa argomenti?

--[no-]foo Do (or do not) foo. Default is do. 

C'è un modo per ottenere argparse di fare questo per me?

Sto usando Python 3.2.


Edit:

Dal momento che non sembra esserci alcun modo per fare questo, io sto optando per questa idea invece ...

parser.add_argument('--foo=', choices=('y', 'n'), default='y', 
        help="Do foo? (default y)") 

IMHO, non è così elegante , ma funziona.


Edit 2:

ho capito un ragionevolmente bella soluzione che coinvolge derivante mia classe derivata da argparse.Action che ho dettagliato in un answer to this question.

+0

No. Il prefisso "no-" è altamente localizzata. Non è coerente in inglese ("un-" è anche abbastanza comune). –

+0

Penso che tu debba scriverlo da solo. Vorrei averlo incorporato. – jterrace

+0

@ S.Lott: È vero. Tuttavia, questo programma non avrà un pubblico globale. :-) E se questa possibilità fosse disponibile, mi aspetterei che il prefisso possa essere personalizzato in qualche modo. – Omnifarious

risposta

21

Ebbene, nessuna delle risposte finora sono abbastanza soddisfacente per una serie di motivi.Così qui è la mia risposta:

class ActionNoYes(argparse.Action): 
    def __init__(self, opt_name, dest, default=True, required=False, help=None): 
     super(ActionNoYes, self).__init__(['--' + opt_name, '--no-' + opt_name], dest, nargs=0, const=None, default=default, required=required, help=help) 
    def __call__(self, parser, namespace, values, option_string=None): 
     if option_string.starts_with('--no-'): 
      setattr(namespace, self.dest, False) 
     else: 
      setattr(namespace, self.dest, True) 

E un esempio di utilizzo:

>>> p = argparse.ArgumentParser() 
>>> p._add_action(ActionNoYes('foo', 'foo', help="Do (or do not) foo. (default do)")) 
ActionNoYes(option_strings=['--foo', '--no-foo'], dest='foo', nargs=0, const=None, default=True, type=None, choices=None, help='Do (or do not) foo. (default do)', metavar=None) 
>>> p.parse_args(['--no-foo', '--foo', '--no-foo']) 
Namespace(foo=False) 
>>> p.print_help() 
usage: -c [-h] [--foo] 

optional arguments: 
    -h, --help  show this help message and exit 
    --foo, --no-foo Do (or do not) foo. (default do) 

Purtroppo, la funzione _add_action membro non è documentata, quindi questo non è 'ufficiale' in termini di essere supportato dall'API. Inoltre, Action è principalmente una classe titolare. Ha un comportamento molto piccolo da solo. Sarebbe bello se fosse possibile usarlo per personalizzare un po 'di più il messaggio di aiuto. Ad esempio dicendo --[no-]foo all'inizio. Ma quella parte viene generata automaticamente da elementi esterni alla classe Action.

+0

questo sembra bello – jterrace

+0

@jterrace: Sfortunatamente è necessario attendere 15 ore prima di accettare la mia risposta. Anche se, davvero, se qualcuno potesse fare meglio di questo mi piacerebbe sapere come. – Omnifarious

+0

Potresti essere in grado di usare l'opzione 'metavar' in qualche modo per ottenere' - [no-] foo'. –

2

Scrivi la tua sottoclasse.

class MyArgParse(argparse.ArgumentParser): 
    def magical_add_paired_arguments(self, *args, **kw): 
     self.add_argument(*args, **kw) 
     self.add_argument('--no'+args[0][2:], *args[1:], **kw) 
+0

Hmm ... questa è un'idea interessante. Esiste un'idea di un 'oggetto argomento' in grado di analizzare le cose stesse e magari generare il proprio messaggio di aiuto? Questo farebbe davvero il trucco. – Omnifarious

+0

@Onniversario: "genera il proprio messaggio di aiuto"? Cosa può significare? Cosa c'è di sbagliato nell'aggiungere più codice come mostrato sopra? Se vuoi che si verifichino ancora più cose magiche, potresti trovare più semplice leggere semplicemente la fonte su "argparse" e vedere come funziona internamente. –

+0

Bene, questo è uno dei grandi vantaggi di argparse. Genera messaggi di aiuto e cose per te. 'add_argument' potrebbe essere pensato come una funzione che costruisce una sorta di argomento oggetto che rappresenta tutte le caratteristiche di un argomento ... come analizzarlo, quale variabile riempirlo, valori predefiniti, come generare aiuto, tutto ciò roba e la mette in una bella lista all'interno del parser. Ma hai ragione, dovrei solo addentrarmi negli interni e vedere se riesco a giocherellare come voglio io. Se non funziona come immagino, dovrebbe. È molto più flessibile. – Omnifarious

6

fa il add_mutually_exclusive_group() di argparse aiuto?

parser = argparse.ArgumentParser() 
exclusive_grp = parser.add_mutually_exclusive_group() 
exclusive_grp.add_argument('--foo', action='store_true', help='do foo') 
exclusive_grp.add_argument('--no-foo', action='store_true', help='do not do foo') 
args = parser.parse_args() 

print 'Starting program', 'with' if args.foo else 'without', 'foo' 
print 'Starting program', 'with' if args.no_foo else 'without', 'no_foo' 

Ecco come appare quando viene eseguito:

./so.py --help 
usage: so.py [-h] [--foo | --no-foo] 

optional arguments: 
    -h, --help show this help message and exit 
    --foo  do foo 
    --no-foo do not do foo 

./so.py 
Starting program without foo 
Starting program without no_foo 

./so.py --no-foo --foo 
usage: so.py [-h] [--foo | --no-foo] 
so.py: error: argument --foo: not allowed with argument --no-foo 

Questo è diverso da quanto segue nel gruppo escludono a vicenda permette opzione nel programma (e sto supponendo che si desidera opzioni a causa della sintassi --). Ciò implica uno o l'altro:

parser.add_argument('--foo=', choices=('y', 'n'), default='y', 
        help="Do foo? (default y)") 

Se questi sono necessari (non opzionale), magari usando add_subparsers() è quello che stai cercando.

Update 1

Logicamente diversa, ma forse più pulito:

... 
exclusive_grp.add_argument('--foo', action='store_true', dest='foo', help='do foo') 
exclusive_grp.add_argument('--no-foo', action='store_false', dest='foo', help='do not do foo') 
args = parser.parse_args() 

print 'Starting program', 'with' if args.foo else 'without', 'foo' 

ed eseguirlo:

./so.py --foo 
Starting program with foo 
./so.py --no-foo 
Starting program without foo 
./so.py 
Starting program without foo 
+1

Puoi impostare '' action = 'store_false''' per '' --no-foo'' e impostare '' dest =' foo''' per entrambi in modo che si presenti in una singola variabile? – jterrace

+0

@jterrace Sì. Suggerimento interessante. Ho aggiunto una soluzione aggiornata. –

+0

bello. potresti avvolgerlo in una funzione come nella risposta di @ s-lott e sarebbe davvero bello – jterrace

2

Per divertimento, ecco un'implementazione completa di S.Lott's answer:

import argparse 

class MyArgParse(argparse.ArgumentParser): 
    def magical_add_paired_arguments(self, *args, **kw): 
     exclusive_grp = self.add_mutually_exclusive_group() 
     exclusive_grp.add_argument(*args, **kw) 
     new_action = 'store_false' if kw['action'] == 'store_true' else 'store_true' 
     del kw['action'] 
     new_help = 'not({})'.format(kw['help']) 
     del kw['help'] 
     exclusive_grp.add_argument('--no-'+args[0][2:], *args[1:], 
          action=new_action, 
          help=new_help, **kw) 

parser = MyArgParse() 
parser.magical_add_paired_arguments('--foo', action='store_true', 
            dest='foo', help='do foo') 
args = parser.parse_args() 

print 'Starting program', 'with' if args.foo else 'without', 'foo' 

ecco l'output:

./so.py --help 
usage: so.py [-h] [--foo | --no-foo] 

optional arguments: 
    -h, --help show this help message and exit 
    --foo  do foo 
    --no-foo not(do foo) 
+0

Questo è molto bello, ma ha un paio di svantaggi. Prima consente di specificare sia '--foo' che' --no-foo' sulla riga di comando e che l'ultimo ha la precedenza. In secondo luogo, l'aiuto è inutilmente prolisso, anche se la cosa di gruppo mutuamente esclusiva li mette insieme. Sono andato per la mia strada e dettagliato il mio approccio in una risposta a questa domanda. – Omnifarious

0

Prima di vedere questa domanda e le risposte che ho scritto la mia propria funzione per affrontare questo:

def on_off(item): 
    return 'on' if item else 'off' 

def argparse_add_toggle(parser, name, **kwargs): 
    """Given a basename of an argument, add --name and --no-name to parser 

    All standard ArgumentParser.add_argument parameters are supported 
    and fed through to add_argument as is with the following exceptions: 
    name  is used to generate both an on and an off 
      switch: --<name>/--no-<name> 
    help  by default is a simple 'Switch on/off <name>' text for the 
      two options. If you provide it make sure it fits english 
      language wise into the template 
       'Switch on <help>. Default: <default>' 
      If you need more control, use help_on and help_off 
    help_on Literally used to provide the help text for --<name> 
    help_off Literally used to provide the help text for --no-<name> 
    """ 
    default = bool(kwargs.pop('default', 0)) 
    dest = kwargs.pop('dest', name) 
    help = kwargs.pop('help', name) 
    help_on = kwargs.pop('help_on', 'Switch on {}. Default: {}'.format(help, on_off(defaults))) 
    help_off = kwargs.pop('help_off', 'Switch off {}.'.format(help)) 

    parser.add_argument('--' + name, action='store_true', dest=dest, default=default, help=help_on) 
    parser.add_argument('--no-' + name, action='store_false', dest=dest, help=help_off) 

Può essere utilizzato in questo modo:

defaults = { 
    'dry_run' : 0, 
    } 

parser = argparse.ArgumentParser(description="Fancy Script", 
           formatter_class=argparse.RawDescriptionHelpFormatter) 
argparse_add_toggle(parser, 'dry_run', default=defaults['dry_run'], 
        help_on='No modifications on the filesystem. No jobs started.', 
        help_off='Normal operation') 
parser.set_defaults(**defaults) 

args = parser.parse_args() 

uscita Aiuto simile a questo:

--dry_run    No modifications on the filesystem. No jobs started. 
    --no-dry_run   Normal operation 

Preferisco l'approccio della sottoclasse argparse.Action che le altre risposte stanno suggerendo la mia semplice funzione perché rende il codice che lo usa più pulito e più facile da leggere.

Questo codice ha il vantaggio di avere una guida predefinita standard, ma anche uno help_on e help_off per riconfigurare i valori predefiniti piuttosto stupidi.

Forse qualcuno può integrare.

3

ho modificato la soluzione di @Omnifarious per renderlo più simile alle azioni standard:

import argparse 

class ActionNoYes(argparse.Action): 
    def __init__(self, option_strings, dest, default=None, required=False, help=None): 

     if default is None: 
      raise ValueError('You must provide a default with Yes/No action') 
     if len(option_strings)!=1: 
      raise ValueError('Only single argument is allowed with YesNo action') 
     opt = option_strings[0] 
     if not opt.startswith('--'): 
      raise ValueError('Yes/No arguments must be prefixed with --') 

     opt = opt[2:] 
     opts = ['--' + opt, '--no-' + opt] 
     super(ActionNoYes, self).__init__(opts, dest, nargs=0, const=None, 
              default=default, required=required, help=help) 
    def __call__(self, parser, namespace, values, option_strings=None): 
     if option_strings.startswith('--no-'): 
      setattr(namespace, self.dest, False) 
     else: 
      setattr(namespace, self.dest, True) 

È possibile aggiungere il Sì/No argomento, come si dovrebbe aggiungere qualsiasi opzione standard. Hai solo bisogno di passare ActionNoYes classe nella discussione action:

parser = argparse.ArgumentParser() 
parser.add_argument('--foo', action=ActionNoYes, default=False) 

Ora, quando si chiama:

>> args = parser.parse_args(['--foo']) 
Namespace(foo=True) 
>> args = parser.parse_args(['--no-foo']) 
Namespace(foo=False) 
>> args = parser.parse_args([]) 
Namespace(foo=False) 
1

Estendere https://stackoverflow.com/a/9236426/1695680 's risposta

import argparse 

class ActionFlagWithNo(argparse.Action): 
    """ 
     Allows a 'no' prefix to disable store_true actions. 
     For example, --debug will have an additional --no-debug to explicitly disable it. 
    """ 
    def __init__(self, opt_name, dest=None, default=True, required=False, help=None): 
     super(ActionFlagWithNo, self).__init__(
      [ 
       '--' + opt_name[0], 
       '--no-' + opt_name[0], 
      ] + opt_name[1:], 
      dest=(opt_name[0].replace('-', '_') if dest is None else dest), 
      nargs=0, const=None, default=default, required=required, help=help, 
     ) 

    def __call__(self, parser, namespace, values, option_string=None): 
     if option_string.startswith('--no-'): 
      setattr(namespace, self.dest, False) 
     else: 
      setattr(namespace, self.dest, True) 

class ActionFlagWithNoFormatter(argparse.HelpFormatter): 
    """ 
     This changes the --help output, what is originally this: 

      --file, --no-file, -f 

     Will be condensed like this: 

      --[no-]file, -f 
    """ 

    def _format_action_invocation(self, action): 
     if action.option_strings[1].startswith('--no-'): 
      return ', '.join(
       [action.option_strings[0][:2] + '[no-]' + action.option_strings[0][2:]] 
       + action.option_strings[2:] 
      ) 
     return super(ActionFlagWithNoFormatter, self)._format_action_invocation(action) 


def main(argp=None): 
    if argp is None: 
     argp = argparse.ArgumentParser(
      formatter_class=ActionFlagWithNoFormatter, 
     ) 
     argp._add_action(ActionFlagWithNo(['flaga', '-a'], default=False, help='...')) 
     argp._add_action(ActionFlagWithNo(['flabb', '-b'], default=False, help='...')) 

     argp = argp.parse_args() 

Questo produce l'uscita di aiuto in questo modo : utilizzo: myscript.py [-h] [--flaga] [--flabb]

optional arguments: 
    -h, --help  show this help message and exit 
    --[no-]flaga, -a ... 
    --[no-]flabb, -b ... 

versione Gist qui, tirare le richieste di benvenuto :) https://gist.github.com/thorsummoner/9850b5d6cd5e6bb5a3b9b7792b69b0a5

Problemi correlati