Мне было интересно узнать о лучших методах указания недопустимых комбинаций аргументов в 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, или есть другой, предпочтительный метод?






Я в основном только что видел встроенный ValueError, используемый в этой ситуации.
Я бы унаследовал от ValueError
class IllegalArgumentError(ValueError):
pass
Иногда лучше создать свои собственные исключения, но унаследовать их от встроенного, что максимально близко к тому, что вы хотите.
Если вам нужно отловить эту конкретную ошибку, полезно иметь имя.
@HamishGrubijan, видео ужасное. Когда кто-нибудь предлагал хорошо использовать класс, он просто блеял: «Не используйте классы». Блестяще. Классы хорошие. Но не верьте мне на слово.
@RobertGrant Нет, ты этого не понимаешь. Это видео не совсем буквально о том, чтобы «не использовать классы». Речь идет о том, чтобы не усложнять слишком много вещей.
@RayLuo, возможно, вы проверили, что говорится в видео, и превратили это в приятное, разумное альтернативное сообщение, но именно об этом говорится в видео, и это то, что уйдет от человека, у которого нет большого опыта и здравого смысла. с.
@RobertGrant Насколько я понимаю, он выступает против неправильного использования классов, а не против любого использования классов (см. С минуты 12:48). И он сказал, что есть риск чрезмерного упрощения при ответе на первый вопрос. Итак, ключ в том, чтобы найти баланс, не так ли?
@SamuelSantana, как я уже сказал, каждый раз, когда кто-нибудь поднимает руку и говорит: "А как насчет X?" где X был хорошей идеей, он просто сказал: «не создавайте еще один класс». Довольно ясно. Я согласен, что главное - это баланс; проблема в том, что это слишком расплывчато, чтобы жить по нему :-)
Не совсем то, что OP хотел отметить. Значения аргументов может быть в порядке, но проблема комбинации аргументов. Так что IllegalArgument - неправильное слово, BadArguments больше похоже на него. Я так много думаю об этой идее, что предлагаю ее как другой ответ.
Я не уверен, что согласен с наследованием от ValueError - моя интерпретация документации заключается в том, что ValueError - это Только, который должен вызываться встроенными командами ... наследование от него или его собственное повышение кажется неправильным.
Raised when a built-in operation or function receives an argument that has the right type but an inappropriate value, and the situation is not described by a more precise exception such as IndexError.
Сравните google.com/codesearch?q=lang:python+class \ + \ wОшибка (([^ E] \ w * | E [^ x] \ w)): с google.com/codesearch?q=lang:python+class \ + \ w * Ошибка (исключение):
@dbr: Да, я думаю, они имеют в виду «(встроенная операция или функция)», а не «(встроенная операция) или функция». Я бы подумал, что они бы противопоставили это, сказав «определяется пользователем» во втором случае.
@MizardX: Это интересно, как и google.com/codesearch?q=lang%3Apython+class \ + IllegalArgument Exception - они разделяются между наследованием от ValueError, Exception и BaseException.
Это рекламное объявление просто означает, что встроенные модули повышают его, а не то, что встроенные модули Только могут его повысить. В данном случае было бы не совсем уместно, чтобы в документации Python говорилось о том, что поднимают внешние библиотеки.
Каждая часть программного обеспечения Python, которое я когда-либо видел, использовала ValueError для такого рода вещей, поэтому я думаю, что вы пытаетесь слишком много вникнуть в документацию.
@James Bennett: Мы процитировали несколько проектов в приведенных выше поисках кода Google, которые не используют ValueError напрямую, поэтому, по крайней мере, кажется, что есть необходимость в разъяснении.
Эээ, если мы собираемся использовать поиск Google Code, чтобы аргументировать это: google.com/codesearch?q=lang%3Apython+raise%5C+ValueError # 66 300 случаев возникновения ValueError, включая Zope, xen, Django, Mozilla (и это только с первой страницы результатов). Если подходит встроенное исключение, используйте его.
Как сказано, документация неоднозначна. Он должен был быть написан либо как «Повышается, когда встроенная операция или встроенная функция получает», либо как «Повышается, когда функция или встроенная операция получает». Конечно, каким бы ни было первоначальное намерение, текущая практика превзошла его (как указывает @dbr). Так что его следует переписать как второй вариант.
Это должен быть комментарий, а не ответ.
Ничего о процитированном документе предотвращает вы не поднимаете ValueError в коде, написанном пользователем. И широко распространенное использование в сообществе Python является для использования в собственном коде.
Я бы просто поднял 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, так почему бы не использовать его?
> "Так почему бы не использовать это?" - Специфика. Возможно, я хочу поймать какой-нибудь внешний слой «MyValueError», но не любой / весь «ValueError».
Да, отчасти вопрос специфичности заключается в том, где еще возникает ValueError. Если вызываемой функции нравятся ваши аргументы, но вызывается math.sqrt (-1) изнутри, вызывающий может уловить ValueError и ожидать, что аргументы это неуместны. Может быть, вы просто проверите сообщение в этом случае ...
Я не уверен, что этот аргумент верен: если кто-то звонит math.sqrt(-1), это ошибка программирования, которую все равно необходимо исправить. ValueError не предназначен для перехвата при нормальном выполнении программы, иначе он будет производным от RuntimeError.
Если ошибка относится к ЧИСЛУ аргументов для функции с переменным числом аргументов ... например, функции, в которой аргументы должны быть четным числом аргументов, то для согласованности следует поднять исключение TypeError. И не создавайте свой собственный класс, если: а) у вас нет варианта использования или б) вы экспортируете библиотеку для использования другими. Преждевременная функциональность - это смерть кода.
Будет ли утверждение приемлемым в этом случае или есть конкретная причина использовать вместо него ValueError?
Согласитесь с предложением Маркуса свернуть собственное исключение, но в тексте исключения следует пояснить, что проблема заключается в списке аргументов, а не в отдельных значениях аргументов. Я бы предложил:
class BadCallError(ValueError):
pass
Используется, когда отсутствуют аргументы ключевого слова, необходимые для конкретного вызова, или значения аргументов действительны по отдельности, но несовместимы друг с другом. ValueError все равно будет прав, если конкретный аргумент имеет правильный тип, но выходит за пределы допустимого диапазона.
Разве это не должно быть стандартным исключением в Python?
В общем, я бы хотел, чтобы стиль Python был немного острее в различении плохих входных данных в функцию (ошибка вызывающего абонента) от плохих результатов внутри функции (моя ошибка). Таким образом, также может быть BadArgumentError, чтобы отличать ошибки значений в аргументах от ошибок значений в локальных.
Я бы поднял KeyError для ключевого слова, которое не найдено (поскольку отсутствующее явное ключевое слово семантически идентично dict **kwargs, в котором отсутствует этот ключ).
Я думаю, что лучший способ справиться с этим - это то, как сам 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», и я удивлен, что очевидный (для меня) ответ не был предложен за десять лет с тех пор, как был задан этот вопрос.
Меня ничего не удивляет, но я на 100% согласен с тем, что TypeError является правильным исключением, если тип неверен для некоторых аргументов, переданных в функцию. ValueError будет подходящим, если переменные имеют правильный тип, но их содержимое и значения не имеют смысла.
Я думаю, что это, вероятно, из-за отсутствующих или необязательных аргументов, в то время как вопрос касается аргументов, которые приведены правильно, но неверны на более высоком уровне абстракции, включая ценить данного аргумента. Но поскольку я на самом деле искал первое, так что все равно оставьте свой голос.
Как сказали @ user3504575 и @Nobody, TypeError используется, если аргументы не соответствуют сигнатуре функции (неправильное количество позиционных аргументов, аргументы ключевого слова с неправильным именем, неправильный тип аргумента), но ValueError используется при вызове функции совпадает с подписью, но значения аргументов недействительны (например, вызов int('a')). источник
Поскольку вопрос OP относится к «недопустимым комбинациям аргументов», кажется, что ошибка TypeError будет подходящей, поскольку это был бы случай, когда сигнатура функции по существу неверна для переданных аргументов.
В вашем примере вызывается sum() без аргументов, то есть TypeError, но OP был обеспокоен «недопустимыми» комбинациями значений аргументов, когда типы аргументов верны. В этом случае и save, и recurse являются булевыми, но если recurse - это True, то save не должен быть False. Это ValueError. Я согласен с тем, что на некоторую интерпретацию названия вопроса ответит TypeError, но не для представленного примера.
Это зависит от того, в чем проблема с аргументами.
Если аргумент имеет неправильный тип, вызовите TypeError. Например, когда вы получаете строку вместо одного из этих логических значений.
if not isinstance(save, bool):
raise TypeError(f"Argument save must be of type bool, not {type(save)}")
Обратите внимание, однако, что в Python мы редко проводим подобные проверки. Если аргумент действительно неверен, за нас, вероятно, пожалуется какая-нибудь более глубокая функция. И если мы проверим только логическое значение, возможно, какой-то пользователь кода позже просто скармливает ему строку, зная, что непустые строки всегда истинны. Это могло бы спасти его от гипсовой повязки.
Если аргументы имеют недопустимые значения, вызовите ValueError. В вашем случае это кажется более подходящим:
if recurse and not save:
raise ValueError("If recurse is True, save should be True too")
Или, в этом конкретном случае, значение True для рекурсии подразумевает значение True для сохранения. Поскольку я считаю это восстановлением после ошибки, вы также можете пожаловаться в журнале.
if recurse and not save:
logging.warning("Bad arguments in import_to_orm() - if recurse is True, so should save be")
save = True
Думаю, это наиболее точный ответ. Это явно недооценено (пока 7 голосов, включая мой).
Прекратить писать классы и пользовательские исключения - pyvideo.org/video/880/stop-writing-classes