I know that if I want to re-raise an exception, I simple use raise without arguments in the respective except block. But given a nested expression like
try:
something()
except SomeError as e:
try:
plan_B()
except AlsoFailsError:
raise e # I'd like to raise the SomeError as if plan_B()
# didn't raise the AlsoFailsError
how can I re-raise the SomeError without breaking the stack trace? raise alone would in this case re-raise the more recent AlsoFailsError. Or how could I refactor my code to avoid this issue?
As of Python 3, the traceback is stored in the exception, so a simple raise e will do the (mostly) right thing:
try:
something()
except SomeError as e:
try:
plan_B()
except AlsoFailsError:
raise e # or raise e from None - see below
The traceback produced will include an additional notice that SomeError occurred while handling AlsoFailsError (because of raise e being inside except AlsoFailsError). This is misleading because what actually happened is the other way around - we encountered AlsoFailsError, and handled it, while trying to recover from SomeError. To obtain a traceback that doesn't include AlsoFailsError, replace raise e with raise e from None.
In Python 2 you'd store the exception type, value, and traceback in local variables and use the three-argument form of raise:
try:
something()
except SomeError:
t, v, tb = sys.exc_info()
try:
plan_B()
except AlsoFailsError:
raise t, v, tb
Even if the accepted solution is right, it's good to point to the Six library which has a Python 2+3 solution, using six.reraise.
six.reraise(exc_type, exc_value, exc_traceback=None)
Reraise an exception, possibly with a different traceback.
[...]
So, you can write:
import six
try:
something()
except SomeError:
t, v, tb = sys.exc_info()
try:
plan_B()
except AlsoFailsError:
six.reraise(t, v, tb)
As per Drew McGowen's suggestion, but taking care of a general case (where a return value s is present), here's an alternative to user4815162342's answer:
try:
s = something()
except SomeError as e:
def wrapped_plan_B():
try:
return False, plan_B()
except:
return True, None
failed, s = wrapped_plan_B()
if failed:
raise
Python 3.5+ attaches the traceback information to the error anyway, so it's no longer necessary to save it separately.
>>> def f():
... try:
... raise SyntaxError
... except Exception as e:
... err = e
... try:
... raise AttributeError
... except Exception as e1:
... raise err from None
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 9, in f
File "<stdin>", line 3, in f
SyntaxError: None
>>>
Related
This question already has an answer here:
Why do I get a `NameError` (or `UnboundLocalError`) from using a named exception after the `except` block?
(1 answer)
Closed last month.
I wrote this small snippet in python2 (2.7.18) to catch the exception in a variable and it works
>>> ex = None
>>> try:
... raise Exception("test")
... except Exception as ex:
... print(ex)
...
test
>>> ex
Exception('test',)
>>>
>>> ex2 = None
>>> try:
... raise Exception("test")
... except Exception as ex2:
... print(ex2)
... finally:
... print(ex2)
...
test
test
When I run the same in python3 (3.10.8), I get NameError
>>> ex = None
>>> try:
... raise Exception("test")
... except Exception as ex:
... print(ex)
...
test
>>> ex
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'ex' is not defined. Did you mean: 'hex'?
>>>
>>> ex2 = None
>>> try:
... raise Exception("test")
... except Exception as ex2:
... print(ex2)
... finally:
... print(ex2)
...
test
Traceback (most recent call last):
File "<stdin>", line 6, in <module>
NameError: name 'ex2' is not defined
What is the reason for this? Is the new python3 compiler doing optimisation where None assignment doesn't mean anything or is the try/except clause doing some magic?
What is the workaround for this which works in both python2 and python3?
It's not a magic really. It's documented here:
When an exception has been assigned using as target, it is cleared at the end of the except clause. This is as if:
except E as N:
foo
was translated to
except E as N:
try:
foo
finally:
del N
And the reason:
Exceptions are cleared
because with the traceback attached to them, they form a reference
cycle with the stack frame, keeping all locals in that frame alive
until the next garbage collection occurs.
You can get the traceback object with ex.__traceback__.
What is the workaround for this which works in both python2 and
python3?
You have to explicitly assign it to a different name(something other than target in as target as it's gonna get deleted):
exc_object = None
try:
raise Exception("test")
except Exception as ex:
print(ex)
exc_object = ex
print(exc_object)
Note: This is only the case with except block and has nothing to do with as assignment statement as you can see in context managers: with...as... block for example.
Is there any way to raise multiple exceptions in python? In the following example, only the first exception is raised.
l = [0]
try:
1 / 0
except ZeroDivisionError as e:
raise Exception('zero division error') from e
try:
l[1]
except IndexError as e:
raise Exception('Index out of range') from e
Is there any other way?
Once an exception is raised and not catched, the execution of your program will stop. Hence, only one exception can be launched in such a simple script.
If you really want your program to raise multiple exceptions, you could however create a custom Exception representing multiple exceptions and launch it inside an except block. Example:
class ZeroDivAndIndexException(Exception):
"""Exception for Zero division and Index Out Of Bounds."""
I = [0]
try:
1 / I[0]
except ZeroDivisionError as e:
try:
I[1]
# Here you may raise Exception('zero division error')
except IndexError as e2:
raise ZeroDivAndIndexException()
Here my solution a bit long but seems to work :
class CustomError(Exception):
pass
l = [0]
exeps = []
try:
1 / 0
except ZeroDivisionError as e:
exeps.append(e)
try:
l[1]
except IndexError as e:
exeps.append(e)
if len(exeps)!=0:
[print(i.args[0]) for i in exeps]
raise CustomError("Multiple errors !!")
Problem
Cannot catch a StopIteration raised from within a iterator.
Example
from typing import (
Generator,
Iterable,
List
)
import sys
import pathlib
from itertools import islice
import numpy as np
def take(n: int, iterable: Iterable) -> List:
taken = list(islice(iterable, 0, n))
if len(taken) > 0:
return taken
else:
print("Raising StopIteration")
raise StopIteration("Nothing to take") # <----- Raise StopIteration
def file_line_stream(path: str) -> Generator[str, None, None]:
if not pathlib.Path(path).is_file():
raise FileNotFoundError(f"file {path} does not exist or non file")
try:
_file = pathlib.Path(path)
with _file.open() as f:
for line in f:
yield line.rstrip()
except (IOError, FileNotFoundError) as e:
raise RuntimeError("Read file failure") from e
def get_sentences(path_to_file, num_sentences):
counter = 0
stream = file_line_stream(path_to_file)
try:
while True:
_lines = take(num_sentences, stream)
print(f"Count {counter}: yielding [{_lines[0]}]\n")
yield np.array(_lines)
counter += 1
finally:
stream.close()
def test():
path_to_input = "test_has_two_lines.txt"
generator = get_sentences(path_to_file=path_to_input, num_sentences=100)
for i in range(10):
# ----------------------------------------------------------------------
# Want to catch the StopIteration raised from within the generator.
# ----------------------------------------------------------------------
try:
next(generator)
except StopIteration as e:
print("Caught at StopIteration except")
generator.close()
except Exception as e: # <--- Raised StopIteration will get caught here
print("Caught at catch-all except: %s", sys.exc_info()[0])
generator.close()
raise e
if __name__ == '__main__':
test()
Result
Count 0: yielding [previous monthly <unk> of the major market index futures and standard & poor 's <unk> index options have produced spectacular volatility]
Raising StopIteration
Caught at catch-all except: %s <class 'RuntimeError'>
Traceback (most recent call last):
File "test_050_word2vec.py", line 39, in get_sentences
_lines = take(num_sentences, stream)
File "test_050_word2vec.py", line 19, in take
raise StopIteration("Nothing to take") # <----- Raise StopIteration
StopIteration: Nothing to take
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "test_050_word2vec.py", line 67, in <module>
test()
File "test_050_word2vec.py", line 63, in test
raise e
File "test_050_word2vec.py", line 55, in test
next(generator)
RuntimeError: generator raised StopIteration <----- Converted into RuntimeError
Cause
exception StopIteration
Changed in version 3.7: Enable PEP 479 for all code by default: a StopIteration error raised in a generator is transformed into a RuntimeError.
Reason
PEP 479 -- Change StopIteration handling inside generators
Currently, StopIteration raised accidentally inside a generator function will be interpreted as the end of the iteration by the loop construct driving the generator.
Don't raise StopIteration, raise something sane like ValueError
StopIteration serves a very specific purpose (to allow a next method to indicate iteration is complete), and reusing it for other purposes will cause problems, as you've seen.
The conversion to RuntimeError here is saving you; if Python hadn't done that, the generator would have silently stopped iterating (StopIteration is swallowed silently, causing iteration to end without propagating the exception; you'd never catch it anyway).
I'm trying to log some expected errors.
Initially, I wrote my script like:
except (BadZipFile, MemoryError) as e:
logger.error(f'No: {n} - {filename} = {e}')
But I noticed in the logger, only the BadZipFile error message seemed to make it. The MemoryError logs seemed to be blank after the = sign
I thought maybe the e was only storing the BadZipFile error message since it comes first, so I tried making a tuple:
except (BadZipFile, MemoryError) as (eb, em):
logger.error(f'No: {n} - {filename} = {eb, em}')
but of course the syntax is wrong. So what's going wrong with my code initially? Why is the MemoryError log not storing?
Whichever exception occurs first will get caught in except block. So, I think in your case 1st exception raising is BadZipFile. refer to #LMKR examples.
try to raise an exception explicitly to check whether it's working or not...
>>> try:
... if True:
... raise MemoryError("MemoryError occured")
... except (BadZipFile, MemoryError) as e:
... print(e)
Execution of program will be stopped when it occurs an exception and move to the except block. So when an exception occurred it is not possible to get another in the same try block. So will always receives one log of exception.
let me explain it with a bit of code, consider we have two types of errors, IndexError and ZeroDivisionError
Case1:
>>> l = []
>>> a = 1
>>> b = 0
>>> try:
... x = l[0]
... y = a/b
... except (IndexError, ZeroDivisionError) as e:
... print(e)
...
list index out of range
Case2:
>>> try:
... x = a/b
... y = l[0]
... except (IndexError, ZeroDivisionError) as e:
... print(e)
...
division by zero
Case3:
>>> try:
... x = l[0]/b
... except (IndexError, ZeroDivisionError) as e:
... print(e)
...
list index out of range
>>>
Case1, IndexError is raised first, so except bock is called with IndexError
Case2, ZeroDivisionError raised first, so except block is called with ZeroDivisionError
Case3, even though both exceptions are in single line line of execution happens from indexing so IndexError is raised.
EDIT:
In your case BadZipFile exception raised first, so your except block except (BadZipFile, MemoryError) as e: is called with BadZipFile exception. That's why your output is empty for MemoryError.
Even if MemoryError raised first with empty arguments then it will log nothing.
>>> try:
... raise MemoryError("This is memory error")
... except MemoryError as e:
... print(e)
...
This is memory error
>>> try:
... raise MemoryError()
... except MemoryError as e:
... print(e)
...
>>>
Why can't I raise an Exception instance after I catch that Exception class? Oddly enough, I run into this error when I run the script from a function but not when ran directly in the python shell.
In [2]: def do():
...: try:
...: raise ValueError('yofoo')
...: except TypeError, ValueError:
...: raise ValueError('yo')
...:
In [3]: do()
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
<ipython-input-3-30c46b84d9a4> in <module>()
----> 1 do()
<ipython-input-2-b62158d6343b> in do()
1 def do():
2 try:
----> 3 raise ValueError('yofoo')
4 except TypeError, ValueError:
5 raise ValueError('yo')
UnboundLocalError: local variable 'ValueError' referenced before assignment
Expected error here:
In [3]: try:
...: raise ValueError("foo")
...: except ValueError:
...: raise ValueError("bar")
...:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-3-d5c83419a5ea> in <module>()
2 raise ValueError("foo")
3 except ValueError:
----> 4 raise ValueError("bar")
5
ValueError: bar
except TypeError, ValueError:
should be
except (TypeError, ValueError):
When you use except TypeError, ValueError:, you are assigning the Exception instance to the variable name ValueError.
With your current code, when Python parses the do function, it notes ValueError is a local variable because except TypeError, ValueError: assigns a value to ValueError. But when you reference it in the first try-suite, with raise ValueError('yofoo'), the local variable ValueError has no value. So you get UnboundLocalError.
From the docs:
... except (RuntimeError, TypeError, NameError):
... pass
Note that the parentheses around this tuple are required, because
except ValueError, e: was the syntax used for what is normally written
as except ValueError as e: in modern Python (described below). The old
syntax is still supported for backwards compatibility. This means
except RuntimeError, TypeError is not equivalent to except
(RuntimeError, TypeError): but to except RuntimeError as TypeError:
which is not what you want.