Inhoud
- Kennismaking
- Invoer en uitvoer
- Selecties
- Lussen en iteraties
- Lijsten
- Functies
- Foutafhandeling
- Bestanden
- Modules & venv
- Basis testen en debuggen
- Eindproject
Antwoorden — Hoofdstuk 1: Variabelen en basisberekeningen
Opmerking: Code is in het Engels, met korte Nederlandse toelichting erboven.
Antwoord 1 — Cirkel: omtrek en oppervlakte
- We lezen een float, gebruiken math.pi, en tonen resultaten met f-strings.
import math
raw = input("Enter radius: ")
radius = float(raw)
circumference = 2 * math.pi * radius
area = math.pi * (radius ** 2)
print(f"Circumference: {circumference}")
print(f"Area: {area}")
Met afronding (optioneel):
print(f"Circumference: {circumference:.2f}")
print(f"Area: {area:.2f}")
Antwoord 2 — BMI met afronding
- We vragen lengte en gewicht; ronden af op 1 decimaal in de uitvoer.
height_m = float(input("Enter height in meters: "))
weight_kg = float(input("Enter weight in kg: "))
bmi = weight_kg / (height_m ** 2)
print(f"BMI is {bmi:.1f}")
Antwoord 3 — BTW 9%
- We gebruiken een variabele voor het percentage en ronden 2 decimalen.
amount = float(input("Amount (ex VAT): "))
vat_rate = 0.09
price_inc_vat = amount * (1 + vat_rate)
print(f"Price incl VAT: {price_inc_vat:.2f}")
Antwoord 4 — Variabelen ruilen (swap)
- Twee varianten: met tijdelijke variabele, en met tuple-swap.
# With a temporary variable
a = 3
b = 7
print("Before:", a, b)
tmp = a
a = b
b = tmp
print("After:", a, b)
# Pythonic tuple swap
a = 3
b = 7
print("Before:", a, b)
a, b = b, a
print("After:", a, b)
Antwoord 5 — Kortingen stapelen
- Eerst 10% en dan 5% is niet exact gelijk aan één keer 15%.
original = 80.0
first = original * 0.90 # 10% off
second = first * 0.95 # then 5% off
single = original * 0.85 # single 15% off
print(f"Stacked discounts: {second:.2f}")
print(f"Single 15%: {single:.2f}")
print("Equal?", abs(second - single) < 1e-9)
Antwoord 6 — Kommagetallen en precisie
- 0.1 + 0.2 geeft een benadering; we tonen ook afgerond.
print(0.1 + 0.2) # likely 0.30000000000000004
print((0.1 + 0.2) == 0.3) # False
print(round(0.1 + 0.2, 2))
Uitdaging (optioneel) — Rechthoek helper
width = float(input("Width: "))
height = float(input("Height: "))
area = width * height
perimeter = 2 * (width + height)
print(f"Area: {area:.2f}")
print(f"Perimeter: {perimeter:.2f}")
Antwoorden — Hoofdstuk 2: Invoer en uitvoer
Opmerking: Code is in het Engels, met korte Nederlandse toelichting erboven of eronder.
Antwoord 1 — Begroeting
- We trimmen spaties met
.strip()en gebruiken een f-string.
first = input("First name: ").strip()
print(f"Hello, {first}!")
Antwoord 2 — Volledige naam
first = input("First name: ").strip()
last = input("Last name: ").strip()
print(f"{last}, {first}")
print(f"{first} {last}")
Antwoord 3 — Bedrag netjes tonen (komma of punt)
raw = input("Amount: ").strip().replace(",", ".")
amount = float(raw)
print(f"Amount: {amount:.2f}")
Antwoord 4 — Percentage format
rate = float(input("Rate (e.g. 0.075): ").strip().replace(",", "."))
print(f"Rate: {rate:.1%}")
Antwoord 5 — Kleine bon (uitlijning)
name = input("Item name: ").strip()
qty = int(input("Quantity: ").strip())
price = float(input("Unit price: ").strip().replace(",", "."))
total = qty * price
print(f"{'Item':<10}{'Qty':>5}{'Price':>8}{'Total':>8}")
print(f"{name:<10}{qty:>5}{price:>8.2f}{total:>8.2f}")
Antwoord 6 — Print parameters
print("a", "b", "c", sep=", ", end="!")
Antwoord 7 — Voortgangspunten
- Twee varianten: vijf losse prints of een kleine lus.
# Simple repeated prints
print(".", end="", flush=True)
print(".", end="", flush=True)
print(".", end="", flush=True)
print(".", end="", flush=True)
print(".", end="", flush=True)
# With a small loop (acceptable here)
for _ in range(5):
print(".", end="", flush=True)
Antwoord 8 — Foutmelding via stderr
import sys
raw = input("Enter integer: ").strip()
try:
n = int(raw)
print(f"OK: {n}")
except ValueError:
sys.stderr.write("Warning: not an integer\n")
Antwoord 9 — Unicode check
# Expect to see the characters rendered correctly in a UTF-8 capable terminal
print("café • naïve • € • λ")
Antwoord 10 — Command-line argument (teaser)
import sys
name = sys.argv[1] if len(sys.argv) > 1 else "world"
print(f"Hello, {name}!")
Uitdaging (optioneel) — Totaal met BTW en korting
raw_amount = input("Amount: ").strip().replace(",", ".")
amount = float(raw_amount)
raw_discount = input("Discount (0-100): ").strip().replace(",", ".")
discount_pct = float(raw_discount)
after_discount = amount * (1 - discount_pct / 100)
vat_rate = 0.21
vat_value = after_discount * vat_rate
final_total = after_discount + vat_value
print(f"Amount: {amount:>8.2f}")
print(f"Discount: {discount_pct:>8.1f}%")
print(f"After discount: {after_discount:>8.2f}")
print(f"VAT (21%): {vat_value:>8.2f}")
print(f"Total: {final_total:>8.2f}")
Antwoorden — Hoofdstuk 3: Selecties (if / elif / else)
Opmerking: Code is in het Engels, met korte Nederlandse toelichting erboven of eronder.
Antwoord 1 — Basis if
- Deelbaarheid door 2 bepaalt even/odd.
n = int(input("Enter an integer: "))
if n % 2 == 0:
print("even")
else:
print("odd")
Antwoord 2 — Leeftijdscategorieën
- Volgorde van drempels is belangrijk: strengste eerst of van hoog naar laag.
age = int(input("Age: "))
if age < 18:
print("minor")
elif age <= 64:
print("adult")
else:
print("senior")
Antwoord 3 — Rapportcijfer naar label
- Zorg dat intervallen elkaar niet overlappen en samen 0..10 afdekken.
score = float(input("Score (0-10): "))
if score < 5.5:
label = "insufficient"
elif score < 7.0:
label = "sufficient"
elif score < 8.5:
label = "good"
else:
label = "excellent"
print(label)
Antwoord 4 — Login-indicatie
- Eenvoudige en/conditie; geen echte beveiliging.
username = input("Username: ")
password = input("Password: ")
if username == "admin" and password == "secret":
print("Welcome!")
else:
print("Access denied")
Antwoord 5 — Korting met lidmaatschap
- Volgorde: eerst de strengere (lid + bedrag), dan de alternatieve drempel.
amount = float(input("Amount: "))
member = input("Member? (yes/no): ").strip().lower() == "yes"
if member and amount >= 100:
discount = 0.10
elif amount >= 150:
discount = 0.05
else:
discount = 0.0
final_price = amount * (1 - discount)
print(f"Discount: {discount*100:.0f}%")
print(f"Final price: {final_price:.2f}")
Antwoord 6 — Truthiness en invoer (ternary)
query = input("Search query: ").strip()
msg = "Empty query" if not query else f"Searching for '{query}'..."
print(msg)
Antwoord 7 — Kettingvergelijking 10..20
x = int(input("x: "))
print(10 <= x <= 20)
Antwoord 8 — Guard clause (functie)
- Vroeg stoppen bij ongeldige
weight_kgen dan per land beslissen.
def shipping_cost(country: str, weight_kg: float) -> float:
if weight_kg <= 0:
raise ValueError("Weight must be positive")
if country == "NL":
return 4.50 if weight_kg <= 2 else 6.75
elif country == "BE":
return 5.20 if weight_kg <= 2 else 7.40
else:
return 9.90
if __name__ == "__main__":
print(shipping_cost("NL", 1.5)) # 4.50
print(shipping_cost("BE", 3.0)) # 7.40
print(shipping_cost("DE", 0.5)) # 9.90
Antwoord 9 — Wachtwoordsterkte-indicatie (simpel)
pwd = input("Password: ")
length = len(pwd)
if length < 6:
label = "weak"
elif length <= 9:
label = "medium"
else:
label = "strong"
print(f"{label} (length={length})")
Antwoord 10 — Netto prijs met btw‑optie
amount = float(input("Amount: "))
mode = input("Type (incl/excl): ").strip().lower()
VAT = 0.21
if mode == "incl":
net = amount / (1 + VAT)
print(f"Net (ex VAT): {net:.2f}")
elif mode == "excl":
gross = amount * (1 + VAT)
print(f"Gross (incl VAT): {gross:.2f}")
else:
print("Unknown type; use 'incl' or 'excl'.")
Uitdaging (optioneel) — Kalenderdag naar type dag
day = input("Day name: ").strip().lower()
weekend = {"saturday", "sunday"}
label = "weekend" if day in weekend else "weekday"
print(label)
Antwoorden — Hoofdstuk 4: Lussen en iteraties
Opmerking: Code is in het Engels, met korte Nederlandse toelichting erboven of eronder.
Antwoord 1 — Tellen met range
range(1, 11)geeft 1..10 (stop is exclusief). We bouwen één regel metend=" ".
for i in range(1, 11):
print(i, end=" ")
print() # newline
Antwoord 2 — Som 1..n (accumulatie)
n = int(input("n: "))
total = 0
for i in range(1, n + 1):
total += i
print(total)
Antwoord 3 — While-teller omlaag
n = 5
while n >= 1:
print(n)
n -= 1
print("Liftoff!")
Antwoord 4 — Alleen even getallen (continue)
n = 0
while n < 20:
n += 1
if n % 2 != 0:
continue
print(n)
Antwoord 5 — Eerste veelvoud vinden (for-else)
- Bij
breakslaan we hetelse-deel over; zonderbreakdraaitelse.
for i in range(1, 51):
if i % 7 == 0 and i % 5 == 0:
print("Found:", i)
break
else:
print("No match")
Antwoord 6 — Genummerde lijst (enumerate)
names = ["Ava", "Bo", "Cid", "Dia"]
for idx, name in enumerate(names, start=1):
print(f"{idx}. {name}")
Antwoord 7 — Itereren over dict
user = {"name": "Liam", "age": 31, "city": "Rotterdam"}
for key, value in user.items():
print(f"{key}: {value}")
Antwoord 8 — Tafel van vermenigvuldiging (genest)
for a in range(1, 11):
for b in range(1, 11):
print(f"{a*b:>4}", end="")
print()
Antwoord 9 — Invoer tot leeg (sentinel)
count = 0
while True:
line = input().strip()
if line == "":
break
count += 1
print(f"Count: {count}")
Antwoord 10 — Filter en transformeer
nums = [3, 8, 1, 7, 2, 10, 5]
# filter evens
evens: list[int] = []
for x in nums:
if x % 2 == 0:
evens.append(x)
# transform squares
squares: list[int] = []
for x in nums:
squares.append(x * x)
print(evens)
print(squares)
Uitdaging (optioneel) — FizzBuzz 1..100
for i in range(1, 101):
if i % 15 == 0:
print("FizzBuzz")
elif i % 3 == 0:
print("Fizz")
elif i % 5 == 0:
print("Buzz")
else:
print(i)
Antwoorden — Hoofdstuk 5: Lijsten (list)
Opmerking: Code is in het Engels, met korte Nederlandse toelichting erboven of eronder.
Antwoord 1 — Basis maken en lezen
fruits = ["apple", "banana", "cherry"]
print(len(fruits))
print(fruits[0]) # first
print(fruits[-1]) # last via negative index
Antwoord 2 — Slices
nums = [10, 20, 30, 40, 50, 60]
print(nums[1:4]) # [20, 30, 40]
print(nums[:3]) # [10, 20, 30]
print(nums[3:]) # [40, 50, 60]
rev = nums[::-1]
print(rev)
Antwoord 3 — Mutatie met index en slice
items = [1, 2, 3, 4, 5]
items[1] = 20
print(items) # [1, 20, 3, 4, 5]
items[2:4] = [7, 8, 9] # replaces 3,4 by 7,8,9
print(items) # [1, 20, 7, 8, 9, 5]
items[2:2] = [100, 101] # insert at position 2
print(items) # [1, 20, 100, 101, 7, 8, 9, 5]
Antwoord 4 — Toevoegen en verwijderen
colors = ["red"]
colors.append("green")
colors.extend(["blue", "green"]) # now ["red","green","blue","green"]
colors.remove("green") # removes first "green"
last = colors.pop() # removes last element
print("popped:", last)
print(colors)
Antwoord 5 — Zoeken en membership
letters = ["a", "b", "a", "c", "a"]
print("b" in letters) # True
print(letters.index("a")) # 0
print(letters.count("a")) # 3
Antwoord 6 — Iteratie en enumerate
names = ["Ava", "Bo", "Cid"]
for n in names:
print(n)
for i, n in enumerate(names, start=1):
print(f"{i}. {n}")
Antwoord 7 — Sorteren met key en reverse
names = ["bo", "Ava", "cid", "Dia"]
# case-insensitive alphabetical
case_ins = sorted(names, key=str.lower)
print(case_ins)
# by length desc, then alphabetical (case-insensitive) for ties
by_len_desc = sorted(names, key=lambda s: (len(s), s.lower()), reverse=True)
print(by_len_desc)
Antwoord 8 — Kopiëren vs. aliasing
original = [1, 2, 3]
alias = original # no copy
alias.append(4)
print(original, alias) # both show [1,2,3,4]
copy = original[:] # shallow copy
copy.append(5)
print(original) # [1, 2, 3, 4]
print(copy) # [1, 2, 3, 4, 5]
Antwoord 9 — 2D‑lijst: vermenigvuldigingsvalkuil
# wrong way (rows alias the same inner list)
m = [[0] * 3] * 2
m[0][0] = 1
print(m) # [[1, 0, 0], [1, 0, 0]]
# correct way (independent rows)
rows = 2; cols = 3
m = [[0 for _ in range(cols)] for _ in range(rows)]
m[0][0] = 1
print(m) # [[1, 0, 0], [0, 0, 0]]
Antwoord 10 — Lijstcomprehensies
squares = [x * x for x in range(11)]
print(squares)
evens = [x for x in range(21) if x % 2 == 0]
print(evens)
labels = ["even" if x % 2 == 0 else "odd" for x in range(6)]
print(labels)
Uitdaging (optioneel) — Unieke elementen met behoud van volgorde
def unique_in_order(items):
seen = set()
result = []
for x in items:
if x not in seen:
seen.add(x)
result.append(x)
return result
print(unique_in_order(["a", "b", "a", "c", "b"])) # ['a', 'b', 'c']
Antwoorden — Hoofdstuk 6: Functies en modulair denken
Opmerking: Code is in het Engels, met korte Nederlandse toelichting erboven of eronder.
Antwoord 1 — Begroeting als functie
from typing import Final
def greet(name: str) -> str:
return f"Hello, {name}!"
if __name__ == "__main__":
print(greet("Ava"))
print(greet("Bo"))
Antwoord 2 — Oppervlakte cirkel (docstring + hints)
import math
def circle_area(radius: float) -> float:
"""Return circle area given a radius.
- Uses math.pi.
- radius must be >= 0 (negative radii don't make physical sense).
"""
if radius < 0:
raise ValueError("radius must be >= 0")
return math.pi * (radius ** 2)
if __name__ == "__main__":
for r in (0.0, 1.5, 2.0):
print(r, circle_area(r))
Antwoord 3 — Standaard exponent
def power(base: float, exp: int = 2) -> float:
return base ** exp
print(power(3)) # 9 (default exp=2)
print(power(3, 3)) # 27
print(power(base=2, exp=5))# 32
Antwoord 4 — Vroege return (guard)
def safe_div(a: float, b: float) -> float | None:
if b == 0:
return None
return a / b
print(safe_div(10, 2)) # 5.0
print(safe_div(3, 0)) # None
Antwoord 5 — Meerdere waarden (min, max, avg)
- We kiezen voor
raise ValueErrorbij lege input en documenteren dat.
from typing import Iterable, Tuple
def min_max_avg(values: list[float]) -> tuple[float, float, float]:
"""Return (min, max, avg) for non-empty list of floats.
Raises ValueError if values is empty.
"""
if not values:
raise ValueError("values must not be empty")
return min(values), max(values), sum(values) / len(values)
print(min_max_avg([3.0, 8.0, 2.0, 7.0]))
Antwoord 6 — Invoer-helper zonder print in kernfunctie
def parse_float(text: str) -> float | None:
try:
return float(text.strip().replace(",", "."))
except ValueError:
return None
if __name__ == "__main__":
raw = input("Enter a number: ")
value = parse_float(raw)
if value is None:
print("Please enter a valid number (use digits, comma or dot).")
else:
print(f"You entered: {value}")
Antwoord 7 — Muteren vs. kopiëren
def append_exclamation_inplace(items: list[str]) -> None:
items.append("!")
def with_exclamation_copy(items: list[str]) -> list[str]:
result = list(items)
result.append("!")
return result
if __name__ == "__main__":
words = ["hello"]
append_exclamation_inplace(words)
print("after inplace:", words) # mutated
words2 = ["hello"]
out = with_exclamation_copy(words2)
print("original:", words2) # unchanged
print("returned:", out)
Antwoord 8 — Scope (LEGB) demonstratie
x = 10 # global
def demo() -> None:
x = 5 # local shadows global x inside this function
print("inside demo:", x)
if __name__ == "__main__":
demo()
print("outside:", x)
# Opmerking: binnen demo() zien we de lokale x=5; buiten blijft x=10.
Antwoord 9 — Docstring en help
def slugify(text: str) -> str:
"""Convert text to a lowercase, dash-separated slug.
Example:
>>> slugify("Hello World!")
'hello-world'
"""
return "-".join(
"".join(ch.lower() for ch in text if ch.isalnum() or ch.isspace()).split()
)
if __name__ == "__main__":
print(slugify.__doc__)
help(slugify) # rendering depends on environment
print(slugify("Hello World!"))
Antwoord 10 — Mutabel standaardargument (slecht vs. goed)
# BAD: default list is created once and reused across calls
def add_item_bad(item, bucket=[]):
bucket.append(item)
return bucket
print(add_item_bad(1)) # [1]
print(add_item_bad(2)) # [1, 2] <-- unexpected accumulation across calls
# GOOD: use None sentinel and create a new list inside
def add_item(item, bucket=None):
if bucket is None:
bucket = []
bucket.append(item)
return bucket
print(add_item(1)) # [1]
print(add_item(2)) # [2] -- independent default per call
Uitdaging (optioneel) — Refactor naar functies
from typing import Iterable
def line_total(qty: int, price: float) -> float:
return qty * price
def format_line(name: str, qty: int, price: float) -> str:
total = line_total(qty, price)
return f"{name:<12}{qty:>4}{price:>9.2f}{total:>9.2f}"
def grand_total(items: list[tuple[str, int, float]]) -> float:
return sum(line_total(q, p) for _, q, p in items)
def render_receipt(items: list[tuple[str, int, float]]) -> str:
lines = [f"{'Item':<12}{'Qty':>4}{'Price':>9}{'Total':>9}"]
for name, qty, price in items:
lines.append(format_line(name, qty, price))
lines.append(f"{'Grand total:':<25}{grand_total(items):>9.2f}")
return "\n".join(lines)
if __name__ == "__main__":
items = [("apple", 3, 0.60), ("banana", 2, 0.50), ("cherry", 10, 0.20)]
print(render_receipt(items))
Antwoorden — Hoofdstuk 7: Foutafhandeling (exceptions)
Opmerking: Code is in het Engels, met korte Nederlandse toelichting erboven of eronder.
Antwoord 1 — safe_int helper
def safe_int(text: str, default: int | None = None) -> int | None:
try:
return int(text)
except ValueError:
return default
if __name__ == "__main__":
print(safe_int("123")) # 123
print(safe_int("abc")) # None
print(safe_int("abc", 0)) # 0
Antwoord 2 — read_float met komma
def read_float(prompt: str) -> float:
while True:
raw = input(prompt).strip().replace(",", ".")
try:
return float(raw)
except ValueError:
print("Please enter a valid number (digits, comma or dot).")
- Blijft vragen tot het lukt; geen exception lekt naar de aanroeper.
Antwoord 3 — Calculator met ^ en nette fouten
def calc(a: float, op: str, b: float) -> float:
if op == "+":
return a + b
if op == "-":
return a - b
if op == "*":
return a * b
if op == "/":
if b == 0:
raise ZeroDivisionError("division by zero")
return a / b
if op == "^":
return a ** b
raise ValueError(f"unknown operator: {op}")
if __name__ == "__main__":
try:
a = float(input("a: ").strip().replace(",", "."))
op = input("op (+ - * / ^): ").strip()
b = float(input("b: ").strip().replace(",", "."))
print("=", calc(a, op, b))
except ZeroDivisionError as e:
print("Error:", e)
except ValueError as e:
print("Error:", e)
Antwoord 4 — else/finally en with
# Variant met try/except/else/finally
f = None
try:
f = open("notes.txt", "r", encoding="utf-8")
text = f.read()
except FileNotFoundError:
print("File not found")
else:
print("Length:", len(text))
finally:
if f is not None:
f.close()
print("Done")
# Equivalent met contextmanager
try:
with open("notes.txt", "r", encoding="utf-8") as f:
text = f.read()
print("Length:", len(text)) # only runs if open+read succeeded
except FileNotFoundError:
print("File not found")
print("Done")
Antwoord 5 — ConfigError en load_config
from pathlib import Path
class ConfigError(Exception):
"""Raised when configuration is invalid or missing."""
def load_config(path: str) -> dict[str, str]:
p = Path(path)
if not p.exists():
raise ConfigError(f"config file not found: {p}")
result: dict[str, str] = {}
try:
for i, line in enumerate(p.read_text(encoding="utf-8").splitlines(), start=1):
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" not in line:
raise ConfigError(f"invalid line {i}: {line!r}")
key, value = line.split("=", 1)
key = key.strip(); value = value.strip()
if not key:
raise ConfigError(f"empty key at line {i}")
if key in result:
raise ConfigError(f"duplicate key at line {i}: {key}")
result[key] = value
return result
except OSError as e:
# I/O issues; chain for context
raise ConfigError(f"failed reading config: {e}") from e
Antwoord 6 — Re‑raise met logging
import logging
logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)
def risky_div(a: float, b: float) -> float:
return a / b
if __name__ == "__main__":
try:
print(risky_div(1, 0))
except Exception:
log.exception("Risky division failed") # logs traceback
raise # re-raise so callers still see the error
Antwoord 7 — Exception chaining
def parse_price(text: str) -> int:
try:
return int(text)
except ValueError as e:
raise ValueError(f"invalid price '{text}'") from e
if __name__ == "__main__":
print(parse_price("12"))
print(parse_price("abc")) # will show chained traceback
Antwoord 8 — Recoverable vs. terminal met teller
import sys
errors = 0
while True:
raw = input("Amount (empty to stop): ").strip()
if raw == "":
break
try:
amount = float(raw.replace(",", "."))
print(f"OK: {amount:.2f}")
errors = 0 # reset on success
except ValueError:
errors += 1
print("Invalid amount, try again (digits, comma or dot).")
if errors >= 3:
print("Too many errors in a row — aborting.")
sys.exit(1)
Antwoord 9 — Te brede excepties aanscherpen
# BAD: catches everything, hides the real cause, and the try-block is too large
# try:
# amount = float(input("Amount: "))
# print(100 / amount)
# except Exception:
# print("Something went wrong")
# GOOD: small risky parts and specific exceptions
raw = input("Amount: ").strip()
try:
amount = float(raw)
except ValueError:
print("Please enter a valid number.")
else:
try:
print(100 / amount)
except ZeroDivisionError:
print("Division by zero is not allowed.")
- Specifieke excepties geven betere feedback en je overschaduwt geen onverwachte fouten.
- Kleine
try‑scopes maken het duidelijker welke regel kon falen.
Antwoord 10 — Logging i.p.v. print
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
log = logging.getLogger("app")
try:
with open("missing.txt", "r", encoding="utf-8") as f:
print(f.read())
except FileNotFoundError:
log.error("File not found: missing.txt")
# logging.exception example (auto-includes traceback)
try:
1 / 0
except ZeroDivisionError:
logging.exception("Computation failed")
Uitdaging (optioneel) — Typed config (schets)
class ConfigError(Exception):
pass
TRUE_SET = {"true", "yes", "1"}
FALSE_SET = {"false", "no", "0"}
def _coerce(value: str):
v = value.strip()
low = v.lower()
if low in TRUE_SET:
return True
if low in FALSE_SET:
return False
try:
if "." in v:
return float(v)
return int(v)
except ValueError:
return v # leave as string
from pathlib import Path
def load_typed_config(path: str) -> dict[str, object]:
p = Path(path)
if not p.exists():
raise ConfigError(f"config file not found: {p}")
result: dict[str, object] = {}
for i, line in enumerate(p.read_text(encoding="utf-8").splitlines(), start=1):
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" not in line:
raise ConfigError(f"invalid line {i}: {line!r}")
key, value = line.split("=", 1)
key = key.strip(); value = value.strip()
if not key:
raise ConfigError(f"empty key at line {i}")
if key in result:
raise ConfigError(f"duplicate key at line {i}: {key}")
result[key] = _coerce(value)
return result
Antwoorden — Hoofdstuk 8: Bestanden lezen en schrijven
Opmerking: Code is in het Engels, met korte Nederlandse toelichting erboven of eronder.
Antwoord 1 — Merge .txt bestanden
from pathlib import Path
def merge_txt(dir_path: str | Path, out_name: str = "all.txt") -> None:
d = Path(dir_path)
out = d / out_name
parts: list[str] = []
for p in sorted(d.glob("*.txt")):
if p.name == out_name:
continue # skip the output file itself if present
text = p.read_text(encoding="utf-8", errors="replace")
parts.append(text.rstrip("\n"))
out.write_text("\n\n".join(parts) + ("\n" if parts else ""), encoding="utf-8")
if __name__ == "__main__":
merge_txt("")
Antwoord 2 — tail(path, n)
from pathlib import Path
from collections import deque
def tail(path: str | Path, n: int) -> list[str]:
# Simple approach: read all lines and slice. For medium files this is ok.
lines = Path(path).read_text(encoding="utf-8", errors="replace").splitlines()
return lines[-n:] if n > 0 else []
if __name__ == "__main__":
print(tail("poem.txt", 5))
Antwoord 3 — copy_file (binair)
from pathlib import Path
def copy_file(src: str | Path, dst: str | Path, chunk_size: int = 8192) -> None:
src_p = Path(src)
dst_p = Path(dst)
with src_p.open("rb") as f_in, dst_p.open("wb") as f_out:
while True:
chunk = f_in.read(chunk_size)
if not chunk:
break
f_out.write(chunk)
if __name__ == "__main__":
copy_file("image.jpg", "copy.jpg")
Antwoord 4 — grep‑achtig filter (case‑insensitive)
from pathlib import Path
def grep_ci(path: str | Path, needle: str) -> None:
n = needle.lower()
with Path(path).open("r", encoding="utf-8", errors="replace") as f:
for line in f:
if n in line.lower():
print(line.rstrip())
if __name__ == "__main__":
grep_ci("app.log", "error")
Antwoord 5 — CSV lezen (naïef) en net printen
from pathlib import Path
def print_items_table(csv_path: str | Path) -> None:
rows: list[tuple[str, int, float]] = []
with Path(csv_path).open("r", encoding="utf-8", newline="") as f:
for line in f:
name, qty, price = line.rstrip("\n").split(",")
rows.append((name.strip(), int(qty), float(price)))
total = 0.0
print(f"{'Item':<12}{'Qty':>5}{'Price':>10}{'Total':>10}")
for name, qty, price in rows:
line_total = qty * price
total += line_total
print(f"{name:<12}{qty:>5}{price:>10.2f}{line_total:>10.2f}")
print(f"{'Grand total:':<27}{total:>10.2f}")
if __name__ == "__main__":
print_items_table("items.csv")
Antwoord 6 — JSON schrijven en lezen
from pathlib import Path
import json
def write_config(path: Path, data: dict) -> None:
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
def read_config(path: Path) -> dict:
text = path.read_text(encoding="utf-8")
return json.loads(text)
if __name__ == "__main__":
cfg_path = Path("config.json")
data = {"name": "Ava", "age": 30}
try:
write_config(cfg_path, data)
loaded = read_config(cfg_path)
print(loaded.get("name"))
except FileNotFoundError:
print("Config file not found")
except json.JSONDecodeError as e:
print("Invalid JSON:", e)
Antwoord 7 — Atomisch schrijven
from pathlib import Path
import os
def atomic_write_text(path: Path, text: str) -> None:
tmp = path.with_suffix(path.suffix + ".tmp")
tmp.write_text(text, encoding="utf-8")
os.replace(tmp, path)
if __name__ == "__main__":
atomic_write_text(Path("data.txt"), "new content\n")
Antwoord 8 — Newline normalisatie
from pathlib import Path
def write_unix_newlines(path: Path, lines: list[str]) -> None:
with path.open("w", encoding="utf-8", newline="\n") as f:
f.writelines([line.rstrip("\n") + "\n" for line in lines])
if __name__ == "__main__":
write_unix_newlines(Path("out.txt"), ["A", "B", "C"])
Antwoord 9 — Safe read_text
from pathlib import Path
from typing import Optional
def safe_read_text(path: Path) -> Optional[str]:
try:
return path.read_text(encoding="utf-8")
except FileNotFoundError:
print("File not found:", path)
return None
except PermissionError:
print("No permission to read:", path)
return None
if __name__ == "__main__":
print(safe_read_text(Path("maybe.txt")))
Antwoord 10 — Directory listing met groottes
from pathlib import Path
def list_dir(path: str | Path) -> None:
p = Path(path)
files = 0
total_bytes = 0
for entry in p.iterdir():
if entry.is_dir():
kind = "DIR"
size_str = ""
else:
kind = "FILE"
size = entry.stat().st_size
total_bytes += size
files += 1
size_str = f" {size:>8d} B"
print(f"{kind:4} {entry.name}{size_str}")
print(f"-- Files: {files}, Total bytes: {total_bytes}")
if __name__ == "__main__":
list_dir(".")
Uitdaging (optioneel) — Mini To‑Do CLI met bestand
from pathlib import Path
import sys
TODO = Path("todo.txt")
def add_item(text: str) -> None:
with TODO.open("a", encoding="utf-8") as f:
f.write(text.strip() + "\n")
def list_items() -> None:
if not TODO.exists():
print("No items yet")
return
with TODO.open("r", encoding="utf-8") as f:
for i, line in enumerate(f, start=1):
print(f"{i}. {line.rstrip()}")
def done(n: int) -> None:
# choice: remove the line permanently
if not TODO.exists():
return
lines = TODO.read_text(encoding="utf-8").splitlines()
if 1 <= n <= len(lines):
del lines[n - 1]
TODO.write_text("\n".join(lines) + ("\n" if lines else ""), encoding="utf-8")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: todo.py [add <text>|list|done <n>]")
raise SystemExit(1)
cmd = sys.argv[1]
if cmd == "add" and len(sys.argv) >= 3:
add_item(" ".join(sys.argv[2:]))
elif cmd == "list":
list_items()
elif cmd == "done" and len(sys.argv) >= 3:
try:
n = int(sys.argv[2])
except ValueError:
print("Please provide an integer index for 'done'.")
raise SystemExit(1)
done(n)
else:
print("Unknown command or missing arguments.")
raise SystemExit(1)
Antwoorden — Hoofdstuk 9: Modules en virtuele omgevingen
Opmerking: Code is in het Engels, met korte Nederlandse toelichtingen in comments.
Antwoord 1 — Eigen module importeren (temperature)
# temperature.py
def c_to_f(c: float) -> float:
return (c * 9/5) + 32
def f_to_c(f: float) -> float:
return (f - 32) * 5/9
# app.py
from temperature import c_to_f, f_to_c
print(c_to_f(20.0)) # 68.0
print(f_to_c(32.0)) # 0.0
Antwoord 2 — import vs from-import
# variant A
import math
print(math.pi)
# variant B
from math import pi
print(pi)
# Comment: with 'import math' you use the qualified name math.pi (clearer where it comes from).
# With 'from math import pi' the name 'pi' is imported directly into your module namespace.
Antwoord 3 — name == "main"
# temperature.py (extended)
import sys
def c_to_f(c: float) -> float:
return (c * 9/5) + 32
def f_to_c(f: float) -> float:
return (f - 32) * 5/9
if __name__ == "__main__":
c = float(sys.argv[1]) if len(sys.argv) > 1 else 0.0
print(c_to_f(c))
Antwoord 4 — Het importpad bekijken
# show_path.py
import sys
for p in sys.path:
print(p)
# Comment (NL): De eerste entry is vaak de directory van het actieve script of '',
# wat de current working directory representeert. Startpositie kan verschillen per aanroeplocatie.
Antwoord 5 — Eenvoudig package calc/
# calc/__init__.py
__all__ = ["add", "sub", "mul", "div"]
from .ops import add, sub, mul, div
# calc/ops.py
def add(a, b):
return a + b
def sub(a, b):
return a - b
def mul(a, b):
return a * b
def div(a, b):
if b == 0:
raise ZeroDivisionError("division by zero")
return a / b
# main.py
from calc.ops import add, div
print(add(2, 3))
print(div(10, 2))
Antwoord 6 — Relatieve import binnen package
# calc/cli.py
from .ops import add
def main() -> None:
import sys
if len(sys.argv) >= 3:
a = float(sys.argv[1]); b = float(sys.argv[2])
else:
a, b = 2.0, 3.0
print(add(a, b))
if __name__ == "__main__":
main()
# Run as module: python -m calc.cli 4 5
Antwoord 7 — Vermijd star-import
# BAD
# from math import *
# GOOD (explicit)
from math import sqrt, pi
print(sqrt(9), pi)
# or
import math
print(math.sqrt(9), math.pi)
# Comment: '*' vervuilt je namespace en kan namen overschrijven, wat debugging lastiger maakt.
Antwoord 8 — Stdlib mini-taken
import random
from datetime import datetime
from pathlib import Path
print(random.choice(["red", "green", "blue"]))
print(datetime.now().isoformat(timespec="seconds"))
print("notes.txt exists?", (Path("notes.txt").exists()))
Antwoord 9 — pip/venv (conceptueel) en requests
# Commands (run in a shell):
python -m venv .venv
# macOS/Linux
source .venv/bin/activate
# Windows PowerShell
# .venv\Scripts\Activate.ps1
python -m pip install requests
python -m pip freeze > requirements.txt
# fetch.py (run inside an activated venv)
import requests
resp = requests.get("https://httpbin.org/get", timeout=10)
print(resp.status_code)
print(resp.headers.get("Content-Type"))
Antwoord 10 — Naamconflict met stdlib
# random.py (your file) -- DON'T DO THIS NAME
def foo():
return "bar"
# other_script.py
import random
print(random.choice([1, 2, 3])) # This will fail or import your local random.py instead of stdlib
# Fix: rename your file to myrandom.py and try again
import random
print(random.choice([1, 2, 3])) # works, using stdlib random
# Comment (NL): De bestandsnaam 'random.py' overschaduwde de standaardbibliotheekmodule met dezelfde naam.
# Door te hernoemen valt de import weer naar de stdlib.
Uitdaging — greet package met CLI
# greet/__init__.py
__all__ = ["hello"]
def hello(name: str) -> str:
return f"Hello, {name}!"
# greet/cli.py
from . import hello
def main() -> None:
import sys
name = sys.argv[1] if len(sys.argv) > 1 else "world"
print(hello(name))
if __name__ == "__main__":
main()
# Usage: python -m greet.cli Ava (ensure PYTHONPATH includes parent dir or install in venv)
Basis testen en debuggen
Antwoorden — Hoofdstuk 12: Eindproject (projectuitwerkingen)
Opmerking:
- Uitleg in het Nederlands; alle code is in het Engels, conform het boek.
- Per projectoptie vind je een referentie‑implementatie die aan de eisen voldoet:
- Minstens 2 modules en 5 functies.
- Persistente opslag waar gevraagd (highscore/todo/questions).
- Basistests: minimaal 5 asserts of 2 unittest‑cases.
- Een korte README‑schets voor studenten.
- Je mag de structuur aanpassen; dit zijn compacte, didactische voorbeelden.
Inhoud
- Project A: Number Guessing Game (met highscore‑bestand)
- Project B: To‑Do CLI (persistente opslag + eenvoudige tests)
- Project C: Tekst‑gebaseerde quiz (vragen uit JSON)
Project A — Number Guessing Game (highscore)
Structuur (voorbeeld):
- guessgame/
- init.py
- game.py (spelloop, input/feedback)
- storage.py (highscore lezen/schrijven)
- cli.py (entry point)
- tests_basic.py (eenvoudige asserts/unittests)
Implementatie (kerncode):
# guessgame/storage.py
from pathlib import Path
from typing import Optional
HIGHSCORE = Path("highscore.txt")
def read_highscore() -> Optional[int]:
if not HIGHSCORE.exists():
return None
try:
txt = HIGHSCORE.read_text(encoding="utf-8").strip()
return int(txt) if txt else None
except ValueError:
return None
def write_highscore(score: int) -> None:
# lower is better (attempts)
prev = read_highscore()
if prev is None or score < prev:
HIGHSCORE.write_text(str(score), encoding="utf-8")
# guessgame/game.py
import random
from typing import Callable, Optional
from .storage import read_highscore, write_highscore
Feedback = str
def pick_secret(low: int = 1, high: int = 100, rng: Optional[random.Random] = None) -> int:
r = rng or random
return r.randint(low, high)
def feedback(secret: int, guess: int) -> Feedback:
if guess < secret:
return "Higher"
if guess > secret:
return "Lower"
return "Correct"
def play_round(secret: int, reader: Callable[[str], str], writer: Callable[[str], None]) -> int:
attempts = 0
while True:
raw = reader("Your guess: ").strip()
if not raw:
writer("Bye!")
break
try:
g = int(raw)
except ValueError:
writer("Enter an integer.")
continue
attempts += 1
fb = feedback(secret, g)
writer(fb)
if fb == "Correct":
break
return attempts
def maybe_update_highscore(attempts: int) -> None:
write_highscore(attempts)
# guessgame/cli.py
from .game import pick_secret, play_round, maybe_update_highscore
from .storage import read_highscore
def main() -> None:
import sys
low, high = 1, 100
if len(sys.argv) == 3:
low = int(sys.argv[1]); high = int(sys.argv[2])
secret = pick_secret(low, high)
print(f"Guess the number between {low} and {high} (empty to quit)")
attempts = play_round(secret, input, print)
if attempts > 0:
print(f"You used {attempts} attempts.")
maybe_update_highscore(attempts)
hs = read_highscore()
if hs is not None:
print(f"Best so far: {hs} attempts")
if __name__ == "__main__":
main()
Basistests (mix van asserts en kleine unittests mogelijk):
# guessgame/tests_basic.py
from guessgame.game import feedback, play_round
# pure function asserts
assert feedback(10, 5) == "Higher"
assert feedback(10, 15) == "Lower"
assert feedback(10, 10) == "Correct"
# simulate I/O for play_round
class IO:
def __init__(self, inputs):
self.inputs = list(inputs)
self.out = []
def reader(self, prompt: str) -> str:
return self.inputs.pop(0) if self.inputs else ""
def writer(self, msg: str) -> None:
self.out.append(msg)
io = IO(["7", "10"]) # suppose secret=10
attempts = play_round(10, io.reader, io.writer)
assert attempts == 2
assert io.out[-1] == "Correct"
README‑schets (voor studenten, in English code allowed, tekst NL):
# Number Guessing Game
- Run: `python -m guessgame.cli [low high]`
- Goal: Guess the secret number; program gives "Higher"/"Lower" feedback.
- Highscore: best (fewest attempts) stored in `highscore.txt`.
- Modules: `game.py` (logic), `storage.py` (file I/O), `cli.py` (entry point).
- Tests: see `tests_basic.py` (asserts on pure functions + simulated I/O).
Project B — To‑Do CLI (persistente opslag)
Structuur (voorbeeld):
- todo/
- init.py
- store.py (bestands‑I/O)
- core.py (bewerkingen op items)
- cli.py (commando’s: add/list/done)
- tests_basic.py
Implementatie (kerncode):
# todo/store.py
from pathlib import Path
from typing import Iterable
TODO = Path("todo.txt")
def read_items() -> list[str]:
if not TODO.exists():
return []
return [line.rstrip("\n") for line in TODO.read_text(encoding="utf-8").splitlines()]
def write_items(items: Iterable[str]) -> None:
text = "\n".join(items) + ("\n" if items else "")
TODO.write_text(text, encoding="utf-8")
# todo/core.py
from typing import Iterable
def add_item(items: list[str], text: str) -> list[str]:
text = text.strip()
return items + [text] if text else items
def list_lines(items: Iterable[str]) -> list[str]:
return [f"{i}. {it}" for i, it in enumerate(items, start=1)]
def done_item(items: list[str], index: int) -> list[str]:
if 1 <= index <= len(items):
new = list(items)
del new[index - 1]
return new
return items
# todo/cli.py
from .store import read_items, write_items
from .core import add_item, list_lines, done_item
def main() -> None:
import sys
args = sys.argv[1:]
if not args:
print("Usage: todo.py [add <text>|list|done <n>]")
raise SystemExit(1)
cmd = args[0]
items = read_items()
if cmd == "add" and len(args) >= 2:
items = add_item(items, " ".join(args[1:]))
write_items(items)
elif cmd == "list":
for line in list_lines(items):
print(line)
elif cmd == "done" and len(args) >= 2:
try:
n = int(args[1])
except ValueError:
print("Please provide an integer index.")
raise SystemExit(1)
items = done_item(items, n)
write_items(items)
else:
print("Unknown command or missing arguments.")
raise SystemExit(1)
if __name__ == "__main__":
main()
Basistests:
# todo/tests_basic.py
from todo.core import add_item, list_lines, done_item
items: list[str] = []
items = add_item(items, "buy milk")
items = add_item(items, "send email")
assert len(items) == 2
assert list_lines(items)[0].startswith("1.")
items2 = done_item(items, 1)
assert len(items2) == 1 and items2[0] == "send email"
# invalid index should keep list unchanged
assert done_item(items, 99) == items
README‑schets:
# To‑Do CLI
Commands:
- `add <text>`: append a new item.
- `list`: show numbered items.
- `done <n>`: remove item n (1‑based).
Modules: `store.py` (file I/O), `core.py` (logic), `cli.py` (entry).
Tests: see `tests_basic.py` for simple asserts over pure functions.
Project C — Tekst‑gebaseerde quiz (JSON)
Structuur (voorbeeld):
- quiz/
- init.py
- io.py (laden vragenbestand)
- engine.py (afspelen quiz, scoring)
- cli.py (entry)
- questions.json (voorbeeldbestand)
- tests_basic.py
Voorbeeld questions.json:
[
{"q": "Capital of France?", "a": "paris"},
{"q": "2+2?", "a": "4"}
]
Implementatie (kerncode):
# quiz/io.py
import json
from pathlib import Path
from typing import Iterable
def load_questions(path: str | Path) -> list[dict]:
p = Path(path)
data = json.loads(p.read_text(encoding="utf-8"))
if not isinstance(data, list):
raise ValueError("questions file must be a JSON list")
return data
# quiz/engine.py
from typing import Callable, Iterable
def ask_one(q: str, answer: str, reader: Callable[[str], str], writer: Callable[[str], None]) -> bool:
user = reader(q + " ").strip().lower()
return user == answer.lower()
def run_quiz(qa: Iterable[dict], reader, writer) -> int:
correct = 0
for item in qa:
if ask_one(item["q"], item["a"], reader, writer):
writer("Correct")
correct += 1
else:
writer("Wrong")
return correct
# quiz/cli.py
from .io import load_questions
from .engine import run_quiz
def main() -> None:
import sys
path = sys.argv[1] if len(sys.argv) >= 2 else "questions.json"
qa = load_questions(path)
print("Answer the questions (empty line to skip)")
def reader(prompt: str) -> str:
try:
return input(prompt)
except EOFError:
return ""
score = run_quiz(qa, reader, print)
print(f"Score: {score}/{len(qa)}")
if __name__ == "__main__":
main()
Basistests:
# quiz/tests_basic.py
from quiz.engine import ask_one, run_quiz
assert ask_one("2+2?", "4", lambda _: "4", lambda _m: None) is True
assert ask_one("2+2?", "4", lambda _: "5", lambda _m: None) is False
class IO:
def __init__(self, answers):
self.answers = list(answers)
self.out = []
def reader(self, _prompt: str) -> str:
return self.answers.pop(0) if self.answers else ""
def writer(self, msg: str) -> None:
self.out.append(msg)
qa = [{"q": "Capital?", "a": "Paris"}, {"q": "2+2?", "a": "4"}]
io = IO(["paris", "4"]) # all correct
score = run_quiz(qa, io.reader, io.writer)
assert score == 2
assert io.out.count("Correct") == 2
README‑schets:
# Quiz (text‑based)
- Provide a JSON file with questions: objects having keys `q` and `a`.
- Run: `python -m quiz.cli questions.json`
- Modules: `io.py` (load), `engine.py` (run/scoring), `cli.py` (entry).
- Tests: see `tests_basic.py` (pure logic tested via simulated I/O).
Tips voor beoordeling (docent):
- Controleer of studenten minimaal 2 modules en 5 functies inzetten (scheiding I/O vs. logica).
- Laat hen een korte README in het project toevoegen met gebruiksvoorbeelden.
- Tests: stimuleer pure functies zodat asserts/unittests eenvoudig zijn.
- Extra punten voor nette foutafhandeling (bestanden/JSON), type hints, en duidelijke naamgeving.