Подтвердить что ты не робот

Как правильно обрабатывать исключения в Python3

Я не могу понять, какие исключения я должен обрабатывать здесь и сейчас, и какие исключения я должен повторно поднимать или просто не обрабатывать здесь, и что делать с ними позже (на более высоком уровне). Например: я написал приложение клиент/сервер, используя 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? Должен ли я справиться с этим здесь или просто ничего не делать и поймать его позже? И многие другие возможные исключения?

  • Клиент использует этот класс (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. Я надеюсь, что кто-то мне поможет. Большое спасибо.

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

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