Python Исключения: EAFP и что на самом деле является исключительным? - PullRequest
15 голосов
/ 21 июня 2010

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

import heapq
...
pq = a_list[:]
heapq.heapify(pq)
while True:
    min1 = heapq.heappop(pq)
    try:
        min2 = heapq.heappop(pq)
    except IndexError:
        break
    else
        heapq.heappush(pq, min1 + min2)
# do something with min1

Исключение возникает только один раз в len(a_list) итерациях цикла, но оно не является действительно исключительным, потому что мы знаем, что оно произойдет в конце концов. Эта настройка избавляет нас от проверки, является ли a_list пустым несколько раз, но (возможно) это менее читабельно, чем использование явных условий.

Каков консенсус по использованию исключений для такого рода неисключительной программной логики?

Ответы [ 5 ]

30 голосов
/ 21 июня 2010

исключения должны вызываться только в действительно исключительные случаи

Не в Python: например, каждый цикл for (если только он преждевременно break s или return s) не завершается из-за исключения (StopIteration), которое выдается и перехватывается. Таким образом, исключение, которое происходит один раз за цикл, вряд ли странно для Python - оно встречается чаще, чем нет!

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

В этом случае мне нравится переписывание Джона (которое должно быть еще более упрощено путем удаления ветви else), потому что оно делает код более компактным - прагматическая причина, наиболее определенно не «закалка» в стиле Python с использованием инопланетного принципа.

9 голосов
/ 22 июня 2010

Создание исключений стоит дорого в большинстве языков низкого уровня, таких как C ++.Это влияет на большую часть «общей мудрости» в отношении исключений и не так сильно применимо к языкам, которые работают в ВМ, например, к Python.В Python нет такой большой цены за использование исключения вместо условного.

(Это тот случай, когда «общая мудрость» становится привычной. Люди приходят к этому из опыта одного типаокружение - языки низкого уровня - и затем применить его к новым доменам, не оценивая, имеет ли это смысл.)

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

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

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

import os, errno, stat

def read_file(fn):
    """
    Read a file and return its contents.  If the file doesn't exist or
    can't be read, return "".
    """
    try:
        return open(fn).read()
    except IOError, e:
        return ""

def read_file_2(fn):
    """
    Read a file and return its contents.  If the file doesn't exist or
    can't be read, return "".
    """
    if not os.access(fn, os.R_OK):
        return ""
    st = os.stat(fn)
    if stat.S_ISDIR(st.st_mode):
        return ""
    return open(fn).read()

print read_file("x")

Конечно, мы можем проверить и избежать ошибки - но мы все усложнили.Мы пытаемся угадать все причины, по которым доступ к файлу может быть неудачным (и это не охватывает их все), возможно, мы представили условия гонки и выполняем гораздо больше работы по вводу / выводу.Это все сделано для нас - просто поймать исключение.

7 голосов
/ 21 июня 2010

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

import heapq
...
pq = heapq.heapify(a_list)
while pq:
    min1 = heapq.heappop(pq)
    if pq:
        min2 = heapq.heappop(pq)
        heapq.heappush(pq, min1 + min2)
# do something with min1

.. и, таким образом, избежать попытки-исключения.

Достижение конца списка, что, как вы знаете, произойдет здесь не исключительно - это гарантировано!Поэтому лучшей практикой было бы справиться с этим заранее.Если у вас есть что-то еще в другом потоке, которое потребляет из той же кучи, тогда используйте try-Кроме того, было бы гораздо больше смысла (например, обработка специального / непредсказуемого случая).-исключает везде, где я могу проверить и избежать ошибки заранее.Это заставляет вас говорить: «Я знаю, что эта плохая ситуация может случиться, вот как я с ней справляюсь».По моему мнению, в результате вы будете стремиться писать более читаемый код.

[Редактировать] Обновлен пример согласно предложению Алекса

4 голосов
/ 22 июня 2010

Просто для записи, я бы написал так:

import heapq
a_list = range(20)
pq = a_list[:]
heapq.heapify(pq)
try:
    while True:
        min1 = heapq.heappop(pq)
        min2 = heapq.heappop(pq)
        heapq.heappush(pq, min1 + min2)
except IndexError:
    pass # we ran out of numbers in pq

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

3 голосов
/ 21 июня 2010

Практика использования исключений в качестве «обычных» инструментов управления потоками довольно широко распространена в Python. Чаще всего он используется в ситуациях, подобных той, которую вы описываете, когда добираетесь до конца какой-то последовательности.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...