Chapter 8: Extra Content#
Error Handling (try/except)#
In Python, we use the try/except block to handle errors that may occur during program execution, preventing it from being interrupted unexpectedly.
Basic example with try/except:
try:
number = int(input("Enter a number: "))
result = 10 / number
print(f"The result is {result}.")
except ZeroDivisionError:
print("Error: division by zero.")
except ValueError:
print("Error: invalid input.")
Output:
Enter a number: 2
The result is 5.0.
Enter a number: 0
Error: division by zero.
Enter a number: abc
Error: invalid input.
else and finally blocks in error handling#
In addition to try and except, Python allows the optional use of else and finally blocks to make error handling more complete.
else
Executed only if no error occurs in the try block.
finally
Executed always, with or without an error. It is useful for releasing resources, such as closing files or database connections.
Example with else and finally:
try:
number = int(input("Enter a number: "))
result = 10 / number
except ZeroDivisionError:
print("Error: division by zero.")
except ValueError:
print("Error: invalid input.")
else:
print(f"The result is {result}.")
finally:
print("Execution finished.")
Output:
Enter a number: 2
The result is 5.0.
Execution finished.
Enter a number: 0
Error: division by zero.
Execution finished.
Enter a number: xyz
Error: invalid input.
Execution finished.
Using try/except in functions#
It is common to use try/except inside functions to make them safer and more reusable.
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
return "Error: division by zero."
print(divide(10, 2)) # Output: 5.0
print(divide(10, 0)) # Output: Error: division by zero.
Output:
5.0
Error: division by zero.
Summary Table#
Block |
When it is executed |
|---|---|
|
Tries to execute code that may error |
|
Executes if an error occurs in the |
|
Executes if no error occurs in the |
|
Always executes, with or without error |
File Handling#
So far, our programs’ data lived only in memory: as soon as the program ended, everything was lost. To store information permanently (a report, a to-do list, settings), we write the data to files.
Opening a file: the open() function#
To work with a file, we first open it with open(), giving the path and the mode:
file = open("data.txt", "r", encoding="utf-8")
The mode defines what we can do with the file:
Mode |
Meaning |
If the file does not exist |
|---|---|---|
|
read |
raises an error |
|
write, erases previous content |
creates a new one |
|
append, writes at the end |
creates a new one |
|
exclusive creation |
creates; error if it already exists |
|
binary mode (combine with others, e.g. |
not applicable |
The encoding="utf-8" parameter ensures that accented and special characters are read and written correctly.
The right way: the with block#
Every open file must be closed to release system resources. Instead of calling file.close() by hand (and risking forgetting it, or the program failing before that), we use the with block, which closes the file automatically at the end, even if an error occurs:
with open("zen.txt", "w", encoding="utf-8") as file:
file.write("Beautiful is better than ugly.\n")
file.write("Explicit is better than implicit.\n")
file.write("Simple is better than complex.\n")
# outside the with, the file is already closed
Best practice - always use with
Always prefer with open(...) as ...:. It handles closing the file for you, avoiding resource leaks and corrupted files. It plays the same role as the finally block we saw in try/except.
Reading a file#
There are a few ways to read the content of a file opened in read mode. The most direct reads everything at once:
with open("zen.txt", "r", encoding="utf-8") as file:
content = file.read() # reads the whole file as a single string
print(content)
For large files, reading line by line is more efficient. The file itself is iterable:
with open("zen.txt", "r", encoding="utf-8") as file:
for line in file:
print(line.strip()) # strip() removes the trailing "\n" of each line
Output:
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Other useful methods are readline() (reads one line at a time) and readlines() (returns a list with all the lines).
Warning - write mode erases the content
Opening an existing file in "w" mode erases all previous content before writing. To add information without losing what is already there, use "a" (append) mode.
Combining with error handling#
File operations fail for many reasons (the file does not exist, lack of permission). That is why files are often combined with try/except:
try:
with open("missing.txt", "r", encoding="utf-8") as file:
print(file.read())
except FileNotFoundError:
print("Error: the file was not found.")
Output:
Error: the file was not found.
Real-world data: dates, JSON and CSV#
Real projects constantly deal with dates, with structured data (JSON), and with tables and spreadsheets (CSV). Python’s standard library already ships ready-made modules for all three.
Dates and times with datetime#
The datetime module represents dates and times and lets us do arithmetic with them:
from datetime import datetime, timedelta
now = datetime.now()
print(now) # e.g. 2024-05-04 14:30:00.123456
# Creating a specific date (year, month, day, hour, minute)
premiere = datetime(1975, 4, 3, 20, 0)
print(premiere.year, premiere.month) # 1975 4
To display a date in a readable format we use strftime(); to parse a date written as text we use strptime():
from datetime import datetime
now = datetime.now()
print(now.strftime("%d/%m/%Y at %H:%M")) # e.g. 04/05/2024 at 14:30
text = "03/04/1975"
date = datetime.strptime(text, "%d/%m/%Y")
print(date.year) # 1975
The difference between two dates becomes a timedelta, which we use for time arithmetic:
from datetime import datetime, timedelta
tv_premiere = datetime(1969, 10, 5) # first Monty Python episode
today = datetime.now()
days_elapsed = (today - tv_premiere).days
print(f"{days_elapsed} days have passed.")
in_one_week = today + timedelta(days=7)
print(in_one_week)
Tip - the main format codes
The most common strftime and strptime codes are %d (day), %m (month), %Y (4-digit year), %H (hour), %M (minute) and %S (second).
JSON: saving structured data#
JSON is the most widely used format for exchanging data between programs and on the web. The json module converts between Python dictionaries and lists and JSON text:
import json
character = {
"name": "King Arthur",
"movie": "Monty Python and the Holy Grail",
"has_horse": False,
"knights": ["Lancelot", "Galahad", "Robin"],
}
# dict -> JSON string
json_text = json.dumps(character, indent=2, ensure_ascii=False)
print(json_text)
# JSON string -> dict
back = json.loads(json_text)
print(back["name"]) # King Arthur
To read and write directly to files, use json.dump() and json.load():
# Write the dictionary to a .json file
with open("character.json", "w", encoding="utf-8") as f:
json.dump(character, f, indent=2, ensure_ascii=False)
# Read the file back into a dictionary
with open("character.json", "r", encoding="utf-8") as f:
data = json.load(f)
Tip - readable JSON with accents
Use indent=2 to produce indented JSON (easier to read) and ensure_ascii=False to keep accents and special characters instead of codes like á.
Notice the pattern: dumps and loads (with an “s” for string) work with text, while dump and load work directly with files.
CSV: tables and spreadsheets#
CSV (Comma-Separated Values) stores data as a table, with one row per record and values separated by commas. It is the format spreadsheets like Excel export. The csv module reads and writes these files:
import csv
movies = [
["title", "year"],
["Monty Python and the Holy Grail", 1975],
["Life of Brian", 1979],
["The Meaning of Life", 1983],
]
# Writing (newline="" avoids extra blank lines)
with open("movies.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerows(movies)
# Reading back
with open("movies.csv", "r", encoding="utf-8") as f:
reader = csv.reader(f)
for row in reader:
print(row)
Output:
['title', 'year']
['Monty Python and the Holy Grail', '1975']
['Life of Brian', '1979']
['The Meaning of Life', '1983']
Note that CSV does not store the data type: the year 1975 comes back from reading as the string '1975'. When each column has a name, csv.DictReader and csv.DictWriter make the code clearer, reading each row as a dictionary.
Going further - the pandas library
For more serious table analysis (filtering, statistics, plots), the pandas library is the standard in the field. The standard-library csv module is enough for simple tasks and requires no installation.
Modules, packages and virtual environments#
As programs grow, we stop writing everything in a single file. We organize the code into modules and packages, and we reuse libraries written by other people.
Modules: reusing code#
A module is simply a .py file. We have already used several standard-library modules, such as random and datetime. To use a module, just import it:
import math
print(math.sqrt(16)) # 4.0
print(math.pi) # 3.141592653589793
We can import only what we need, or give it an alias with as:
from math import sqrt, pi # imports specific names
import numpy as np # alias, common with libraries
Creating your own module#
Any .py file of yours is also a module. Suppose a file greetings.py:
# greetings.py
def hello(name):
return f"Hello, {name}!"
PI = 3.14159
In another file, in the same folder, you import it by name (without the .py):
import greetings
print(greetings.hello("Arthur")) # Hello, Arthur!
The if __name__ == "__main__" idiom#
When a file can be both run directly and imported by another, we protect the test code with if __name__ == "__main__":. This block only runs when the file is executed directly, not when it is imported:
# greetings.py
def hello(name):
return f"Hello, {name}!"
if __name__ == "__main__":
# Only runs when executing: python greetings.py
print(hello("world"))
Packages#
A package is a folder that groups several related modules. In practice, it is a directory with .py files (and, traditionally, an __init__.py file). Large libraries such as NumPy and Matplotlib are packages.
Installing libraries with pip#
Not everything ships in the standard library. Thousands of third-party libraries are available on PyPI (Python Package Index) and are installed with pip, Python’s package manager. In the terminal:
pip install requests
Once installed, the library is used like any other:
import requests
response = requests.get("https://api.github.com")
print(response.status_code) # 200
Tip - pip in Colab and Jupyter
Inside a notebook (such as Google Colab), run pip in a cell prefixed with !, like this: !pip install requests. The ! marks the line as a terminal command, not Python code.
Virtual environments (venv)#
Different projects may need different versions of the same library. To avoid conflicts, we create a virtual environment: an isolated installation of Python and its libraries, separate per project. The venv module does this. In the terminal:
# Creates the environment in a folder named .venv
python -m venv .venv
# Activates the environment
source .venv/bin/activate # Linux and macOS
.venv\Scripts\activate # Windows
# With the environment active, pip installs only into it
pip install requests
# When done, deactivate
deactivate
Best practice - one environment per project
Create a virtual environment for each project and avoid installing everything into the system Python. This way, the dependencies of one project do not interfere with another’s, and it is easy to recreate the environment on another machine (for example, by listing the libraries in a requirements.txt file).
A modern alternative: uv#
In recent years, uv has become popular as a fast replacement for pip and venv. Written in Rust by Astral, it is much faster and combines, in a single command, creating environments and installing packages.
Installation (once, on the system):
# Official installer (Linux and macOS)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Or, if you already have pip
pip install uv
In daily use, it creates the environment and installs packages much faster than pip:
# Creates the virtual environment (equivalent to python -m venv .venv)
uv venv
# Installs packages (pip-compatible interface)
uv pip install requests
uv also manages the whole project, tracking dependencies in a pyproject.toml file:
uv init my_project # creates the project structure
cd my_project
uv add requests # adds a dependency (and updates pyproject.toml)
uv run main.py # runs the script inside the project environment
Tip - why uv?
uv plays the role of venv and pip (and more) while being dozens of times faster. The uv venv and uv pip install commands are almost identical to the ones you already know, so the transition is easy. It is today the recommended tool for new projects.
Learn more
Official pip and PyPI documentation: https://pypi.org/. The full list of standard-library modules (which need no installation) is at https://docs.python.org/3/library/. uv documentation: https://docs.astral.sh/uv/.