Python: как узнать, какие исключения могут быть вызваны при вызове метода

87

Есть ли способ узнать (во время кодирования), каких исключений ожидать при выполнении кода Python? В конце концов, я ловлю базовый класс исключений в 90% случаев, так как я не знаю, какой тип исключения может быть сгенерирован (и не говорю мне читать документацию. Много раз исключение может быть распространено из глубины. И многие раз документация не обновляется или не корректируется). Есть какой-нибудь инструмент, чтобы это проверить? (например, читая код Python и библиотеки)?

GabiMe
источник
2
Имейте в виду, что в Python <2.6 вы также можете использовать raiseстроки, а не только BaseExceptionподклассы. Поэтому, если вы вызываете библиотечный код, который находится вне вашего контроля, этого даже except Exceptionнедостаточно, поскольку он не перехватывает исключения строк. Как отмечали другие, здесь вы лаете не на то дерево.
Дэниел Прайден,
Я этого не знал. Думал кроме Exception: .. ловит почти все.
GabiMe
2
except Exceptionотлично работает для перехвата исключений строк в Python 2.6 и новее.
Джеффри Харрис,

Ответы:

22

Думаю, решение могло быть неточным только из-за отсутствия правил статической типизации.

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

В качестве первой попытки вы могли бы написать функцию, которая строит AST, находит все Raiseузлы, а затем пытается выяснить общие шаблоны создания исключений (например, вызов конструктора напрямую)

Пусть xбудет следующая программа:

x = '''\
if f(x):
    raise IOError(errno.ENOENT, 'not found')
else:
    e = g(x)
    raise e
'''

Соберите AST с помощью compilerпакета:

tree = compiler.parse(x)

Затем определите Raiseкласс посетителя:

class RaiseVisitor(object):
    def __init__(self):
        self.nodes = []
    def visitRaise(self, n):
        self.nodes.append(n)

И пройдемся по собирающим Raiseузлам AST :

v = RaiseVisitor()
compiler.walk(tree, v)

>>> print v.nodes
[
    Raise(
        CallFunc(
            Name('IOError'),
            [Getattr(Name('errno'), 'ENOENT'), Const('not found')],
            None, None),
        None, None),
    Raise(Name('e'), None, None),
]

Вы можете продолжить, разрешая символы, используя таблицы символов компилятора, анализируя зависимости данных и т. Д. Или вы можете просто сделать вывод, что CallFunc(Name('IOError'), ...)«определенно должно означать повышение IOError», что вполне нормально для быстрых практических результатов :)

Андрей Власовских
источник
Спасибо за интересный ответ. Я не понимал, почему я должен искать что-то большее, чем все узлы повышения. Почему я должен «разрешать символы с использованием таблиц символов компилятора, анализируя зависимости данных»? Разве не единственный способ вызвать исключение - это raise ()?
GabiMe
1
Учитывая указанное v.nodesвыше значение, вы не можете сказать, что это за штука Name('IOError')или Name('e'). Вы не знаете, на какое значение (значения) они IOErrorи eмогут указывать, поскольку это так называемые свободные переменные. Даже если их контекст привязки был известен (здесь вступают в игру таблицы символов), вы должны выполнить какой-то анализ зависимостей данных, чтобы вывести их точные значения (это должно быть сложно в Python).
Андрей Власовских
Поскольку вы ищете практическое полуавтоматическое решение, список, ['IOError(errno.ENOENT, "not found")', 'e']отображаемый пользователю, вполне подойдет. Но вы не можете сделать вывод о реальных классах значений переменных, представленных строками :) (извините за репост)
Андрей Власовских
1
Да. Этот метод, хотя и умен, на самом деле не дает вам полного покрытия. Из-за динамической природы Python вполне возможно (хотя, очевидно, плохая идея) для кода, который вы вызываете, делать что-то вроде exc_class = raw_input(); exec "raise " + exc_class. Дело в том, что такой статический анализ на самом деле невозможен на динамическом языке, таком как Python.
Дэниел Прайден,
7
Кстати, можно просто find /path/to/library -name '*.py' | grep 'raise 'получить аналогичные результаты :)
Андрей Власовских
24

Вам следует перехватывать только те исключения, которые вы будете обрабатывать.

Улавливать все исключения по их конкретным типам - нонсенс. Вы должны поймать определенные исключения, которые вы можете и будете обрабатывать. Для других исключений вы можете написать общий улов, который улавливает «базовое исключение», регистрирует его (используйте str()функцию) и завершает вашу программу (или делает что-то еще, что уместно в аварийной ситуации).

Если вы действительно собираетесь обрабатывать все исключения и уверены, что ни одно из них не является фатальным (например, если вы запускаете код в какой-то изолированной среде), то ваш подход к отлову универсального BaseException соответствует вашим целям.

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

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

В любом случае вам придется запускать тесты, поскольку, даже если бы метод получения исключений по исходному коду существовал, он не дал бы вам никакого представления, как вы должны справиться с любым из них . Возможно, вы должны показывать сообщение об ошибке «Файл needful.txt не найден!» когда поймаешь IndexError? Только тест может сказать.

П Швед
источник
26
Конечно, но как можно решить, с какими исключениями он должен обрабатывать, если он не знает, что может быть выброшено?
GabiMe
@ bugspy.net, исправил мой ответ, чтобы отразить этот вопрос
П. Швед,
Может быть, пришло время анализатора исходного кода, который сможет это выяснить? Думаю, развиваться не должно быть слишком сложно,
GabiMe
@ bugspy.net, я придал смелости пункту, почему сейчас не время для этого.
П Швед
Уверен, ты прав. Однако во время разработки все еще может быть интересно узнать, какие типы исключений могут возникать.
hek2mgl
11

Правильный инструмент для решения этой проблемы - unittests. Если у вас есть исключения, вызванные реальным кодом, которые не вызывают модульные тесты, вам нужно больше модульных тестов.

Учти это

def f(duck):
    try:
        duck.quack()
    except ??? could be anything

утка может быть любым предметом

Очевидно, у вас может быть кряк, AttributeErrorесли у утки нет шарлатана, а TypeErrorесли у утки есть шарлатан, но его нельзя вызвать. Вы не представляете, что duck.quack()может поднять, может быть, даже DuckErrorчто-то

Теперь предположим, что у вас есть такой код

arr[i] = get_something_from_database()

Если он вызывает, IndexErrorвы не знаете, пришло ли оно из arr [i] или из глубины функции базы данных. обычно не так важно, где произошло исключение, скорее, что-то пошло не так и то, что вы хотели, не произошло.

Удобный метод - поймать и, возможно, повторно вызвать исключение, подобное этому

except Exception as e
    #inspect e, decide what to do
    raise
Джон Ла Рой
источник
Зачем вообще его ловить, если вы собираетесь его «ререйзить»?
Tarnay Kálmán
Вам не нужно его повторно повышать, это то, что должен был обозначать комментарий.
Джон Ла Рой,
2
Вы также можете записать исключение где-нибудь, а затем сделать ререйз
Джон Ла Рой
2
Я не думаю, что написание модульных тестов - это ответ. Вопрос в том, «как мне узнать, каких исключений следует ожидать», и написание модульных тестов не поможет вам в этом выяснить. Фактически, чтобы написать модульный тест, вы уже должны знать, каких исключений ожидать, поэтому, чтобы написать правильный модульный тест, вы также должны ответить на исходный вопрос.
Bruno Ranschaert
6

Никто до сих пор не объяснил, почему у вас не может быть полного, 100% правильного списка исключений, поэтому я подумал, что стоит прокомментировать. Одна из причин - первоклассная функция. Допустим, у вас есть такая функция:

def apl(f,arg):
   return f(arg)

Теперь aplможно вызвать любое возникшее исключение f. Хотя в основной библиотеке не так много таких функций, это затрагивает все, что использует понимание списка с настраиваемыми фильтрами, отображение, сокращение и т. Д.

Документация и анализаторы источников - единственные "серьезные" источники информации здесь. Просто имейте в виду, чего они не могут сделать.

вираптор
источник
4

Я столкнулся с этим при использовании сокета, я хотел узнать все условия ошибки, в которых я бы столкнулся (поэтому вместо того, чтобы пытаться создавать ошибки и выяснять, какой сокет мне просто нужен краткий список). В конце концов, я ввел grep'ing "/usr/lib64/python2.4/test/test_socket.py" для "поднять":

$ grep raise test_socket.py
Any exceptions raised by the clients during their tests
        raise TypeError, "test_func must be a callable function"
    raise NotImplementedError, "clientSetUp must be implemented."
    def raise_error(*args, **kwargs):
        raise socket.error
    def raise_herror(*args, **kwargs):
        raise socket.herror
    def raise_gaierror(*args, **kwargs):
        raise socket.gaierror
    self.failUnlessRaises(socket.error, raise_error,
    self.failUnlessRaises(socket.error, raise_herror,
    self.failUnlessRaises(socket.error, raise_gaierror,
        raise socket.error
    # Check that setting it to an invalid value raises ValueError
    # Check that setting it to an invalid type raises TypeError
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,

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

Курт
источник
4
Это усиливает мой аргумент, что обработка исключений в Python очень проблематична, если нам нужно использовать grep или анализаторы исходного кода для решения чего-то столь простого (что, например, в java существовало с первого дня. Иногда многословие - это хорошо. Java многословна но, по крайней мере, здесь нет неприятных сюрпризов)
GabiMe
@GabiMe, Это не похоже на то, что эта способность (или статическая типизация в целом) является серебряной пулей для предотвращения всех ошибок. Java полна неприятных сюрпризов. Вот почему eclipse регулярно дает сбой.
Джон Ла Рой
2

Есть два способа, которые я нашел информативными. Во-первых, запустите код в iPython, который отобразит тип исключения.

n = 2
str = 'me '
str + 2
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Во втором случае мы довольствуемся слишком большим уловом и со временем улучшаем его. Включите tryвыражение в свой код и поймите except Exception as err. Выведите достаточно данных, чтобы узнать, какое исключение было сгенерировано. По мере появления исключений улучшайте свой код, добавляя более точное exceptпредложение. Когда вы почувствуете, что перехватили все соответствующие исключения, удалите все включенное. В любом случае это хорошо, потому что это проглатывает ошибки программирования.

try:
   so something
except Exception as err:
   print "Some message"
   print err.__class__
   print err
   exit(1)
Рахав
источник
1

Обычно вам нужно перехватить исключение только в нескольких строках кода. Вы бы не хотели помещать mainв try exceptпредложение всю свою функцию . для каждых нескольких строк вы всегда должны (или иметь возможность легко проверить), какое исключение может возникнуть.

в документации есть исчерпывающий список встроенных исключений . не пытайтесь исключить те исключения, которых вы не ожидаете, они могут быть обработаны / ожидаемы в вызывающем коде.

edit : то, что может быть выброшено, очевидно, зависит от того, что вы делаете! доступ к случайному элементу последовательности:, IndexErrorслучайному элементу dict: KeyErrorи т. д.

Просто попробуйте запустить эти несколько строк в IDLE и вызвать исключение. Но, естественно, unittest было бы лучшим решением.

Тихий призрак
источник
1
Это не ответ на мой простой вопрос. Я не спрашиваю, как спроектировать мою обработку исключений, или когда и как ловить. Я спрашиваю, как узнать, что может быть брошено
GabiMe
1
@ bugspy.net: невозможно сделать то, что вы просите, и это вполне допустимый обходной путь.
Дэниел Прайден,