问题 为什么异常可迭代?


我最近被一些意想不到的东西咬了。我想做出类似的东西:

try :
     thing.merge(iterable) # this is an iterable so I add it to the list
except TypeError :
     thing.append(iterable) # this is not iterable, so I add it

好吧,它工作正常,直到我传递了一个继承自Exception的对象,该对象应该被添加。

不幸的是,异常是可迭代的。以下代码不会引发任何问题 TypeError

for x in Exception() :
    print 1

有人知道为什么吗?


1054
2017-11-03 09:46


起源



答案:


请注意,正在发生的事情与任何类型的隐式字符串转换等无关,但是因为Exception类实现了__getitem __(),并使用它来返回args元组中的值(ex.args)。您可以通过以下事实看到这一点:您将整个字符串作为迭代中的第一个也是唯一一个项目,而不是迭代字符串时获得的逐个字符结果。

这也令我感到惊讶,但考虑到这一点,我猜它是出于向后兼容的原因。 Python习惯了(预1.5)缺少当前的异常类层次结构。相反,抛出字符串,(通常)一个元组参数,用于应传递给处理块的任何细节。即:

try:
    raise "something failed", (42, "some other details")
except "something failed", args:
    errCode, msg = args
    print "something failed.  error code %d: %s" % (errCode, msg)

看起来这种行为是为了避免破坏1.5之前的代码,期望一个参数元组,而不是一个不可迭代的异常对象。在上面的致命破坏部分中有几个这样的例子与IOError 链接

字符串异常已经被推迟了一段时间,并且在Python 3中消失了。我现在已经检查了Python 3如何处理异常对象,看起来它们在那里不再可迭代:

>>> list(Exception("test"))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Exception' object is not iterable

[编辑]检查python3的行为


11
2017-11-03 15:08



Brillant!谢谢你的解释。我想接受答案,但似乎在我被允许之前我们需要更多的投票。 - e-satis


答案:


请注意,正在发生的事情与任何类型的隐式字符串转换等无关,但是因为Exception类实现了__getitem __(),并使用它来返回args元组中的值(ex.args)。您可以通过以下事实看到这一点:您将整个字符串作为迭代中的第一个也是唯一一个项目,而不是迭代字符串时获得的逐个字符结果。

这也令我感到惊讶,但考虑到这一点,我猜它是出于向后兼容的原因。 Python习惯了(预1.5)缺少当前的异常类层次结构。相反,抛出字符串,(通常)一个元组参数,用于应传递给处理块的任何细节。即:

try:
    raise "something failed", (42, "some other details")
except "something failed", args:
    errCode, msg = args
    print "something failed.  error code %d: %s" % (errCode, msg)

看起来这种行为是为了避免破坏1.5之前的代码,期望一个参数元组,而不是一个不可迭代的异常对象。在上面的致命破坏部分中有几个这样的例子与IOError 链接

字符串异常已经被推迟了一段时间,并且在Python 3中消失了。我现在已经检查了Python 3如何处理异常对象,看起来它们在那里不再可迭代:

>>> list(Exception("test"))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Exception' object is not iterable

[编辑]检查python3的行为


11
2017-11-03 15:08



Brillant!谢谢你的解释。我想接受答案,但似乎在我被允许之前我们需要更多的投票。 - e-satis


无效。检查Brian anwser。

好的,我刚刚得到它:

for x in Exception("test") :
    print x
   ....:     
   ....:     
test

不要打扰;-)

无论如何,这很好知道。

编辑:期待评论,我想补充一些解释。

异常包含在实例化期间传递给的消息:

raise Exception("test") 

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Exception: test

可以说,消息是将Exception定义为最佳的消息,因此str()返回它:

print Exception("test") 
test

现在,当Exceptions用于除Exception上下文之外的其他内容时,它会被隐式转换为字符串。

所以当我这样做时:

for x in Exception("test") :
    print x

我正在迭代字符串“test”。

当我这样做时:

for x in Exception() :
    print x

我迭代一个空字符串。棘手。因为谈到我的问题:

try :
    thing.merge(ExceptionLikeObject)
except TypeError :
    ...

由于ExceptionLikeObject被视为字符串,因此不会引发任何内容。

那么现在,我们知道如何,但我仍然不是为什么。也许内置的Exception继承自内置的String?因为据我所知:

  • 加入 海峡 不会使任何对象可迭代。
  • 我通过过度使用来绕过这个问题 ITER,使它提出TypeError!

不再是问题,但仍然是一个谜。


3
2017-11-03 09:47



实际上它并没有将它视为字符串,而是将其视为参数元组。即list(Exception(“test”))== [“test”],而不是[“t”,“e”,“s”,“t”]。类似地列表(例外(1,2))== [1,2]。请参阅下面的答案,了解为何可能出现这种情况。 - Brian


实际上,我还是不太明白。我可以看到迭代一个Exception为你提供了异常的原始args,我只是不确定为什么有人会想要这个。隐式迭代是我认为Python中为数不多的陷阱之一仍然让我兴奋不已。


2
2017-11-03 12:48



它不是隐式迭代导致它,它是隐式转换。异常会自动转换为字符串,这是可迭代的。 - Jason Baker
投票原因我认为因为你不理解某事而被投票放弃是不公平的。我会在答案中添加更多解释。 - e-satis
@Jason:不 - 例外实际上是一个参数列表(它实现了 的GetItem)。即list(ex)等同于list(ex.args)我发现它也很奇怪 - 我怀疑它的行为是这样的,因为旧的字符串+元组样式异常具有向后兼容性的原因。 - Brian
你是对的。我的错。我解除了我的downvote。 :-) - Jason Baker