LINUX.ORG.RU

Как правильно обрабатывать ошибки в Python?

 ,


2

4

Всем привет. Задумался - а как правильно обрабатывать ошибки в Python? Допустим мне нужно запустить в модуле module функцию run, и так неколько раз где-то в коде, она может выдать множество разных исключений - каждый раз надоест отлавливать. И на каждое исключение мне нужно как-то изменить действия программы. Я придумал такой способ - в самой функции run отлавливаются ошибки, а результат она возвращает в таком виде

answer = {'completed': True, 'exception': None}

и ещё какие-нибудь значения. И в итоге всего я просто ловлю вывод от функции, его разбираю, и умещается это всё в несколько строк, и в принципе удобно. А теперь вопрос - как правильно это делать, может есть какой-то более удобный способ? Всем спасибо.

Если исключение является нормальной работой в твоём случае — надо написать логику на случай этого исключения.
Если нет — пусть падает.

Самый удобный способ — это заняться архитекрутой и изучением инструмента вместо глупых вопросов на ЛОРе.

Goury ★★★★★
()

И в чём же получилась разница между «ловить все возможные исключения» и «парсить все возможные значения»? Ну, кроме того, что первое встроено в язык специально для этого, а второе костыль?

greatperson
()
Ответ на: комментарий от Goury

Да, у меня исключение на исключении, и падать программе нельзя. Вообще у меня всё так и построено - функция ловит исключения, и потом делает return dictionary с инфой по процессу выполнения, где какое исключение схватила, и т.п. В принципе мне удобно. Просто решил узнать как остальные люди делают :-)

mahalaka ★★
() автор топика

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

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

MrClon ★★★★★
()
Ответ на: комментарий от mahalaka

Я так понимаю в твоём случае все исключения сворачиваются в одну булеву переменную? Т.е. и обрабатываются они все одинаково? Тогда, ЕМНИП, можно обойтись одним блоком except.
Вот только не знаю можно-ли задать блок except со списком исключений, или надо перехватывать всё и потому проверять входит-ли исключение в список.

MrClon ★★★★★
()
Ответ на: комментарий от mahalaka

А в чем смысл отображения этих исключений? Просто чтоб быть в курсе или что? Приложение то от этого не падает. Как по мне разбираться в логах когда «исключение на исключении» та еще радость, круче только явовые стэктрейсы.

alozovskoy ★★★★★
()
Ответ на: комментарий от alozovskoy

Строить какую-то логику. Вообще я ещё раз подумал, и что-то уже не то в голове, что было до того как я создал этот тред, раньше было больше смысла. :-) Вообще конечно логику можно строить и с помощью try except, но например если мне нужно отловить лишь одно исключение, а на остальные наплевать, то с моим способом я могу сделать просто if else, а с помощью try except это дольше, и игнорируемые исключения нужно описывать точно, либо просто добавить строчку для игнорирования остальных исключений, но всё-равно по-моему это дольше. :-)

mahalaka ★★
() автор топика
Ответ на: комментарий от mahalaka

Что-же это за приложение такое в котором штатно возникает куча разных исключений которые можно просто игнорить?

MrClon ★★★★★
()

Скажем там, паттерн «return status, value» является вполне себе стандартным (особенно в функциональном программировании) и я его достаточно часто использую. У этого метода есть недостаток: в отличии от эксепшена, «вложенные» ошибки нужно явно передавать на верхний уровень. Это бывает неудобно. Например, в ядре линукса, по моим наблюдениям, чуть ли не 70% кода в духе «r = some_func(); if (!r) {return ERR_STATUS}». Т.е. каждый вызов функции требует явной проверки результата и явной обработки ошибки и код раздувается в разы.

Короче, серебряной пули нет.

она может выдать множество разных исключений - каждый раз надоест отлавливать

Да, вполне себе. Не всегда удобно городить try..except, иногда проще do_something || do_on_failure(). Но у эксепшенов есть один плюс: если ты их забудешь обработать то у тебя программа просто упадёт. А если ты забыл проверить возвращаемое значение то программа будет работать неправильно. Помни об этом.

Ну а в целом, лучше покажи код. Возможно, ты что-то делаешь неправильно.

true_admin ★★★★★
()
Ответ на: комментарий от MrClon

Херня на Selenium :-) На каждый шаг я могу словить исключение, и в зависимости от него надо как-то двигаться дальше.

mahalaka ★★
() автор топика
Ответ на: комментарий от true_admin

Вот это и хотел услышать, спасибо. :-) Да, иногда это неудобно, но мои программы в принципе просты пока что, так что пойдёт.

Код не могу показать, т.к. стыдно и закрыто :-) Да и ситуацию я описал в треде в принципе из самого кода.

mahalaka ★★
() автор топика

Питона не знаю, но если возникает исключение и ты знаешь где, но не знаешь как обработать, ты его можешь поймать, а потом пробросить дальше со всем стеком.

nikolnik ★★★
()
Ответ на: комментарий от mahalaka

Херня на Selenium

А, господи :) Ты хочешь чтобы тесты продолжали выполняться даже если один зафейлится? Я бы не рекомендовал делать обработчики в каждом тесте отдельно. Я бы сделал что-то вроде запускалки тестов которая будет отлавливать ошибки на верхнем уровне. Типа такого:

failures = []
for test in tests:
  try:
    test()
  except Error as e:
    log.error("test {} kaput".format(test))
    failures.append(test)

if failures:
  raise SomeException("one or more tests failed", failures)

«И на каждое исключение мне нужно как-то изменить действия программы»

Я бы этого избегал в тестировании. Правда, я не понимаю о каком состоянии идёт речь.

true_admin ★★★★★
()
Ответ на: комментарий от mahalaka

Нельзя падать — пиши так, чтобы не падала в исключения

Goury ★★★★★
()

как правильно это делать

как и все - исключениями. Не хочешь чтобы прерывалось, пиши их в лог или(и) catch и в output стэктрейс.

Но вообще во всяких go и js, если не изменяет память просто возвращают кортеж из двух значений (output, err) и err === null если все окей

Dred ★★★★★
()
Последнее исправление: Dred (всего исправлений: 1)

Есть несколько известных стратегий обработки ошибок:

1. Глобальные переменные и функции ошибок,errno errmsg

2. Коды возврата функции, результат функции тогда передается через параметры

3. Булево значение функции с дополнительной схемой информирования об ошибке

4. Эксепшены

В питоне лучше применять эксепшены, так как там есть проблемы с прозрачной передачей параметров по ссылке, то есть пункты 2 и 3 реализуются с трудом и неочевидно. К тому-же в питоне нету перечислений, что опять-же мешает реализации пунков 2 и 3. В примципе никто не мешает реализовать пункт 1, но эксепшены всё-таки красивее и удобнее.

pup_kin
()

Я надеюсь, ты нигде не работаешь и никому не портить жизнь с такими наркоманскими ретурнами.

Судя по тому, что ты «придумал» - придумывать тебе рано.

slaykovsky ★★★
()
Ответ на: комментарий от slaykovsky

Работаю, но только я один, и мне в дальнейшем это всё поддерживать. :-) А что наркоманского, если подумать? Эта обёртка используется только для Selenium, т.к. там везде возникают ошибки, постоянно писать try except долго, и оборачиваю я в неё только вызовы Selenium. Плюс надо в каждом модуле, который будет общаться с Selenium, импортировать тоже эти исключения. А так у меня есть некая функция, которой я пинаю браузер, а она мне всё красиво возвращает.

mahalaka ★★
() автор топика
Ответ на: комментарий от mahalaka

Плюс надо в каждом модуле, который будет общаться с Selenium, импортировать тоже эти исключения.

Так напиши себе декоратор, который будет выполнять обёрнутую функцию и ловить один и тот же набор исключений.

greatperson
()
Последнее исправление: greatperson (всего исправлений: 1)
Ответ на: комментарий от mahalaka
def works_with_selenium(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except SomeSeleniumException as e:
            ...
    
    return wrapper

И затем используешь как

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

greatperson
()
Ответ на: комментарий от greatperson

Ну да, тоже в принципе неплохой вариант, спасибо, подумаю. :-)

mahalaka ★★
() автор топика

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

1) Если возникла критическая ошибка в коде, выбрасываем исключение и рушимся. Иногда просто нет смысла обрабатывать ошибки, которые полностью блокируют работу программы (например, отсутствуют нужные модули).

2) Все оформляется классами. Если возникла ошибка при обработке данных и продолжение работы невозможно, уведомляем об этом пользователя, возвращаем None. Класс на подхвате проверяет входные данные в __init__. Если обнаруживает недопустимые данные, например, None, назначает self.Success = False. Далее каждая процедура класса проверяет значение self.Success. Если получили False, то класс просто не выполняется. Все остальные классы в цепочке делают то же самое, и в итоге мы возвращаемся в меню. Возможно, self.Success - костыль, но ничего лучше я пока не придумал.

3) Обычно цепочка действий выглядит так:

action1 -> action2 -> action3 -> action4

Соответственно, если код не продуман до конца, если где-нибудь в action3 возникает ошибка, то цепочку может быть сложно порвать (если не оформить как в п.2).

Чтобы не возникало таких проблем, можно поступить следующим образом. Допустим, наша программа принимает на входе Текст 1 и Текст 2, затем сравнивает их, оформляет результат в виде html-кода, сохраняет файл и открывает в браузере по умолчанию. Допустим, что это только одна из цепочек действий, которые выполняет данная программа. Что мы можем делать? Вместо того, чтобы делать ввод текста 1 (0) -> ввод текста 2 (1) -> сравнение (2) -> оформление в виде html (3) -> запись файла (4) -> открытие (5), мы можем сразу попытаться открыть файл, если знаем его путь! Его наличие проверяем в __init__ результирующего класса. Если файла нет, передаем управление классу, который создаст файл. Этот класс, в свою очередь, проверяет данные на входе. Если они None, он вызывает GUI, в котором пользователь вводит данные. Если все это было сделано, управление снова автоматически передается финальному классу (в __init__, где проверялось наличие файла), затем выполняется код, который идет после проверки. Таким образом, цепочку вообще рвать не нужно. Если где-нибудь возникает обрабатываемая ошибка, например, не удается записать файл, то мы просто попадаем в исходное меню, откуда было вызвано сравнение файлов.

Для примера, код может выглядеть так:

#!/usr/bin/python3

import os

class Launch:
	
	def __init__(self,path):
		print('Running Launch')
		self.Success = True
		self.path = path
		if not os.path.exists(self.path):
			h_create = Create(self.path)
			self.Success = h_create.Success
		self.launch()
			
	def launch(self):
		if self.Success:
			print('Launch.launch: Opening file "%s" in an external program...' % self.path)
		else:
			print('Launch.launch: Failing...')



class Create:
	
	def __init__(self,path,data=None):
		print('Running Create')
		self.Success = True
		self.path = path
		self.data = data
		if not os.path.exists(self.path):
			if not self.data:
				h_comp = Compare()
				self.data = h_comp.get()
			if self.data:
				self.write()
				
	def write(self):
		try:
			print('Create.write: Writing "%s" to "%s"...' % (self.data,self.path))
			#raise Exception('Failed to write!!')
		except:
			print('Create.write: Failing...')
			self.Success = False

		

class Compare:
	
	def __init__(self,text1='',text2=''):
		print('Running Compare')
		self._text1 = text1
		self._text2 = text2
		self._compare = ''
			
	def text1(self):
		if not self._text1:
			print('Compare.text1: Input Text 1:')
			self._text1 = 'Text1'
		return self._text1
		
	def text2(self):
		if not self._text2:
			print('Compare.text2: Input Text 2:')
			self._text2 = 'Text2'
		return self._text2
	
	def compare(self):
		print('Compare.compare: Comparing texts...')
		if self.text1() and self.text2():
			self._compare = self._text1 + ';' + self._text2
		
	def get(self):
		if not self._compare:
			self.compare()
		return self._compare



if __name__ == '__main__':
	Launch('/tmp/0lfsdgl.txt')

Deleted
()
Ответ на: комментарий от pup_kin

проблемы с прозрачной передачей параметров по ссылке

Нет никаких проблем и это не нужно потому что питон может вернуть несколько значений в tuple. Например:

status, result = somefunc()
if not status:
  ...

нету перечислений

Ты про enum.Enum?

true_admin ★★★★★
()
Ответ на: комментарий от pup_kin

Дык, блин, такое самому сделать пять минут :).

Вот нормального паттерн-матчинга, к сожалению, нету и нет присвоений в if. Но я как-то научился с этим жить.

true_admin ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.