2012-02-10 10 views
26

Piszę program, w którym chciałbym mieć argumenty, jak to:W Python argparse, czy możliwe jest sparowanie --no-coś/- coś argumenty?

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

Czy istnieje sposób, aby uzyskać argparse to zrobić dla mnie?

Używam Python 3.2.


Edit:

Ponieważ wydaje się, że nie da się tego zrobić, mam decyduje się na ten pomysł, a nie ...

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

IMHO, nie tak elegancki , ale działa.


Edit 2:

zorientowali się stosunkowo ładne rozwiązanie obejmujące wynikające moja własna klasa pochodzi od argparse.Action że mam szczegółowy w answer to this question.

+0

nr „NIE” prefiks jest wysoce zlokalizowane. Nie jest spójny w języku angielskim ("un-" jest również dość powszechny.) –

+0

Myślę, że musisz napisać to sam. Chciałbym, żeby to wszystko zostało wbudowane. – jterrace

+0

@ S.Lott: To prawda. Ten program nie będzie jednak miał globalnej publiczności. :-) A gdyby taka możliwość była dostępna, spodziewałbym się, że prefiks będzie mógł być w jakiś sposób dostosowany. – Omnifarious

Odpowiedz

21

Cóż, żadna z odpowiedzi jak dotąd są dość zadowalające z różnych powodów.Więc tutaj jest moja własna odpowiedź:

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) 

i przykład użycia:

>>> 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) 

Niestety, funkcja _add_action członek nie jest udokumentowane, więc nie jest to „oficjalna” w kategoriach bycia obsługiwane przez API. Ponadto, Action jest głównie klasą uchwytów. Sam ma bardzo niewielkie zachowanie. Byłoby miło, gdyby można było go użyć, aby nieco bardziej dostosować komunikat pomocy. Na przykład mówiąc: --[no-]foo na początku. Ale ta część jest generowana automatycznie przez elementy spoza klasy Action.

+0

to wygląda ładnie – jterrace

+0

@jterrace: Niestety wymaga ode mnie 15 godzin, zanim przyjmiesz moją własną odpowiedź. Chociaż naprawdę, jeśli ktoś mógłby zrobić coś lepszego, chciałbym wiedzieć jak. – Omnifarious

+0

Możliwe, że będziesz w stanie użyć opcji 'metavar', aby uzyskać' - [no-] foo'. –

2

Napisz własną podklasę.

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 ... to ciekawy pomysł. Czy istnieje pomysł "obiektu argumentu", który może sam parsować rzeczy i może wygenerować własny komunikat pomocy? To by naprawdę poradziło. – Omnifarious

+0

@Omnifarious: "wygeneruj własną wiadomość pomocy"? Co to może oznaczać? Co jest złego w dodawaniu więcej kodu, jak pokazano powyżej? Jeśli chcesz, aby pojawiły się jeszcze bardziej magiczne rzeczy, może łatwiej jest po prostu odczytać źródło do 'argparse' i zobaczyć, jak działa wewnętrznie. –

+0

Cóż, to jedna z największych zalet argparse. Generuje komunikaty pomocy i takie tam. 'add_argument' może być uważany za funkcję, która konstruuje jakiś obiekt argumentu, który reprezentuje wszystkie cechy argumentu ... jak go sparsować, którą zmienną wstawić, domyślne wartości, jak wygenerować pomoc, wszystko to rzeczy i umieszcza je w ładnej liście wewnątrz analizatora składni. Ale masz rację, powinienem sam zagłębić się w wnętrza i zobaczyć, czy mogę to zrobić tak, jak chcę. Jeśli to nie działa tak, jak sobie wyobrażam, powinno. Jest dużo bardziej elastyczny. – Omnifarious

6

Czy add_mutually_exclusive_group() z argparse pomoc?

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' 

Oto jak to wygląda, gdy bieg:

./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 

Inaczej jest z następujących opcji w wzajemnie wyłącznej grupie pozwala ani opcja w programie (i jestem przy założeniu, że chcesz opcje ze względu na składnię --). Oznacza to jedno albo drugie:

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

Jeśli są wymagane (non-opcja), może za pomocą add_subparsers() jest to, czego szukasz.

Update 1

Logicznie różne, ale może czystsze:

... 
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' 

i uruchomienie go:

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

Czy mógłbyś ustawić '' action = 'store_false''' dla '' --no-foo'' i ustawić '' dest =' foo''' dla obu tak, aby wyświetlało się w pojedynczej zmiennej? – jterrace

+0

@jterrace Tak. Interesująca sugestia. Dodałem zaktualizowane rozwiązanie. –

+0

ładny. mógłbyś zawinąć go w funkcję podobną do odpowiedzi @s-lotta i byłby naprawdę niezły – jterrace

2

dla zabawy, tu jest pełna realizacja 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' 

Oto wynik:

./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

To jest bardzo ładne, ale ma kilka wad. Po pierwsze pozwala na określenie zarówno '--foo', jak i' --no-foo' w wierszu poleceń, a ostatni ma pierwszeństwo. Po drugie, pomoc jest niepotrzebnie szczegółowa, mimo że wzajemnie wykluczająca się grupa składa je razem. Poszedłem swoją własną drogą i szczegółowo opisałem swoje podejście w odpowiedzi na to pytanie. – Omnifarious

0

Przed widząc to pytanie i odpowiedź pisałam własną funkcję do czynienia z tym:

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) 

Może być stosowany tak:

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() 

wyjście pomoc wygląda następująco:

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

Preferuję podejście podklasy argparse.Action, że inne odpowiedzi sugerują moją prostą funkcję, ponieważ sprawia, że ​​kod z niej korzystniejszy i łatwiejszy do odczytania.

Kod ten ma tę zaletę, że ma standardową pomoc domyślną, ale także help_on i help_off, aby zmienić konfigurację dość głupich wartości domyślnych.

Może ktoś może się zintegrować.

3

I zmodyfikowane rozwiązanie @Omnifarious aby uczynić go bardziej jak standardowych działań:

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) 

można dodać tak/nie argument, jak można dodać dowolną opcję standardową. Trzeba tylko przejść ActionNoYes klasę w action argumentu:

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

Teraz gdy to nazwać:

>> 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

Rozszerzanie https://stackoverflow.com/a/9236426/1695680 „s odpowiedź

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() 

Daje wyjście pomocy jak tak : sposób użycia: myscript.py [-h] [--flaga] [--flabb]

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

wersja Gist tu wyciągnąć wnioski mile widziane :) https://gist.github.com/thorsummoner/9850b5d6cd5e6bb5a3b9b7792b69b0a5

Powiązane problemy