I/O

Working with files

  • Writing programs that run on different operating systems is tricky: home directories are C:\Users\andgein on Windows, /home/andgein on Linux, and /Users/andgein on macOS
  • Operating systems can use different file systems, such as FAT32, NTFS, ext3/ext4, and APFS
  • Python provides an abstraction layer, but paths still need care: does open("C:\new\test.txt") look correct?
  • The classic approach uses open, import os, and related APIs
  • The modern approach uses import pathlib

Pathlib

from pathlib import Path
from tempfile import gettempdir

# E.g. /tmp on Linux, /var/folders/.../T on macOS,
# C:\Users\<user>\AppData\Local\Temp on Windows
path = Path(gettempdir()) / "notes.txt"

file = path.open("w")
file.write("Operator := is called a walrus operator\n")
file.write("Operator |= merges another dictionary into this dictionary in place\n")
file.close()

You must always close open files. But what happens if an error is raised between open() and close()?

Writing a file

from pathlib import Path
from tempfile import gettempdir

# E.g. /tmp on Linux, /var/folders/.../T on macOS,
# C:\Users\<user>\AppData\Local\Temp on Windows
path = Path(gettempdir()) / "notes.txt"

with path.open("w") as file:
    file.write("Operator := is called a walrus operator\n")
    file.write("Operator |= merges another dictionary into this dictionary in place\n")

Another approach

path.write_text(
    "Operator := is called a walrus operator\n"
    "Operator |= merges another dictionary into this dictionary in place\n"
)

It does not require opening or closing the file, and it replaces its entire contents

Reading a file

with path.open("r") as file:
    line = file.readline()         # Read the next line
    lines = file.readlines()       # Read remaining lines into a list

with path.open("r") as file:
    part = file.read(100)          # Read the next 100 characters
    content = file.read()          # Read all remaining text

with path.open("r") as file:
    for line in file:              # Iterate over lines
        print(line)

content = path.read_text()         # Read all text at once

By default, reading starts at the beginning of a file and advances its position with each read

Text vs bytes

  • Python opens files in text mode by default
  • Not every file stores text — so what makes a binary file different?
  • Use "rb" or "wb" for binary files, or the read_bytes() and write_bytes() shortcuts
with path.open("rb") as file:
    content = file.read()

print(content)

Bytes

  • A byte is a number from 0 to 255
  • A sequence of bytes has the type bytes and behaves much like a list of integers from 0 to 255
print(list(b"Operator := is called a walrus operator\n"))
  • Those numbers are ASCII codes for Latin letters and common characters

ASCII

CodeCharacterCodeCharacterCodeCharacterCodeCharacter
00Null32Space64@96`
01Start heading33!65A97a
02Start text34"66B98b
03End text35#67C99c
04End transmission36$68D100d
05Enquiry37%69E101e
06Acknowledge38&70F102f
07Bell39'71G103g
08Backspace40(72H104h
09Tab41)73I105i
10Line feed42*74J106j
11Vertical tab43+75K107k
12Form feed44,76L108l
13Carriage return45-77M109m
14Shift out46.78N110n
15Shift in47/79O111o
16Data escape48080P112p
17Control 149181Q113q
18Control 250282R114r
19Control 351383S115s
20Control 452484T116t
21Negative ack53585U117u
22Synchronous idle54686V118v
23End block55787W119w
24Cancel56888X120x
25End medium57989Y121y
26Substitute58:90Z122z
27Escape59;91[123{
28File separator60<92\124|
29Group separator61=93]125}
30Record separator62>94^126~
31Unit separator63?95_127Delete

Bytes

  • But there are many non-Latin letters — ß, δ, я — and other characters, such as ⌘ and ⌀
  • How do we represent them?
  • One character can span multiple bytes!
Unicode!
text = "⌘"
print(text.encode())
print(list(text.encode()))

Network

  • Exchanging data over a network is similar: we read and write bytes
  • Both sides must agree on protocols though, from TCP or UDP to DNS, TLS, HTTP, and IMAP
  • Network programming is outside this course’s scope — unfortunately

HTTP

  • HTTP (and HTTPS) is the most common kind of network communication for Python developers
  • Use it to download pages or call APIs
  • Choose your weapon: requests, httpx2, or aiohttp
import requests

response = requests.get("https://swapi.dev/api/planets/")
data = response.json()
for planet in data["results"]:
    print(planet["name"], planet["population"], planet["url"], sep=" — ")

HTTP

Let’s iterate over pages and make some other improvements

import requests

url = "https://swapi.dev/api/planets/"
while url:
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    data = response.json()
    for planet in data["results"]:
        print(planet["name"], planet["population"], planet["url"], sep=" — ")
    url = data["next"]

HTTP Serverbonus

  • You can create your own HTTP applications with Python too!
  • We’ll cover that later. For now, useful keywords are Flask, FastAPI, Django, uvicorn, and gunicorn