"Прошу прощения, а не разрешения", - объясняют
Я не спрашиваю о личных "религиозных" мнениях об этой философии, скорее о чем-то более техническом.
Я понимаю, что эта фраза - один из нескольких лакмусовых тестов, чтобы увидеть, является ли ваш код "питоническим". Но для меня pythonic означает чистый, простой и интуитивно понятный, не загруженный обработчиками исключений для плохого кодирования.
Итак, практический пример. Я определяю класс:
class foo(object):
bar = None
def __init__(self):
# a million lines of code
self.bar = "Spike is my favorite vampire."
# a million more lines of code
Теперь, исходя из процедурного фона, в другой функции я хочу сделать это:
if foo.bar:
# do stuff
Я получу исключение атрибута, если я был нетерпелив и не сделал начальный foo = None. Итак, "прошу прощения, а не разрешения" предлагает мне сделать это вместо этого?
try:
if foo.bar:
# do stuff
except:
# this runs because my other code was sloppy?
Почему для меня было бы лучше добавить дополнительную логику в блок try, чтобы я мог оставить свое определение класса более двусмысленным? Почему бы не определить все изначально, а потому явно дать разрешение?
(Не расстраивайтесь из-за использования блоков try/ кроме... Я использую их повсюду. Я просто не считаю правильным использовать их для обнаружения моих собственных ошибок, потому что я не был полным программистом.)
Или... я совершенно не понимаю мантру "Прошу прощения"?
8 ответов
Классический пример "прошу прощения, а не разрешения" - это доступ к значениям из dict
это может не существовать. Например:
names = { 'joe': 'Joe Nathan', 'jo': 'Jo Mama', 'joy': 'Joy Full' }
name = 'hikaru'
try:
print names[name]
except KeyError:
print "Sorry, don't know this '{}' person".format(name)
Здесь исключение, которое может произойти (KeyError
), так что вы не просите прощения за каждую возможную ошибку, а только за ту, которая возникнет естественным образом. Для сравнения, подход "сначала спросите разрешение" может выглядеть так:
if name in names:
print names[name]
else:
print "Sorry, don't know this '{}' person".format(name)
или же
real_name = names.get(name, None)
if real_name:
print real_name
else:
print "Sorry, don't know this '{}' person".format(name)
Такие примеры "попросить прощения" часто слишком просты. ИМО это не кристально ясно, что try
/except
блоки по своей сути лучше, чем if
/else
, Реальное значение намного яснее при выполнении операций, которые могут не работать различными способами, такими как анализ; с помощью eval()
; доступ к операционной системе, промежуточному программному обеспечению, базе данных или сетевым ресурсам; или выполнение сложной математики. Когда есть несколько возможных способов отказа, готовность получить прощение чрезвычайно ценна.
Другие заметки о ваших примерах кода:
Вам не нужно ковш try
/except
блокирует каждую переменную использования. Это было бы ужасно. И вам не нужно устанавливать self.bar
в вашем __init__()
так как он установлен в вашем class
определение выше. Обычно его определяют либо в классе (если эти данные могут быть общими для всех экземпляров класса), либо в __init__()
(если это данные экземпляра, специфичные для каждого экземпляра).
Значение None
не является неопределенным, или ошибка, кстати. Это конкретное и допустимое значение, означающее "нет", "ноль", "ноль" или "ничего". Многие языки имеют такие значения, поэтому программисты не "перегружают" 0
, -1
, ''
(пустая строка) или аналогичные полезные значения.
"Прошу прощения, а не разрешения" противопоставляет два стиля программирования. "Спроси разрешения" звучит так:
if can_do_operation():
perform_operation()
else:
handle_error_case()
"Прошу прощения" звучит так:
try:
perform_operation()
except Unable_to_perform:
handle_error_case()
Это ситуация, когда ожидается, что попытка выполнить операцию может закончиться неудачей, и вам придется справиться с ситуацией, когда операция невозможна, так или иначе. Например, если операция обращается к файлу, файл может не существовать.
Есть две основные причины, почему лучше попросить прощения:
- В параллельном мире (в многопоточной программе, или если операция включает в себя объекты, которые являются внешними по отношению к программе, такие как файлы, другие процессы, сетевые ресурсы и т. Д.), Ситуация может измениться между временем запуска
can_do_operation()
и время, когда вы бежитеperform_operation()
, Так что вам все равно придется обработать ошибку. - Вы должны использовать точно правильный критерий для запроса разрешения. Если вы ошиблись, вы либо не сможете выполнить какую-либо операцию, которую можете выполнить, либо произойдет ошибка, потому что вы все равно не сможете выполнить операцию. Например, если вы проверяете, существует ли файл перед его открытием, возможно, что файл существует, но вы не можете открыть его, потому что у вас нет разрешения. И наоборот, возможно, файл создается, когда вы его открываете (например, потому что он приходит по сетевому соединению, которое вызывается только тогда, когда вы действительно открываете файл, а не когда вы просто нажимаете, чтобы увидеть, есть ли он там).
Что общего в ситуациях с просьбой о прощении, так это то, что вы пытаетесь выполнить операцию, и вы знаете, что операция может завершиться неудачей.
Когда ты пишешь foo.bar
несуществование bar
обычно не считается отказом объекта foo
, Обычно это ошибка программиста: попытка использовать объект таким образом, для которого он не предназначен. Следствием ошибки программиста в Python является необработанное исключение (если вам повезет: конечно, некоторые ошибки программиста не могут быть обнаружены автоматически). Так что если bar
является необязательной частью объекта, нормальный способ справиться с этим состоит в том, чтобы иметь bar
поле, которое инициализируется в None
и установите другое значение, если присутствует необязательная часть. Чтобы проверить, bar
присутствует, пиши
if foo.bar is not None:
handle_optional_part(foo.bar)
else:
default_handling()
Вы можете сократить if foo.bar is not None:
в if foo.bar:
только если bar
всегда будет истиной, когда интерпретируется как логическое значение - если bar
может быть 0, []
, {}
или любой другой объект, который имеет значение ложной истины, вам нужно is not None
, Это также более понятно, если вы тестируете дополнительную деталь (в отличие от тестирования между True
а также False
).
В этот момент вы можете спросить: почему бы не пропустить инициализацию bar
когда его там нет, проверьте его наличие hasattr
или поймать его с AttributeError
обработчик? Потому что ваш код имеет смысл только в двух случаях:
- у объекта нет
bar
поле; - у объекта есть
bar
поле, которое означает, что вы думаете, что это значит.
Поэтому, когда вы пишете или решаете использовать объект, вы должны убедиться, что у него нет bar
поле с другим значением. Если вам нужно использовать какой-то другой объект, который не имеет bar
поле, это, вероятно, не единственное, что вам нужно адаптировать, так что вы, вероятно, захотите создать производный класс или инкапсулировать объект в другой.
Здесь много хороших ответов, я просто подумал, что хотел бы добавить пункт, который я до сих пор не упоминал.
Часто просить прощения вместо разрешения приводит к улучшению производительности.
- Когда вы запрашиваете разрешение, вы должны выполнить дополнительную операцию, чтобы запрашивать разрешение каждый раз.
- Когда вы просите прощения, вам иногда нужно выполнить дополнительную операцию, то есть когда она не удалась.
Обычно случаи сбоев встречаются редко, а это означает, что если вы запрашиваете только разрешение, вам вряд ли когда-либо придется выполнять какие-либо дополнительные операции. Да, когда он терпит неудачу, он генерирует исключение, а также выполняет дополнительную операцию, но исключения в python очень быстрые. Вы можете увидеть некоторые моменты здесь: https://jeffknupp.com/blog/2013/02/06/write-cleaner-python-use-exceptions/
Вы правы - цель try
а также except
не для покрытия вашего неаккуратного кодирования. Это просто приводит к более небрежному кодированию.
Обработка исключений должна использоваться для обработки исключительных обстоятельств (небрежное кодирование не является исключительным обстоятельством). Однако часто легко предсказать, какие исключительные обстоятельства могут произойти. (Например, ваша программа принимает ввод пользователя и использует его для доступа к словарю, но ввод пользователя не был ключом в словаре...)
Мое личное нерелигиозное мнение заключается в том, что указанная мантра применима в основном к документированным и хорошо понятным условиям выхода и крайним случаям (например, ошибки ввода-вывода) и никогда не должна использоваться в качестве карты выхода из тюрьмы для небрежного программирования.
Это сказало, try/except
часто используется, когда существуют "лучшие" альтернативы. Например:
# Do this
value = my_dict.get("key", None)
# instead of this
try:
value = my_dict["key"]
except KeyError:
value = None
Что касается вашего примера, используйте if hasattr(foo, "bar")
если у вас нет контроля над foo
и нужно проверить соответствие вашим ожиданиям, в противном случае просто используйте foo.bar
и пусть возникшая ошибка станет вашим руководством для выявления и исправления неаккуратного кода.
Хотя уже есть много качественных ответов, большинство в первую очередь обсуждают это со стилистической точки зрения, а скорее с функциональной.
В некоторых случаях нам нужно просить прощения, а не разрешения застраховать правильный код (за исключением многопоточных программ).
Канонический пример:
if file_exists:
open_it()
В этом примере файл мог быть удален между проверкой и попыткой фактически открыть файл. Этого можно избежать с помощью try
:
try:
open_it()
except FileNotFoundException:
pass # file doesn't exist
Это возникает в разных местах, часто работая с файловыми системами или внешними API.
В контексте Python "Просить прощения, а не разрешения" подразумевает стиль программирования, при котором вы не проверяете, чтобы все было так, как вы ожидаете, а вместо этого вы обрабатываете ошибки, которые возникают, если их нет. Классический пример - не проверка того, что словарь содержит заданный ключ, как в:
d = {}
k = "k"
if k in d.keys():
print d[k]
else:
print "key \"" + k + "\" missing"
Но лучше обработать полученную ошибку, если ключ отсутствует:
d = {}
k = "k"
try:
print d[k]
except KeyError:
print "key \"" + k + "\" missing"
Однако дело не в том, чтобы заменить каждый if
в вашем коде с try
/except
; это сделало бы ваш код явно грязным. Вместо этого вы должны ловить ошибки только тогда, когда вы действительно можете что-то с ними сделать. В идеале это уменьшит объем обработки ошибок в вашем коде и сделает его реальное назначение более очевидным.
Просить прощения, а не разрешения, предназначено для упрощения кода. Это предназначено для кода, который будет написан так, когда есть разумное ожидание, что .bar
может вызвать AttributeError.
try:
print foo.bar
except AttributeError as e
print "No Foo!"
Похоже, ваш код спрашивает разрешения И прощения:)
Дело в том, что если вы разумно ожидаете, что что-то не получится, используйте try / catch. Если вы не ожидаете, что что-то потерпит неудачу, и это все равно не получится, то исключение, которое выдается, становится эквивалентом ошибочного утверждения в других языках. Вы видите, где происходит непредвиденное исключение, и соответственно корректируете свой код / предположения.