为什么异常代理 __str__ 到 args 上?
Why does Exception proxy __str__ onto the args?
为什么打印异常实例打印的是exc.args
的值,而不是直接表示exc
?文档称其为 convenience,但在实践中实际上是 不便 。
无法区分 *args 和元组:
>>> print(Exception(123, 456))
(123, 456)
>>> print(Exception((123, 456)))
(123, 456)
无法可靠地辨别类型:
>>> print(Exception('123'))
123
>>> print(Exception(123))
123
还有可爱的"invisible"例外:
>>> print(Exception())
>>>
除非您特别要求不要继承,否则您将继承:
>>> class MyError(Exception):
... """an error in MyLibrary"""
...
>>> print(MyError())
>>>
如果您忘记专门使用 repr
记录错误实例,这可能是一个真正的问题 - 日志文件中的默认字符串表示已不可逆转地丢失信息。
Exception.__str__
如此奇怪的实现的基本原理是什么?大概如果用户想要打印 exc.args
那么他们应该只打印 exc.args
?
BaseException.__str__
可以用 Python 3 以向后不兼容的方式修复,以至少包括异常的类型,但也许没有人注意到这是应该的固定的。
当前的实现可以追溯到 PEP 0352,它提供了基本原理:
No restriction is placed upon what may be passed in for args
for backwards-compatibility reasons. In practice, though, only a single string argument should be used. This keeps the string representation of the exception to be a useful message about the exception that is human-readable; this is why the __str__
method special-cases on length-1 args
value. Including programmatic information (e.g., an error code number) should be stored as a separate attribute in a subclass.
当然 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
是一个 string 值... 'ValueError'
>>> ValueError, type(ValueError)
('ValueError', <type 'string'>)
根本没有例外 class,你只能 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 中捕获这个 "exception",你在 e
中得到的是:
>>> try:
... raise ValueError, ('invalid value for x', 42)
... except ValueError, e:
... print e, type(e)
...
('invalid value for x', 42) 42 <type 'tuple'>
一个元组!
让我们试试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
class PEP 0352; PEP 0352 最初是在 Python 2.5.
中实现的
在 Python 3 中,删除了旧语法 - 您无法使用 raise discriminator, (arg, um, ents)
引发异常; except
只能使用 Exception as e
语法。
PEP 0352 讨论了放弃对 BaseException
:
的多个参数的支持
It was decided that it would be better to deprecate the message
attribute in Python 2.6 (and remove it in Python 2.7 and Python 3.0) and consider a more long-term transition strategy in Python 3.0 to remove multiple-argument support in BaseException
in preference of accepting only a single argument. Thus the introduction of message and the original deprecation of args
has been retracted.
似乎对 args
的弃用被遗忘了,因为它在 Python 3.7 中仍然存在,并且是 唯一 访问给定参数的方式许多内置异常。同样,__str__
不再需要委托给 args,并且实际上可以为 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',)
输出中
为什么打印异常实例打印的是exc.args
的值,而不是直接表示exc
?文档称其为 convenience,但在实践中实际上是 不便 。
无法区分 *args 和元组:
>>> print(Exception(123, 456))
(123, 456)
>>> print(Exception((123, 456)))
(123, 456)
无法可靠地辨别类型:
>>> print(Exception('123'))
123
>>> print(Exception(123))
123
还有可爱的"invisible"例外:
>>> print(Exception())
>>>
除非您特别要求不要继承,否则您将继承:
>>> class MyError(Exception):
... """an error in MyLibrary"""
...
>>> print(MyError())
>>>
如果您忘记专门使用 repr
记录错误实例,这可能是一个真正的问题 - 日志文件中的默认字符串表示已不可逆转地丢失信息。
Exception.__str__
如此奇怪的实现的基本原理是什么?大概如果用户想要打印 exc.args
那么他们应该只打印 exc.args
?
BaseException.__str__
可以用 Python 3 以向后不兼容的方式修复,以至少包括异常的类型,但也许没有人注意到这是应该的固定的。
当前的实现可以追溯到 PEP 0352,它提供了基本原理:
No restriction is placed upon what may be passed in for
args
for backwards-compatibility reasons. In practice, though, only a single string argument should be used. This keeps the string representation of the exception to be a useful message about the exception that is human-readable; this is why the__str__
method special-cases on length-1args
value. Including programmatic information (e.g., an error code number) should be stored as a separate attribute in a subclass.
当然 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
是一个 string 值... 'ValueError'
>>> ValueError, type(ValueError)
('ValueError', <type 'string'>)
根本没有例外 class,你只能 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 中捕获这个 "exception",你在 e
中得到的是:
>>> try:
... raise ValueError, ('invalid value for x', 42)
... except ValueError, e:
... print e, type(e)
...
('invalid value for x', 42) 42 <type 'tuple'>
一个元组!
让我们试试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
class PEP 0352; PEP 0352 最初是在 Python 2.5.
在 Python 3 中,删除了旧语法 - 您无法使用 raise discriminator, (arg, um, ents)
引发异常; except
只能使用 Exception as e
语法。
PEP 0352 讨论了放弃对 BaseException
:
It was decided that it would be better to deprecate the
message
attribute in Python 2.6 (and remove it in Python 2.7 and Python 3.0) and consider a more long-term transition strategy in Python 3.0 to remove multiple-argument support inBaseException
in preference of accepting only a single argument. Thus the introduction of message and the original deprecation ofargs
has been retracted.
似乎对 args
的弃用被遗忘了,因为它在 Python 3.7 中仍然存在,并且是 唯一 访问给定参数的方式许多内置异常。同样,__str__
不再需要委托给 args,并且实际上可以为 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',)
输出中