Как правильно обрабатывать исключения в Python3 - PullRequest
12 голосов
/ 26 мая 2010

Я не могу понять, какие исключения я должен обрабатывать «здесь и сейчас», и какие исключения я должен повторно вызывать или просто не обрабатывать здесь, и что делать с ними позже (на более высоком уровне ). Например: я написал клиент-серверное приложение, используя python3 с ssl-связью. Предполагается, что клиент проверяет файлы на наличие различий по ним, и, если существует diff, он должен отправить этот «обновленный» файл на сервер.


class BasicConnection:
    #blablabla
    def sendMessage(self, sock, url, port, fileToSend, buffSize):
        try:
            sock.connect((url, port))
            while True:
                data = fileToSend.read(buffSize)
                if not data: break
                sock.send(data)
            return True
        except socket.timeout as toErr:
            raise ConnectionError("TimeOutError trying to send File to remote socket: %s:%d"
                                  % (url,port)) from toErr
        except socket.error as sErr:
            raise ConnectionError("Error trying to send File to remote socket: %s:%d"
                                  % (url,port)) from sErr
        except ssl.SSLError as sslErr:
            raise ConnectionError("SSLError trying to send File to remote socket: %s:%d"
                                  % (url,port)) from sslErr
        finally:
            sock.close()

Это правильный способ использовать исключения в Python? Проблема в том, что если file.read () выдает IOError? Должен ли я справиться с этим здесь, или просто ничего не делать и поймать это позже? И много других возможных исключений?

  1. Клиент использует этот класс (BasicConnection) для отправки обновленных файлов на сервер:

class PClient():
    def __init__(self, DATA):
        '''DATA = { 'sendTo'      : {'host':'','port':''},
                    'use_ssl'     : {'use_ssl':'', 'fileKey':'', 'fileCert':'', 'fileCaCert':''},
                    'dirToCheck'  : '',
                    'localStorage': '',
                    'timeToCheck' : '',
                    'buffSize'    : '',
                    'logFile'     : ''}   '''
        self._DATA = DATA
        self._running = False
        self.configureLogging()


    def configureLogging(self):
        #blablabla

    def isRun(self):
        return self._running

    def initPClient(self):
        try:
            #blablabla

            return True
        except ConnectionError as conErr:
            self._mainLogger.exception(conErr)
            return False
        except FileCheckingError as fcErr:
            self._mainLogger.exception(fcErr)
            return False
        except IOError as ioErr:
            self._mainLogger.exception(ioErr)
            return False
        except OSError as osErr:
            self._mainLogger.exception(osErr)
            return False


    def startPClient(self):
        try:
            self._running = True
            while self.isRun():
                try :
                    self._mainLogger.debug("Checking differences")
                    diffFiles = FileChecker().checkDictionary(self._dict)

                    if len(diffFiles) != 0:
                        for fileName in diffFiles:
                            try:
                                self._mainLogger.info("Sending updated file: %s to remote socket: %s:%d"
                                    % (fileName,self._DATA['sendTo']['host'],self._DATA['sendTo']['port']))
                                fileToSend = io.open(fileName, "rb")
                                result = False
                                result = BasicConnection().sendMessage(self._sock, self._DATA['sendTo']['host'],
                                                                       self._DATA['sendTo']['port'], fileToSend, self._DATA['buffSize'])
                                if result:
                                    self._mainLogger.info("Updated file: %s was successfully delivered  to remote socket: %s:%d"
                                    % (fileName,self._DATA['sendTo']['host'],self._DATA['sendTo']['port']))
                            except ConnectionError as conErr:
                                self._mainLogger.exception(conErr)
                            except IOError as ioErr:
                                self._mainLogger.exception(ioErr)
                            except OSError as osErr:
                                self._mainLogger.exception(osErr)

                        self._mainLogger.debug("Updating localStorage %s from %s " %(self._DATA['localStorage'], self._DATA['dirToCheck']))
                        FileChecker().updateLocalStorage(self._DATA['dirToCheck'],
                                                         self._DATA['localStorage'])
                    self._mainLogger.info("Directory %s were checked" %(self._DATA['dirToCheck']))
                    time.sleep(self._DATA['timeToCheck'])
                except FileCheckingError as fcErr:
                    self._mainLogger.exception(fcErr)
                except IOError as ioErr:
                    self._mainLogger.exception(ioErr)
                except OSError as osErr:
                    self._mainLogger.exception(osErr)
        except KeyboardInterrupt:
            self._mainLogger.info("Shutting down...")
            self.stopPClient()
        except Exception as exc:
            self._mainLogger.exception(exc)
            self.stopPClient()
            raise RuntimeError("Something goes wrong...") from exc

    def stopPClient(self):
        self._running = False

Это правильно? Может быть, кто-то проводит свое время и просто помогает мне понять питонический стиль обработки исключений? Я не могу понять, что делать с такими исключениями, как NameError, TypeError, KeyError, ValueError ... и т. Д. ....... Они могут быть выброшены при любом утверждении, в любое время ... и что делать с ними, если я захочу все записать.

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

  2. Надеюсь, кто-нибудь мне поможет. Большое спасибо.

Ответы [ 2 ]

24 голосов
/ 26 мая 2010

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

NameError, TypeError, KeyError, ValueError, SyntaxError, AttributeError и т. Д. Могут рассматриваться как ошибки в программе - ошибки, а не проблемы вне управление программистом. Если вы выпускаете библиотеку или фреймворк, так что ваш код будет вызываться другим кодом, находящимся вне вашего контроля, то вполне вероятно, что такие ошибки могут быть в этом другом коде; обычно вы должны позволить распространению исключения, чтобы помочь другому программисту отлаживать свои собственные ошибки. Если вы выпускаете приложение, у вас есть ошибки, и вы должны выбрать стратегию, которая поможет вам их найти.

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

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

Иногда некоторые исключения, подобные этим, появляются просто потому, что «проще просить прощения, чем разрешения» (EAFP) - вполне приемлемый метод программирования в Python. В этом случае, конечно, вы должны обращаться с ними сразу. Например:

try:
    return mylist[theindex]
except IndexError:
    return None

здесь можно ожидать, что theindex, как правило, является допустимым индексом для mylist, но иногда выходит за пределы mylist - и в последнем случае, по семантике гипотетического приложения, к которому относится этот фрагмент , это не ошибка, просто небольшая аномалия, которую нужно исправить, считая, что список концептуально расширен с обеих сторон бесконечными числами None с. Проще просто попробовать / исключить, чем правильно проверить положительные и отрицательные значения индекса (и быстрее, если выход за пределы является действительно редким явлением).

Аналогично соответствующие случаи для KeyError и AttributeError происходят реже, благодаря встроенным методам getattr и get dicts (которые позволяют указывать значение по умолчанию), collections.defaultdict и т. Д .; но списки не имеют прямого эквивалента таковым, поэтому попытка / исключение встречается чаще для IndexError.

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

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

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

3 голосов
/ 26 мая 2010

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

Кроме того, вы определенно не хотите конвертировать все исключения в RuntimeError. Пусть это всплывет. Метод stopClient () сейчас не имеет смысла. Когда это произойдет, мы посмотрим на это ..

Вы можете в основном обернуть ConnectionError, IOError и OSError вместе (например, ре-рейзить как-нибудь еще), но не намного больше ...

...