Почему для обработки нескольких исключений требуется кортеж, а не список?

Рассмотрим следующий пример:

def main_list(error_type):

    try:
        if error_type == 'runtime':
            raise RuntimeError("list error")
        if error_type == 'valueerror':
            raise ValueError("list error")

    except [RuntimeError, ValueError] as e:
        print str(e)

def main_tuple(error_type):

    try:
        if error_type == 'runtime':
            raise RuntimeError("tuple error")
        if error_type == 'valueerror':
            raise ValueError("tuple error")

    except (RuntimeError, ValueError) as e:
        print str(e)


main_tuple('runtime')
main_tuple('valueerror')

main_list('runtime')
main_list('valueerror')

Кортеж - это правильный способ обработки нескольких типов исключений. Использование списка для нескольких типов исключений не приводит к обработке.

Мне интересно, почему синтаксис Python требует кортежа для нескольких типов исключений. docs говорят, что он использует кортеж, поэтому, возможно, он просто "никогда не был реализован с использованием списка вместо кортежа".

Мне кажется разумным, что список можно также использовать в этой ситуации, по крайней мере, концептуально.

Есть ли причина, по которой Python использует кортеж вместо списка для этой ситуации?

Ответ 1

  Почему для обработки нескольких исключений требуется кортеж, а не список?

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

По крайней мере один разработчик ядра Python рекомендует использовать обработку исключений для потока управления. Добавление списков в качестве дополнительного типа для проверки будет работать против этой стратегии.

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

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

Демонстрация со списком неудач по сравнению с кортежами

exceptions = TypeError, RuntimeError
list_of_exceptions = list(exceptions)

Поймать кортеж исключений работает:

try:
    raise TypeError('foo')
except exceptions as error:
    print(error)

выходы:

foo

Но перехват списка исключений не работает:

try:
    raise TypeError('foo')
except list_of_exceptions as error:
    print(error)

принтами:


Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: foo

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
TypeError: catching classes that do not inherit from BaseException is not allowed

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

Анализ исходного кода

Анализ источника согласуется с этим выводом.

Грамматика

Это не проблема для Python грамматики или анализа. Он примет любое выражение. Таким образом, любое выражение, которое приводит к исключению или кортежу исключений, должно быть законным.

Демонтажные

Если мы разберем функцию, которая делает это в Python 3, мы увидим, что она ищет соответствие исключению с операцией сравнения.

def catch(exceptions):
    try:
        raise Exception
    except exceptions:
        pass

import dis
dis.dis(catch)

Какие выводы:

      2           0 SETUP_EXCEPT            10 (to 13)

      3           3 LOAD_GLOBAL              0 (Exception)
                  6 RAISE_VARARGS            1
                  9 POP_BLOCK
                 10 JUMP_FORWARD            18 (to 31)

      4     >>   13 DUP_TOP
                 14 LOAD_FAST                0 (exceptions)
                 17 COMPARE_OP              10 (exception match)
    ...

Это приводит нас внутрь интерпретатора Python.

Поток внутреннего контроля - детали реализации CPython

Сначала поток управления CPython проверяет, является ли значение кортежем. Если это так, он перебирает кортеж с использованием кода, специфичного для кортежа, и ищет значение, являющееся исключением:

case PyCmp_EXC_MATCH:
    if (PyTuple_Check(w)) {
        Py_ssize_t i, length;
        length = PyTuple_Size(w);
        for (i = 0; i < length; i += 1) {
            PyObject *exc = PyTuple_GET_ITEM(w, i);
            if (!PyExceptionClass_Check(exc)) {
                _PyErr_SetString(tstate, PyExc_TypeError,
                                 CANNOT_CATCH_MSG);
                return NULL;
            }
        }
    }
    else {
        if (!PyExceptionClass_Check(w)) {
            _PyErr_SetString(tstate, PyExc_TypeError,
                             CANNOT_CATCH_MSG);
            return NULL;
        }
    }
    res = PyErr_GivenExceptionMatches(v, w);
    break;

Добавление другого типа потребовало бы большего внутреннего потока управления, замедляя поток управления внутри интерпретатора Python.

Размеры контейнеров Python

Кортежи - это легкие массивы указателей. Так же как и списки, но им может быть выделено дополнительное пространство, чтобы вы могли быстро добавить к ним (вплоть до того момента, когда они должны увеличиться). В Python 3.7.3 для Linux:

>>> from sys import getsizeof
>>> getsizeof((1,2,3))
72
>>> getsizeof([1,2,3])
88

Наборы занимают еще больше места, потому что они являются хеш-таблицами. У них есть хеш объекта, который они содержат, а также указатель на то, на что они указывают.

Заключение

Это для основной команды разработчиков CPython, чтобы обсудить и принять решение.

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

После рассуждений вышеизложенного я бы не предложил, чтобы они добавили это.

Ответ 2

@BrenBarn Спасибо за вашу ссылку на обсуждение https://mail.python.org/pipermail/python-list/2012-January/619107.html

Я думаю, что самый лучший и ясный ответ - ответ Стивена Д'Апрано на https://mail.python.org/pipermail/python-list/2012-January/619120.html

Я скопировал содержимое ниже для удобства чтения.


Стивен ответил:

Простота.

Если вы также разрешаете списки, то почему бы не разрешить произвольные последовательности? Какие об итераторах, разрешаете ли вы их? Это может быть неудобно, потому что итераторы могут выполняться только один раз. Словари также являются итерабельными, поэтому, когда вы разрешаете произвольные итерации, вы получаете dicts. Все это становится беспорядком. Лучше держать его простым и разрешать только один канонический тип коллекции, а в Python этот тип является кортежем, а не списком.

Кортежи - это канонический тип коллекции, потому что у них есть ряд желаемые свойства:

  • Кортежи небольшие и память эффективна, используя наименьшее количество памяти, необходимой для хранения своих предметов. Обычно списки содержат блок резервная память, чтобы сделать вставки быстро.

  • Следовательно, виртуальная машина Python может создавать их быстро и эффективно.

  • Кортежи неизменяемы, поэтому вам не нужно беспокоиться о том, чтобы передать один функция и функция изменит ее за спиной.

  • Корреспонденты упорядочены, в зависимости от времени, в котором это имеет значение.

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

  • Кортежи просты в написании: в общем, вам нужны только запятые между Предметы. Иногда, чтобы избежать двусмысленности или изменить приоритет вычисления, вам также нужны круглые скобки (скобки для американцев). Кроме клауз, это один из тех случаев.

  • Фрозенца и комплекты исключены по историческим причинам: они не существуют до Python 2.3. Кроме того, что бы вы предпочли написать?

    ( "abc", "def" ) frozenset ([abc "," def "])

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

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

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

Оба списка и кортежи - это отдельные вещи сами по себе. Оба списка и Кортежи представляют собой контейнеры:

Список - это одна вещь, которая содержит другие вещи.

Кортеж - это одна вещь, которая содержит другие вещи.