Diferències
Ací es mostren les diferències entre la revisió seleccionada i la versió actual de la pàgina.
| Ambdós costats versió prèvia Revisió prèvia | |||
| info:cursos:pue:python-pcpp1:m1:3.1 [05/11/2023 21:34] – suprimit - edició externa (Unknown date) 127.0.0.1 | info:cursos:pue:python-pcpp1:m1:3.1 [05/11/2023 21:34] (actual) – ↷ Page moved from info:cursos:pue:python-pcpp1:3.1 to info:cursos:pue:python-pcpp1:m1:3.1 mate | ||
|---|---|---|---|
| Línia 1: | Línia 1: | ||
| + | = 3.1 Advanced techniques of creating and serving exceptions | ||
| + | In this module, we'll talk about Python exceptions – objects that represent errors which occur during the execution of a program that disrupts the normal flow of the program' | ||
| + | Plan for the module: | ||
| + | |||
| + | * short introduction to exceptions; | ||
| + | * review of the named attributes of exception objects; | ||
| + | * introduction to chained exceptions; | ||
| + | * analysis of the traceback object of each exception. | ||
| + | |||
| + | == Exceptions - short introduction | ||
| + | When Python executes a script and encounters a situation that it cannot cope with, it: | ||
| + | |||
| + | * stops your program; | ||
| + | * creates a special kind of data, called an **exception**. Of course, this exception is an object. | ||
| + | |||
| + | Both of these activities are called **raising an exception**. We can say that Python always raises an exception (or that an exception has been raised) when it has no idea what do to with your code. | ||
| + | |||
| + | What happens next? | ||
| + | |||
| + | * the raised exception expects somebody or something to notice it and take care of it; | ||
| + | * if nothing happens to take care of the raised exception, the program will be **forcibly terminated**, | ||
| + | * otherwise, if the exception is taken care of and **handled** properly, the suspended program can be resumed and its execution can continue. | ||
| + | |||
| + | Python provides effective tools that allow you to **observe exceptions, identify them and handle them efficiently**. This is possible due to the fact that all potential exceptions have their unambiguous names, so you can categorize them and react appropriately. | ||
| + | |||
| + | You may already have met an exception error message like the following: | ||
| + | <code ; output> | ||
| + | IndexError: list index out of range | ||
| + | </ | ||
| + | |||
| + | Sooner or later, every Pythonista will write a code that raises an exception, and that is why it is so important to know how to deal with exceptions. | ||
| + | |||
| + | Python comes with 63((This number may vary across different Python versions.)) built-in exceptions, and they can be represented in the form of a tree-shaped hierarchy. The reason for this is that exceptions are inherited from BaseException, | ||
| + | |||
| + | And this approach tells you that you can also create your own specific exception classes – the only constraint is: you have to subclass BaseException or any other derived exception class. | ||
| + | |||
| + | == Exception handling | ||
| + | When you suspect that the code may raise an exception, you should use the '' | ||
| + | |||
| + | Let's look at typical try ... except statement. | ||
| + | |||
| + | <code python> | ||
| + | try: | ||
| + | print(int(' | ||
| + | except ValueError: | ||
| + | print(' | ||
| + | </ | ||
| + | |||
| + | Whenever you try to convert letter ' | ||
| + | |||
| + | == Advanced exceptions - named attributes | ||
| + | Now it’s time to dive more deeply into the interesting features of exception concepts and get familiar with the possible ways of using those concepts in your applications. | ||
| + | |||
| + | Once again, let's look at a typical try ... except statement. | ||
| + | |||
| + | <code python> | ||
| + | try: | ||
| + | print(int(' | ||
| + | except ValueError as e_variable: | ||
| + | print(e_variable.args) | ||
| + | </ | ||
| + | |||
| + | The except clause may specify a variable after the exception name. In this example it’s an '' | ||
| + | |||
| + | Some exception objects carry additional information about the exception itself. | ||
| + | |||
| + | The '' | ||
| + | * name – represents the name of the module that was attempted to be imported; | ||
| + | * path – represents the path to any file which triggered the exception, respectively. Could be None. | ||
| + | |||
| + | See the output of this snippet to analyze the attributes' | ||
| + | |||
| + | <code python> | ||
| + | try: | ||
| + | import abcdefghijk | ||
| + | |||
| + | except ImportError as e: | ||
| + | print(e.args) | ||
| + | print(e.name) | ||
| + | print(e.path) | ||
| + | </ | ||
| + | <code ; output> | ||
| + | ("No module named ' | ||
| + | abcdefghijk | ||
| + | None | ||
| + | </ | ||
| + | |||
| + | The '' | ||
| + | |||
| + | The UnicodeError has attributes that describe an encoding or decoding error. | ||
| + | |||
| + | * **encoding** – the name of the encoding that raised the error. | ||
| + | * **reason** – a string describing the specific codec error. | ||
| + | * **object** – the object the codec was attempting to encode or decode. | ||
| + | * **start** – the first index of invalid data in the object. | ||
| + | * **end** – the index after the last invalid data in the object. | ||
| + | |||
| + | See the output of this snippet to analyze the attributes' | ||
| + | <code python> | ||
| + | try: | ||
| + | b' | ||
| + | except UnicodeError as e: | ||
| + | print(e) | ||
| + | print(e.encoding) | ||
| + | print(e.reason) | ||
| + | print(e.object) | ||
| + | print(e.start) | ||
| + | print(e.end) | ||
| + | |||
| + | </ | ||
| + | <code ; output> | ||
| + | ' | ||
| + | utf-8 | ||
| + | invalid start byte | ||
| + | b' | ||
| + | 0 | ||
| + | 1 | ||
| + | </ | ||
| + | | ||
| + | == What are chained exceptions? | ||
| + | Python 3 introduced a very interesting feature called ' | ||
| + | |||
| + | Imagine a situation where you are already handling an exception and your code incidentally triggers an additional exception. Should your code lose the information about the previous exception? Of course not. So the information should be available to the code following the erroneous code. This is an example of implicit chaining. | ||
| + | |||
| + | Another case pops up when we knowingly wish to handle an exception and translate it to another type of exception. Such a situation is typical when you have a good reason for the unifying behavior of one piece of code to act similarly to another piece of code, like a legacy code. In this situation it would also be nice to keep the details of the former exception. This is an example of explicit chaining. | ||
| + | |||
| + | This chaining concept introduces two attributes of exception instances: | ||
| + | |||
| + | the '' | ||
| + | the '' | ||
| + | Those attributes help the programmer to keep a reference to the original exception object in a handy and consistent way for later processing like logging, etc. | ||
| + | |||
| + | Look at the following code and the output traceback. Pay attention to the fact that we are not raising any exception explicitly with a raise statement, but we cause it implicitly (BTW: dividing by 0 is always a good way to cause an exception or error in most of the programming languages): | ||
| + | <code ; output> | ||
| + | Traceback (most recent call last): | ||
| + | File " | ||
| + | print(a_list[3]) | ||
| + | IndexError: list index out of range | ||
| + | |||
| + | During handling of the above exception, another exception occurred: | ||
| + | |||
| + | Traceback (most recent call last): | ||
| + | File " | ||
| + | print(1 / 0) | ||
| + | ZeroDivisionError: | ||
| + | </ | ||
| + | <code python> | ||
| + | a_list = [' | ||
| + | |||
| + | try: | ||
| + | print(a_list[3]) | ||
| + | except Exception as e: | ||
| + | print(0 / 0) | ||
| + | </ | ||
| + | |||
| + | The original exception object'' | ||
| + | <code ; output> | ||
| + | Inner exception (f): division by zero | ||
| + | Outer exception (e): list index out of range | ||
| + | Outer exception referenced: list index out of range | ||
| + | Is it the same object: True | ||
| + | </ | ||
| + | |||
| + | The '' | ||
| + | |||
| + | So, when a subsequent exception (much better forecasted) occurs, we still can say a lot about the nature of the first exception. | ||
| + | |||
| + | Isn't it handled with ease? | ||
| + | |||
| + | <code python> | ||
| + | a_list = [' | ||
| + | |||
| + | try: | ||
| + | print(a_list[3]) | ||
| + | except Exception as e: | ||
| + | try: | ||
| + | # the following line is a developer mistake - they wanted to print progress as 1/10 but wrote 1/0 | ||
| + | print(1 / 0) | ||
| + | except ZeroDivisionError as f: | ||
| + | print(' | ||
| + | print(' | ||
| + | print(' | ||
| + | print(' | ||
| + | </ | ||
| + | <code ; output> | ||
| + | Inner exception (f): division by zero | ||
| + | Outer exception (e): list index out of range | ||
| + | Outer exception referenced: list index out of range | ||
| + | Is it the same object: True | ||
| + | </ | ||
| + | |||
| + | == Advanced exceptions - explicitly chained exceptions | ||
| + | This time we'd like to convert an explicit type of exception object to another type of exception object **at the moment when the second exception is occurring**. | ||
| + | |||
| + | Imagine that your code is responsible for the final checking process before the rocket is launched. The list of checks is a long one, and different checks could result in different exceptions. | ||
| + | |||
| + | But as it is a very serious process, you should be sure that all checks are passed. If any fails, it should be marked in the log book and re-checked next time. | ||
| + | |||
| + | Now you see that it would be convenient to convert each type of exception into its own exception (like RocketNotReadyError) and to log the origin of the exception. | ||
| + | <code ; output> | ||
| + | Final check procedure | ||
| + | The captain' | ||
| + | The pilot' | ||
| + | The mechanic' | ||
| + | Traceback (most recent call last): | ||
| + | File " | ||
| + | print(" | ||
| + | IndexError: list index out of range | ||
| + | |||
| + | The above exception was the direct cause of the following exception: | ||
| + | |||
| + | Traceback (most recent call last): | ||
| + | File " | ||
| + | personnel_check() | ||
| + | File " | ||
| + | raise RocketNotReadyError(' | ||
| + | __main__.RocketNotReadyError: | ||
| + | </ | ||
| + | <code python> | ||
| + | class RocketNotReadyError(Exception): | ||
| + | pass | ||
| + | |||
| + | |||
| + | def personnel_check(): | ||
| + | try: | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | except IndexError as e: | ||
| + | raise RocketNotReadyError(' | ||
| + | |||
| + | crew = [' | ||
| + | print(' | ||
| + | |||
| + | personnel_check() | ||
| + | |||
| + | </ | ||
| + | |||
| + | To catch the cause of the** RocketNotReadyError** exception, you should access the '' | ||
| + | |||
| + | Run the code in the right pane and examine the output. | ||
| + | |||
| + | This time the report is handled in a safe way, and you can be sure that you’re doing a good job. | ||
| + | Once again, the result of the code execution contains an interesting piece of information indicating that we have just witnessed a chain of exceptions: | ||
| + | <code ; output> | ||
| + | Final check procedure | ||
| + | The captain' | ||
| + | The pilot' | ||
| + | The mechanic' | ||
| + | General exception: "Crew is incomplete", | ||
| + | </ | ||
| + | <code python> | ||
| + | class RocketNotReadyError(Exception): | ||
| + | pass | ||
| + | |||
| + | |||
| + | def personnel_check(): | ||
| + | try: | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | except IndexError as e: | ||
| + | raise RocketNotReadyError(' | ||
| + | |||
| + | crew = [' | ||
| + | print(' | ||
| + | |||
| + | try: | ||
| + | personnel_check() | ||
| + | except RocketNotReadyError as f: | ||
| + | print(' | ||
| + | |||
| + | </ | ||
| + | |||
| + | Have a look at an extended checklist script. | ||
| + | |||
| + | <code python> | ||
| + | class RocketNotReadyError(Exception): | ||
| + | pass | ||
| + | |||
| + | |||
| + | def personnel_check(): | ||
| + | try: | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | except IndexError as e: | ||
| + | raise RocketNotReadyError(' | ||
| + | |||
| + | |||
| + | def fuel_check(): | ||
| + | try: | ||
| + | print(' | ||
| + | except ZeroDivisionError as e: | ||
| + | raise RocketNotReadyError(' | ||
| + | |||
| + | |||
| + | crew = [' | ||
| + | fuel = 100 | ||
| + | check_list = [personnel_check, | ||
| + | |||
| + | print(' | ||
| + | |||
| + | for check in check_list: | ||
| + | try: | ||
| + | check() | ||
| + | except RocketNotReadyError as f: | ||
| + | print(' | ||
| + | |||
| + | </ | ||
| + | |||
| + | Pay attention to the fact that thanks to polymorphism and explicit chaining, our approach has become more generic: we are able to run two different checks, each returning a different exception type. | ||
| + | |||
| + | And we’re still able to handle them correctly, as we’re hiding some details behind the RocketNotReadyError exception object. | ||
| + | |||
| + | <code ; output> | ||
| + | The captain' | ||
| + | The pilot' | ||
| + | The mechanic' | ||
| + | RocketNotReady exception: "Crew is incomplete", | ||
| + | RocketNotReady exception: " | ||
| + | </ | ||
| + | |||
| + | == LAB | ||
| + | === Scenario | ||
| + | * Try to extend the check list script to handle more different checks, all reported as RocketNotReady exceptions. | ||
| + | * Add your own checks for: batteries and circuits. | ||
| + | * <code python> | ||
| + | class RocketNotReadyError(Exception): | ||
| + | pass | ||
| + | |||
| + | |||
| + | def personnel_check(): | ||
| + | try: | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | except IndexError as e: | ||
| + | raise RocketNotReadyError(' | ||
| + | |||
| + | |||
| + | def fuel_check(): | ||
| + | try: | ||
| + | print(' | ||
| + | except ZeroDivisionError as e: | ||
| + | raise RocketNotReadyError(' | ||
| + | |||
| + | def batteries_check(): | ||
| + | # add your own implementation | ||
| + | pass | ||
| + | |||
| + | def circuits_check(): | ||
| + | # add your own implementation | ||
| + | pass | ||
| + | |||
| + | |||
| + | crew = [' | ||
| + | fuel = 100 | ||
| + | check_list = [personnel_check, | ||
| + | |||
| + | print(' | ||
| + | |||
| + | for check in check_list: | ||
| + | try: | ||
| + | check() | ||
| + | except RocketNotReadyError as f: | ||
| + | print(' | ||
| + | |||
| + | </ | ||
| + | |||
| + | == Advanced exceptions - the traceback attribute | ||
| + | Each exception object owns a '' | ||
| + | |||
| + | Python allows us to operate on the traceback details because each exception object (not only chained ones) owns a '' | ||
| + | |||
| + | Let's examine such an object while we’re preparing our rocket to launch.ç | ||
| + | |||
| + | <code python> | ||
| + | class RocketNotReadyError(Exception): | ||
| + | pass | ||
| + | |||
| + | |||
| + | def personnel_check(): | ||
| + | try: | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | except IndexError as e: | ||
| + | raise RocketNotReadyError(' | ||
| + | |||
| + | |||
| + | crew = [' | ||
| + | |||
| + | print(' | ||
| + | |||
| + | try: | ||
| + | personnel_check() | ||
| + | except RocketNotReadyError as f: | ||
| + | print(f.__traceback__) | ||
| + | print(type(f.__traceback__)) | ||
| + | |||
| + | </ | ||
| + | <code ; output> | ||
| + | Final check procedure | ||
| + | The captain' | ||
| + | The pilot' | ||
| + | The mechanic' | ||
| + | < | ||
| + | <class ' | ||
| + | </ | ||
| + | |||
| + | From the output presented on the previous page, we can conclude that we have to deal with a traceback type object. | ||
| + | |||
| + | To achieve this, we could use the '' | ||
| + | |||
| + | We could use the '' | ||
| + | |||
| + | The corresponding output reveals the sequence of exceptions and proves that the execution was not interrupted by the exceptions because we see the final wording **Final check is over**. | ||
| + | |||
| + | <code python> | ||
| + | import traceback | ||
| + | |||
| + | class RocketNotReadyError(Exception): | ||
| + | pass | ||
| + | |||
| + | |||
| + | def personnel_check(): | ||
| + | try: | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | except IndexError as e: | ||
| + | raise RocketNotReadyError(' | ||
| + | |||
| + | |||
| + | crew = [' | ||
| + | |||
| + | print(' | ||
| + | |||
| + | try: | ||
| + | personnel_check() | ||
| + | except RocketNotReadyError as f: | ||
| + | print(f.__traceback__) | ||
| + | print(type(f.__traceback__)) | ||
| + | print(' | ||
| + | details = traceback.format_tb(f.__traceback__) | ||
| + | print(" | ||
| + | |||
| + | print(' | ||
| + | |||
| + | </ | ||
| + | <code ; output> | ||
| + | Final check procedure | ||
| + | The captain' | ||
| + | The pilot' | ||
| + | The mechanic' | ||
| + | < | ||
| + | <class ' | ||
| + | |||
| + | Traceback details | ||
| + | File " | ||
| + | personnel_check() | ||
| + | |||
| + | File " | ||
| + | raise RocketNotReadyError(' | ||
| + | |||
| + | Final check is over | ||
| + | </ | ||
| + | Now your log book could be filled with lots of details about your rocket launch for later investigation. | ||
| + | |||
| + | In real life development projects, you may make use of logged tracebacks after comprehensive test sessions to gather statistics or even automate bug reporting processes. | ||
| + | |||
| + | For more information about chained exceptions and traceback attributes, look at the PEP 3134 document. | ||