Подтвердить что ты не робот

Лучшая практика в python для возвращаемого значения при ошибке и успехе

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

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 при ошибке в этом случае, а список в противном случае (пустой или нет).

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

  • Если при успехе вы должны были вернуть True или False, и вы поймаете ошибку?
  • Если при успехе вы должны были вернуть список, и вы поймали ошибку?
  • Если при успешном завершении вы должны были возвратить дескриптор файла и вы поймали ошибку?
  • et cetera
4b9b3361

Ответ 1

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

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

Мой совет для вас - посмотреть библиотеку 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:

OP говорит:

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

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

Ответ 2

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

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.

Ответ 4

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

(True, some_result)

(False, some_useful_response)

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

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

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

Код, Response = some_function (...)

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

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

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я исхожу из фона Erlang: -)

Ответ 5

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

  • intersect_two_lists(None, None)
  • intersect_two_lists([], ())
  • intersect_two_lists('12', '23')
  • intersect_two_lists([1, 2], {1: 'one', 2: 'two'})
  • intersect_two_lists(False, [1])
  • intersect_two_lists(None, [1])

Я бы ожидал, что (5) выдает исключение, так как передача False является ошибкой типа. Остальные из них, однако, имеют какой-то смысл, но это действительно зависит от контракта, который заявляет функция. Если intersect_two_lists были определены как возвращающие пересечение двух iterables, тогда все, кроме (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 Based Design. Я хотел бы упомянуть об этом на всякий случай, если вы раньше не изучали этот вариант.

Если контракт для 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(...)

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