Sub-comando predefinito o non gestire alcun sub-comando con Argparse
-
28-10-2019 - |
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
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