Python: Як я можу знати, які винятки можуть бути викликані викликом методу


87

Чи є спосіб знати (під час кодування), яких винятків очікувати при виконанні коду python? У підсумку я вловлюю базовий клас винятків 90% часу, оскільки я не знаю, який тип винятків може бути викинутий (і не кажи мені читати документацію. Багато разів виняток можна поширити з глибини. І багато разів документація не оновлюється або не правильна). Чи є якийсь інструмент для перевірки цього? (наприклад, читаючи код python та бібліотеки)?


2
Майте на увазі, що в Python <2.6 ви можете також raiseстворювати рядки, а не лише BaseExceptionпідкласи. Отже, якщо ви звертаєтесь до коду бібліотеки, який не під вашим контролем, навіть except 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", що цілком нормально для швидких практичних результатів :)


Дякую за цю цікаву відповідь. Не зрозумів, хоча навіщо мені шукати щось більше, ніж усі вузли підвищення. Чому я повинен "вирішувати символи за допомогою таблиць символів компілятора, аналізуючи залежності даних"? Хіба не єдиний спосіб підвищити виняток - підвищення ()?
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, виправив мою відповідь, щоб відобразити це питання
P Shved

Можливо, настав час аналізатору вихідного коду, який може це з’ясувати? Думаю, це не повинно бути занадто складно для розвитку
GabiMe

@ bugspy.net, я підбадьорив пункт, чому це може бути не час для цього.
П Швед

Звичайно, ти маєш рацію. Однак все ще може бути цікаво - під час розробки - знати, які типи винятків можуть мати місце.
hek2mgl

11

Правильним інструментом для вирішення цієї проблеми є unittests. Якщо у вас є винятки, викликані реальним кодом, які unittests не викликають, тоді вам потрібно більше unittests.

Розгляньте це

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

качка може бути будь-яким предметом

Очевидно, ви можете мати AttributeErrorякщо качка не має шарлатана, TypeErrorякщо качка має шарлатан, але вона не викликається. Ти не уявляєш щоduck.quack() може підняти, можливо, навіть a DuckErrorчи щось інше

Тепер припустимо, у вас є такий код

arr[i] = get_something_from_database()

Якщо це підвищує IndexError ви не знаєте, чи воно походить від arr [i] чи глибоко всередині функції бази даних. зазвичай не так важливо, де стався виняток, а те, що щось пішло не так, і те, що ви хотіли, не сталося.

Зручна техніка полягає в тому, щоб зловити і, можливо, повторити такий виняток

except Exception as e
    #inspect e, decide what to do
    raise

Навіщо взагалі його ловити, якщо ви збираєтеся його «підняти»?
Тарней Калман

Вам не потрібно його піднімати, саме на це повинен вказувати коментар.
Джон Ла Рой,

2
Ви також можете десь зафіксувати виняток, а потім повторно підняти
Джон Ла Рой,

2
Я не думаю, що написання модульних тестів - це відповідь. Питання в тому, "як я можу дізнатися, на які винятки очікувати", і написання модульних тестів не допоможе вам це дізнатись. Насправді, для того, щоб написати модульний тест, ви вже повинні знати, яких винятків очікувати, щоб написати правильний модульний тест, ви також повинні поставити оригінальне запитання.
Бруно Раншаерт,

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 сповнена неприємних сюрпризів. Тому затемнення регулярно падає.
Джон Ла Рой,

2

Є два способи, які я знайшов інформативними. Перший, запустіть код у iPython, який буде відображати тип виключення.

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

По-друге, ми задовольняємось тим, що ловимо занадто багато, і з часом вдосконалюємось. Включіть tryвираз у свій код та catch 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 пункт. для кожних кількох рядків ви завжди повинні зараз (або мати можливість легко перевірити), який виняток може бути застосований.

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

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

Просто спробуйте запустити ці кілька рядків у IDLE та викликати виняток. Але unittest було б кращим рішенням, природно.


1
Це не відповідь на моє просте запитання. Я не запитую, як сконструювати свою обробку винятків, ані коли і як ловити. Я запитую, як дізнатись, що можна кинути
GabiMe

1
@ bugspy.net: Неможливо зробити те, про що ти просиш, і це цілком дійсне обхідне рішення.
Даніель Приден
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.