Python create error message

In this article, let us learn about printing error messages from Exceptions with the help of 5 specifically chosen examples.

In this article, let us learn about printing error messages from Exceptions with the help of 5 specifically chosen examples.

I have divided this article into 2 major sections

  1. Printing custom error messages and
  2. Printing a specific part of the default error message. By “default error message“, I mean the error message that you typically get in the command line if you did not catch a given exception)

Depending on which of the 2 options above you are looking for, you can jump to the respective section of the article using the table of content below.

So, let’s begin!

Printing Custom Error messages

There are 3 ways to print custom error messages in Python. Let us start with the simplest of the 3, which is using a print() statement.

Option#1: Using a simple print() statement

The first and easiest option is to print error messages using a simple print() statement as shown in the example below.

try:
    #Some Problematic code that can produce Exceptions
    x = 5/0
except Exception as e:
  print('A problem has occurred from the Problematic code: ', e)

Running this code will give the output below.

A problem has occurred from the Problematic code: division by zero

Here the line “x = 5/0″ in Example 1 above raised a “ZeroDivisionError” which was caught by our except clause and the print() statement printed the default error message which is “division by zero” to the standard output.

One thing to note here is the line “except Exception as e“. This line of code’s function is to catch all possible exceptions, whichever occurs first as an “Exception” object. This object is stored in the variable “e” (line 4), which returns the string ‘division by zero‘ when used with the print() statement (line 5).

To summarize if you wish to print out the default error message along with a custom message use Option#1.

This is the simplest way to print error messages in python. But this option of putting your custom messages into print statements might not work in cases where you might be handling a list of exceptions using a single except clause. If you are not exactly sure how to catch a list of exceptions using a single except clause, I suggest reading my other article in the link below.

Python: 3 Ways to Catch Multiple Exceptions in a single “except” clause

There I have explained the 3 ways through which you can catch a list of exceptions along with tips on when is the right situation to catch each of these exceptions.

Now that we have learned how to print the default string which comes with an exception object, let us next learn how to customize the message that e carried (the string ‘division by zero‘) and replace that with our own custom error message.

Option#2: Using Custom Exception classes to get customized error messages

In Python, you can define your own custom exception classes by inheriting from another Exception class as shown in the code below.

class MyOwnException(Exception):
    def __str__(self):
        return 'My Own Exception has occurred'

    def __repr__(self):
        return str(type(self))
try:
    raise MyOwnException
except MyOwnException as e:
    print(e)
    print(repr(e))

How to choose the exception class to inherit from?

In the above example, I have inherited from the Exception class in python, but the recommended practice is to choose a class that closely resembles your use-case.

For example, say you are trying to work with a string type object and you are given a list type object instead, here you should inherit your custom exception from TypeError since this Exception type closely resembles your use case which is “the variable is not of expected type”.

If you are looking for getting an appropriate Exception class to inherit from, I recommend having a look at all the built-in exceptions from the official python page here. For the sake of keeping this example simple, I have chosen the higher-level exception type named “Exception” class to inherit from.

In the code below, we are collecting values from the user and to tell the user that there is an error in the value entered we are using the ValueError class.

class EnteredGarbageError(ValueError):
    def __str__(self):
        return 'You did not select an option provided!'    

try:
    options = ['A', 'B', 'C']
    x = input('Type A or B or C: ')
    if x not in options:
        raise EnteredGarbageError
    else:
        print ('You have chosen: ', x)

except EnteredGarbageError as err:
    print(err)

Now that we understand how to choose a class to inherit from, let us next have a look at how to customize the default error messages that these classes return.

How to customize the error message in our custom exception class?

To help us achieve our purpose here which is to print some custom error messages, all objects in python come with 2 methods named __str__ and __repr__. This is pronounced “dunder-str” and “dunder-repr” where “dunder” is short for “double underscore”.

Dunder-str method:

The method __str__ returns a string and this is what the built-in print() function calls whenever we pass it an object to print.

print(object1)

In the line above, python will call the __str__ method of the object and prints out the string returned by that method.

Let us have a look at what python’s official documentation over at python.org has to say about the str method.

https://docs.python.org/3/reference/datamodel.html#object.str

In simpler words, the str method returns a human-readable string for logging purposes, and when this information is passed to the built-in function print(), the string it returns gets printed.

So since our implementation of str returns the string “My Own Exception has occurred” this string got printed on the first line of the exception message.

Dunder-repr method:

__repr__ is another method available in all objects in python.

Where it differs from the dunder-str method is the fact that while the __str__ is used for getting a “friendly message”, the __repr__ method is used for getting, a more of a, “formal message”. You can think of str as a text you got from your friends and repr as a notice you got from a legal representative!

The below screenshot from python’s official documentation explains the use of __repr__ method.

https://docs.python.org/3/reference/datamodel.html#object.repr

Again, in simpler words, repr is typically used to print some “formal” or “official” information about an object in Python

In our Example 2 above, the repr method returned the class name using the built-in type() function.

Next, let us see another variation where we can print different error messages using a single Exception class without making a custom class.

Option#3: Custom Error messages from the raise statement

try:
  raise Exception('I wish to print this message')
except Exception as error:
  print(error)

Lucky for us, python has made this process incredibly simple! Just pass in the message as an argument to the type of exception you wish to raise and this will print that custom message instead!

In the above code, we are throwing an exception of type “Exception” by calling its constructor and giving the custom message as an argument, which then overrides the default __str__ method to return the string passed in.

If you wish to learn more about raise statement, I suggest reading my other article in the link below
Python: Manually throw/raise an Exception using the “raise” statement

where I have explained 3 ways you can use the raise statement in python and when to use each.

But when to use option 2 and when to use option 3?

On the surface, Option#3 of passing in the custom message may look like it made option#2 of using custom classes useless. But the main reason to use Option#2 is the fact that Option#2 can be used to override more than just the __str__ method.

Let’s next move on to section 2 of this article and look at how to choose a specific part of the default error message (the error printed on the console when you don’t catch an exception) and use that to make our own error messages

Choosing Parts of Default Error Messages to print

To understand what I mean by “Default Error Message” let us see an example

raise ValueError("This is an ValueError")

This line when run, will print the following error message

Traceback (most recent call last):

  File "<ipython-input-24-57127e33a735>", line 1, in <module>
    raise ValueError("This is an ValueError")

ValueError: This is an ValueError

This error message contains 3 Parts

  • Exception Type (ValueError)
  • Error message (This is an ValueError)
  • and the stack trace (the 1st few lines showing us where exactly in the program the exception has occurred)

The information needed

  • to extract and use each of the individual pieces of information listed above and
  • when to use what piece of information

is already covered with the help of several examples in my previous article in the link below

Python Exceptions: Getting and Handling Error Messages as strings.

And with that I will end this article!

If you are looking for another interesting read, try the article in the link below.

Exceptions in Python: Everything You Need To Know!

The above article covers all the basics of Exception handling in Python like

  • when and how to ignore exceptions
  • when and how to retry the problematic code that produced the exception and
  • when and how to log the errors

I hope you enjoyed reading this article and got some value from it.

Feel free to share it with your friends and colleagues!

An Exception is raised whenever there is an error encountered, and it signifies that something went wrong with the program. By default, there are many exceptions that the language defines for us, such as TypeError when the wrong type is passed. In this article, we shall look at how we can create our own Custom Exceptions in Python.

But before we take a look at how custom exceptions are implemented, let us find out how we could raise different types of exceptions in Python.


Raise Exceptions

Python allows the programmer to raise an Exception manually using the raise keyword.

Format: raise ExceptionName

The below function raises different exceptions depending on the input passed to the function.

def exception_raiser(string):
    if isinstance(string, int):
        raise ValueError
    elif isinstance(string, str):
        raise IndexError
    else:
        raise TypeError

Output:

>>> exception_raiser(123)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in exception_raiser
ValueError
>>> exception_raiser('abc')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in exception_raiser
IndexError
>>> exception_raiser([123, 456])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in exception_raiser
TypeError

As you can observe, different types of Exceptions are raised based on the input, at the programmer’s choice. This allows for good flexibility of Error Handling as well, since we can actively predict why an Exception can be raised.


Defining Custom Exceptions

Similarly, Python also allows us to define our own custom Exceptions. We are in complete control of what this Exception can do, and when it can be raised, using the raise keyword. Let us look at how we can define and implement some custom Exceptions.

1. Create a Custom Exception Class

We can create a custom Exception class to define the new Exception. Again, the idea behind using a Class is because Python treats everything as a Class. So it doesn’t seem that outlandish that an Exception can be a class as well!

All Exceptions inherit the parent Exception Class, which we shall also inherit when creating our class.

We shall create a Class called MyException, which raises an Exception only if the input passed to it is a list and the number of elements in the list is odd.

class MyException(Exception):
	pass

def list_check(lst):
    if len(lst) % 2 != 0:
        raise MyException

# MyException will not be raised
list_check([1, 2, 3, 4])

# MyException will be raised
list_check([1, 3, 5])    

Output:

[email protected]:~# python3 exceptions.py
Traceback (most recent call last):
  File "exceptions.py", line 12, in <module>
    list_check([1, 3, 5])
  File "exceptions.py", line 6, in list_check
    raise MyException
__main__.MyException

2. Add a custom Message and Error

We can add our own error messages and print them to the console for our Custom Exception. This involves passing two other parameters in our MyException class, the message and error parameters.

Let us modify our original code to account for a custom Message and Error for our Exception.

class MyException(Exception):
    def __init__(self, message, errors):
        # Call Exception.__init__(message)
        # to use the same Message header as the parent class
        super().__init__(message)
        self.errors = errors
        # Display the errors
        print('Printing Errors:')
        print(errors)

def list_check(lst):
    if len(lst) % 2 != 0:
        raise MyException('Custom Message', 'Custom Error')

# MyException will not be raised
list_check([1, 2, 3, 4])

# MyException will be raised
list_check([1, 3, 5])

Output:

Printing Errors:
Custom Error
Traceback (most recent call last):
  File "exceptions.py", line 17, in <module>
    list_check([1, 3, 5])
  File "exceptions.py", line 11, in list_check
    raise MyException('Custom Message', 'Custom Error')
__main__.MyException: Custom Message

We have thus successfully implemented our own Custom Exceptions, including adding custom error messages for debugging purposes! This can be very useful if you are building a Library/API and another programmer wants to know what exactly went wrong when the custom Exception is raised.


Conclusion

In this article, we learned how to raise Exceptions using the raise keyword, and also build our own Exceptions using a Class and add error messages to our Exception.

References

  • JournalDev article on Custom Exceptions
  • Exception Handling in Python

Exceptions

So far we have made programs that ask the user to enter a string, and
we also know how to convert that to an integer.

text = input("Enter something: ")
number = int(text)
print("Your number doubled:", number*2)

That works.

Enter a number: 3
Your number doubled: 6

But that doesn’t work if the user does not enter a number.

Enter a number: lol
Traceback (most recent call last):
  File "/some/place/file.py", line 2, in <module>
    number = int(text)
ValueError: invalid literal for int() with base 10: 'lol'

So how can we fix that?

What are exceptions?

In the previous example we got a ValueError. ValueError is an
exception. In other words, ValueError is an error that can occur
in our program. If an exception occurs, the program will stop and we
get an error message. The interactive prompt will display an error
message and keep going.

>>> int('lol')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'lol'
>>>

Exceptions are classes.

>>> ValueError
<class 'ValueError'>
>>>

We can also create exceptions. We won’t get an error message by doing
that, but we’ll use this for displaying our own error messages later.

>>> the_problem = ValueError('oh no')
>>> the_problem
ValueError('oh no',)
>>>

Catching exceptions

If we need to try to do something and see if we get an exception, we
can use try and except. This is also known as catching the
exception.

>>> try:
...     print(int('lol'))
... except ValueError:
...     print("Oops!")
...
Oops!
>>>

The except part doesn’t run if the try part succeeds.

>>> try:
...     print("Hello World!")
... except ValueError:
...     print("What the heck? Printing failed!")
...
Hello World!
>>>

ValueError is raised when something gets an invalid value, but the
value’s type is correct. In this case, int can take a string as an
argument, but the string needs to contain a number, not lol. If
the type is wrong, we will get a TypeError instead.

>>> 123 + 'hello'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
>>>

Exceptions always interrupt the code even if we catch them. Here the
print never runs because it’s after the error but inside the try
block. Everything after the try block runs normally.

>>> try:
...     123 + 'hello'
...     print("This doesn't get printed.")
... except TypeError:
...     print("Oops!")
...
Oops!
>>>

Does an except ValueError also catch TypeErrors?

>>> try:
...     print(123 + 'hello')
... except ValueError:
...     print("Oops!")
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
>>>

No, it doesn’t. But maybe we could except for both ValueError and
TypeError?

>>> try:
...     int('lol')
... except ValueError:
...     print('wrong value')
... except TypeError:
...     print('wrong type')
...
wrong value
>>> try:
...     123 + 'hello'
... except ValueError:
...     print('wrong value')
... except TypeError:
...     print('wrong type')
...
wrong type
>>>

Seems to be working.

We can also also catch multiple exceptions by catching
a tuple of exceptions:

>>> try:
...     123 + 'hello'
... except (ValueError, TypeError):
...     print('wrong value or type')
...
wrong value or type
>>> try:
...     int('lol')
... except (ValueError, TypeError):
...     print('wrong value or type')
...
wrong value or type
>>>

Catching Exception will catch all errors. We’ll learn more about why
it does that in a moment.

>>> try:
...     123 + 'hello'
... except Exception:
...     print("Oops!")
...
Oops!
>>> try:
...     int('lol')
... except Exception:
...     print("Oops!")
...
Oops!
>>>

It’s also possible to catch an exception and store it in a variable.
Here we are catching an exception that Python created and storing it in
our_error.

>>> try:
...     123 + 'hello'
... except TypeError as e:
...     our_error = e
...
>>> our_error
TypeError("unsupported operand type(s) for +: 'int' and 'str'",)
>>> type(our_error)
<class 'TypeError'>
>>>

When should we catch exceptions?

Do not do things like this:

try:
    # many lines of code
except Exception:
    print("Oops! Something went wrong.")

There’s many things that can go wrong in the try block. If something
goes wrong all we have is an oops message that doesn’t tell us which
line caused the problem. This makes fixing the program really annoying.
If we want to catch exceptions we need to be specific about what exactly
we want to catch and where instead of catching everything we can in the
whole program.

There’s nothing wrong with doing things like this:

try:
    with open('some file', 'r') as f:
        content = f.read()
except OSError:     # we can't read the file but we can work without it
    content = some_default_content

Usually catching errors that the user has caused is also a good idea:

import sys

text = input("Enter a number: ")
try:
    number = int(text)
except ValueError:
    print(f"'{text}' is not a number.", file=sys.stderr)
    sys.exit(1)
print(f"Your number doubled is {(number * 2)}.")

Raising exceptions

Now we know how to create exceptions and how to handle errors that
Python creates. But we can also create error messages manually. This
is known as raising an exception and throwing an exception.

Raising an exception is easy. All we need to do is to type raise
and then an exception we want to raise:

>>> raise ValueError("lol is not a number")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: lol is not a number
>>>

Of course, we can also raise an exception from a variable.

>>> oops = ValueError("lol is not a number")
>>> raise oops
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: lol is not a number
>>>

If we define a function that raises an
exception and call it we’ll notice that the error message also
says which functions we ran to get to that error.

>>> def oops():
...     raise ValueError("oh no!")
...
>>> def do_the_oops():
...     oops()
...
>>> do_the_oops()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in do_the_oops
  File "<stdin>", line 2, in oops
ValueError: oh no!
>>>

If our code was in a file we would also see the line of code
that raised the error.

When should we raise exceptions?

Back in the module chapter we learned to display error
messages by printing to sys.stderr and then calling sys.exit(1), so
when should we use that and when should we raise an exception?

Exceptions are meant for programmers, so if we are writing something
that other people will import we should use exceptions. If our program
is working like it should be and the user has done something wrong,
it’s usually better to use sys.stderr and sys.exit.

Exception hierarchy

Exceptions are organized like this. I made this tree with this
program on Python 3.7. You may
have more or less exceptions than I have if your Python is newer or
older than mine, but they should be mostly similar.

Exception
├── ArithmeticError
│   ├── FloatingPointError
│   ├── OverflowError
│   └── ZeroDivisionError
├── AssertionError
├── AttributeError
├── BufferError
├── EOFError
├── ImportError
│   └── ModuleNotFoundError
├── LookupError
│   ├── IndexError
│   └── KeyError
├── MemoryError
├── NameError
│   └── UnboundLocalError
├── OSError
│   ├── BlockingIOError
│   ├── ChildProcessError
│   ├── ConnectionError
│   │   ├── BrokenPipeError
│   │   ├── ConnectionAbortedError
│   │   ├── ConnectionRefusedError
│   │   └── ConnectionResetError
│   ├── FileExistsError
│   ├── FileNotFoundError
│   ├── InterruptedError
│   ├── IsADirectoryError
│   ├── NotADirectoryError
│   ├── PermissionError
│   ├── ProcessLookupError
│   └── TimeoutError
├── ReferenceError
├── RuntimeError
│   ├── NotImplementedError
│   └── RecursionError
├── StopAsyncIteration
├── StopIteration
├── SyntaxError
│   └── IndentationError
│       └── TabError
├── SystemError
├── TypeError
├── ValueError
│   └── UnicodeError
│       ├── UnicodeDecodeError
│       ├── UnicodeEncodeError
│       └── UnicodeTranslateError
└── Warning
    ├── BytesWarning
    ├── DeprecationWarning
    ├── FutureWarning
    ├── ImportWarning
    ├── PendingDeprecationWarning
    ├── ResourceWarning
    ├── RuntimeWarning
    ├── SyntaxWarning
    ├── UnicodeWarning
    └── UserWarning

Catching an exception also catches everything that’s under it in this
tree. For example, catching OSError catches errors that we typically
get when processing files, and catching Exception catches
all of these errors. You don’t need to remember this tree, running
help('builtins') should display a larger tree that this is a part of.

There are also a few exceptions that are not in this tree like
SystemExit and KeyboardInterrupt, but most of the time we shouldn’t
catch them. Catching Exception doesn’t catch them either.

Summary

  • Exceptions are classes and they can be used just like all other classes.
  • ValueError and TypeError are some of the most commonly used exceptions.
  • The try and except keywords can be used for attempting to do
    something and then doing something else if we get an error. This is
    known as catching exceptions.
  • It’s possible to raise exceptions with the raise keyword. This
    is also known as throwing exceptions.
  • Raise exceptions if they are meant to be displayed for programmers and
    use sys.stderr and sys.exit otherwise.

Examples

Keep asking a number from the user until it’s entered correctly.

while True:
    try:
        number = int(input("Enter a number: "))
        break
    except ValueError:
        print("That's not a valid number! Try again.")

print("Your number doubled is:", number * 2)

This program allows the user to customize the message it prints by
modifying a file the greeting is stored in, and it can create the
file for the user if it doesn’t exist already. This example also uses
things from the file chapter, the function defining
chapter and the module chapter.

# These are here so you can change them to customize the program
# easily.
default_greeting = "Hello World!"
filename = "greeting.txt"


import sys

def askyesno(question):
    while True:
        answer = input(question + ' (y or n) ')
        if answer == 'Y' or answer == 'y':
            return True
        if answer == 'N' or answer == 'n':
            return False

def greet():
    with open(filename, 'r') as f:
        for line in f:
            print(line.rstrip('n'))

try:
    greet()
except OSError:
    print(f"Cannot read '{filename}'!", file=sys.stderr)
    if askyesno("Would you like to create a default greeting file?"):
        with open(filename, 'w') as f:
            print(default_greeting, file=f)
        greet()

If you have trouble with this tutorial, please
tell me about it and I’ll make this tutorial better,
or ask for help online.
If you like this tutorial, please give it a
star.

You may use this tutorial freely at your own risk. See
LICENSE.

Previous | Next |
List of contents

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Raising and Handling Python Exceptions

A Python program terminates as soon as it encounters an error. In Python, an error can be a syntax error or an exception. In this article, you will see what an exception is and how it differs from a syntax error. After that, you will learn about raising exceptions and making assertions. Then, you’ll finish with a demonstration of the try and except block.

An introduction to exceptions in Python

Exceptions versus Syntax Errors

Syntax errors occur when the parser detects an incorrect statement. Observe the following example:

>>> print( 0 / 0 ))
  File "<stdin>", line 1
    print( 0 / 0 ))
                  ^
SyntaxError: invalid syntax

The arrow indicates where the parser ran into the syntax error. In this example, there was one bracket too many. Remove it and run your code again:

>>> print( 0 / 0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

This time, you ran into an exception error. This type of error occurs whenever syntactically correct Python code results in an error. The last line of the message indicated what type of exception error you ran into.

Instead of showing the message exception error, Python details what type of exception error was encountered. In this case, it was a ZeroDivisionError. Python comes with various built-in exceptions as well as the possibility to create self-defined exceptions.

Raising an Exception

We can use raise to throw an exception if a condition occurs. The statement can be complemented with a custom exception.

Illustration of  raise statement usage

If you want to throw an error when a certain condition occurs using raise, you could go about it like this:

x = 10
if x > 5:
    raise Exception('x should not exceed 5. The value of x was: {}'.format(x))

When you run this code, the output will be the following:

Traceback (most recent call last):
  File "<input>", line 4, in <module>
Exception: x should not exceed 5. The value of x was: 10

The program comes to a halt and displays our exception to screen, offering clues about what went wrong.

The AssertionError Exception

Instead of waiting for a program to crash midway, you can also start by making an assertion in Python. We assert that a certain condition is met. If this condition turns out to be True, then that is excellent! The program can continue. If the condition turns out to be False, you can have the program throw an AssertionError exception.

Python assert statement

Have a look at the following example, where it is asserted that the code will be executed on a Linux system:

import sys
assert ('linux' in sys.platform), "This code runs on Linux only."

If you run this code on a Linux machine, the assertion passes. If you were to run this code on a Windows machine, the outcome of the assertion would be False and the result would be the following:

Traceback (most recent call last):
  File "<input>", line 2, in <module>
AssertionError: This code runs on Linux only.

In this example, throwing an AssertionError exception is the last thing that the program will do. The program will come to halt and will not continue. What if that is not what you want?

The try and except Block: Handling Exceptions

The try and except block in Python is used to catch and handle exceptions. Python executes code following the try statement as a “normal” part of the program. The code that follows the except statement is the program’s response to any exceptions in the preceding try clause.

Diagram showing try and except statements

As you saw earlier, when syntactically correct code runs into an error, Python will throw an exception error. This exception error will crash the program if it is unhandled. The except clause determines how your program responds to exceptions.

The following function can help you understand the try and except block:

def linux_interaction():
    assert ('linux' in sys.platform), "Function can only run on Linux systems."
    print('Doing something.')

The linux_interaction() can only run on a Linux system. The assert in this function will throw an AssertionError exception if you call it on an operating system other then Linux.

You can give the function a try using the following code:

try:
    linux_interaction()
except:
    pass

The way you handled the error here is by handing out a pass. If you were to run this code on a Windows machine, you would get the following output:

You got nothing. The good thing here is that the program did not crash. But it would be nice to see if some type of exception occurred whenever you ran your code. To this end, you can change the pass into something that would generate an informative message, like so:

try:
    linux_interaction()
except:
    print('Linux function was not executed')

Execute this code on a Windows machine:

Linux function was not executed

When an exception occurs in a program running this function, the program will continue as well as inform you about the fact that the function call was not successful.

What you did not get to see was the type of error that was thrown as a result of the function call. In order to see exactly what went wrong, you would need to catch the error that the function threw.

The following code is an example where you capture the AssertionError and output that message to screen:

try:
    linux_interaction()
except AssertionError as error:
    print(error)
    print('The linux_interaction() function was not executed')

Running this function on a Windows machine outputs the following:

Function can only run on Linux systems.
The linux_interaction() function was not executed

The first message is the AssertionError, informing you that the function can only be executed on a Linux machine. The second message tells you which function was not executed.

In the previous example, you called a function that you wrote yourself. When you executed the function, you caught the AssertionError exception and printed it to screen.

Here’s another example where you open a file and use a built-in exception:

try:
    with open('file.log') as file:
        read_data = file.read()
except:
    print('Could not open file.log')

If file.log does not exist, this block of code will output the following:

This is an informative message, and our program will still continue to run. In the Python docs, you can see that there are a lot of built-in exceptions that you can use here. One exception described on that page is the following:

Exception FileNotFoundError

Raised when a file or directory is requested but doesn’t exist. Corresponds to errno ENOENT.

To catch this type of exception and print it to screen, you could use the following code:

try:
    with open('file.log') as file:
        read_data = file.read()
except FileNotFoundError as fnf_error:
    print(fnf_error)

In this case, if file.log does not exist, the output will be the following:

[Errno 2] No such file or directory: 'file.log'

You can have more than one function call in your try clause and anticipate catching various exceptions. A thing to note here is that the code in the try clause will stop as soon as an exception is encountered.

Look at the following code. Here, you first call the linux_interaction() function and then try to open a file:

try:
    linux_interaction()
    with open('file.log') as file:
        read_data = file.read()
except FileNotFoundError as fnf_error:
    print(fnf_error)
except AssertionError as error:
    print(error)
    print('Linux linux_interaction() function was not executed')

If the file does not exist, running this code on a Windows machine will output the following:

Function can only run on Linux systems.
Linux linux_interaction() function was not executed

Inside the try clause, you ran into an exception immediately and did not get to the part where you attempt to open file.log. Now look at what happens when you run the code on a Linux machine:

[Errno 2] No such file or directory: 'file.log'

Here are the key takeaways:

  • A try clause is executed up until the point where the first exception is encountered.
  • Inside the except clause, or the exception handler, you determine how the program responds to the exception.
  • You can anticipate multiple exceptions and differentiate how the program should respond to them.
  • Avoid using bare except clauses.

The else Clause

In Python, using the else statement, you can instruct a program to execute a certain block of code only in the absence of exceptions.

Diagram of try, except, and else statements in Python

Look at the following example:

try:
    linux_interaction()
except AssertionError as error:
    print(error)
else:
    print('Executing the else clause.')

If you were to run this code on a Linux system, the output would be the following:

Doing something.
Executing the else clause.

Because the program did not run into any exceptions, the else clause was executed.

You can also try to run code inside the else clause and catch possible exceptions there as well:

try:
    linux_interaction()
except AssertionError as error:
    print(error)
else:
    try:
        with open('file.log') as file:
            read_data = file.read()
    except FileNotFoundError as fnf_error:
        print(fnf_error)

If you were to execute this code on a Linux machine, you would get the following result:

Doing something.
[Errno 2] No such file or directory: 'file.log'

From the output, you can see that the linux_interaction() function ran. Because no exceptions were encountered, an attempt to open file.log was made. That file did not exist, and instead of opening the file, you caught the FileNotFoundError exception.

Cleaning Up After Using finally

Imagine that you always had to implement some sort of action to clean up after executing your code. Python enables you to do so using the finally clause.

Diagram explaining try except else finally statements

Have a look at the following example:

try:
    linux_interaction()
except AssertionError as error:
    print(error)
else:
    try:
        with open('file.log') as file:
            read_data = file.read()
    except FileNotFoundError as fnf_error:
        print(fnf_error)
finally:
    print('Cleaning up, irrespective of any exceptions.')

In the previous code, everything in the finally clause will be executed. It does not matter if you encounter an exception somewhere in the try or else clauses. Running the previous code on a Windows machine would output the following:

Function can only run on Linux systems.
Cleaning up, irrespective of any exceptions.

Summing Up

After seeing the difference between syntax errors and exceptions, you learned about various ways to raise, catch, and handle exceptions in Python. In this article, you saw the following options:

  • raise allows you to throw an exception at any time.
  • assert enables you to verify if a certain condition is met and throw an exception if it isn’t.
  • In the try clause, all statements are executed until an exception is encountered.
  • except is used to catch and handle the exception(s) that are encountered in the try clause.
  • else lets you code sections that should run only when no exceptions are encountered in the try clause.
  • finally enables you to execute sections of code that should always run, with or without any previously encountered exceptions.

Hopefully, this article helped you understand the basic tools that Python has to offer when dealing with exceptions.

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Raising and Handling Python Exceptions

Время прочтения
10 мин

Просмотры 31K

Одним из недостатков гибких языков, таких как Python, является предположение, что если что-то работает, то скорее всего оно сделано правильно. Я хочу написать скромное руководство по эффективному использованию исключений в Python, правильной их обработке и логировании.

Эффективная обработка исключений

Введение

Давайте рассмотрим следующую систему. У нас есть микросервис, который отвечает за:

·  Прослушивание событий о новом заказе;

·  Получение заказа из базы данных;

·  Проверку состояния принтера;

·  Печать квитанции;

·  Отправка квитанции в налоговую систему (IRS).

В любой момент может сломаться что угодно. У вас могут возникнуть проблемы с объектом заказа, в котором может не быть нужной информации, или в принтере может закончиться бумага, или же сервис налоговой не будет работать, и вы не сможете синхронизировать с ними квитанцию об оплате, а может быть ваша база данных окажется недоступна.

Ваша задача правильно и проактивно реагировать на любую ситуацию, чтобы избежать ошибок при обработке новых заказов.

И примерно вот такой код на этот случай пишут люди (он, конечно, работает, но плохо и неэффективно):

class OrderService:
    def emit(self, order_id: str) -> dict:

        try:
            order_status = status_service.get_order_status(order_id)
        except Exception as e:
            logger.exception(
                f"Order {order_id} was not found in db "
                f"to emit. Error: {e}."
            )
            raise e

        (
            is_order_locked_in_emission,
            seconds_in_emission,
        ) = status_service.is_order_locked_in_emission(order_id)
        if is_order_locked_in_emission:
            logger.info(
                "Redoing emission because "
                "it was locked in that state after a long time! "
                f"Time spent in that state: {seconds_in_emission} seconds "
                f"Order: {order_id}, "
                f"order_status: {order_status.value}"
            )

        elif order_status == OrderStatus.EMISSION_IN_PROGRESS:
            logger.info("Aborting emission request because it is already in progress!")
            return {"order_id": order_id, "order_status": order_status.value}

        elif order_status == OrderStatus.EMISSION_SUCCESSFUL:
            logger.info(
                "Aborting emission because it already happened! "
                f"Order: {order_id}, "
                f"order_status: {order_status.value}"
            )
            return {"order_id": order_id, "order_status": order_status.value}

        try:
            receipt_note = receipt_service.create(order_id)
        except Exception as e:
            logger.exception(
                "Error found during emission! "
                f"Order: {order_id}, "
                f"exception: {e}"
            )
            raise e

        try:
            broker.emit_receipt_note(receipt_note)
        except Exception as e:
            logger.exception(
                "Emission failed! "
                f"Order: {order_id}, "
                f"exception: {e}"
            )
            raise e

        order_status = status_service.get_order_status(order_id)
        return {"order_id": order_id, "order_status": order_status.value}

Сначала я сосредоточусь на том, что OrderService слишком много знает, и все эти данные делают его чем-то вроде blob, а чуть позже расскажу о правильной обработке и правильном логировании исключений.

Почему этот сервис — blob?

Этот сервис знает слишком много. Кто-то может сказать, что он знает только то, что ему нужно (то есть все шаги, связанные с формированием чека), но на самом деле он знает куда больше.

Он сосредоточен на создании ошибок (например, база данных, печать, статус заказа), а не на том, что он делает (например, извлекает, проверяет статус, генерирует, отправляет) и на том, как следует реагировать в случае сбоев.

 В этом смысле мне кажется, что клиент учит сервис тому, какие исключения он может выдать. Если мы решим переиспользовать его на любом другом этапе (например, клиент захочет получить еще одну печатную копию более старого чека по заказу), мы скопируем большую часть этого кода.

Несмотря на то, что сервис работает нормально, поддерживать его трудно, и неясно, как один шаг соотносится с другим из-за повторяющихся блоков except между шагами, которые отвлекают наше внимание на вопрос «как» вместо того, чтобы думать о «когда».

Первое улучшение: делайте исключения конкретными

Давайте сначала сделаем исключения более точными и конкретными. Преимущества не видны сразу, поэтому я не буду тратить слишком много времени на объяснение этого прямо сейчас. Однако обратите внимание на то, как изменяется код.

Я выделю только то, что мы поменяли:

try:
    order_status = status_service.get_order_status(order_id)
except Exception as e:
    logger.exception(...)
    raise OrderNotFound(order_id) from e

...

try:
    ...
except Exception as e:
    logger.exception(...)
    raise ReceiptGenerationFailed(order_id) from e

try:
    broker.emit_receipt_note(receipt_note)
except Exception as e:
    logger.exception(...)
    raise ReceiptEmissionFailed(order_id) from e

Обратите внимание, что на этот раз я пользуюсь from e, что является правильным способом создания одного исключения из другого и сохраняет полную трассировку стека.

Второе улучшение: не лезьте не в свое дело

Теперь, когда у нас есть кастомные исключения, мы можем перейти к мысли «не учите классы тому, что может пойти не так» — они сами скажут, если это случится!

# Services

class StatusService:
    def get_order_status(order_id):
        try:
            ...
        except Exception as e:
            raise OrderNotFound(order_id) from e


class ReceiptService:
    def create(order_id):
        try:
            ...
        except Exception as e:
            raise ReceiptGenerationFailed(order_id) from e


class Broker:
    def emit_receipt_note(receipt_note):
        try:
            ...
        except Exception as e:
            raise ReceiptEmissionFailed(order_id) from e

# Main class

class OrderService:
    def emit(self, order_id: str) -> dict:
        try:
            order_status = status_service.get_order_status(order_id)

            (
                is_order_locked_in_emission,
                seconds_in_emission,
            ) = status_service.is_order_locked_in_emission(order_id)
            if is_order_locked_in_emission:
                logger.info(
                    "Redoing emission because "
                    "it was locked in that state after a long time! "
                    f"Time spent in that state: {seconds_in_emission} seconds "
                    f"Order: {order_id}, "
                    f"order_status: {order_status.value}"
                )

            elif order_status == OrderStatus.EMISSION_IN_PROGRESS:
                logger.info("Aborting emission request because it is already in progress!")
                return {"order_id": order_id, "order_status": order_status.value}

            elif order_status == OrderStatus.EMISSION_SUCCESSFUL:
                logger.info(
                    "Aborting emission because it already happened! "
                    f"Order: {order_id}, "
                    f"order_status: {order_status.value}"
                )
                return {"order_id": order_id, "order_status": order_status.value}

            receipt_note = receipt_service.create(order_id)
            broker.emit_receipt_note(receipt_note)
            order_status = status_service.get_order_status(order_id)
        except OrderNotFound as e:
            logger.exception(
                f"Order {order_id} was not found in db "
                f"to emit. Error: {e}."
            )
            raise
        except ReceiptGenerationFailed as e:
            logger.exception(
                "Error found during emission! "
                f"Order: {order_id}, "
                f"exception: {e}"
            )
            raise
        except ReceiptEmissionFailed as e:
            logger.exception(
                "Emission failed! "
                f"Order: {order_id}, "
                f"exception: {e}"
            )
            raise
        else:
            return {"order_id": order_id, "order_status": order_status.value}

Как вам? Намного лучше, правда? У нас есть один блок try, который построен достаточно логично, чтобы понять, что произойдет дальше. Вы сгруппировали конкретные блоки, за исключением тех, которые помогают вам понять «когда» и крайние случаи. И, наконец, у вас есть блок else, в котором описано, что произойдет, если все отработает как надо.

Кроме того, пожалуйста, обратите внимание на то, что я сохранил инструкции raise без объявления объекта исключения. Это не опечатка. На самом деле, это правильный способ повторного вызова исключения: простой и немногословный.

Но это еще не все. Логирование продолжает меня раздражать.

Третье улучшение: улучшение логирования

Этот шаг напоминает мне принцип «говори, а не спрашивай», хотя это все же не совсем он. Вместо того, чтобы запрашивать подробности исключения и на их основе выдавать полезные сообщения, исключения должны выдавать их сами – в конце концов, я их конкретизировал!

### Exceptions

class OrderCreationException(Exception):
    pass


class OrderNotFound(OrderCreationException):
    def __init__(self, order_id):
        self.order_id = order_id
        super().__init__(
            f"Order {order_id} was not found in db "
            "to emit."
        )


class ReceiptGenerationFailed(OrderCreationException):
    def __init__(self, order_id):
        self.order_id = order_id
        super().__init__(
            "Error found during emission! "
            f"Order: {order_id}"
        )


class ReceiptEmissionFailed(OrderCreationException):
    def __init__(self, order_id):
        self.order_id = order_id
        super().__init__(
            "Emission failed! "
            f"Order: {order_id} "
        )

### Main class

class OrderService:
    def emit(self, order_id: str) -> dict:
        try:
            ...
        except OrderNotFound:
            logger.exception("We got a database exception")
            raise
        except ReceiptGenerationFailed:
            logger.exception("We got a problem generating the receipt")
            raise
        except ReceiptEmissionFailed:
            logger.exception("Unable to emit the receipt")
            raise
        else:
            return {"order_id": order_id, "order_status": order_status.value}

Наконец-то мои глаза чувствуют облегчение. Поменьше повторений, пожалуйста! Примите к сведению, что рекомендуемый способ выглядит так, как я написал его выше: logger.exception(«ЛЮБОЕ СООБЩЕНИЕ»). Вам даже не нужно передавать исключение, поскольку его наличие уже подразумевается. Кроме того, кастомное сообщение, которое мы определили в каждом исключении с идентификатором order_id, будет отображаться в логах, поэтому вам не нужно повторяться и не нужно оперировать внутренними данными об исключениях.

Вот пример вывода ваших логов:

❯ python3 testme.py
Unable to emit the receipt # <<-- My log message
Traceback (most recent call last):
  File "/path/testme.py", line 19, in <module>
    tryme()
  File "/path/testme.py", line 14, in tryme
    raise ReceiptEmissionFailed(order_id)
ReceiptEmissionFailed: Emission failed! Order: 10 # <<-- My exception message

Теперь всякий раз, когда я получаю это исключение, сообщение уже ясно и понятно, и мне не нужно помнить о логировании order_id, который я сгенерировал.

Последнее улучшение: упрощение

После более детального рассмотрения нашего окончательного кода, он кажется лучше, теперь его легко читать и поддерживать.

Но управляет ли OrderService бизнес-логикой? Я не думаю, что это сервис в общем смысле. Он больше похож на координацию вызовов настоящих сервисов обеспечивающих бизнес-логику, которая выглядит получше, чем паттерн facade.

Кроме того, можно заметить, что он запрашивает данные у status_service, чтобы что-то с ними сделать. (Что, на этот раз, действительно разрушает идею «Говори, а не спрашивай»).

Перейдем к упрощению.

class OrderFacade:  # Renamed to match what it actually is
    def emit(self, order_id: str) -> dict:
        try:
            # NOTE: info logging still happens inside
            status_service.ensure_order_unlocked(order_id)
            receipt_note = receipt_service.create(order_id)
            broker.emit_receipt_note(receipt_note)
            order_status = status_service.get_order_status(order_id)
        except OrderAlreadyInProgress as e:
            # New block
            logger.info("Aborting emission request because it is already in progress!")
            return {"order_id": order_id, "order_status": e.order_status.value}
        except OrderAlreadyEmitted as e:
            # New block
            logger.info(f"Aborting emission because it already happened! {e}")
            return {"order_id": order_id, "order_status": e.order_status.value}
        except OrderNotFound:
            logger.exception("We got a database exception")
            raise
        except ReceiptGenerationFailed:
            logger.exception("We got a problem generating the receipt")
            raise
        except ReceiptEmissionFailed:
            logger.exception("Unable to emit the receipt")
            raise
        else:
            return {"order_id": order_id, "order_status": order_status.value}

Мы только что создали новый метод ensure_order_unlocked для нашего status_service, который теперь отвечает за создание исключений/логирование в случае, если что-то идет не так.

Хорошо, а теперь скажите, насколько легче теперь стало это читать?

Я могу понять все return при беглом просмотре. Я знаю, что происходит, когда все идет хорошо, и как крайние случаи могут привести к разным результатам. И все это без прокрутки взад-вперед.

Теперь этот код такой же простой, каким (в основном) должен быть любой код.

Обратите внимание, что я решил вывести объект исключения e в логах, поскольку под капотом он будет запускать str(e), который вернет сообщение об исключении. Я подумал, что было бы полезно говорить подробно, поскольку мы не используем log.exception для этого блока, поэтому сообщение об исключении не будет отображаться.

Теперь давайте разберемся с некоторыми хитростями, которые помогут вам сделать код понятным для чтения и простым в обслуживании.

Эффективное создание исключений

Всегда классифицируйте свои исключения через базовое и расширяйте все конкретные исключения от него. С помощью этой полезной практики вы можете переиспользовать логику для связанного кода.

Исключения – это объекты, которые несут в себе информацию, поэтому не стесняйтесь добавлять кастомные атрибуты, которые могут помочь вам понять, что происходит. Не позволяйте своему бизнес-коду учить вас тому, как он должен быть построен, ведь с таким количеством сообщений и деталей потерять себя становится трудно.

# Base category exception
class OrderCreationException(Exception):
    pass

# Specific error with custom message. Order id is required.
class OrderNotFound(OrderCreationException):
    def __init__(self, order_id):
        self.order_id = order_id  # custom property
        super().__init__(
            f"Order {order_id} was not found in db "
            f"to emit."
        )


# Specific error with custom message. Order id is required.
class ReceiptGenerationFailed(OrderCreationException):
    def __init__(self, order_id):
        self.order_id = order_id  # custom property
        super().__init__(
            "Error found during emission! "
            f"Order: {order_id}"
        )

В примере выше я мог бы выйти за рамки и расширить базовый класс, чтобы всегда получать order_id, если мне это нужно. Этот совет поможет сохранить код сухим, поскольку мне не нужно быть многословным при создании исключений. Так можно использовать всего лишь одну переменную.

def func1(order_id):
    raise OrderNotFound(order_id)
    # instead of raise OrderNotFound(f"Can't find order {order_id}")


def func2(order_id):
    raise OrderNotFound(order_id)
    # instead of raise OrderNotFound(f"Can't find order {order_id}")

  В тестировании также будет больше смысла, поскольку я могу сделать assert order_id через строку.

assert e.order_id == order_id
# instead of assert order_id in str(e)

Ловим и создаем исключения эффективно

Еще одна вещь, которую люди часто делают неправильно – это отлавливают и повторно создают исключения.

Согласно PEP 3134 Python, делать нужно следующим образом.

Повторное создание исключения

Обычной инструкции raise более чем достаточно.

try:
    ...
except CustomException as ex:
    # do stuff (e.g. logging)
    raise

Создание одного исключения из другого

Этот вариант особо актуален, поскольку он сохраняет всю трассировку стека и помогает вашей команде отлаживать основные проблемы.

try:
    ...
except CustomException as ex:
    raise MyNewException() from ex

Эффективное логирование исключений

Еще один совет, который не позволит вам быть слишком многословным.

Используйте logger.exception

Вам не нужно логировать объект исключения. Функция exception логгера предназначена для использования внутри блоков except. Она уже обрабатывает трассировку стека с информацией о выполнении и отображает, какое исключение вызвало ее, с сообщением, установленном на уровне ошибки!

try:
    ...
except CustomException:
    logger.exception("custom message")

А что, если это не ошибка?

Если по какой-то причине вы не хотите логировать исключение как ошибку, то возможно, это предупреждение или просто информация, как было показано выше.

Вы можете принять решение установить exc_info в True, если хотите сохранить трассировку стека. Кроме того, было бы неплохо использовать объект исключения внутри сообщения.

Источники

Документация Python:

·  Python logging.logger.exception

·  Python PEP 3134

Принципы и качество кода:

·  Говори, а не спрашивай

·  Паттерн facade

·  Blob


РЕГИСТРАЦИЯ

  1. Create a Custom Exception Class in Python
  2. Execute Exception-Handling Using the try...except Block in Python

Create Custom Exceptions in Python

This tutorial will demonstrate you can create custom exception classes in Python. Here, we’ll show how you can properly perform exception handling, define custom exception classes, and override existing built-in exceptions.

Exceptions are a type of event that occurs whenever something within a program doesn’t go as intended or disrupts the flow of the intended use-case of the program. Without exception handling, the program will cease to execute entirely, and the exception would have to either be fixed or handled.

Create a Custom Exception Class in Python

Creating an Exception Class in Python is done the same way as a regular class. The main difference is you have to include the Python’s base Exception class to inform the compiler that the class you’re making is an exception class.

Let’s test this method out to create an exception class called DemoException and use the placeholder control flow keyword pass inside as a placeholder.

class DemoException(Exception):
    pass

Execute Exception-Raising Using the Keyword raise in Python

To test the DemoException class and see what it displays when it’s actually triggered, perform exception raising. Exception-raising is synonymous with exception-throwing in other programming languages.

Using the keyword raise, trigger an exception using the given exception class and outputs an exception message.

class DemoException(Exception):
    pass

raise DemoException

Output:

Traceback (most recent call last):
  File "/Users/demo/python/demo_exception.py", line 4, in <module>
    raise DemoException
__main__.DemoException

A standard exception will look like in the terminal if no custom exception message has been declared.

Declare a Custom Exception Message in Python

To declare a custom exception message for DemoException, override the __init__() method of the exception class and include the message that should be outputted for the exception in the parameters, along with the mandatory self-referential parameter self.

For example, let’s override the __init__() method and create a custom message for the DemoException class:

class DemoException(Exception):
    def __init__(self, message):
        super().__init__(message)

Take note that for the message to be integrated into your exception successfully, call the base Exception class, __init__() method, and include the message as an argument.

Let’s call the exception class again using the raise keyword, and now, passing a custom message with it:

class DemoException(Exception):
    def __init__(self, message):
        super().__init__(message)
        
message = "Exception Triggered! Something went wrong."
raise DemoException(message)

The output should look like this:

Traceback (most recent call last):
  File "/Users/demo/python/helloworld.py", line 6, in <module>
    raise DemoException(message)
__main__.DemoException: Exception Triggered! Something went wrong.

We’ve now successfully created and triggered an exception class with a custom error message.

For actual situations that may trigger an exception, how do we handle and raise these exceptions? You can solve this problem neatly by implementing exception-handling using the try...except block.

Execute Exception-Handling Using the try...except Block in Python

The try...except block is much like the try-catch block in other languages like Java.

The try...except block has 2 main blocks and 2 optional blocks:

  • try (required) — The main block responsible for encapsulating the code block where the exception might be triggered. The try block halts the whole process within it whenever an exception is triggered.
  • except (required) — The block program proceeds whenever a specified exception is triggered. This block typically contains a descriptive error message for the caller or just a simple print() statement. There may be more than one except block in a single try block, each one catching different exceptions.
  • else (optional) — This optional block is where the program will proceed if the try block did not trigger any exceptions.
  • finally (optional) — This optional block runs once everything from the previous 3 blocks has been performed regardless if an exception is triggered or not.

Let’s use the previous example using the DemoException class to try a simple try...except block.

First, wrap the raise keyword in a function and put it inside the try...except block.

The function that we’ll create for this example is a function that accepts a number and throws an exception if it sends 0. If it sends any other number, then the code will proceed as intended. Check the example below:

class DemoException(Exception):
    def __init__(self, message):
        super().__init__(message)
        

message = "Exception Triggered! Something went wrong."

def triggerException(num):
    if (num == 0):
        raise DemoException(message)
    else:
        print(num)


try:
    triggerException(0)
    print("Code has successfully been executed.")
except DemoException:
    print("Error: Number should not be 0.")

Since the triggerException() passed 0 as an argument, the code should trigger DemoException. Here we should expect the raise keyword message to be overridden with whatever is inside the except block as the output.

Notice that the print() line after the triggerException() function call was not outputted. It’s because the function raised an exception; therefore, it immediately halted all the processes within the try block and proceeded directly to the except block.

Output:

Error: Number should not be 0.

Now, let’s try passing a valid number like 20, for example.

try:
    triggerException(20)
    print("Code has successfully been executed.")
except DemoException:
    print("Error: Number should not be 0.")

Output:

20
Code has successfully been executed.

Let’s try chaining the except blocks and create another exception. Let’s call the new exception NumberFormatException, which triggers if the given input is not a number. For this exception class, let’s declare the message inside the class.

class NumberFormatException(Exception, value):
    message = f'{value} is not a number'
    def __init__(self):
        super().__init__(message)

Now, modify the code above to handle the new exception class NumberFormatException:

class DemoException(Exception):
    def __init__(self, message):
        super().__init__(message)
        
class NumberFormatException(Exception):
    def __init__(self, message, value):
        message = f'{value} is not a number'
        super().__init__(message)
        
message = "Exception occured."

def triggerException(num):
    if (not num.isdigit()):
        raise NumberFormatException(message, num)
    elif (num == 0):
        raise DemoException(message)
    else:
        print(num)

num = "sample string"
try:
    triggerException(num)
    print("Code has successfully been executed.")
except DemoException:
    print("Error: Number should not be 0.")
except NumberFormatException:
    print(num+" is not a number.")

In this code, the value of num that was passed to triggerException() is a string 'sample string' so the NumberFormatException should be triggered.

Output:

sample string is not a number.

In summary, creating custom exceptions in Python is as simple as creating a new class, but with the Exception class as an extra argument in the class definition. The raise keyword is used to trigger exceptions given the Exception Class. The try...except blocks are used to wrap one or more exceptions within a code block and modify what the code does when handling that exception and not just shutting down the program entirely.

Until now error messages haven’t been more than mentioned, but if you have tried
out the examples you have probably seen some. There are (at least) two
distinguishable kinds of errors: syntax errors and exceptions.

8.1. Syntax Errors¶

Syntax errors, also known as parsing errors, are perhaps the most common kind of
complaint you get while you are still learning Python:

>>> while True print('Hello world')
  File "<stdin>", line 1
    while True print('Hello world')
                   ^
SyntaxError: invalid syntax

The parser repeats the offending line and displays a little ‘arrow’ pointing at
the earliest point in the line where the error was detected. The error is
caused by (or at least detected at) the token preceding the arrow: in the
example, the error is detected at the function print(), since a colon
(':') is missing before it. File name and line number are printed so you
know where to look in case the input came from a script.

8.2. Exceptions¶

Even if a statement or expression is syntactically correct, it may cause an
error when an attempt is made to execute it. Errors detected during execution
are called exceptions and are not unconditionally fatal: you will soon learn
how to handle them in Python programs. Most exceptions are not handled by
programs, however, and result in error messages as shown here:

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't convert 'int' object to str implicitly

The last line of the error message indicates what happened. Exceptions come in
different types, and the type is printed as part of the message: the types in
the example are ZeroDivisionError, NameError and TypeError.
The string printed as the exception type is the name of the built-in exception
that occurred. This is true for all built-in exceptions, but need not be true
for user-defined exceptions (although it is a useful convention). Standard
exception names are built-in identifiers (not reserved keywords).

The rest of the line provides detail based on the type of exception and what
caused it.

The preceding part of the error message shows the context where the exception
happened, in the form of a stack traceback. In general it contains a stack
traceback listing source lines; however, it will not display lines read from
standard input.

Built-in Exceptions lists the built-in exceptions and their meanings.

8.3. Handling Exceptions¶

It is possible to write programs that handle selected exceptions. Look at the
following example, which asks the user for input until a valid integer has been
entered, but allows the user to interrupt the program (using Control-C or
whatever the operating system supports); note that a user-generated interruption
is signalled by raising the KeyboardInterrupt exception.

>>> while True:
...     try:
...         x = int(input("Please enter a number: "))
...         break
...     except ValueError:
...         print("Oops!  That was no valid number.  Try again...")
...

The try statement works as follows.

  • First, the try clause (the statement(s) between the try and
    except keywords) is executed.
  • If no exception occurs, the except clause is skipped and execution of the
    try statement is finished.
  • If an exception occurs during execution of the try clause, the rest of the
    clause is skipped. Then if its type matches the exception named after the
    except keyword, the except clause is executed, and then execution
    continues after the try statement.
  • If an exception occurs which does not match the exception named in the except
    clause, it is passed on to outer try statements; if no handler is
    found, it is an unhandled exception and execution stops with a message as
    shown above.

A try statement may have more than one except clause, to specify
handlers for different exceptions. At most one handler will be executed.
Handlers only handle exceptions that occur in the corresponding try clause, not
in other handlers of the same try statement. An except clause may
name multiple exceptions as a parenthesized tuple, for example:

... except (RuntimeError, TypeError, NameError):
...     pass

A class in an except clause is compatible with an exception if it is
the same class or a base class thereof (but not the other way around — an
except clause listing a derived class is not compatible with a base class). For
example, the following code will print B, C, D in that order:

class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except D:
        print("D")
    except C:
        print("C")
    except B:
        print("B")

Note that if the except clauses were reversed (with except B first), it
would have printed B, B, B — the first matching except clause is triggered.

The last except clause may omit the exception name(s), to serve as a wildcard.
Use this with extreme caution, since it is easy to mask a real programming error
in this way! It can also be used to print an error message and then re-raise
the exception (allowing a caller to handle the exception as well):

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("OS error: {0}".format(err))
except ValueError:
    print("Could not convert data to an integer.")
except:
    print("Unexpected error:", sys.exc_info()[0])
    raise

The tryexcept statement has an optional else
clause
, which, when present, must follow all except clauses. It is useful for
code that must be executed if the try clause does not raise an exception. For
example:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except OSError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

The use of the else clause is better than adding additional code to
the try clause because it avoids accidentally catching an exception
that wasn’t raised by the code being protected by the try
except statement.

When an exception occurs, it may have an associated value, also known as the
exception’s argument. The presence and type of the argument depend on the
exception type.

The except clause may specify a variable after the exception name. The
variable is bound to an exception instance with the arguments stored in
instance.args. For convenience, the exception instance defines
__str__() so the arguments can be printed directly without having to
reference .args. One may also instantiate an exception first before
raising it and add any attributes to it as desired.

>>> try:
...     raise Exception('spam', 'eggs')
... except Exception as inst:
...     print(type(inst))    # the exception instance
...     print(inst.args)     # arguments stored in .args
...     print(inst)          # __str__ allows args to be printed directly,
...                          # but may be overridden in exception subclasses
...     x, y = inst.args     # unpack args
...     print('x =', x)
...     print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

If an exception has arguments, they are printed as the last part (‘detail’) of
the message for unhandled exceptions.

Exception handlers don’t just handle exceptions if they occur immediately in the
try clause, but also if they occur inside functions that are called (even
indirectly) in the try clause. For example:

>>> def this_fails():
...     x = 1/0
...
>>> try:
...     this_fails()
... except ZeroDivisionError as err:
...     print('Handling run-time error:', err)
...
Handling run-time error: division by zero

8.4. Raising Exceptions¶

The raise statement allows the programmer to force a specified
exception to occur. For example:

>>> raise NameError('HiThere')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: HiThere

The sole argument to raise indicates the exception to be raised.
This must be either an exception instance or an exception class (a class that
derives from Exception). If an exception class is passed, it will
be implicitly instantiated by calling its constructor with no arguments:

raise ValueError  # shorthand for 'raise ValueError()'

If you need to determine whether an exception was raised but don’t intend to
handle it, a simpler form of the raise statement allows you to
re-raise the exception:

>>> try:
...     raise NameError('HiThere')
... except NameError:
...     print('An exception flew by!')
...     raise
...
An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
NameError: HiThere

8.5. User-defined Exceptions¶

Programs may name their own exceptions by creating a new exception class (see
Classes for more about Python classes). Exceptions should typically
be derived from the Exception class, either directly or indirectly.

Exception classes can be defined which do anything any other class can do, but
are usually kept simple, often only offering a number of attributes that allow
information about the error to be extracted by handlers for the exception. When
creating a module that can raise several distinct errors, a common practice is
to create a base class for exceptions defined by that module, and subclass that
to create specific exception classes for different error conditions:

class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class InputError(Error):
    """Exception raised for errors in the input.

    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """Raised when an operation attempts a state transition that's not
    allowed.

    Attributes:
        previous -- state at beginning of transition
        next -- attempted new state
        message -- explanation of why the specific transition is not allowed
    """

    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

Most exceptions are defined with names that end in “Error,” similar to the
naming of the standard exceptions.

Many standard modules define their own exceptions to report errors that may
occur in functions they define. More information on classes is presented in
chapter Classes.

8.6. Defining Clean-up Actions¶

The try statement has another optional clause which is intended to
define clean-up actions that must be executed under all circumstances. For
example:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print('Goodbye, world!')
...
Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>

A finally clause is always executed before leaving the try
statement, whether an exception has occurred or not. When an exception has
occurred in the try clause and has not been handled by an
except clause (or it has occurred in an except or
else clause), it is re-raised after the finally clause has
been executed. The finally clause is also executed “on the way out”
when any other clause of the try statement is left via a
break, continue or return statement. A more
complicated example:

>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print("division by zero!")
...     else:
...         print("result is", result)
...     finally:
...         print("executing finally clause")
...
>>> divide(2, 1)
result is 2.0
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

As you can see, the finally clause is executed in any event. The
TypeError raised by dividing two strings is not handled by the
except clause and therefore re-raised after the finally
clause has been executed.

In real world applications, the finally clause is useful for
releasing external resources (such as files or network connections), regardless
of whether the use of the resource was successful.

8.7. Predefined Clean-up Actions¶

Some objects define standard clean-up actions to be undertaken when the object
is no longer needed, regardless of whether or not the operation using the object
succeeded or failed. Look at the following example, which tries to open a file
and print its contents to the screen.

for line in open("myfile.txt"):
    print(line, end="")

The problem with this code is that it leaves the file open for an indeterminate
amount of time after this part of the code has finished executing.
This is not an issue in simple scripts, but can be a problem for larger
applications. The with statement allows objects like files to be
used in a way that ensures they are always cleaned up promptly and correctly.

with open("myfile.txt") as f:
    for line in f:
        print(line, end="")

After the statement is executed, the file f is always closed, even if a
problem was encountered while processing the lines. Objects which, like files,
provide predefined clean-up actions will indicate this in their documentation.

Понравилась статья? Поделить с друзьями:
  • Python continue on error
  • Python connection error exception
  • Python configure error c compiler cannot create executables
  • Pxe e99 unexpected network error
  • Python check syntax error