为什么Exception将__str__代理到args?

wim*_*wim 5 python exception python-internals

为什么打印异常实例会打印值exc.args而不是exc直接表示?文档称之为便利,但实际上这实际上是一种不便.

无法区分*args和元组之间的区别:

>>> print(Exception(123, 456))
(123, 456)
>>> print(Exception((123, 456)))
(123, 456)
Run Code Online (Sandbox Code Playgroud)

无法可靠辨别类型:

>>> print(Exception('123'))
123
>>> print(Exception(123))
123
Run Code Online (Sandbox Code Playgroud)

和可爱的"隐形"例外:

>>> print(Exception())

>>> 
Run Code Online (Sandbox Code Playgroud)

您将继承哪个,除非您明确要求不:

>>> class MyError(Exception):
...     """an error in MyLibrary"""
...     
>>> print(MyError())

>>> 
Run Code Online (Sandbox Code Playgroud)

如果您忘记专门记录错误实例,这可能是一个真正的问题repr- 日志文件中的默认字符串表示具有不可逆转的丢失信息.

这种奇怪的实现的理由是Exception.__str__什么?据推测,如果用户想要打印,exc.args那么他们应该只打印exc.args

Ant*_*ala 10

BaseException.__str__ 可能已经与Python 3以向后不兼容的方式修复,至少包括异常的类型,但也许没有人注意到它是一个应该修复的东西.

目前的实施可以追溯到PEP 0352,它提供了理由:

args出于向后兼容性的原因,对于可能传递的内容没有限制.但实际上,只应使用单个字符串参数.这使得异常的字符串表示形式成为关于人类可读异常的有用消息; 这就是为什么__str__方法特殊情况下长度为1的args值.包括程序信息(例如,错误代码编号)应该作为子类中的单独属性存储.

当然,在很多情况下,Python本身就打破了有用的人类可读消息的原则 - 例如,a的字符串化KeyError是未找到的密钥,这会导致调试消息,如

An error occurred: 42
Run Code Online (Sandbox Code Playgroud)

原因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
Run Code Online (Sandbox Code Playgroud)

Python保留了向后兼容性1.0到2.7,因此你可以在Python 2.7中运行大多数Python 1.0程序(就像你从未想象的那样):

>>> raise ValueError, 'x must be positive'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: x must be positive
Run Code Online (Sandbox Code Playgroud)

同样,在Python的1.0,你会赶上ValueError

>>> try:
...     raise ValueError, 'foo'
... except ValueError, e:
...     print 'Got ValueError', e
Run Code Online (Sandbox Code Playgroud)

在Python 2.7中没有改变.

但是内部工作方式的机制发生了变化:在Python 1.0.1中,ValueError是一个有价值的字符串 ......'ValueError'

>>> ValueError, type(ValueError)
('ValueError', <type 'string'>)
Run Code Online (Sandbox Code Playgroud)

根本没有异常类,你只能raise使用字符串作为鉴别器的单个参数或元组:

>>> class MyCustomException: 
...     pass
...   
>>> raise MyCustomException, 'my custom exception'
Traceback (innermost last):
  File "<stdin>", line 1
TypeError: exceptions must be strings
Run Code Online (Sandbox Code Playgroud)

也可以将元组作为参数:

>>> raise ValueError, ('invalid value for x', 42)
Traceback (innermost last):
  File "<stdin>", line 1
ValueError: ('invalid value for x', 42)
Run Code Online (Sandbox Code Playgroud)

如果你在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'>
Run Code Online (Sandbox Code Playgroud)

一个元组!

我们来试试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'>
Run Code Online (Sandbox Code Playgroud)

除了值的类型外,输出看起来相同; 这是一个tuple之前和现在的异常......不仅Exception委托__str__args成员,而且它还支持像元组那样的索引 - 以及解包,迭代等等:

Python 2.7

>>> a, b, c = ValueError(1, 2, 3)
>>> print a, b, c
1 2 3
Run Code Online (Sandbox Code Playgroud)

所有这些黑客都是为了保持向后兼容性.

Python 2.7行为来自PEP 0352BaseException中引入的类; 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__不再需要委托给args,并且实际上可以别名,BaseException.__repr__它提供了更好,更明确的表示:

>>> BaseException.__str__(ValueError('foo', 'bar', 'baz'))
"('foo', 'bar', 'baz')"
>>> BaseException.__repr__(ValueError('foo', 'bar', 'baz'))
"ValueError('foo', 'bar', 'baz')"
Run Code Online (Sandbox Code Playgroud)

但没有人考虑过它.


PS repr异常是有用的 - 下次尝试使用!r格式打印您的异常:

print(f'Oops, I got a {e!r}')
Run Code Online (Sandbox Code Playgroud)

结果

ZeroDivisionError('division by zero',)
Run Code Online (Sandbox Code Playgroud)

正在输出.