Это плохая форма, чтобы рассчитывать на исключения?

Моя текущая ситуация связана с созданием игры на питоне с использованием библиотеки pygame. У меня есть метод, называемый getTile(), который возвращает плитку каждый раз, когда игрок перемещается. LvlDict составляет мир.

def getTile(self, pos):
    x = pos[0]
    y = pos[1]
    return self.lvlDict[x,y].kind

и я думаю об изменении его на

def getTile(self, pos):
    try:
        x = pos[0]
        y = pos[1]
        return self.lvlDict[x,y].kind
    except KeyError:
        return None

где будет некоторая интерпретация того, что делать, если бы это было Нет. Скорее всего, вернемся туда, где он был раньше. Является ли это неотъемлемо плохим или приемлемым? Даже если это только вопрос мнения, я хотел бы знать, что люди думают об этом.

Ответ 1

Исключения являются медленными, поэтому лучше всего их сохранить для действительно исключительных обстоятельств. Если это непредвиденная ситуация, которая обычно не возникает, то исключение разумно. Если пустая плитка является обычным явлением, лучше использовать вместо нее проверку if.

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

Ответ 2

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

In [1]: d = {i:i for i in xrange(10000)}

In [4]: def f(d):
    try:                     
        d["blue"]
    except KeyError:
        pass   

In [5]: def g(d):
    if "blue" in d: d["blue"]  

#Case: Key not in Dict

In [7]: timeit f(d)
1000000 loops, best of 3: 950 ns per loop

In [8]: timeit g(d)
10000000 loops, best of 3: 135 ns per loop

#Case: Key in Dict

In [9]: d["blue"] = 8 

In [10]: timeit f(d)
10000000 loops, best of 3: 151 ns per loop

In [11]: timeit f(d)
10000000 loops, best of 3: 151 ns per loop

Ответ 3

Хотя верно, что исключения часто будут медленнее, чем проверка возможных сбоев, которые могут произойти, также чаще всего это не разница, которая имеет значение в практических приложениях. В частности, это обычно не является существенной разницей для приложений, для которых Python используется как язык выбора. Поэтому, как объясняет глоссарий Python для EAFP:

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

В самом деле, в Python принято использовать исключения (и просить о прощении) для обработки ошибок, даже если эти ошибки будут возникать часто и на регулярной основе.

Теперь в вашем конкретном случае кажется столь же элегантным просто проверить, находится ли ключ в словаре (как предложено другими ответами). Тем не менее, вы не должны бояться использовать исключения и их использование (даже для ожидаемых) обработки ошибок полностью Pythonic.

Ответ 4

Использование исключений для реализации нормального потока через ваш код не является плохой формой в python. Такой подход используется некоторыми языковыми конструкциями, включая генераторы, которые не вызывают больше элементов, поднимая StopIteration.

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

Я бы сказал, что правильность в целом важнее производительности (и если этот фрагмент кода действительно критичен по производительности, вы, вероятно, не будете писать его на питоне для начала). Поэтому, если есть какая-либо возможность для dict изменить между проверкой наличия ключа и доступом к нему, я бы определенно пошел с исключением. Если вы знаете, что такая гонка не может произойти, я предлагаю вам использовать подход, который вы считаете наиболее читаемым.

Существует еще один аспект вашего кода, который выглядит плохой. Внутри блока try я вижу три разных места, в которых может быть поднят KeyError. Я предполагаю, что вы только намеревались поймать исключения из одного из этих мест. Улавливая исключение из двух других мест, которые можно было бы повысить, вы могли бы отлаживать код сложнее. Условие, вызывающее ошибку, изменилось бы от сбоя с описательной трассировкой до тонко неправильного поведения.

Таким образом, поместите эти строки внутри блока try, которые действительно должны быть там.

Я предполагаю, что эта конструкция ближе к тому, что вы намеревались:

def getTile(self, pos):
    x = pos[0]
    y = pos[1]
    try:
        return self.lvlDict[x,y].kind
    except KeyError:
        return None

Если у вас есть блок try, в котором первая строка может вызвать исключение, а остальные строки находятся внутри блока try, потому что вы не хотите, чтобы они выполнялись в случае исключения, тогда вы должны использовать try - except - else.

Ответ 5

С точки зрения, отличной от Python, я бы сказал, что исключения должны использоваться только для обработки недействительных случаев и задавать вопрос: обсуждаем ли мы номинальный регистр, регистр или случай недопустимого ввода/результата? Если ваш случай "Нет" действителен, но кейс края, я бы сказал, что он должен обрабатываться логикой программы (не генерируйте исключение). Если "Нет" может произойти, но на самом деле никогда не должно быть, тогда это должно быть обработано исключением.

Не знаком с Python, но предполагает параллели с другими средами программирования, например. С#:

  • Блокирование блоков кода с помощью try/catch обеспечивает незначительное влияние производительности; как только возникает исключение, вещи происходят за кулисами, которые имеют нетривиальные эксплуатационные расходы.

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

  • Используемые надлежащим образом исключения помогают вам писать более чистый код, не загромождая дополнительную логику if-then, оставляя только истинную "бизнес-логику". Они также уводят вас от неловких сигнатур функций со значениями возврата кэш-памяти для сигнализации об ошибках. Однако, для превышения, исключения могут в конечном итоге скрывать "деловую" логику, запутывая ваш код.