Являются ли вложенные try/except блоки в python хорошей практикой программирования?
Я пишу свой собственный контейнер, который должен предоставлять доступ к словарю внутри с помощью вызовов атрибутов. Типичное использование контейнера будет выглядеть так:
dict_container = DictContainer()
dict_container['foo'] = bar
...
print dict_container.foo
Я знаю, что было бы глупо писать что-то подобное, но эту функциональность мне нужно предоставить. Я думал о реализации этого следующим образом:
def __getattribute__(self, item):
try:
return object.__getattribute__(item)
except AttributeError:
try:
return self.dict[item]
except KeyError:
print "The object doesn't have such attribute"
Я не уверен, являются ли вложенные блоки try/кроме хорошей практикой, поэтому другим способом было бы использовать hasattr()
и has_key()
:
def __getattribute__(self, item):
if hasattr(self, item):
return object.__getattribute__(item)
else:
if self.dict.has_key(item):
return self.dict[item]
else:
raise AttributeError("some customised error")
Или использовать один из них и один блок catch типа try:
def __getattribute__(self, item):
if hasattr(self, item):
return object.__getattribute__(item)
else:
try:
return self.dict[item]
except KeyError:
raise AttributeError("some customised error")
Какой вариант является наиболее питонным и элегантным?
Ответы
Ответ 1
Ваш первый пример отлично. Даже официальные документы Python рекомендуют этот стиль, известный как EAFP.
Лично я предпочитаю избегать гнездования, когда это не нужно:
def __getattribute__(self, item):
try:
return object.__getattribute__(item)
except AttributeError:
pass # fallback to dict
try:
return self.dict[item]
except KeyError:
raise AttributeError("The object doesn't have such attribute") from None
PS. has_key()
долгое время устарел в Python 2. Вместо этого используйте item in self.dict
.
Ответ 2
В то время как в Java действительно плохоиспользуется Исключения для управления потоком (главным образом потому, что исключения заставляют jvm собирать ресурсы (далее здесь)), в Python вы имеют 2 важных принципа: Duck Typing и EAFP. Это в основном означает, что вам предлагается попробовать использовать объект так, как вы думаете, что он будет работать, и обрабатывать, когда это не так.
Таким образом, единственная проблема заключается в том, что ваш код будет слишком сильно отступать. Если вам так хочется, попробуйте упростить некоторые из вложенных мест, например lqc
Ответ 3
В вашем конкретном примере вам фактически не нужно вставлять их. Если выражение в блоке try
завершается успешно, функция вернется, поэтому любой код после всего блока try/except будет запущен только в случае сбоя первой попытки. Поэтому вы можете просто сделать:
def __getattribute__(self, item):
try:
return object.__getattribute__(item)
except AttributeError:
pass
# execution only reaches here when try block raised AttributeError
try:
return self.dict[item]
except KeyError:
print "The object doesn't have such attribute"
Вложение их не плохо, но я чувствую, что оставляю его на месте, что делает структуру более ясной: вы последовательно пытаетесь выполнить ряд вещей и возвращаете первый, который работает.
Кстати, вам может понадобиться подумать о том, действительно ли вы хотите использовать __getattribute__
вместо __getattr__
здесь. Использование __getattr__
упростит ситуацию, потому что вы узнаете, что процесс поиска нормального атрибута уже сработал.
Ответ 4
По-моему, это был бы самый питоновский способ справиться с этим, хотя и потому, что он делает ваш вопрос спорным. Обратите внимание, что это определяет __getattr__()
вместо __getattribute__()
, потому что это означает, что он должен иметь дело только со "специальными" атрибутами, хранящимися во внутреннем словаре.
def __getattr__(self, name):
"""only called when an attribute lookup in the usual places has failed"""
try:
return self.my_dict[name]
except KeyError:
raise AttributeError("some customized error message")
Ответ 5
В Python легче просить прощения, чем разрешения. Не следует использовать обработку вложенных исключений.
(Кроме того, has*
почти всегда использует исключения под обложкой в любом случае.)
Ответ 6
Только будьте осторожны - в этом случае сначала, finally
происходит касание, НО пропускается тоже.
def a(z):
try:
100/z
except ZeroDivisionError:
try:
print('x')
finally:
return 42
finally:
return 1
In [1]: a(0)
x
Out[1]: 1
Ответ 7
В соответствии с документацией лучше обработать несколько исключений через кортежи или вот так:
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except IOError as e:
print "I/O error({0}): {1}".format(e.errno, e.strerror)
except ValueError:
print "Could not convert data to an integer."
except:
print "Unexpected error:", sys.exc_info()[0]
raise
Ответ 8
Одна вещь, которую мне нравится избегать, - это создание нового исключения при обработке старого. Это затрудняет чтение сообщений об ошибках.
Например, в моем коде я изначально писал
try:
return tuple.__getitem__(self, i)(key)
except IndexError:
raise KeyError(key)
И я получил это сообщение.
>>> During handling of above exception, another exception occurred.
Что я хотел, так это:
try:
return tuple.__getitem__(self, i)(key)
except IndexError:
pass
raise KeyError(key)
Это не влияет на обработку исключений. В любом блоке кода KeyError был бы пойман. Это всего лишь проблема получения точек стиля.
Ответ 9
Я не думаю, что это вопрос быть питоническим или элегантным.
Это вопрос предотвращения исключений столько, сколько вы можете.
Исключения предназначены для обработки ошибок, которые могут возникнуть в коде или событиях, которые вы не контролируете.
В этом случае у вас есть полный контроль при проверке, является ли элемент атрибутом или в словаре, поэтому избегайте вложенных исключений и придерживайтесь второй попытки.
Ответ 10
Если try-Кроме-наконец вложен в блок, наконец, результат "потомок", наконец, сохраняется. Я еще не нашел официального объяснения, но следующий фрагмент кода демонстрирует это поведение в Python 3.6.
def f2():
try:
a = 4
raise SyntaxError
except SyntaxError as se:
print('log SE')
raise se from None
finally:
try:
raise ValueError
except ValueError as ve:
a = 5
print('log VE')
raise ve from None
finally:
return 6
return a
In [1]: f2()
log SE
log VE
Out[2]: 6
Ответ 11
Хорошим и простым примером для вложенной попытки/кроме может быть следующий:
import numpy as np
def divide(x, y):
try:
out = x/y
except:
try:
out = np.inf * x / abs(x)
except:
out = np.nan
finally:
return out
Теперь попробуйте различные комбинации, и вы получите правильный результат:
divide(15, 3)
# 5.0
divide(15, 0)
# inf
divide(-15, 0)
# -inf
divide(0, 0)
# nan
[конечно у нас есть numpy, поэтому нам не нужно создавать эту функцию]