What are the scoping rules for exception handling? [duplicate] - python

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

Related

Raising exception with two arguments

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.

Python Exception instances can be indexed directly [duplicate]

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.

Get ALL exceptions that can result from int(some_variable)

I do not want to do:
try:
pid = int(a_variable)
except StandardError as e:
pass
So, the question is what is the method of figuring out all of the possible exceptions can result in int(a_variable). Obviously, TypeError and ValueError can happen, but how do I work out what else can result?
You can pick the method that suits you from traceback module and combine it with a logger object (see logging module):
import traceback
import sys
import logging
try:
pid = int(sys.argv[1])
except Exception as e:
logging.error(traceback.print_exc())
Execution examples:
You can catch anIndexError exception with python begueradj.py
begueradj#begueradj:~/Desktop$ python begueradj.py
Traceback (most recent call last):
File "tester.py", line 6, in <module>
pid = int(sys.argv[1])
IndexError: list index out of range
You can catch a ValueError exception with python begueradj.py +:
begueradj#begueradj:~/Desktop$ python begueradj.py +
Traceback (most recent call last):
File "tester.py", line 6, in <module>
pid = int(sys.argv[1])
ValueError: invalid literal for int() with base 10: '+'
I only see two possible exceptions that can be generated by your code:
pid = int(sys.argv[1])
ValueError if the string in sys.argv[1] is no valid integer representation (like "one" or "1.5")
IndexError if the sys.argv list does not contain at least two elements, because the script was executed without command-line arguments.
There's no chance (assuming you don't override the builtins) that your code can raise a TypeError, because sys.argv is always a list of strings. Period.
You could eventually also even get a NameError if you forgot the import sys in your code above this line as well, but that's no runtime error but a programming error.
What could happen is that while your command is running the interpreter raises an unrelated exception like KeyboardInterrupt because the user pressed Ctrl+C in that exact millisecond - but you usually don't want to catch those locally.
As you're saying in your comment that the code line in your question was just an example, if we generalize this to int(x) (with x being a literal or a variable, but nothing that is able to throw exceptions itself, like method calls or operations), you can additionally get those exceptions:
TypeError if the argument is neither a numeric type (int, long, float, complex, bool), nor a string-like type (str, bytes, bytearray) nor any other object having a __int__ method or (since 3.4) a __index__ method.
NameError if you use a variable name as input which has not been defined yet in this context. However this is very unlikely unless you use eval or exec and let the user input a variable name (discouraged!). As this is a pure programming error and no runtime error and your IDE will highlight the use of undeclared variable names, it's usually not useful to check for this error.

Python Try/Except firing TypeError despite Except clause

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!

Python: Exception raised even when caught in try/except clause [duplicate]

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.

Categories