Raise X or Raise X()?

Steven D'Aprano steve+comp.lang.python at pearwood.info
Mon Mar 12 16:08:49 CET 2012


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.

Normally the exception is instantiated just before you raise it. Of 
course, that's not compulsory. You can pre-load it if you want:

instance = ValueError("spam")
time.sleep(60)
raise instance

but generally we say 

raise ValueError("spam")

and be done with it.


> 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:

>>> 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)

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.


> If the instantiation of the exception has side
> effects, or if it fails for some reason (e.g. bad or missing arguments),
> the time of instantiation may make a difference.

I expect this is only relevant if you pre-instantiate the exception ahead 
of time.

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.


> 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*. Or at least known side-effects, such as 
import or list.append.


> 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.


> But since it's hard
> to tell if that will happen or not, it would be a rather misguided micro
> optimisation for most Python code to base your decision on this, at
> least without prior benchmarking (with a real work-load etc.).
> 
> In general, I tend to raise exception types only for very safe and
> common built-in exceptions, such as StopIteration, but whenever they
> take an argument, I raise the instance instead. For user provided
> exceptions, there is no real excuse for raising the type.

My excuses are:

* sometimes I don't need an error message, so why give one?
* and it makes no difference whether I instantiate the exception or let 
raise do it for me



> 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




-- 
Steven



More information about the Python-list mailing list