Який виняток слід підкреслити щодо помилкових / незаконних комбінацій аргументів у Python?


542

Мені було цікаво про найкращі практики вказівки недійсних комбінацій аргументів у 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- чи добре зрозуміло, що всі будуть створювати свої власні BadValueErrors в Python чи існує інший, бажаний метод?

Відповіді:


608

Я б просто підняв 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 , так чому б не використовувати його?


65
> "так чому б не використати це?" - Специфіка. Можливо, я хочу потрапити на якийсь зовнішній шар "MyValueError", але не будь-який / усі "ValueError".
Кевін Маленький

7
Так, частина питання про специфіку полягає в тому, де ще порушене значення ValueError. Якщо функція callee любить ваші аргументи, але викликає math.sqrt (-1) всередині, абонент може ловити ValueError, очікуючи, що його аргументи були невідповідними. Можливо, ви просто перевірте повідомлення в цьому випадку ...
cdleary

3
Я не впевнений, що аргумент справедливий: якщо хтось телефонує math.sqrt(-1), це помилка програмування, яку все одно потрібно виправити. ValueErrorне призначений для спіймання у звичайному виконанні програми, або це випливає з цього RuntimeError.
ereOn

2
Якщо помилка є НА ЧИСЛІ аргументів, для функції зі змінною кількістю аргументів ... наприклад, функції, де аргументи повинні бути парним числом аргументів, тоді вам слід підняти TypeError, щоб бути послідовним. І не створюйте свій власний клас, якщо: a) у вас є випадок використання або б) ви експортуєте бібліотеку, щоб її використовували інші. Передчасна функціональність - це загибель коду.
Ерік Аронесті

104

Я б успадкував від ValueError

class IllegalArgumentError(ValueError):
    pass

Іноді краще створити власні винятки, але успадкувати від вбудованого, який максимально наближений до того, що ви хочете.

Якщо вам потрібна помилка, корисно мати ім’я.


26
Перестаньте писати заняття та користувацькі винятки - pyvideo.org/video/880/stop-writing-classes
Хаміш Грубіян

40
@HamishGrubijan це відео жахливе. Коли хтось запропонував вдало використовувати клас, він просто вивільнив "Не використовуйте заняття". Блискуча. Заняття хороші. Але не сприймай мого слова .
Роб Грант

12
@RobertGrant Ні, ви цього не розумієте. Це відео насправді не означає буквально "не використовуйте заняття". Йдеться про не надто складні речі.
RayLuo

15
@RayLuo Ви, можливо, перевірили, на що йдеться у відео, і перетворили його на смачне, розумне альтернативне повідомлення, але це те, про що йдеться у відео, і це те, що хтось, хто не має багато досвіду та здорового глузду, відійде з.
Роб Грант

3
@SamuelSantana, як я вже говорив, кожен раз, коли хтось підніме руку і сказав "а як із X?" де Х була гарною ідеєю, він просто сказав: "Не складайте іншого класу". Досить чітко. Я погоджуюся, що ключ - баланс; проблема полягає в тому, що насправді занадто розпливчасто, щоб насправді жити :-)
Роб Грант

18

Я думаю, що найкращий спосіб впоратися з цим - це так, як сам пітон справляється з цим. 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

Я думаю, що це, мабуть, пропущено або не вимагається аргументів, тоді як питання стосується аргументів, які наведені правильно, але невірні на більш високому рівні абстракції, що стосуються значення даного аргументу. Але як я насправді шукав колишнього, так і маю нагоду в будь-якому разі.
Ніхто

2
Як сказали @ user3504575 та @Nobody, TypeError використовується, якщо аргументи не відповідають функції підпису функції (неправильна кількість аргументів позиції, аргументи ключових слів з неправильним іменем, неправильний тип аргументу), але ValueError використовується при виклику функції відповідає підпису, але значення аргументу недійсні (наприклад, виклик int('a')). джерело
goodmami

Оскільки питання ОП посилалося на "недійсні комбінації аргументів", видається, що TypeError було б доречним, оскільки це був би випадок, коли підпис функції по суті неправильний для переданих аргументів.
J Bones

Ваш приклад закликає sum()без аргументів, що є TypeError, але ОП стосувалося "незаконних" комбінацій значень аргументів, коли типи аргументів є правильними. У цьому випадку і те, saveі recurseце булі, але якщо recurseє, Trueто saveне повинно бути False. Це ValueError. Я погоджуюсь, що на деякі тлумачення назви питання відповідатиме TypeError, але не для прикладу, який подано.
goodmami


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")

Або в цьому конкретному випадку значення 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 голосів поки що включають мій).
Сіу Чінг Понг -Asuka Kenji-

-1

Я не впевнений , що я згоден з успадкуванням від ValueError- моїй інтерпретації документації , що ValueErrorце тільки передбачається , піднятий ... наслідуючи вбудованих команд від нього або підняти його самі , здається неправильним.

Піднімається, коли вбудована операція або функція отримує аргумент, який має правильний тип, але невідповідне значення, і ситуація не описується більш точним винятком, таким як IndexError.

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


Порівняйте google.com/codesearch?q=lang:python+class \ + \ w Помилка (([^ E] \ w * | E [^ x] \ w )): з google.com/codesearch?q=lang: python + class \ + \ w * Помилка (виняток):
Markus Jarderot

13
Це розмиття просто означає, що вбудовані модулі піднімають його, а не те, що тільки вбудовані можуть підняти його. У цьому випадку було б не зовсім доречно в документації Python говорити про те, що піднімають зовнішні бібліотеки.
Ігнасіо Васкес-Абрамс

5
Кожен фрагмент програмного забезпечення Python, який я коли-небудь бачив, використовував ValueErrorдля подібних речей, тому я думаю, що ви намагаєтеся прочитати занадто багато документації.
Джеймс Беннетт

6
Помилка, якщо ми будемо використовувати пошук Google Code для аргументації цього: 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 для розрізнення помилок значення у аргументах від помилок значення у місцевих жителях.


Я б підняв KeyErrorза ключове слово, яке не знайдено (оскільки відсутнє явне ключове слово семантично ідентично **kwargsдікта, в якому відсутній цей ключ).
коуберт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.