Sous-commande par défaut, ou traitement sans sous-commande avec argparse
Comment puis-je avoir un défaut sous-commande, ou gérer le cas où aucune sous-commande n'est donnée en utilisant argparse
<!--5?
import argparse
a = argparse.ArgumentParser()
b = a.add_subparsers()
b.add_parser('hi')
a.parse_args()
ici, je voudrais qu'une commande soit sélectionnée, ou que les arguments soient manipulés en se basant uniquement sur le niveau le plus élevé de l'analyseur (dans ce cas, l'analyseur de haut niveau).
joiner@X:~/src> python3 default_subcommand.py usage: default_subcommand.py [-h] {hi} ... default_subcommand.py: error: too few arguments
7 réponses
Sur Python 3.2 (et 2.7), vous obtiendrez cette erreur, mais pas sur les 3.3 et 3.4 (pas de réponse). Par conséquent, sur 3.3 / 3.4 vous pouvez tester pour parsed_args
vide Namespace
.
une solution plus générale est d'ajouter une méthode set_default_subparser()
(tiré de la ruamel.MST.argparse paquet) et appelez cette méthode juste avant 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()
cela fonctionnera avec 2.6 (si argparse
est installé à partir de PyPI), 2.7, 3.2, 3.3, 3.4. Et vous permet de faire les deux
python3 default_subcommand.py
et
python3 default_subcommand.py hi
avec le même effet.
Permettant de choisir un nouveau subparser par défaut, au lieu de l'un de ceux existants.
la première version du code permet de définir par défaut l'un des sous-traitants définis précédemment. La modification suivante permet d'ajouter un nouveau sous-analyseur par défaut, qui pourrait ensuite être utilisé pour traiter spécifiquement le cas où aucun sous-analyseur n'a été sélectionné par l'utilisateur (différentes lignes marquées dans le code)
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'option "default" n'apparaîtra toujours pas dans l'aide:
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
cependant, il est maintenant possible de faire la différence entre et traiter séparément l'appel à l'un des sous-analyseurs fournis et l'appel au sous-analyseur par défaut quand aucun argument n'a été fourni:
$ python test_parser.py hi
Namespace(cmd='hi')
$ python test_parser.py
Namespace(cmd='hey')
il semble que j'ai moi-même trébuché sur la solution.
Si la commande est facultative, ceci fait de la commande une option. Dans ma configuration d'origine, j'avais un package
commande qui pourrait prendre une gamme d'étapes possibles, ou il effectuerait toutes les étapes si aucune n'était donnée. Cela rend l'étape de choix:
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...
Voici une jolie manière de l'ajout d'un set_default_subparser
méthode:
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
)
peut-être que ce que vous cherchez est le dest
argument de add_subparsers
:
(Avertissement: les travaux de Python 3.4, mais pas dans 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)
Maintenant, vous pouvez simplement utiliser la valeur de cmd
:
if cmd in [None, 'hi']:
print('command "hi"')
Vous pouvez ajouter un argument avec une valeur par défaut qui sera utilisé lorsque rien n'est réglé, je crois.
Voir ceci: http://docs.python.org/dev/library/argparse.html#default
Edit:
Désolé, j'ai lu votre question un peu vite.
Je ne pense pas que vous auriez une façon directe de faire ce que vous voulez via argparse. Mais vous pouvez vérifier la longueur de sys.argv et si sa longueur est 1 (Nom du script seulement) alors vous pouvez passer manuellement paramètres par défaut pour l'analyse, faire quelque chose comme ceci:
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()
je pense que cela devrait faire ce que vous voulez, mais je suis d'accord qu'il serait agréable d'avoir ce hors de la boîte.
de référence Pour plus tard:
...
b = a.add_subparsers(dest='cmd')
b.set_defaults(cmd='hey') # <-- this makes hey as default
b.add_parser('hi')
donc, ces deux-là vont même:
- python main.py Hé!--7-->
- python main.py
en python 2.7, vous pouvez modifier le comportement d'erreur dans une sous-classe (dommage qu'il n'y ait pas de meilleur moyen de différencier l'erreur):
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