← Python/Modules en tooling
Les 3 van 8
MODULE 09 · 03◷ 35 min

Modules groeperen in een pakket

Na deze les kun je
  • Bestanden en volledige modulenamen met elkaar verbinden.
  • Helpers binnen hetzelfde pakket relatief importeren.
  • Een publieke interface en een pakketstartpunt onderscheiden.

Je bestelprogramma krijgt helpers voor prijzen, labels en de opdrachtregel. Alle bestanden in één map laten slingeren maakt steeds minder duidelijk welke code bij elkaar hoort. Een pakket geeft samenhangende modules één herkenbare plek en een gezamenlijke naam.

Een naam voor een groep modules

Een pakket organiseert modules in een hiërarchie. In deze les hoort het bestand tickets/pricing.py bij de module met de volledige naam tickets.pricing. De punt in de modulenaam komt hier overeen met een mapgrens in het project.

We maken een gewoon pakket met een bestand __init__.py in de map tickets. Python voert dit bestand uit wanneer het pakket voor het eerst wordt geïmporteerd. Het bestand mag leeg zijn, maar kan ook bewust enkele namen beschikbaar maken.

De bestandsboom laat zien welke bestanden samen het pakket vormen. De meegeleverde helpers voor prijzen, labels en argumenten hoef je niet te veranderen; je verbindt ze via de drie bewerkbare bestanden.

tickets/
  __init__.py
  api.py
  pricing.py
  labels.py
  cli.py
  __main__.py

Binnen hetzelfde pakket blijven

Een relatieve import beschrijft een import vanuit het pakket van de huidige module. In tickets/api.py betekent from . import pricing: laad pricing binnen tickets. De punt verwijst niet naar de huidige werkmap van de gebruiker.

Dat verschil is belangrijk wanneer een ander project jouw pakket importeert. De gebruiker hoeft niet eerst naar de map tickets te wisselen. De import blijft tickets.pricing aanwijzen, ook wanneer de werkmap elders ligt.

Je kent import pricing uit de eerste les. Zonder de voorste punt zoekt Python naar een module op het bovenste zoekpad, niet specifiek naar een buur binnen tickets. Gebruik hier daarom de expliciete pakketrelatie.

Een kleine publieke ingang

Een publieke API is de verzameling namen die je bewust aanbiedt aan gebruikers van je pakket. Door in tickets/__init__.py namen uit .api te importeren, kan een gebruiker bijvoorbeeld tickets.describe aanroepen. Die gebruiker hoeft dan niet te weten in welk intern bestand de functie precies staat.

Het eerste voorbeeld start het pakket walks als module en gebruikt een publieke functie. De functie blijft gedefinieerd in haar eigen module; het pakket verwijst naar datzelfde functieobject.

Een pakket gebruikt een publieke functie en een module als startpunt
walks/__init__.py
from .summary import minutes
walks/rules.py
MINUTES = 7
walks/summary.py
from .rules import MINUTES


def minutes(stages):
    return stages * MINUTES
walks/__main__.py
from . import minutes


if __name__ == "__main__":
    print(minutes(2))
    print(minutes.__module__)
Verwachte uitvoer
14
walks.summary

Exporteer niet alles alleen omdat het bestaat. In de opdracht zijn order_total en describe de twee gekozen publieke functies. De helpermodules blijven onderdeel van de interne verdeling en worden niet als losse functies nagebouwd in __init__.py.

Een pakket starten

Bij python -m tickets zoekt Python het bestand tickets/__main__.py als startpunt. Dit bestand is iets anders dan __init__.py: de eerste hoort bij het bewust starten van het pakket, de tweede bij het laden van het pakket. Houd het startbestand klein en laat het de meegeleverde cli.main gebruiken.

De startbewaking en SystemExit ken je uit de vorige les. Pas die hier op dezelfde manier toe. De herbruikbare functies staan in api.py; de argumentparser en console-uitvoer horen bij cli.py.

Veelgemaakte fout: een intern bestand los starten

Bij het los starten van een intern bestand kan de pakketcontext ontbreken. Een relatieve import weet dan niet welk bovenliggend pakket je bedoelt. Het tweede voorbeeld laat die echte ImportError zien.

Een los gestart intern bestand mist de pakketcontext
walks/__init__.py
walks/rules.py
MINUTES = 7
walks/summary.py
from .rules import MINUTES
print(2 * MINUTES)
Verwachte fout
attempted relative import with no known parent package

Start het pakket via zijn modulenaam en laat zijn modules elkaar importeren. Bestanden heen en weer kopiëren of de werkmap laten bepalen welke helper toevallig gevonden wordt, lost de onduidelijke structuur niet op.

AAN DE SLAG 0/4
1

Een prijshelper binnen het eigen pakket gebruiken

Maak order_total(prices, names) in tickets/api.py. Importeer pricing met from . import pricing en geef het resultaat van de huidige pricing.total(prices, names) ongewijzigd terug. Dubbele namen tellen mee; een onbekende naam laat de echte KeyError doorlopen.

2

De publieke berekening met een label combineren

Maak describe(prices, names) in tickets/api.py. Gebruik de huidige order_total voor de berekening en de huidige labels.line voor de tekst, met label Totaal. Importeer labels als module binnen hetzelfde pakket en geef de ontvangen labeltekst terug.

3

Twee bestaande functies publiek aanbieden

Exporteer in tickets/__init__.py precies de bestaande order_total en describe uit .api. tickets.order_total en tickets.describe moeten dezelfde functieobjecten zijn als de overeenkomstige namen in tickets.api; definieer geen kopieën of wrappers.

4

Een klein pakketstartpunt toevoegen

Maak tickets/__main__.py. Importeer main uit .cli en gebruik onder if __name__ == "__main__": de aanroep raise SystemExit(main()). python -m tickets boek kaart boek schrijft Totaal: 300 cent met een newline en eindigt met nul; zonder namen is het bedrag nul.

Onthoud dit

  • Een pakket geeft samenhangende modules een gezamenlijke naam.
  • Een relatieve import gebruikt de pakketcontext van de huidige module.
  • __init__.py biedt gekozen namen aan; __main__.py start het pakket.

Hierna beschrijf je de identiteit en vereisten van een project in pyproject.toml.

tickets/api.py
UitvoerPython
›
›_

Hier komt je resultaat.

Voer je code uit en ontdek
wat je hebt gemaakt.

0/4 stappen
EVEN MEEKIJKEN

Bekijk hoe het kan.

Je mag de oplossing bekijken. Deze poging telt dan niet mee als afgeronde les. Met Reset kun je daarna opnieuw zelf oefenen.