Domanda

Come posso avere un valore predefinito sub-comand, o gestire il caso in cui non viene fornito alcun sub-comand argparse?

import argparse

a = argparse.ArgumentParser()
b = a.add_subparsers()
b.add_parser('hi')
a.parse_args()

Qui vorrei che un comando fosse selezionato o gli argomenti da gestire in base al prossimo livello più alto di parser (in questo caso il parser di alto livello).

joiner@X:~/src> python3 default_subcommand.py
usage: default_subcommand.py [-h] {hi} ...
default_subcommand.py: error: too few arguments
È stato utile?

Soluzione 2

Sembra che alla fine mi sono imbattuto nella soluzione.

Se il comando è facoltativo, allora Questo rende il comando un'opzione. Nella mia configurazione di parser originale, avevo un file package Comando che potrebbe adottare una serie di possibili passaggi o eseguirebbe tutti i passaggi se non fosse stato dato nessuno. Questo rende il passo una scelta:

parser = argparse.ArgumentParser()

command_parser = subparsers.add_parser('command')
command_parser.add_argument('--step', choices=['prepare', 'configure', 'compile', 'stage', 'package'])

...other command parsers

parsed_args = parser.parse_args()

if parsed_args.step is None:
    do all the steps...

Altri suggerimenti

Su Python 3.2 (e 2.7) otterrai quell'errore, ma non su 3.3 e 3.4 (nessuna risposta). Pertanto, su 3.3/3.4 è possibile testare parsed_args Essere un vuoto Namespace.

Una soluzione più generale è aggiungere un metodo set_default_subparser() (prelevato dal Ruamel.std.argparse pacchetto) e chiama quel metodo appena prima parse_args():

import argparse
import sys

def set_default_subparser(self, name, args=None, positional_args=0):
    """default subparser selection. Call after setup, just before parse_args()
    name: is the name of the subparser to call by default
    args: if set is the argument list handed to parse_args()

    , tested with 2.7, 3.2, 3.3, 3.4
    it works with 2.6 assuming argparse is installed
    """
    subparser_found = False
    for arg in sys.argv[1:]:
        if arg in ['-h', '--help']:  # global help if no subparser
            break
    else:
        for x in self._subparsers._actions:
            if not isinstance(x, argparse._SubParsersAction):
                continue
            for sp_name in x._name_parser_map.keys():
                if sp_name in sys.argv[1:]:
                    subparser_found = True
        if not subparser_found:
            # insert default in last position before global positional
            # arguments, this implies no global options are specified after
            # first positional argument
            if args is None:
                sys.argv.insert(len(sys.argv) - positional_args, name)
            else:
                args.insert(len(args) - positional_args, name)

argparse.ArgumentParser.set_default_subparser = set_default_subparser

def do_hi():
    print('inside hi')

a = argparse.ArgumentParser()
b = a.add_subparsers()
sp = b.add_parser('hi')
sp.set_defaults(func=do_hi)

a.set_default_subparser('hi')
parsed_args = a.parse_args()

if hasattr(parsed_args, 'func'):
    parsed_args.func()

Questo funzionerà con 2.6 (se argparse è installato da PYPI), 2.7, 3.2, 3.3, 3.4. E ti permette di fare entrambe le cose

python3 default_subcommand.py

e

python3 default_subcommand.py hi

con lo stesso effetto.

Consentendo di scegliere un nuovo sottoparser per impostazione predefinita, anziché uno di quelli esistenti.

La prima versione del codice consente l'impostazione di uno dei secondari precedentemente definiti come predefinito. La seguente modifica consente l'aggiunta di un nuovo sottofondo predefinito, che potrebbe quindi essere utilizzato per elaborare in modo specifico il caso in cui non è stato selezionato alcun sottoparetore dall'utente (righe diverse contrassegnate nel codice)

def set_default_subparser(self, name, args=None, positional_args=0):
    """default subparser selection. Call after setup, just before parse_args()
    name: is the name of the subparser to call by default
    args: if set is the argument list handed to parse_args()

    , tested with 2.7, 3.2, 3.3, 3.4
    it works with 2.6 assuming argparse is installed
    """
    subparser_found = False
    existing_default = False # check if default parser previously defined
    for arg in sys.argv[1:]:
        if arg in ['-h', '--help']:  # global help if no subparser
            break
    else:
        for x in self._subparsers._actions:
            if not isinstance(x, argparse._SubParsersAction):
                continue
            for sp_name in x._name_parser_map.keys():
                if sp_name in sys.argv[1:]:
                    subparser_found = True
                if sp_name == name: # check existance of default parser
                    existing_default = True
        if not subparser_found:
            # If the default subparser is not among the existing ones,
            # create a new parser.
            # As this is called just before 'parse_args', the default
            # parser created here will not pollute the help output.

            if not existing_default:
                for x in self._subparsers._actions:
                    if not isinstance(x, argparse._SubParsersAction):
                        continue
                    x.add_parser(name)
                    break # this works OK, but should I check further?

            # insert default in last position before global positional
            # arguments, this implies no global options are specified after
            # first positional argument
            if args is None:
                sys.argv.insert(len(sys.argv) - positional_args, name)
            else:
                args.insert(len(args) - positional_args, name)

argparse.ArgumentParser.set_default_subparser = set_default_subparser

a = argparse.ArgumentParser()
b = a.add_subparsers(dest ='cmd')
sp = b.add_parser('hi')
sp2 = b.add_parser('hai')

a.set_default_subparser('hey')
parsed_args = a.parse_args()

print(parsed_args)

L'opzione "predefinita" non verrà ancora visualizzata nell'aiuto:

python test_parser.py -h
usage: test_parser.py [-h] {hi,hai} ...

positional arguments:
  {hi,hai}

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

Tuttavia, è ora possibile distinguere tra e gestire separatamente la chiamata di uno dei sottoparenti forniti e chiamare il sottoparser predefinito quando non è stato fornito alcun argomento:

$ python test_parser.py hi
Namespace(cmd='hi')
$ python test_parser.py 
Namespace(cmd='hey')

Ecco un modo più bello di aggiungere un set_default_subparser metodo:

class DefaultSubcommandArgParse(argparse.ArgumentParser):
    __default_subparser = None

    def set_default_subparser(self, name):
        self.__default_subparser = name

    def _parse_known_args(self, arg_strings, *args, **kwargs):
        in_args = set(arg_strings)
        d_sp = self.__default_subparser
        if d_sp is not None and not {'-h', '--help'}.intersection(in_args):
            for x in self._subparsers._actions:
                subparser_found = (
                    isinstance(x, argparse._SubParsersAction) and
                    in_args.intersection(x._name_parser_map.keys())
                )
                if subparser_found:
                    break
            else:
                # insert default in first position, this implies no
                # global options without a sub_parsers specified
                arg_strings = [d_sp] + arg_strings
        return super(DefaultSubcommandArgParse, self)._parse_known_args(
            arg_strings, *args, **kwargs
        )

Forse quello che stai cercando è il dest argomento di add_subparsers:

(ATTENZIONE: funziona in Python 3.4, ma non in 2.7)

import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='cmd')
parser_hi = subparsers.add_parser('hi')
parser.parse_args([]) # Namespace(cmd=None)

Ora puoi semplicemente usare il valore di cmd:

if cmd in [None, 'hi']:
    print('command "hi"')

Puoi aggiungere un argomento con un valore predefinito che verrà utilizzato quando non è impostato nulla.

Guarda questo: http://docs.python.org/dev/library/argparse.html#default

Modificare:

Scusa, ho letto la tua domanda un po 'velocemente.

Non credo che avresti un modo diretto di fare ciò che vuoi tramite Argparse. Ma potresti controllare la lunghezza di sys.argv e se la sua lunghezza è 1 (solo nome script), potresti passare manualmente i parametri predefiniti per l'analisi, facendo qualcosa del genere:

import argparse

a = argparse.ArgumentParser()
b = a.add_subparsers()
b.add_parser('hi')

if len(sys.argv) == 1:
   a.parse_args(['hi'])
else:
   a.parse_args()

Penso che dovrebbe fare quello che vuoi, ma sono d'accordo che sarebbe bello averlo fuori dalla scatola.

In Python 2.7, puoi sovrascrivere il comportamento dell'errore in una sottoclasse (un peccato non c'è un modo più bello per differenziare l'errore):

import argparse

class ExceptionArgParser(argparse.ArgumentParser):

    def error(self, message):
        if "invalid choice" in message:
            # throw exception (of your choice) to catch
            raise RuntimeError(message)
        else:
            # restore normal behaviour
            super(ExceptionArgParser, self).error(message)


parser = ExceptionArgParser()
subparsers = parser.add_subparsers(title='Modes', dest='mode')

default_parser = subparsers.add_parser('default')
default_parser.add_argument('a', nargs="+")

other_parser = subparsers.add_parser('other')
other_parser.add_argument('b', nargs="+")

try:
    args = parser.parse_args()
except RuntimeError:
    args = default_parser.parse_args()
    # force the mode into namespace
    setattr(args, 'mode', 'default') 

print args

È possibile duplicare l'azione predefinita di un secondario specifico sul parser principale, rendendolo effettivamente il valore predefinito.

import argparse
p = argparse.ArgumentParser()
sp = p.add_subparsers()

a = sp.add_parser('a')
a.set_defaults(func=do_a)

b = sp.add_parser('b')
b.set_defaults(func=do_b)

p.set_defaults(func=do_b)
args = p.parse_args()

if args.func:
    args.func()
else:
    parser.print_help()

Non funziona con add_subparsers(required=True), ecco perché il if args.func è laggiù.

Nel mio caso ho trovato più semplice fornire esplicitamente il nome del sottocomando parse_args() quando argv era vuoto.

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='commands')

runParser = subparsers.add_parser('run', help='[DEFAULT ACTION]')

altParser = subparsers.add_parser('alt', help='Alternate command')
altParser.add_argument('alt_val', type=str, help='Value required for alt command.')

# Here's my shortcut: If `argv` only contains the script name,
# manually inject our "default" command.
args = parser.parse_args(['run'] if len(sys.argv) == 1 else None)
print args

Esempio di esecuzione:

$ ./test.py 
Namespace()

$ ./test.py alt blah
Namespace(alt_val='blah')

$ ./test.py blah
usage: test.py [-h] {run,alt} ...
test.py: error: invalid choice: 'blah' (choose from 'run', 'alt')

Per un riferimento successivo:

...
b = a.add_subparsers(dest='cmd')
b.set_defaults(cmd='hey')  # <-- this makes hey as default

b.add_parser('hi')

Quindi, questi due saranno gli stessi:

  • pithon main.py hey
  • Python Main.py
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top