Лучшая практика в Python для возврата значения ошибки и успеха - PullRequest
46 голосов
/ 27 октября 2009

В общем , допустим, у вас есть метод, подобный приведенному ниже.

def intersect_two_lists(self, list1, list2):
    if not list1:
        self.trap_error("union_two_lists: list1 must not be empty.")
        return False
    if not list2:
        self.trap_error("union_two_lists: list2 must not be empty.")
        return False
    #http://bytes.com/topic/python/answers/19083-standard
    return filter(lambda x:x in list1,list2)

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

У меня вопрос: каков наилучший опыт в таких областях, а не только для списков? Верните все, что, черт возьми, я хочу, и убедитесь, что я задокументировал это для чтения пользователем? :-) Что большинство из вас, ребята, делают:

  1. Если в случае успеха вы должны были вернуть True или False и обнаружили ошибку?
  2. Если в случае успеха вы должны были вернуть список и обнаружили ошибку?
  3. Если в случае успеха вы должны были вернуть дескриптор файла и обнаружили ошибку?
  4. и так далее

Ответы [ 6 ]

56 голосов
/ 27 октября 2009

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

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

Мой совет для вас - взглянуть на библиотеку Python для похожих функций и посмотреть, как Python обрабатывает эти особые случаи. Например, посмотрите на метод пересечения в множестве, он, как правило, прощающий. Здесь я пытаюсь пересечь пустой набор с пустым списком:

>>> b = []
>>> a = set()
>>> a.intersection(b)
set([])

>>> b = [1, 2]
>>> a = set([1, 3])
>>> a.intersection(b)
set([1])

Ошибки выдаются только при необходимости:

>>> b = 1
>>> a.intersection(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable

Конечно, есть случаи, когда возвращение True или False в случае успеха или неудачи может быть хорошим. Но очень важно быть последовательным. Функция всегда должна возвращать один и тот же тип или структуру. Очень странно иметь функцию, которая может возвращать список или логическое значение. Или вернуть тот же тип, но значение этого значения может быть другим в случае ошибки.

EDIT:

ОП говорит:

Я хочу вернуть что-то, чтобы указать параметры были неверными.

Ничто не говорит о том, что ошибка лучше, чем исключение. Если вы хотите указать, что параметры неверны, используйте исключения и поместите полезное сообщение об ошибке. Возвращение результата в этом случае просто сбивает с толку. Возможны и другие случаи, когда вы хотите указать, что ничего не произошло, но это не ошибка. Например, если у вас есть метод, который удаляет записи из таблицы, а запрошенная для удаления запись не существует. В этом случае было бы неплохо просто вернуть True или False в случае успеха или неудачи. Это зависит от приложения и предполагаемого поведения

20 голосов
/ 27 октября 2009

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

class IntersectException(Exception):
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return self.msg

def intersect_two_lists(self, list1, list2):
    if not list1: raise IntersectException("list1 must not be empty.")
    if not list2: raise IntersectException("list2 must not be empty.")

    #http://bytes.com/topic/python/answers/19083-standard
    return filter(lambda x:x in list1,list2)

В этом конкретном случае я бы, вероятно, просто отбросил тесты. Нет ничего плохого в том, чтобы пересекать пустые списки. Кроме того, в наши дни lambda отчасти разочаровывает предпочтение пониманию списка. См. Найти пересечение двух списков? для нескольких способов написать это без использования lambda.

13 голосов
/ 27 октября 2009

Мне нравится возвращать кортеж:

(True, some_result)

(False, some_useful_response)

Объект some_useful_response может использоваться для обработки условия возврата или может отображать информацию об отладке.

ПРИМЕЧАНИЕ : этот метод применяется для возвращаемых значений любого рода. Не следует путать с исключительными случаями .

На приемном конце вам просто нужно распаковать:

Code, Response = some_function (...)

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

Также стоит отметить: этот метод помогает нормализовать возврат функций. И программист, и пользователь функций знают, чего ожидать .

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я родом из Эрланга: -)

11 голосов
/ 27 октября 2009

Исключения определенно лучше (и более Pythonic), чем возврат статуса. Подробнее об этом: Исключения и возвраты статуса

3 голосов
/ 27 октября 2009

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

  1. intersect_two_lists(None, None)
  2. intersect_two_lists([], ())
  3. intersect_two_lists('12', '23')
  4. intersect_two_lists([1, 2], {1: 'one', 2: 'two'})
  5. intersect_two_lists(False, [1])
  6. intersect_two_lists(None, [1])

Я ожидаю, что (5) выдает исключение, поскольку передача False является ошибкой типа. Остальные, однако, имеют некоторый смысл, но это действительно зависит от контракта, который устанавливает функция. Если intersect_two_lists было определено как возвращающее пересечение двух итераций , то все, кроме (5) должно работать, пока вы сделаете None действительным представлением пустого набора, Реализация будет выглядеть примерно так:

def intersect_two_lists(seq1, seq2):
    if seq1 is None: seq1 = []
    if seq2 is None: seq2 = []
    if not isinstance(seq1, collections.Iterable):
        raise TypeError("seq1 is not Iterable")
    if not isinstance(seq2, collections.Iterable):
        raise TypeError("seq1 is not Iterable")
    return filter(...)

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

def require_iterable(name, arg):
    """Returns an iterable representation of arg or raises an exception."""
    if arg is not None:
        if not isinstance(arg, collections.Iterable):
            raise TypeError(name + " is not Iterable")
        return arg
    return []

def intersect_two_lists(seq1, seq2):
    list1 = require_iterable("seq1", seq1)
    list2 = require_iterable("seq2", seq2)
    return filter(...)

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

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

def require_non_empty_list(name, var):
    if not isinstance(var, list):
        raise TypeError(name + " is not a list")
    if var == []:
        raise ValueError(name + " is empty")

def intersect_two_lists(list1, list2):
    require_non_empty_list('list1', list1)
    require_non_empty_list('list2', list2)
    return filter(...)

Я думаю, что мораль этой истории что бы вы ни делали, делайте это последовательно и будьте откровенны . Лично я обычно выступаю за повышение исключений, когда нарушается договор или мне дают значение, которое я действительно не могу использовать. Если значения, которые мне дают, являются разумными, то я стараюсь сделать что-то разумное взамен. Возможно, вы также захотите прочитать C ++ FAQ Lite об исключениях. Эта конкретная статья дает вам еще пищу для размышлений об исключениях.

1 голос
/ 27 сентября 2018

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

Для результатов, не относящихся к сбору, существует несколько способов обработки условных возвратов:

  1. Как уже объяснялось во многих ответах, использование Исключений является одним из способов для решения этой проблемы и является идиоматическим питоном. Тем не менее, я предпочитаю не использовать исключения для потока управления, так как я считаю, что это создает двусмысленность в намерениях исключения. Вместо этого возбуждайте исключения в реальных исключительных ситуациях.
  2. Другое решение - вернуть None вместо ожидаемого результата, но это вынуждает пользователя добавлять защитные проверки повсюду на своих сайтах вызовов, запутывая фактическую бизнес-логику, которую он пытается на самом деле выполнить.
  3. Третий способ - использовать тип коллекции, который может содержать только один элемент (или быть явно пустым). Это называется опциональным и является моим предпочтительным методом, потому что он позволяет поддерживать чистоту логики сайта вызовов. Тем не менее, Python не имеет встроенного необязательного типа, поэтому я использую свой собственный. Я опубликовал его в крошечной библиотеке под названием option.py , если кто-то захочет попробовать. Вы можете установить его, используя pip install optional.py. Я приветствую комментарии, пожелания и комментарии.
...