Python argparse и контроль/переопределение кода состояния выхода

Помимо работы с источником argparse, есть ли способ контролировать код состояния выхода, если возникнет проблема при вызове parse_args(), например, отсутствие необходимого переключателя?


person Kev    schedule 09.05.2011    source источник


Ответы (8)


Мне неизвестен какой-либо механизм для указания кода выхода для каждого аргумента. Вы можете перехватить исключение SystemExit, вызванное .parse_args(), но я не уверен, как вы тогда сможете выяснить, что конкретно вызвало ошибку.

EDIT: Для тех, кто ищет практическое решение, следующая ситуация:

  • ArgumentError() поднимается соответствующим образом, когда происходит сбой синтаксического анализа аргумента. Ему передается экземпляр аргумента и сообщение
  • ArgumentError() не сохраняет аргумент как атрибут экземпляра, несмотря на то, что передается (что было бы удобно)
  • Можно повторно вызвать исключение ArgumentError путем создания подкласса ArgumentParser, переопределения .error() и получения исключения из sys.exc_info().

Все это означает, что следующий код, хотя и уродливый, позволяет нам перехватывать исключение ArgumentError, получать оскорбительный аргумент и сообщение об ошибке и делать то, что мы считаем нужным:

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

Не проверено каким-либо полезным способом. Применяется обычная гарантия возмещения ущерба «не вините меня, если это сломается».

person Rob Cowie    schedule 09.05.2011
comment
+1. Это огромная проблема с дизайном argparse — он решает, как обрабатывать ошибки, и делает невозможным сделать это так, как вы хотите, без изменения исходного кода. Он действительно должен иметь иерархию исключений и вызывать их, чтобы вы могли точно сказать, что произошло, и действовать соответствующим образом. ИМО, библиотека должна помочь вам достичь того, чего вы хотите, а не диктовать, как ведет себя ваш код. - person Blair; 10.05.2011
comment
@blair - прочитав исходный код, я согласен. Я задал вопрос, потому что думал, что пропустил какое-то волшебное свойство единорога, которое позволило бы мне контролировать :) - person Kev; 10.05.2011
comment
Подобные опасения, очевидно, высказывались и раньше. См. python.org/dev/ peps/pep-0389/ - person Rob Cowie; 10.05.2011
comment
По какой причине не поддержали лучший способ не выходить? Аргумент при создании парсера кажется лучшим, например. exit=True - person Thoughtful Dragon; 26.01.2016
comment
В 2018 году 1-й комментарий совершенно не соответствует действительности. Просто переопределите def error, это двухстрочный ;-). - person JL Peyret; 06.01.2018
comment
Однако было бы полезно, если бы синтаксический анализатор сохранил полученное исключение перед вызовом error, потому что все, что вы знаете, это строка сообщения, а не первоначальная причина. - person JL Peyret; 06.01.2018

Все ответы хорошо объясняют детали реализации argparse.

Действительно, как предложено в PEP (и указано Роб Коуи) следует наследовать ArgumentParser и переопределять поведение методов error или exit.

В моем случае я просто хотел заменить печать использования на полную справку в случае ошибки:

class ArgumentParser(argparse.ArgumentParser):

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

В случае переопределения основной код будет по-прежнему содержать минималистичный..

# Parse arguments.
args = parser.parse_args()
# On error this will print help and cause exit with explanation message.
person Yauhen Yakimovich    schedule 05.06.2013
comment
Лучшее решение здесь! Никаких изменений кода не требуется. Спасибо! Вместо выхода я вызвал исключение в своей функции ошибок. Это делает argaparse гладким. - person Georg W.; 08.02.2019
comment
Я думаю, что это решение выявило проблему с argparse. Если вы сделаете это: parser.add_argument(-f, --filter, help=фильтровать результаты, type=str), то args = parser.parse_args([-f]), вы получите исключение из argparse. Если вы удалите метод ошибки из своего подкласса, он будет работать как обычно. - person Samuel; 30.12.2020

Возможно, перехват исключения SystemExit будет простым обходным путем:

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

У меня работает даже в интерактивном сеансе.

Редактировать: Похоже, @Rob Cowie опередил меня до выключателя. Как он сказал, у этого не очень большой диагностический потенциал, если только вы не хотите валять дурака и пытаться собрать информацию из трассировки.

person Greg Haskins    schedule 09.05.2011
comment
Это решение идеально подходит для моего варианта использования, потому что я просто хотел изменить код выхода, ничего больше. - person vvaltchev; 19.11.2020


Вам бы повозиться. Посмотрите на argparse.ArgumentParser.error, который вызывается внутри. Или вы можете сделать аргументы необязательными, затем проверить и выйти за пределы argparse.

person Tobu    schedule 09.05.2011

Вы можете использовать один из методов выхода: http://docs.python.org/library/argparse.html#exiting-methods. Однако он уже должен обрабатывать ситуации, когда аргументы недействительны (при условии, что вы правильно определили свои аргументы).

Использование недопустимых аргументов:

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

Хотя argparse.error является методом, а не классом, невозможно «попробовать», «за исключением» всех ошибок «нераспознанных аргументов». Если вы хотите сделать это, вам нужно переопределить функцию ошибки из argparse:

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

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

при неверном вводе он теперь будет печатать:

unrecognised
person d0n    schedule 18.12.2014
comment
Да, Python позволяет заменять методы таких объектов. Это удобно для тестирования и разовых изменений. Создание подклассов лучше, если вам нужно неоднократно вносить это изменение. - person hpaulj; 20.12.2014

Мне нужен был простой способ поймать ошибку argparse при запуске приложения и передать ее в форму wxPython. Объединение лучших ответов из приведенных выше привело к следующему небольшому решению:

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)

запустить его:

>python test.py
the following arguments are required: arg1
person Mace    schedule 27.11.2019