BaseException.__str__
мог быть исправлен в несовместимом с Python 3 варианте, чтобы включить хотя бы тип исключения, но, возможно, никто не заметил, что это нужно исправить.
Текущая реализация восходит к PEP 0352, который обеспечивает обоснование:
Нет ограничений на то, что может быть передано для args
по причинам обратной совместимости. На практике, однако, должен использоваться только один строковый аргумент. Это сохраняет строковое представление исключения как полезное сообщение об исключении, которое может быть прочитано человеком; Вот почему особые случаи метода __str__
для значения length-1 args
. Включая программную информацию (например, номер кода ошибки) следует хранить как отдельный атрибут в подклассе.
Конечно, сам Python во многих случаях нарушает этот принцип полезных для человека сообщений - например, строка не найдена KeyError
- это ключ, который не был найден, что приводит к сообщениям отладки, таким как
An error occurred: 42
Причина, по которой str(e)
по сути str(e.args)
или str(e.args[0])
, изначально была обратно совместима с Python 1.0. В Python 1.0 синтаксис для вызова исключения, такого как ValueError
, был бы:
>>> raise ValueError, 'x must be positive'
Traceback (innermost last):
File "<stdin>", line 1
ValueError: x must be positive
Python сохранил обратную совместимость с 1.0 до 2.7, так что вы можете запускать большинство программ на Python 1.0 без изменений в Python 2.7 (как никогда раньше):
>>> raise ValueError, 'x must be positive'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: x must be positive
Аналогично, в Python 1.0 вы бы поймали ValueError
с
>>> try:
... raise ValueError, 'foo'
... except ValueError, e:
... print 'Got ValueError', e
, который работал без изменений в Python 2.7.
Но механизм того, как это работает внутренне, изменился: в Python 1.0.1 ValueError
была строкой со значением ... 'ValueError'
>>> ValueError, type(ValueError)
('ValueError', <type 'string'>)
Классов исключений не было вообще, и вы могли raise
иметь только один аргумент или кортеж со строкой в качестве дискриминатора:
>>> class MyCustomException:
... pass
...
>>> raise MyCustomException, 'my custom exception'
Traceback (innermost last):
File "<stdin>", line 1
TypeError: exceptions must be strings
Можно также привести кортеж в качестве аргумента:
>>> raise ValueError, ('invalid value for x', 42)
Traceback (innermost last):
File "<stdin>", line 1
ValueError: ('invalid value for x', 42)
И если вы поймаете это «исключение» в Python 1.0 , то в e
вы получите:
>>> try:
... raise ValueError, ('invalid value for x', 42)
... except ValueError, e:
... print e, type(e)
...
('invalid value for x', 42) 42 <type 'tuple'>
A кортеж !
Давайте попробуем код в Python 2.7 :
>>> try:
... raise ValueError, ('invalid value for x', 42)
... except ValueError, e:
... print e, e[1], type(e)
...
('invalid value for x', 42) 42 <type 'exceptions.ValueError'>
Вывод выглядит идентично, за исключением типа значения; который был tuple
до и теперь исключением ... Не только делегат Exception
__str__
члену args
, но также поддерживает индексацию, как это делает кортеж - и распаковку, итерацию и так далее:
Python 2.7
>>> a, b, c = ValueError(1, 2, 3)
>>> print a, b, c
1 2 3
Все эти хаки с целью обеспечения обратной совместимости.
Поведение Python 2.7 происходит от класса BaseException
, который был представлен в PEP 0352 ; PEP 0352 изначально был реализован в Python 2.5.
В Python 3 старый синтаксис был удален - вы не можете вызывать исключения с помощью raise discriminator, (arg, um, ents)
; и except
может использовать только синтаксис Exception as e
.
В PEP 0352 обсуждается вопрос о снижении поддержки нескольких аргументов до BaseException
:
Было решено, что было бы лучше отказаться от атрибута message
в Python 2.6 (и удалить его в Python 2.7 и Python 3.0) и рассмотреть более долгосрочную стратегию перехода в Python 3.0 для устранения поддержки нескольких аргументов в BaseException
в предпочтении принятия только одного аргумента. Таким образом, введение сообщения и первоначальное осуждение args
были отменены.
По-видимому, это устаревание args
было забыто, так как оно все еще существует в Python 3.7 и является единственным способом доступа к аргументам, данным для многих встроенных исключений. Точно так же __str__
больше не нужно делегировать аргументам, и он может фактически псевдоним BaseException.__repr__
, который дает более хорошее, однозначное представление:
>>> BaseException.__str__(ValueError('foo', 'bar', 'baz'))
"('foo', 'bar', 'baz')"
>>> BaseException.__repr__(ValueError('foo', 'bar', 'baz'))
"ValueError('foo', 'bar', 'baz')"
но никто не учел это.
P.S. repr
исключения полезно - в следующий раз попробуйте распечатать исключение в формате !r
:
print(f'Oops, I got a {e!r}')
, что приводит к
ZeroDivisionError('division by zero',)
выводится.