views:

439

answers:

3

I have been bitten by something unexpected recently. I wanted to make something like that:

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

Well, It was working fine until I passed an object inheriting from Exception which was supposed to be added.

Unfortunetly, an Exception is iterable. The following code does not raise any TypeError:

for x in Exception() :
    print 1

Does anybody know why?

+3  A: 

NOT VALID. Check Brian anwser.

Ok, I just got it :

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

Don't bother ;-)

Anyway, it's good to know.

EDIT : looking to the comments, I feel like adding some explanations.

An exception contains a message you passed to during instantiation :

raise Exception("test") 

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

It's fair to say that the message is what defines the Exception the best, so str() returns it :

print Exception("test") 
test

Now, it happens that Exceptions are implicitly converted to string when used in something else than an Exception context.

So when I do :

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

I am iterating over the string "test".

And when I do :

for x in Exception() :
    print x

I do iterate over an empty string. Tricky. Because when it comes to my issue :

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

This won't raise anything since ExceptionLikeObject is considered as a string.

Well now, we know the HOW, but I still not the WHY. Maybe the built-in Exception inherit from the built-in String ? Because as far as I know :

  • adding str does not make any object iterable.
  • I bypassed the problem by overloding iter, making it raising TypeError !

Not a problem anymore, but still a mystery.

e-satis
Actually it's not treating it as a string, it's treating it as the argument tuple. ie list(Exception("test")) == ["test"], not ["t","e","s","t"]. Similarly list(Exception(1,2)) == [1, 2]. See my answer below for some speculation as to why this may be.
Brian
+2  A: 

Actually, I still don't quite get it. I can see that iterating an Exception gives you the original args to the exception, I'm just not sure why anyone would want that. Implicit iteration is I think one of the few gotchas in Python that still trips me up.

Ned Batchelder
It's not implicit iteration that causes this, it's implicit casting. The Exception gets cast to a string automatically, which is iterable.
Jason Baker
Voted up cause I think it's unfair to be down voted because you don't understandand something. I'll add more explaination in my answer.
e-satis
@Jason: No - the exception is actually acting as a list of parameters (it implements __getitem__). ie list(ex) is equivalent to list(ex.args) I find it odd too - I suspect it is acting like this for backward compatability reasons with old string + tuple style exceptions.
Brian
You're correct. My mistake. I've undid my downvote. :-)
Jason Baker
+8  A: 

Note that what is happening is not related to any kind of implicit string conversion etc, but because the Exception class implements __getitem__(), and uses it to return the values in the args tuple (ex.args). You can see this by the fact that you get the whole string as your first and only item in the iteration, rather than the character-by-character result you'd get if you iterate over the string.

This surprised me too, but thinking about it, I'm guessing it is for backwards compatability reasons. Python used to (pre-1.5) lack the current class hierarchy of exceptions. Instead, strings were thrown, with (usually) a tuple argument for any details that should be passed to the handling block. ie:

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

It looks like this behavior was put in to avoid breaking pre-1.5 code expecting a tuple of arguments, rather than a non-iterable exception object. There are a couple of examples of this with IOError in the Fatal Breakage section of the above link

String exceptions have been depecated for a while, and are going away in Python 3. I've now checked how Python 3 handles exception objects, and it looks like they are no longer iterable there:

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

[Edit] Checked python3's behaviour

Brian
Brillant ! Thx for the explanation. I'l like to accept the answer but it seems like we need some more vote up before I am allowed to.
e-satis