Лучшая практика в 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
Ответы
Ответ 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
.
Ответ 3
Исключения, безусловно, лучше (и более Pythonic), чем возврат статуса. Подробнее об этом: Исключения против статуса возвращаются
Ответ 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 об исключениях. Эта конкретная запись дает вам больше пищи для размышлений об исключениях.