Raise X or Raise X()?

Stefan Behnel stefan_ml at behnel.de
Mon Mar 12 17:08:09 CET 2012


Steven D'Aprano, 12.03.2012 16:08:
> On Mon, 12 Mar 2012 14:52:49 +0100, Stefan Behnel wrote:
>>> "raise X" is a special case of the 3-args raise. Effectively it just
>>> raises an instance of X which is constructed with an empty argument
>>> list. Therefore, "raise X()" is equivalent, as far as I know.
>>
>> Not completely, although that may be considered an implementation
>> detail.
>>
>> When you raise an exception instance, it gets instantiated before being
>> raised (by your own code). So you control exactly how the exception
>> instance comes to life.
>
>> When you raise the class instead of the instance, it may or may not get
>> instantiated, depending on how it is being caught or discarded (and by
>> whom, e.g. internally in the interpreter). In most cases, it will be
>> instantiated, but only when someone catches it, i.e. at a later time,
>> after raising it.
> 
> I don't think that is correct. Either the exception gets raised, or it 
> doesn't. If it doesn't get raised, then no instance is instantiated 
> (unless you did it yourself, as in the example above). But if it does get 
> raised, then regardless of which form you use (the class or the 
> instance), the exception instance *will* be instantiated. If you don't do 
> it yourself, the raise statement will do it.
> 
> Using Python 3.2:

Note that the exception handling was seriously reworked in Python 3, so
there are substantial differences to Python 2. Although maybe not so many
at the Python code level.


> >>> class TestException(Exception):
> ....     def __init__(self, *args):
> ....             print("initialising exception")
> ....             super().__init__(*args)
> .... 
> >>> try:
> ....     raise TestException
> .... except:
> ....     pass
> .... 
> initialising exception
> 
> Whether you catch the exception or not, it still gets instantiated. Try 
> it and see, and if you can find some way to actually raise the exception 
> without instantiating the instance, I would love to see it.
> 
> Implementation-wise, at least for Python 3.2, what seems to happen as 
> best as I can tell from reading ceval.c, is that the opcode for raise 
> checks the argument. If it is already an instance, it uses that; if it is 
> not, it instantiates it immediately.
> 
> (see function do_raise in ceval.c)

Right, it normalises the exception immediately, also in older Python
versions. That's different at the C layer, but it looks like the
interpreter does the right (i.e. only safe) thing for Python code. Good to
know - and thanks for clearing this up.


> So, technically, there may be a minuscule timing difference depending on 
> whether you instantiate the exception in Python code or in C code, but I 
> don't believe that this meaningful.

The difference can be substantial in C code, especially for something like
StopIteration if the instantiation can be avoided (in some cases even
raising the exception is avoided completely!).

However, given that Python code apparently can't raise types without
instantiating them, I agree that it's not worth looking for a difference at
that level.


> I suppose it is conceivable that, in a particularly odd corner case or 
> two (perhaps using exceptions with side effects and/or threads or 
> something) the nanosecond difference between raise X() and raise X might 
> make a difference. But I'm having difficulty seeing that this is 
> plausible. I think you will have to show me an example to prove it.
> 
> Certainly, without threads, I don't think there is any difference.

Even with threads it will be hard to get at this difference due to the GIL.
I can't see anything in do_raise() that would allow foreign code to run
between the evaluation of the "raise" opcode and starting to instantiate
the exception.


>> It's obviously bad
>> design to use side effects here, but it's equally bad design to silently
>> rely on it being side effect free.
> 
> I don't understand what you are trying to say here. We rely on code being 
> side-effect free *all the time*.

That's fine for builtin stuff, functions etc. But for user provided
exceptions, why should you make your code dependent on a side effect free
instantiation just to be able to raise them without parentheses? Sounds
like too little a gain to me.

Besides, this is getting way hypothetical.


>> Note that if the exception happens to never get instantiated, you will
>> safe a tiny bit of time for the overall propagation.
> 
> I don't believe that is true. Looking at the C code, the exception 
> appears to always be instantiated once you call raise.

My fault, I was thinking at the C level again.


>> BTW, StopIteration takes an optional argument in Python 3.3, 
> 
> The time machine strikes again. StopIteration takes an optional argument 
> going back to at least 2.6.
> 
> steve at runes:~$ python2.6
> Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40) 
> [GCC 4.4.5] on linux2
> Type "help", "copyright", "credits" or "license" for more information.
> >>> raise StopIteration("out of fuel")
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
> StopIteration: out of fuel

Right, I think it's even at least 2.4 and likely much older than that. What
I meant to say was that StopIteration has a "value" (an actual property) in
Python 3.3, which represents its first argument. So that argument actually
has specific semantics (see PEP 380), instead of just "being there".

Stefan




More information about the Python-list mailing list