web-dev-qa-db-fra.com

Python argparse et contrôler / remplacer le code d'état de sortie

Outre le bricolage avec la source argparse, existe-t-il un moyen de contrôler le code d'état de sortie en cas de problème lorsque parse_args() est appelé, par exemple, un commutateur requis manquant?

41
Kev

Je ne connais aucun mécanisme pour spécifier un code de sortie par argument. Vous pouvez intercepter l'exception SystemExit déclenchée sur .parse_args() mais je ne sais pas comment vous pourriez alors déterminer ce que spécifiquement a provoqué l'erreur.

EDIT: Pour tous ceux qui recherchent une solution pratique, voici la situation:

  • ArgumentError() est correctement levé lorsque l'analyse de l'argument échoue. Il est passé l'instance d'argument et un message
  • ArgumentError() ne pas stocke l'argument en tant qu'attribut d'instance, bien qu'il soit passé (ce qui serait pratique)
  • Il est possible de relancer l'exception ArgumentError en sous-classant ArgumentParser, en remplaçant .error () et en récupérant l'exception depuis sys.exc_info ()

Tout cela signifie que le code suivant - tout en étant laid - nous permet d'attraper l'exception ArgumentError, de saisir l'argument et le message d'erreur incriminés et de faire ce que nous jugeons opportun:

import argparse
import sys

class ArgumentParser(argparse.ArgumentParser):    
    def _get_action_from_name(self, name):
        """Given a name, get the Action instance registered with this parser.
        If only it were made available in the ArgumentError object. It is 
        passed as it's first arg...
        """
        container = self._actions
        if name is None:
            return None
        for action in container:
            if '/'.join(action.option_strings) == name:
                return action
            Elif action.metavar == name:
                return action
            Elif action.dest == name:
                return action

    def error(self, message):
        exc = sys.exc_info()[1]
        if exc:
            exc.argument = self._get_action_from_name(exc.argument_name)
            raise exc
        super(ArgumentParser, self).error(message)

## usage:
parser = ArgumentParser()
parser.add_argument('--foo', type=int)
try:
    parser.parse_args(['--foo=d'])
except argparse.ArgumentError, exc:
    print exc.message, '\n', exc.argument

Non testé d'aucune manière utile. L'indemnité habituelle de ne pas me blâmer en cas de rupture s'applique.

44
Rob Cowie

Toutes les réponses expliquent bien les détails de la mise en œuvre argparse .

En effet, comme proposé dans PEP (et pointé par Rob Cowie), il faut hériter ArgumentParser et remplacer le comportement de erreur ou sortie méthodes.

Dans mon cas, je voulais juste remplacer l'impression d'utilisation par une aide complète en cas d'erreur:

class ArgumentParser(argparse.ArgumentParser):

    def error(self, message):
        self.print_help(sys.stderr)
        self.exit(2, '%s: error: %s\n' % (self.prog, message))

En cas de remplacement, le code principal continuera à contenir le minimaliste.

# Parse arguments.
args = parser.parse_args()
# On error this will print help and cause exit with explanation message.
39

Peut-être attraper l'exception SystemExit serait une solution de contournement simple:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('foo')
try:
    args = parser.parse_args()
except SystemExit:
    print("do something else")

Fonctionne pour moi, même dans une session interactive.

Edit: On dirait que @Rob Cowie m'a battu à l'interrupteur. Comme il l'a dit, cela n'a pas beaucoup de potentiel de diagnostic, sauf si vous voulez devenir idiot et essayer de glaner des informations sur le traçage.

34
Greg Haskins

Vous pouvez utiliser l'une des méthodes de sortie: http://docs.python.org/library/argparse.html#exiting-methods . Cependant, il devrait déjà gérer les situations où les arguments ne sont pas valides (en supposant que vous ayez défini vos arguments correctement).

Utilisation d'arguments non valides:

% [ $(./test_argparse.py> /dev/null 2>&1) ] || { echo error } 
error # exited with status code 2
2
zeekay

Il faudrait bricoler. Regarder argparse.ArgumentParser.error, qui est appelé en interne. Ou vous pouvez rendre les arguments non obligatoires, puis vérifier et quitter en dehors d'argparse.

2
Tobu

Bien que argparse.error soit une méthode et non une classe, il n'est pas possible "d'essayer", sauf "toutes les" erreurs "d'arguments non reconnus". Si vous le souhaitez, vous devez remplacer la fonction d'erreur d'argparse:

def print_help(errmsg):
   print(errmsg.split(' ')[0])

parser.error = print_help
args = parser.parse_args()

sur une entrée invalide, il imprimera maintenant:

unrecognised
1
d0n

J'avais besoin d'une méthode simple pour détecter une erreur d'argparse au démarrage de l'application et transmettre l'erreur à un formulaire wxPython. La combinaison des meilleures réponses ci-dessus a abouti à la petite solution suivante:

import argparse

# sub class ArgumentParser to catch an error message and prevent application closing
class MyArgumentParser(argparse.ArgumentParser):

    def __init__(self, *args, **kwargs):
        super(MyArgumentParser, self).__init__(*args, **kwargs)

        self.error_message = ''

    def error(self, message):
        self.error_message = message

    def parse_args(self, *args, **kwargs):
        # catch SystemExit exception to prevent closing the application
        result = None
        try:
            result = super().parse_args(*args, **kwargs)
        except SystemExit:
            pass
        return result


# testing ------- 
my_parser = MyArgumentParser()
my_parser.add_argument('arg1')

my_parser.parse_args()

# check for an error
if my_parser.error_message:
    print(my_parser.error_message)

l'exécuter:

>python test.py
the following arguments are required: arg1
0
Mace