I'm in doubt in a situation not so common but that I've seen some developers use which is the case of exception chaining.
I've read the Python documentation about them but it's not clear how Python works behind the scenes in order to silence the first exception. If you can give me an explained code example that would be great.
try: 1 / 0 except ZeroDivisionError: print("Exception: ZeroDivisionError") raise Exception from None
There's not much mystery there: "silencing" an exception inside an except is part of the way Python is done.
Always keep in mind that unlike compiled code, where an error in the program will put everything that is running in an invalid state, in Python, program execution is under the control of the Python runtime. An exception in running Python code is an object, like any other object – with its attributes and etc… what's different is that when an exception occurs (either because it happened, or because of a
raise command), the interpreter stops the Python code execution where it is, and it returns from all the functions (technically, it goes to the previous execution
frames ) until it finds an "except" block that corresponds to the exception that occurred (if the try/ except for at the same point where the exception occurred, it may not return from any function). At the point where the except is, Python binds the exception object to a local variable (the one in the
except ), and starts executing the code in the
except block normally: at that point, the program is consistent, and "under control". not in an error condition. So much so that it's perfectly legal to simply put a
pass inside an except clause, to ignore an error (if we know it's a kind of temporary error that can happen from time to time and doesn't get in the way of the program).
However, if there is a
raise from inside the
except , Python simply creates a new exception object, copies some data from the original exception object to this new one, and restarts the "backwards" task in the code until it finds a corresponding except. In the case of the
raise ...from None you are explicitly saying not to get data from the original exception – it would be the same as a
raise Exception without the
Language does this. Now why do programmers put this in their code? This is perhaps your doubt, and it is a question of the people who expressed themselves in the comments.
Let's take the example of a web application running inside a framework: the web application functions themselves, like views, are called when there is a web request that the framework directs to them – but they are not the "gateway" : the framework in general receives data about the web request, "dissects the url", and decides which view is called, fixes one or more internal objects (eg "request"), and then calls the application's view. This is all Python code, within the framework. The Framework will usually call the view for the user inside a "try:/except" block; Otherwise, any exception in the view would stop the framework's server process (ie it failed to serve a page, the whole server stops -not what you want in general – you want the server to continue serving other pages to the other users and even for the one who found the error).
If this framework try/except block catches an exception that it knows nothing about, it will generate an HTTP "500" error. : That is, it will look at your app's settings, or use the framework's default settings, and return to the browser the html that is set to when there is an 'error 500'. It turns out that the framework in general will have exceptions for it even, which your application can use to indicate that it wasn't a "500" error, but a "404" or "403" error (page not found or access denied), or any other http error (and both the framework and the your application may want to show specific pages in that case).
So, if inside your view code, you go to the database and you didn't find the object you were looking for – or you got some other error, the search can generate an
IndexError (if you make a query to the database, you expect to have a result and has zero, for example) – if you let the IndexError "raise" to the framework code, you will get a 500 error. But in the view code, you, when writing the code, know that if an "IndexError" happens at that point , is because the user, or another object was not found in the database – and, it may want to show a "404 error" in the browser –
So, it's a natural pattern something like
@view("/") def minha_view(request): try: user = get_from_users(request.user) except IndexError as error: raise HTTPError404 from error # aqui continua o código para uma requisição bem sucedida ....
(In this case, the "HTTPError404" would be an exception defined in the framework).