Являются ли вложенные 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, поэтому нам не нужно создавать эту функцию]