Question

Comment puis-je avoir une sous-commande par défaut,ou gérer le cas où aucune sous-commande n'est donnée en utilisant argparse ?

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 les arguments à gérer uniquement en fonction du niveau supérieur d'analyseur suivant (dans ce cas, l'analyseur de niveau supérieur).

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

La solution 2

Il semble que j'ai fini par trouver la solution moi-même.

Si la commande est facultative, alors cela fait de la commande une option .Dans ma configuration originale d'analyseur, j'avais une commande package qui pouvait prendre une gamme d'étapes possibles, ou elle effectuerait toutes les étapes si aucune n'était donnée.Cela fait de l'étape un 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...

Autres conseils

Sur Python 3.2 (et 2.7), vous obtiendrez cette erreur, mais pas sur 3.3 et 3.4 (pas de réponse). Par conséquent, sur la version 3.3 / 3.4, vous pouvez tester le parsed_args comme étant un Namespace vide.

Une solution plus générale consiste à ajouter une méthode set_default_subparser() (tirée du ruamel.std.argparse package) 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.

Permettre de choisir un nouveau sous-analyseur par défaut, au lieu de l'un des existants.

La première version du code permet de définir l'un des sous-analyseurs précédemment définis par défaut. 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 "par défaut" 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 différencier et de gérer séparément l'appel de l'un des sous-analyseurs fournis et l'appel du sous-analyseur par défaut lorsqu'aucun argument n'a été fourni:

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

Voici une meilleure façon d'ajouter une méthode set_default_subparser:

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 vous cherchez l'argument dest de add_subparsers:

( Attention: fonctionne en Python 3.4, mais pas en 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)

Vous pouvez maintenant utiliser simplement 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ée lorsque rien n'est défini, je crois.

Voir ceci: http://docs.python.org/dev/library/argparse.html#default

Modifier:

Désolé, j'ai lu votre question un peu rapidement.

Je ne pense pas que vous ayez un moyen direct de faire ce que vous voulez via argparse.Mais vous pouvez vérifier la longueur de sys.argv et si sa longueur est de 1 (uniquement le nom du script), vous pouvez passer manuellement les paramètres par défaut pour l'analyse, en faisant 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 que ce serait bien de l'avoir prêt à l'emploi.

En python 2.7, vous pouvez remplacer le comportement d'erreur dans une sous-classe (dommage qu'il n'y ait pas de meilleure façon 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

Vous pouvez dupliquer l'action par défaut d'un sous-analyseur spécifique sur l'analyseur principal, ce qui en fait la valeur par défaut.

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

Ne fonctionne pas avec add_subparsers(required=True), c'est pourquoi le if args.func est là-bas.

Dans mon cas, j'ai trouvé plus facile de fournir explicitement le nom de la sous-commande à parse_args() lorsque argv était vide.

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

Exemple d'exécutions:

$ ./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')

Pour référence ultérieure:

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

b.add_parser('hi')

donc, ces deux seront identiques:

  • python main.py hey
  • python main.py
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top