Previously, we have learned about complex data types in Python. πππ
Now it is time to take another step. Let's quickly recall our simple script that prints greeting messages.
def print_hi(name):
greetings = f'Hi, {name}'
for _ in range(10):
print(greetings)
if __name__ == '__main__':
print_hi(f'PyCharm')
In the above script, the number of times the message is printed is fixed or hardcoded to 10. We can modify it slightly, remove that hardcode, and prompt a user for the number. Therefore, we are going to use a bunch of things that we have learned so far:
input() function to prompt the user for the number,
int() function to convert the string provided by the input() function,
range() along with for loop as the number of iterations is known upfront,
type hints to increase code readability.
First of all, we create a function that prompts a user for the number, reads the number, converts it to an integer, and finally returns it.
def get_repeat_number() -> int:
prompt: str = f'Please provide the positive integer number\n'
return int(input(prompt))
We have created the get_repeat_number() function that returns an integer. In the body, we have defined the prompt message "Please provide the positive integer number" and passed it down into the input() function. The result from the input() function is passed directly into int() for the conversion.
π‘ \n results in printing a new line on the terminal output.
Now, let's modify the print_hi() function, so we can adjust the number of times greetings message is printed.
def print_hi(name: str, times: int):
greetings: str = f'Hi, {name}'
for _ in range(times):
print(greetings)
We have slightly modified the print_hi() function and added an additional times parameter to the function. The parameter is used in the range() function to adjust the number of iterations. The final piece of this puzzle is calling these functions.
if __name__ == '__main__':
repeat: int = get_repeat_number()
print_hi(name=f'PyCharm', times=repeat)
Now, instead of calling the print_hi() function directly, first we call the get_repeat_number() function to obtain the desired number of iterations, and then we call the print_hi() function passing with the obtained number. The full code snippet is below.
def get_repeat_number() -> int:
prompt: str = f'Please provide the positive integer number\n'
return int(input(prompt))
def print_hi(name: str, times: int):
greetings: str = f'Hi, {name}'
for _ in range(times):
print(greetings)
if __name__ == '__main__':
repeat: int = get_repeat_number()
print_hi(name=f'PyCharm', times=repeat)
We are ready to run the script. We are going to pass number 10 when prompted.
β― python3 main.py
Please provide the positive integer number
10
Hi, PyCharm
Hi, PyCharm
Hi, PyCharm
Hi, PyCharm
Hi, PyCharm
Hi, PyCharm
Hi, PyCharm
Hi, PyCharm
Hi, PyCharm
Hi, PyCharm
Voila! The greetings message has been printed 10 times! But what will happen when we provide something different than a number?
Houston, We Have a Problem!
Let's try to type "python" when prompted for the number.
β― python3 main.py
Please provide the positive integer number
python
Traceback (most recent call last):
File "main.py", line 13, in <module>
repeat: int = get_repeat_number()
File "main.py", line 3, in get_repeat_number
return int(input(prompt))
ValueError: invalid literal for int() with base 10: 'python'
The script has ended with an error. The ValueError exception was thrown! The int() function couldn't convert "python" to any integer value. So what is an exception?
An exception is an event that occurs during the execution of a program, which disrupts the normal flow of the program's instructions. Exceptions are raised when an error or unexpected condition occurs, and they allow the program to handle these exceptional situations gracefully, rather than crashing.
The simplest way to handle an exceptional case correctly is to enclose code that might throw an exception with the try-except
block and provide the type of exception we want to react upon.
if __name__ == '__main__':
try:
an_integer: int = int(f'python')
except ValueError as e:
print(f'We have an exceptional situation!')
print(e)
Let's run it!
β― python3 main.py
We have an exceptional situation!
invalid literal for int() with base 10: 'python'
We have executed the code in the except
block, i.e. we have printed a message, then have printed an exception and what is more, the script hasnβt finished abnormally as we have handled the exceptional situation. Now let's look a bit deeper at how to handle exceptions in Python.
try:
# code that might throw an exception
except ExceptionType as name:
# code to be executed to handle an exception
To provide basic exception handling we can use a try
block to enclose code that might raise an exception and an except
block to handle the exception. However, there are also situations where we need to handle multiple exception types.
try:
# code that might throw an exception
except ExceptionType1 as name1:
# code to be executed to handle ExceptionType1
except ExceptionType2 as name2:
# code to be executed to handle ExceptionType2
We can handle different exceptions separately using multiple except
blocks. Additionally, we can "merge" multiple exceptions in the case when we handle them in a common fashion.
try:
# code that might throw an exception
except (ExceptionType1, ExceptionType2) as name:
# code to be executed to handle ExceptionType1 and/or ExceptionType2
So basically it boils down to handling multiple exceptions in a single except
block.
In some situations, we may need to execute a block of code regardless of whether an exception was thrown or not. We can use the finally
block for this purpose.
try:
# code that might throw an exception
except ExceptionType as name:
# code to be executed to handle an exception
finally:
# code that is always executed
It is typically used for cleanup operations or actions that must be performed no matter what. The code in the finally
block will execute even if there is a return
statement in the try
or except
block, which allows us to ensure that cleanup tasks are completed.
Another way of implementing a try-except
block involves the else
clause.
try:
# Code that may or may not raise an exception
except ExceptionType:
# Exception handling code
else:
# Code to run when no exceptions occur
The else
block is executed only if no exceptions are raised in the try
block. It is used for code that should run when no exceptions occur, providing an alternative path to follow when everything goes smoothly. The code in the else
block is skipped if an exception is raised.
Built-in exceptions
Python has plenty of built-in exceptions to cover common corner cases.
Exception Description SyntaxError Raised when there is a syntax error in our code. IndentationError Raised when there is an issue with the indentation of our code, typically in relation to block structures like loops or functions. NameError Raised when we try to access a variable or name that doesnβt exist in the current scope. TypeError Raised when an operation or function is applied to an object of an inappropriate type. ValueError Raised when a function receives an argument of the correct type but an inappropriate value. ZeroDivisionError Raised when we try to divide a number by zero. IndexError Raised when we try to access an index that is out of range for a sequence (e.g., list, tuple, string). KeyError Raised when we try to access a dictionary key that doesnβt exist. FileNotFoundError Raised when trying to open a file that does not exist. ImportError Raised when an imported module cannot be found. AttributeError Raised when we try to access an attribute or method that does not exist for an object. RuntimeError A generic error that can be raised when something unexpected happens during runtime.
Raising an exception
When writing our own code, we can also leverage exceptions to signal erroneous situations and handle them gracefully. When a specific error condition is encountered, the raise
statement is used to explicitly raise an exception. It allows us to raise
custom or built-in exceptions in our code.
def divide(x: int, y: int) -> float:
if y == 0:
raise ZeroDivisionError(f'Division by zero is not allowed.')
return x / y
try:
result: float = divide(10, 0)
except ZeroDivisionError as e:
print(f'An error occurred: {e}')
In the above code, we have defined a function that makes division. It contains the conditional statement that checks for division by zero and raises an exception when necessary.
β― python3 main.py
An error occurred: Division by zero is not allowed.
The exception was handled gracefully and the error message was printed.
We can also create our own custom exceptions by defining a new exception class (usually inheriting from the Exception class) and then raising instances of it when needed.
class MyZeroDivisionError(Exception):
def __init__(self, message: str):
super().__init__(message)
def divide(x: int, y: int) -> float:
if y == 0:
raise MyZeroDivisionError(f'Must not divide by 0!!!')
return x / y
try:
result: float = divide(10, 0)
except MyZeroDivisionError as e:
print(f'An error occurred: {e}')
In the above code, we have defined MyZeroDivisionError to signal an erroneous situation.
β― python3 main.py
An error occurred: Must not divide by 0!!!
Yet another exception has been handled gracefully. π
π‘ We will explore classes and inheritance in upcoming posts.
Prepare for unexpected
Now let's get back to our starting point.
def get_repeat_number() -> int:
prompt: str = f'Please provide the positive integer number\n'
return int(input(prompt))
def print_hi(name: str, times: int):
greetings: str = f'Hi, {name}'
for _ in range(times):
print(greetings)
if __name__ == '__main__':
repeat: int = get_repeat_number()
print_hi(name=f'PyCharm', times=repeat)
We already know that the above code is not prepared for corner cases, so it needs fixing.
We will enclose the get_repeat_number() function with a try
block as this function might raise an exception. When the exception occurs, we will print the error message and prompt the user again. Therefore we have to add the except
block to print the error message. Additionally, we will enclose the whole try-except
block with a while
loop to achieve a retry mechanism. Finally, we will add the else
clause to break
the loop if the is no error.
def get_repeat_number() -> int:
prompt: str = f'Please provide the positive integer number\n'
return int(input(prompt))
def print_hi(name: str, times: int):
greetings: str = f'Hi, {name}'
for _ in range(times):
print(greetings)
if __name__ == '__main__':
while True:
try:
repeat: int = get_repeat_number()
except ValueError:
print(f'The positive integer number must be provided!!!')
else:
break
print_hi(name=f'PyCharm', times=repeat)
We are ready to run our script!
β― python3 main.py
Please provide the positive integer number
a
The positive integer number must be provided!!!
Please provide the positive integer number
a
The positive integer number must be provided!!!
Please provide the positive integer number
d
The positive integer number must be provided!!!
Please provide the positive integer number
v
The positive integer number must be provided!!!
Please provide the positive integer number
9
Hi, PyCharm
Hi, PyCharm
Hi, PyCharm
Hi, PyCharm
Hi, PyCharm
Hi, PyCharm
Hi, PyCharm
Hi, PyCharm
Hi, PyCharm
Everything works as expected - when we don't provide an integer, an error is printed and we are prompted again, perfect!
Summary
Exceptions are events that disrupt the normal flow of a program due to errors or exceptional situations. They allow for graceful error handling and include a variety of built-in exceptions, such as SyntaxError, ValueError, and ZeroDivisionError. Exception handling is achieved using try-except
blocks, with optional else
and finally
clauses. This mechanism helps developers write more reliable and resilient code by providing a structured way to deal with unexpected issues.