Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I gracefully include Python 3.3 from None exception syntax in a Python 3.2 program?

I'm trying to re-raise an exception to give the user better information about the actual error. Python 3.3 includes PEP 409. It adds the raise NewException from None syntax to suppress the context of the original exception.

However, I am targeting Python 3.2. The Python script will parse, but at runtime if it encounters the from None syntax it will produce TypeError: exception causes must derive from BaseException. For example:

try:
    regex_c = re.compile('^{}$'.format(regex)) 
except re.error as e:
    e_msg = 'Regular expression error in "{}"'.format(regex)
    e_reraise = Exception(e_msg)
    # Makes use of the new Python 3.3 exception syntax [from None]
    # to suppress the context of the original exception
    # Causes an additional TypeError exception in Python 3.2
    raise e_reraise from None

Encapsulating raise e_reraise from None in a try just produces an even larger exception stacktrace. A version check doesn't work either, since my python3.3 on Xubuntu 12.10 pulls modules from /usr/lib/python3/dist-packages/* which was is setup for python3.2 modules. (You get a convenient Error in sys.excepthook: which creates a massive traceback.)

Is there a way to use the PEP 409 feature when running in Python 3.3, while silently ignoring it in Python 3.2?

like image 591
dpyro Avatar asked Feb 17 '23 09:02

dpyro


2 Answers

You can set exc.__cause__ = None to suppress the context printing in Python 3.3:

except re.error as e:
    e_msg = 'Regular expression error in "{}"'.format(regex)
    e_reraise = Exception(e_msg)
    e_reraise.__cause__ = None  # 'raise e_reraise from None'
    raise e_reraise    

In Python 3.3, when you use raise exc from cause what really happens is:

exc.__cause__ = cause
raise exc

and setting exc.__cause__ in turn implicitly sets exc.__suppress_context__ = True. See PEP 415, which details how raise exc from None is handled.

When you set exc.__cause__ = None in Python 3.2, nothing changes:

$ python3.2
Python 3.2.3 (default, Apr 13 2012, 13:31:19) 
[GCC 4.2.1 Compatible Apple Clang 3.0 (tags/Apple/clang-211.12)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> try:
...     raise ValueError()
... except:
...     exc = TypeError()
...     exc.__cause__ = None
...     raise exc
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ValueError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 6, in <module>
TypeError

But in Python 3.3, the context is suppressed instead:

$ python3.3
Python 3.3.0 (default, Sep 29 2012, 08:16:08) 
[GCC 4.2.1 Compatible Apple Clang 3.1 (tags/Apple/clang-318.0.58)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> try:
...     raise ValueError()
... except:
...     exc = TypeError()
...     exc.__cause__ = None
...     raise exc
... 
Traceback (most recent call last):
  File "<stdin>", line 6, in <module>
TypeError

just as if you had used raise exc from None:

>>> try:
...     raise ValueError()
... except:
...     raise TypeError() from None
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
TypeError
like image 133
Martijn Pieters Avatar answered Feb 20 '23 09:02

Martijn Pieters


The PEP you linked provides the solution:

  • raise NewException() from None

Follows existing syntax of explicitly declaring the originating exception

  • exc = NewException(); exc.__context__ = None; raise exc

Very verbose way of the previous method

So, you simply have to avoid the new syntax and use the verbose equivalent.

If you don't want to see the assignments you can put the code into a function:

def suppress_context(exc):
    exc.__context__ = None
    return exc

And then do:

raise suppress_context(TheErrorClass())

Edit: as pointed out by Martijn PEP 415 changed this behaviour:

To summarize, raise exc from cause will be equivalent to:

exc.__cause__ = cause
raise exc

Thus, instead of setting __context__ to None you should set __cause__ to None.

If you really want to use the new syntax, then the only way to do this is to replace sys.excepthook with something that parses the traceback output and removes the the parts that you don't want. But in this case you also must do this:

try:
    raise error from None
except TypeError:
    raise error

Then the excepthook should search the traceback and if it should remove the parts related to the raise error from None line. Not a simple task and you end up with more code than the other solution.

like image 32
Bakuriu Avatar answered Feb 20 '23 10:02

Bakuriu