Как часто следует определять пользовательские исключения в python?

#python

#python

Вопрос:

Пытаясь устранить потенциальное состояние гонки в модуле python, который я написал для мониторинга некоторых специализированных рабочих процессов, я узнал о стиле программирования python «проще попросить прощения, чем разрешения» (EAFP), и теперь я создаю множество пользовательских исключений с помощью блоков try / except, где я использовал if / thens.

Я новичок в python, и этот стиль EAFP логически логичен и, кажется, делает мой код более надежным, но что-то в этом кажется чрезмерным. Является ли плохой практикой определять одно или несколько исключений для каждого метода?

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

Вот пример метода, например:

 class UploadTimeoutFileMissing(Exception):
    def __init__(self, value):
        self.parameter = value
    def __str__(self):
        return repr(self.parameter)

class UploadTimeoutTooSlow(Exception):
    def __init__(self, value):
        self.parameter = value
    def __str__(self):
        return repr(self.parameter)

def check_upload(file, timeout_seconds, max_age_seconds, min_age_seconds):

    timeout = time.time()   timeout_seconds

    ## Check until file found or timeout
    while (time.time() < timeout):

        time.sleep(5)
        try:
            filetime = os.path.getmtime(file)
            filesize = os.path.getsize(file)
        except OSError:
            print "File not found %s" % file
            continue

        fileage = time.time() - filetime

        ## Make sure file isn't pre-existing
        if fileage > max_age_seconds:
            print "File too old %s" % file
            continue

        ## Make sure file isn't still uploading
        elif fileage <= min_age_seconds:
            print "File too new %s" % file
            continue

        return(filetime, filesize)

    ## Timeout
    try:
        filetime
        filesize
        raise UploadTimeoutTooSlow("File still uploading")

    except NameError:
        raise UploadTimeoutFileMissing("File not sent")
 

Комментарии:

1. Стандартная библиотека Python содержит ~ 200 тыс. строк кода и обходится 165 исключениями — уверены, что вам нужны свои собственные? (Я вытащил цифры из выступления под названием » Прекратить писать классы «)

Ответ №1:

определите одно или несколько исключений для каждого метода

Если вы имеете в виду, что исключение фактически определяется для каждого метода, как в «внутри тела метода», тогда да. Это плохая практика. Это верно также, если вы определяете два исключения, которые будут относиться к одной и той же ошибке, но вы создаете два, потому что их вызывают два разных метода.

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

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

Комментарии:

1. возможно, он имел в виду количество (количество) исключений в зависимости от количества методов. т.е. 3 метода => 4 исключения, 8 методов => 11 исключений

2. возможно, он имел в виду количество (количество) определений исключений в модуле в зависимости от количества их использования. Другими словами, если можно определить UploadTimeoutTooSlow UploadTimeoutFileMissing и использовать оба из них один раз в модуле. Я бы рекомендовал определить немного более общее UploadTimeout исключение и передать конкретные строки в качестве аргументов этого исключения.

Ответ №2:

Является ли плохой практикой определять одно или несколько исключений для каждого метода?

ДА.

По одному на модуль является более типичным. Это зависит, конечно, от подробной семантики. Вопрос сводится к следующему: «Что вы действительно попытаетесь поймать?»

Если вы никогда не собираетесь использовать except ThisVeryDetailedException: в своем коде, то ваше очень подробное исключение не очень полезно.

Если вы можете сделать это: except Error as e: if e.some_special_case в течение очень немногих случаев, когда это имеет значение, тогда вы можете легко упростить до одного исключения для каждого модуля и обрабатывать ваши особые случаи как атрибуты исключения, а не разные типы исключений.

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

 try: 
    something
except some_module.Error as e:
    carry on
 

Это дает вам хорошее соглашение об именовании : module.Error . Это покрывает многочисленные грехи.


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

Ответ №3:

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

Я являюсь архитектором конвейера для компании, занимающейся визуальными эффектами. Большая часть того, что я делаю, включает в себя разработку того, что я называю «Facility API» — это система из множества модулей, которые обрабатывают все: от размещения объектов в файловой системе, управления конфигурацией модуля / инструмента / проекта до обработки типов данных из различных CGприложения для совместной работы.

Я прилагаю все усилия, чтобы убедиться, что встроенные исключения Python никогда не всплывают. Поскольку наши разработчики будут полагаться на экосистему существующих модулей для создания своих собственных инструментов поверх, использование API, позволяющего общий IOError escape, контрпродуктивно — тем более, что вызывающая процедура может даже не знать, что она читает файловую систему (абстракция — прекрасная вещь). Если базовый модуль не может выразить что-то значимое об этой ошибке, необходимо проделать дополнительную работу.

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

Все виды отчетов встроены в класс исключений facility — исключения не отображаются пользователю, если о них также не сообщается внутри. Для ряда исключений я получаю сообщение IM каждый раз, когда оно возникает. Другие просто тихо сообщают в базу данных, к которой я могу запросить последние (ежедневные или еженедельные) отчеты. Каждое из них ссылается на ОБШИРНЫЕ данные, полученные из сеанса пользователя, обычно включающие скриншот, трассировку стека, конфигурацию системы и многое другое. Это означает, что я могу эффективно устранять проблемы до того, как о них будет сообщено, и у меня под рукой больше информации, чем большинство пользователей, вероятно, могут предоставить.

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

Так что нет — я не думаю, что определение одного или двух исключений для каждого модуля неразумно, но они должны быть значимыми и добавлять что-то в проект. Если вы просто IOError переносите to raise MyIOError("I got an IO error!") , тогда вы можете переосмыслить это.

Ответ №4:

Я не думаю, что необходимо иметь чрезвычайно конкретное исключение для каждого возможного сценария. UploadTimeoutError Вероятно, будет достаточно одного, и вы можете просто настроить строку исключения — в конце концов, для этого и нужны строки. Обратите внимание, что в python нет отдельного исключения для каждого возможного типа синтаксической ошибки, только общее SyntaxError .

Кроме того, действительно ли необходимо определять методы __init__ and __str__ для каждого из ваших пользовательских исключений? Насколько я могу судить, если вы не реализуете какое-либо необычное поведение, вам не нужно добавлять какой-либо код:

 >>> class MyException(Exception): pass
... 
>>> raise MyException("oops!")
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
MyException: oops!    
>>> str(MyException("oops!"))
'oops!'