Какое исключение я должен поднять на неправильные / недопустимые комбинации аргументов в Python?

545

Мне было интересно узнать, как лучше всего указывать недопустимые комбинации аргументов в Python. Я сталкивался с несколькими ситуациями, когда у вас есть такая функция:

def import_to_orm(name, save=False, recurse=False):
    """
    :param name: Name of some external entity to import.
    :param save: Save the ORM object before returning.
    :param recurse: Attempt to import associated objects as well. Because you
        need the original object to have a key to relate to, save must be
        `True` for recurse to be `True`.
    :raise BadValueError: If `recurse and not save`.
    :return: The ORM object.
    """
    pass

Единственное раздражение в этом заключается в том, что каждый пакет имеет свои, обычно немного отличающиеся BadValueError. Я знаю, что в Java существует java.lang.IllegalArgumentException- хорошо ли понимают, что все будут создавать свои собственные BadValueErrorв Python или есть другой, предпочтительный метод?

cdleary
источник

Ответы:

609

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

def import_to_orm(name, save=False, recurse=False):
    if recurse and not save:
        raise ValueError("save must be True if recurse is True")

В этом нет никакого смысла class BadValueError(ValueError):pass- ваш пользовательский класс идентичен в использовании ValueError , так почему бы не использовать его?

DBR
источник
65
> "так почему бы не использовать это?" - Специфика. Возможно, я хочу поймать на некотором внешнем слое «MyValueError», но не любой / все «ValueError».
Кевин Литтл
7
Да, поэтому часть вопроса о специфике заключается в том, где еще возникает ValueError. Если функции вызываемого пользователя нравятся ваши аргументы, но внутри вызывается math.sqrt (-1), то вызывающая сторона может перехватывать ValueError, ожидая, что ее аргументы неуместны. Может быть, вы просто проверьте сообщение в этом случае ...
cledary
3
Я не уверен, что аргумент верен: если кто-то звонит math.sqrt(-1), это программная ошибка, которую нужно все равно исправить. ValueErrorне предназначен, чтобы быть пойманным в нормальном выполнении программы, или это было бы получено из RuntimeError.
августа
2
Если ошибка связана с NUMBER аргументов, для функции с переменным числом аргументов ... например, для функции, где аргументы должны быть четным числом аргументов, вы должны вызвать TypeError, чтобы быть последовательным. И не создавайте свой собственный класс, если а) у вас нет варианта использования или б) вы экспортируете библиотеку для использования другими. Преждевременная функциональность - смерть кода.
Эрик Аронесты
104

Я бы унаследовал от ValueError

class IllegalArgumentError(ValueError):
    pass

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

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

Маркус Жардеро
источник
26
Хватит писать классы и пользовательские исключения - pyvideo.org/video/880/stop-writing-classes
Хэмиш Грубиджан
41
@HamishGrubijan это видео ужасно. Когда кто-либо предлагал хорошее использование класса, он просто блеял: «Не используйте классы». Brilliant. Классы хорошие. Но не верьте мне на слово .
Роб Грант
12
@RobertGrant Нет, ты не понимаешь. Это видео на самом деле не о буквально "не использовать классы". Речь идет о не слишком усложнять вещи.
RayLuo
16
@RayLuo, возможно, вы проверили здравый смысл того, что говорится в видео, и превратили его в приемлемое, осмысленное альтернативное сообщение, но это то, что говорит видео, и это то, что уйдет тот, у кого нет большого опыта и здравого смысла. с.
Роб Грант
4
@SamuelSantana, как я уже сказал, каждый раз, когда кто-нибудь поднимает руку и говорит: «А как же Х?» где Х был хорошей идеей, он просто сказал: «Не устраивайте еще один урок». Довольно ясно. Я согласен, что ключ - баланс; проблема в том, что это слишком расплывчато, чтобы на самом деле жить :-)
Роб Грант
18

Я думаю, что лучший способ справиться с этим - это то, как сам Python справляется с этим. Python вызывает ошибку TypeError. Например:

$ python -c 'print(sum())'
Traceback (most recent call last):
File "<string>", line 1, in <module>
TypeError: sum expected at least 1 arguments, got 0

Наш младший разработчик только что нашел эту страницу в поиске Google для «неправильных аргументов исключения Python», и я удивлен, что очевидный (для меня) ответ никогда не предлагался в течение десятилетия с тех пор, как был задан этот вопрос.

Дж Бонс
источник
8
Ничто не удивляет меня, но я согласен на 100%, что TypeError является правильным исключением, если в некоторых аргументах, переданных в функцию, указан неверный тип. Ошибка ValueError подойдет, если переменные имеют правильный тип, но их содержимое и значения не имеют смысла.
user3504575
Я думаю, что это, вероятно, из-за отсутствия или невостребованности аргументов, в то время как вопрос об аргументах, которые даны правильно, но неверны на более высоком уровне абстракции, включающем значение данного аргумента. Но так как я на самом деле искал первое, так что в любом случае имейте upvote.
Никто
2
Как сказали @ user3504575 и @Nobody, TypeError используется, если аргументы не соответствуют сигнатуре функции (неправильное количество позиционных аргументов, аргументы ключевых слов с неправильным именем, неверный тип аргумента), но ValueError используется, когда вызов функции соответствует подписи, но значения аргумента недопустимы (например, вызов int('a')). источник
goodmami
Поскольку вопрос OP ссылался на «недопустимые комбинации аргументов», кажется, что TypeError был бы уместен, поскольку это был бы случай, когда сигнатура функции по существу неверна для переданных аргументов.
Дж. Боунс
В вашем примере вызываются sum()без аргументов, что является TypeError, но OP касался «недопустимых» комбинаций значений аргументов, когда типы аргументов верны. В этом случае оба saveи recurseявляются bools, но если recurseесть, Trueто saveне должно быть False. Это ValueError. Я согласен, что на некоторую интерпретацию названия вопроса будет дан ответ TypeError, но не для приведенного примера.
Goodmami
11

Я в основном только что видел встроенный ValueErrorв этой ситуации.

Эли Кортрайт
источник
8

Это зависит от того, в чем проблема с аргументами.

Если аргумент имеет неправильный тип, вызовите TypeError. Например, когда вы получаете строку вместо одного из этих логических значений.

if not isinstance(save, bool):
    raise TypeError(f"Argument save must be of type bool, not {type(save)}")

Однако обратите внимание, что в Python мы редко делаем какие-либо проверки, подобные этой. Если аргумент действительно недействителен, более глубокая функция, вероятно, сделает жалобу за нас. И если мы проверим только логическое значение, возможно, какой-то пользователь кода позже просто передаст ему строку, зная, что непустые строки всегда равны True. Это может спасти его.

Если аргументы имеют недопустимые значения, вызовите ValueError. Это кажется более подходящим в вашем случае:

if recurse and not save:
    raise ValueError("If recurse is True, save should be True too")

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

if recurse and not save:
    logging.warning("Bad arguments in import_to_orm() - if recurse is True, so should save be")
    save = True
Gloweye
источник
Я думаю, что это самый точный ответ. Это явно недооценено (пока 7 голосов, включая мое).
Сиу Чинг Понг-Асука Кэндзи -
-1

Я не уверен , что я согласен с наследованием от ValueError- моей интерпретации документации , что ValueErrorэто только предполагается , поднятый ... наследуя встроенных команд от него или поднять его сами , кажется неправильным.

Возникает, когда встроенная операция или функция получает аргумент, который имеет правильный тип, но недопустимое значение, и ситуация не описывается более точным исключением, таким как IndexError.

- ValueError документация

cdleary
источник
Сравните google.com/codesearch?q=lang:python+class \ + \ w Ошибка (([^ E] \ w * | E [^ x] \ w )): с google.com/codesearch?q=lang: python + class \ + \ w * Ошибка (исключение):
Маркус Жардерот
13
Эта реклама просто означает, что встроенные модули поднимают ее, а не только встроенные могут ее поднять. В этом случае не совсем уместно, чтобы в документации по Python говорилось о том, что вызывают внешние библиотеки.
Игнасио Васкес-Абрамс
5
Каждая часть программного обеспечения Python, которую я когда-либо видел, использовалась ValueErrorдля такого рода вещей, поэтому я думаю, что вы пытаетесь слишком много прочитать в документации.
Джеймс Беннетт
6
О, если мы собираемся использовать поиски по коду Google, чтобы спорить об этом: google.com/codesearch?q=lang%3Apython+raise%5C+ValueError # 66,300 случаев вызова ValueError, включая Zope, xen, Django, Mozilla (и это только с первой страницы результатов). Если через встроенное исключение припадков, используйте его ..
DBR
7
Как указано, документация неоднозначна. Это должно было быть написано либо как "Повышено при получении встроенной операции или встроенной функции", либо как "Повышено при получении функции или встроенной операции". Конечно, каковы бы ни были первоначальные намерения, нынешняя практика превзошла их (как указывает @dbr). Так что стоит переписать как второй вариант.
одноименный
-1

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

class BadCallError(ValueError):
    pass

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

Разве это не должно быть стандартным исключением в Python?

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

BobHy
источник
Я бы поднял KeyErrorдля ключевого слова не найдено (так как пропущенное явное ключевое слово семантически идентично **kwargsdict, в котором отсутствует этот ключ).
Cowbert