Greetings again StackOverflow Community,
I was reading through a library a colleague wrote and found something that I don't quite grasp what they are trying to do. But maybe this is something I am missing with respect to Python syntax.
class SampleClass:
def some_function(self) -> None:
try:
self.do_something()
except CustomException as e:
raise DifferentExceptionClass("Could not do something", e)
# The previous line is the cause of bewilderment.
def do_something(self) -> None:
raise CustomException("Tried to do something and failed.")
I've read that raise can accept arguments but this seems to raise DifferentExceptionClass exception with a tuple as the value. What is the difference between what my colleague has done here and doing something like raise DifferentExeptionClass("Could not do something. {}".format(e)) Is there a benefit to raising an exception his way?
The output to the function call to some_function() is:
test = SampleClass()
test.some_function()
Traceback (most recent call last):
File "<input>", line 4, in some_function
File "<input>", line 10, in do_something
CustomException: Tried to do something and failed.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 6, in some_function
DifferentExceptionClass: ('Could not do something', CustomException('Tried to do something and failed.',))
EDIT: The colleague is unreachable for comment. Also they wrote this library a long time ago and might not remember the "mood" they were in when they wrote this. I thought it would also make a good conversation on SO in case anyone else had seen similar implementation.
I've read that raise can accept arguments but this seems to raise DifferentExceptionClass exception with a tuple as the value.
Exceptions are in fact classes as well. Indeed somewhere you will find something like:
class DifferentExceptionClass(Exception):
def __init__(self,message,innerException):
# ...
pass
So you call a constructor. How the arguments are handled is up to the exception. It is possible that the message is formatted with the inner exception, but it is also possible that it does something totally different.
The advantage is for instance that the innerException (or other parameters) can be stored, inspected and handled accordingly. If you format the exception, the real exception data is lost: you only have a textual representation of it.
There's definitely benefit to doing this. You chain exceptions and provide that chain as information to the user rather than providing the most recent exception created.
Of course, your colleague could of done it in a better way by using syntax Python provides for this. The raise exc from raised_exc syntax is used to raise an exception after another exception has been raised:
except CustomException as e:
raise DifferentExceptionClass("Could not do something") from e
and causes e (the raised exception, CustomException here) to be stored as the __cause__ attribute of the most recent exception (DifferentExceptionClass here) if you need to peek at it.
In the case where a raise is used inside an except handler (as happens in your code snippet), the previous exception (e) is already implicitly stored as a __context__ attribute for it. So, passing it as an argument too, doesn't do anything else but also store the exception in the args tuple.
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 1 year ago.
I stumbled on an interesting scenario following this question. Consider the following simple example:
try:
1/0
error = "error"
except Exception as error:
print(error)
print(error)
The output is:
division by zero
Traceback (most recent call last):
File "main.py", line 7, in <module>
print(error)
NameError: name 'error' is not defined
What happens is of course the first assignment to error is skipped because of the exception, and the except is executed. It prints the exception and - to my understanding - assigns, in a way, the exception object to error. But then, surprisingly, the following print complains that error is not defined.
I tried to understand the scoping rule for exceptions but all I could find in the Exceptions docs was this:
The variable is bound to an exception instance
Which doesn't really explain what is the scope or life-span of this exception instance.
The most similar construct I can think of is a with statement, which doesn't behave the same way:
with open("test.txt") as file:
pass
file.read()
This code will output a maybe expected output of:
Traceback (most recent call last):
File "main.py", line 4, in <module>
file.read()
ValueError: I/O operation on closed file.
But still it is not a NameError which means that file is well-defined.
So can anyone explain what are the scoping rules for exception handling and what is happening here?
This is documented under, The try statement
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
This means the exception must be assigned to a different name to be
able to refer to it after the except clause
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?
Note that what is happening is not related to any kind of implicit string conversion etc, but because the Exception class implements __getitem__ to return the values from 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 compatibility 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, i.e:
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 deprecated for a while, and are gone in Python 3. Exception objects are no longer iterable in Python 3:
>>> list(Exception("test"))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Exception' object is not iterable
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.
I'm following along with the Python docs for try/except with the following effort:
def profile_check(self):
try:
profile = self.profile_type
return profile
except TypeError:
...
self.profile_type is a field in a Django model which doesn't exist in this case (therefore it returns as None). However, something seems to be missing, because it never moves on to the actions for except, rather it immediately throws the TypeError exception:
>>> a.profile_check()
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: 'NoneType' object is not callable
This is my first effort with try-catch, so I know it's something basic.
First off, your problem is that profile_check is set to None for some reason. That's why it's giving you that error when you call it.
As for your try/catch problem, if a variable doesn't exist, then the Python interpreter will throw a NameError at you. Try substituting TypeError for that.
I can't help you with your profile_check-is-None situation though without anymore context though. Sorry!
This question already has answers here:
How can I more easily suppress previous exceptions when I raise my own exception in response?
(3 answers)
Closed 8 years ago.
In my code I want to catch an exception when it occurs, print some information abut the exception to the screen, and then end the script once I have done so. I tried to use something equivalent to the following code, but I don't understand why I get the traceback I do.
When executing:
try:
1 / 0
except ZeroDivisionError:
print("Exception: ZeroDivisionError")
raise Exception
Console reads:
Exception: ZeroDivisionError
Traceback (most recent call last):
File "<pyshell#19>", line 2, in <module>
1 / 0
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<pyshell#19>", line 5, in <module>
raise Exception
Exception
I thought that if I catch the ZeroDivisionError, it would no longer be raised, and the only thing that would show is the raise Exception I do at the end, but both show in the console.
Why do they both show, and how do I alter the code so only the second shows, or is there a better way to achieve what I want?
The console shows the context here; when an exception is raised from an exception handler, Python attaches the active exception as the __context__ attribute and Python shows that context later on if the new exception is not being handled. If you don't want the context to be shown, you need to supply a cause instead; you can supply an empty cause with with raise ... from None:
try:
1 / 0
except ZeroDivisionError:
print("Exception: ZeroDivisionError")
raise Exception from None
Quoting the raise statement documentation:
The from clause is used for exception chaining: if given, the second expression must be another exception class or instance, which will then be attached to the raised exception as the __cause__ attribute (which is writable). If the raised exception is not handled, both exceptions will be printed[...]
A similar mechanism works implicitly if an exception is raised inside an exception handler: the previous exception is then attached as the new exception’s __context__ attribute[...]
And from the Exceptions documentation:
When raising (or re-raising) an exception in an except clause __context__ is automatically set to the last exception caught; if the new exception is not handled the traceback that is eventually displayed will include the originating exception(s) and the final exception.
When raising a new exception (rather than using a bare raise to re-raise the exception currently being handled), the implicit exception context can be supplemented with an explicit cause by using from with raise:
raise new_exc from original_exc
The expression following from must be an exception or None. It will be set as __cause__ on the raised exception. Setting __cause__ also implicitly sets the __suppress_context__ attribute to True, so that using raise new_exc from None effectively replaces the old exception with the new one for display purposes (e.g. converting KeyError to AttributeError), while leaving the old exception available in __context__ for introspection when debugging.
The default traceback display code shows these chained exceptions in addition to the traceback for the exception itself. An explicitly chained exception in __cause__ is always shown when present. An implicitly chained exception in __context__ is shown only if __cause__ is None and __suppress_context__ is false.
Python documentations states:
Exceptions should typically be derived from the Exception class,
either directly or indirectly.
the word 'typically' leaves me in an ambiguous state.
consider the code:
class good(Exception): pass
class bad(object): pass
Heaven = good()
Hell = bad()
>>> raise Heaven
Traceback (most recent call last):
File "<pyshell#163>", line 1, in <module>
raise Heaven
good
>>> raise Hell
Traceback (most recent call last):
File "<pyshell#171>", line 1, in <module>
raise Hell
TypeError: exceptions must be classes or instances, not bad
so when reading the python docs, should i replace 'typically' with ''?
what if i have a class hierarchy that has nothing to do with the Exception class, and I want to 'raise' objects belonging to the hierarchy?
I can always raise an exception with an argument:
raise Exception, Hell
This seems slightly awkward to me
What's so special about the Exception (EDIT: or BaseException) class, that only its family members can be raised?
There are other valid classes you can inherit from apart from Exception, for example BaseException.
See the documentation for the exception hierarchy.
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StandardError
etc..
In older versions of Python it was possible to throw things other than exceptions. For example in Python 2.5:
>>> raise "foo"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
foo
But you get this deprecation warning:
DeprecationWarning: raising a string exception is deprecated
In newer versions this is not allowed. Everything you raise must derive from BaseException.
"so when reading the python docs,
should i change 'typically' with ''?"
No.
Typically, you inherit from Exception. Period. That's what it says.
Sometimes, you might inherit from BaseException. That's what it doesn't say. You might extend BaseExcetion because you want to defeat except Exception handlers.
What's so special about ...
They're subclasses of BaseException. What more do you need to know? The source is readily available. You can read the source code for the raise statement to see exactly what it checks before it throws the TypeError.
http://svn.python.org/view/python/trunk/Python/ceval.c?annotate=80817
Lines 3456 to 3563.
However, all that matters from a practical stand-point is "subclasses of BaseException."
'Typically' is used because there are a few very rare types of exception that don't want to be caught by a generic Exception handler. If in doubt inherit from Exception, but there are exceptions to that rule.
Most exceptions are used to indicate some sort of error or exceptional condition that is a result of the code and data. These exceptions are all subclasses of Exception. If you want to raise your own exception it will probably fall into that category and should therefore also inherit Exception. If you want a generic exception handler, e.g. to log errors then it is perfectly reasonable to catch Exception and expect to catch any errors that way.
The other exceptions which inherit directly from BaseException are slightly different. SystemExit is raised when you call sys.exit() (or you can raise it directly). If you do have some top level code that logs errors then you probably don't want it to handle SystemExit in the same way. You used to have to include a separate handler for SystemExit just to stop the generic Exception handler catching that case.
KeyboardInterrupt does represent an unexpected condition, but it's one that is raised by external input from the user so it can happen anywhere in your code; it doesn't depend in any way on the code or data. That means even if you do want to handle it you probably want to handle it differently than the exceptions which inherit from Exception.
Things derived from the various error/exception classes can also be raised, but those are generally reserved for other things.
Sometimes you might want to raise things you do not consider an exception (but the rule). For these rare cases one can consider it nice to raise something else besides just exceptions.
In a recursive computation in which at some point an answer is found, raising it to the upwardly waiting catcher can be pretty neat (instead of returning it which means the recursion has to expect this, pass it upwards as well etc.).
But nowadays only old-style classes (for compatibility reasons I guess) and derivations from BaseException can be raised because too many people abused things like strings for exceptions.