Sous-commande par défaut, ou traitement sans sous-commande avec argparse

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')
16
répondu Anthon 2018-05-21 16:06:06

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...
7
répondu Matt Joiner 2011-06-18 09:09:09

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
        )
3
répondu Thomas Grainger 2016-06-02 13:42:11

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"')
2
répondu Cédric Van Rompay 2016-01-30 10:36:44

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.

1
répondu Locksfree 2011-06-16 07:45:06

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
0
répondu JSBach 2018-04-11 12:08:25

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
0
répondu Frank Dumont 2018-08-29 15:48:04