Modules en imports
Een bestand dat je zelf schreef gebruiken vanuit een ander bestand, wat een import precies doet, waarom `if __name__ == "__main__"` nodig is, de standaardbibliotheek, en het verbergen van een module achter een eigen bestand.
Hoe wil je leren?
Het probleem dat we oplossen
In het vorige hoofdstuk schreven we een functie line_total. Nu heeft een tweede programma hetzelfde nodig.
De makkelijke weg is kopiëren. Op dag één werkt dat.
Dan verandert het belastingtarief. Je past één bestand aan en vergeet het andere. Nu tonen twee programma's twee verschillende totalen voor dezelfde bestelling, en er is geen manier om te zeggen welke klopt — want volgens hun eigen bestand kloppen ze allebei.
Het probleem is niet het werk van het kopiëren. Het probleem is dat de waarheid nu op twee plekken staat.
import pricing
print(pricing.TAX_RATE)
print(pricing.line_total(15.0, 3))0.15
51.75Eén bestand, en zoveel programma's als je wilt die het gebruiken. Als het tarief verandert, verandert het op één plek.
Dit hoofdstuk gaat over die ene regel — import — en over wat er achter de schermen echt gebeurt.
Aan het einde van dit hoofdstuk kun je
- Een bestand dat je zelf schreef vanuit een ander bestand importeren
- Uitleggen wat
import x,from x import yenimport x as zvan elkaar onderscheidt - Uitleggen wat er op het moment van een import gebeurt, en hoe vaak dat gebeurt
- Uitleggen waarom
if __name__ == "__main__":wordt geschreven - Modules uit de standaardbibliotheek binnenhalen
- Weten waar Python zoekt, en voorkomen dat je een module verbergt achter een eigen bestand
Voorkennis: Scope en veranderlijke standaardwaarden.
Een module is gewoon een bestand
Een bestand dat pricing.py heet, is een module met de naam pricing. Er hoeft niets gedeclareerd te worden en er hoeft geen speciale regel geschreven te worden — het bestand zelf is genoeg.
import pricing haalt de hele module binnen, en dingen erin bereik je met een punt: pricing.TAX_RATE, pricing.line_total(...).
De punt kan aanvoelen als extra typewerk, maar hij heeft een functie — bij het lezen vertelt hij waar het ding vandaan komt. In een groot bestand helpt dat enorm.
Alleen wat je nodig hebt binnenhalen
from pricing import line_total
print(line_total(15.0, 3))
print(TAX_RATE)51.75
NameError: name 'TAX_RATE' is not definedfrom ... import ... haalt alleen de naam binnen die gevraagd is, en die naam gebruik je dan direct — zonder punt.
(Vóór de foutregel print Python ook een paar traceback-regels; in dit hoofdstuk wordt meestal alleen de laatste regel getoond.)
Maar de andere namen komen niet mee. TAX_RATE werd niet gevraagd, dus is hij hier niet, ook al staat hij in hetzelfde bestand.
Wat gebruik je? Ruwweg: from als je twee of drie namen nodig hebt, de hele module als je er veel nodig hebt. En één ding doe je nooit — from pricing import *. Dat haalt stilletjes elke naam binnen, en daarna is er geen manier meer om te achterhalen waar line_total vandaan kwam.
Een nieuwe naam geven bij het importeren
import pricing as p
print(p.line_total(15.0, 3))51.75as geeft de module een kortere naam binnen dit bestand.
Gebruik het als de naam echt lang is, of als er een gangbare conventie voor bestaat. Een naam van één letter uit luiheid gooit de leesbaarheid weg die de punt bood — niemand die p.line_total(...) leest kan zien waar het vandaan komt.
Importeren betekent het bestand uitvoeren
Dit is het deel dat de meeste mensen later ontdekken, meestal op een slecht moment.
tools.py:
print("tools is being set up")
def ping():
return "pong"main.py:
print("main starts")
import tools
import tools
print(tools.ping())
print("main ends")main starts
tools is being set up
pong
main endsTwee dingen om op te letten.
Importeren betekent het bestand uitvoeren, van boven naar beneden. De def-regels maken functies, en al het andere — zoals die print — gebeurt echt. Zet je een print of een input op het hoogste niveau van een module, dan komt die terecht bij iedereen die hem importeert.
De tweede import tools deed niets. Een module draait één keer per programma; daarna geeft Python degene terug die het al heeft. Dus dezelfde module op tien plekken importeren is veilig en goedkoop.
__name__ en die beroemde regel
Elke module heeft binnenin een naam die __name__ heet, en die zet Python zelf.
tools.py:
def ping():
return "pong"
print("module name is", __name__)
if __name__ == "__main__":
print("running directly")Uitgevoerd als python tools.py:
module name is __main__
running directlyMaar import tools vanuit een ander bestand:
module name is toolsDe regel running directly verscheen deze keer niet, omdat de voorwaarde onwaar was.
De regel is eenvoudig: het bestand dat je uitvoert heeft __name__ gelijk aan "__main__"; bestanden die worden geïmporteerd hebben hun eigen naam.
Wat er dus binnen if __name__ == "__main__": staat, draait alleen wanneer het bestand direct wordt uitgevoerd, en nooit wanneer het wordt geïmporteerd.
Waarom dat nodig is, volgt uit de vorige sectie. Importeren betekent het bestand uitvoeren — dus als main() onderaan een bestand staat, draait iemand die het importeert ineens ook jouw programma. Die regel sluit die deur.
De standaardbibliotheek
Heel veel modules komen met Python zelf mee. Er hoeft niets geïnstalleerd te worden.
import math
import statistics
print(math.sqrt(144))
print(math.floor(3.7), math.ceil(3.2))
print(statistics.mean([10, 20, 60]))12.0
3 4
30Nog een die vaak voorkomt:
import random
random.seed(7)
print(random.randint(1, 100))
print(random.choice(["pen", "bag", "ink"]))42
penZonder random.seed(7) zou elke uitvoering andere getallen geven — en daar gaat het uiteindelijk ook om. Een seed instellen legt de reeks vast, dus op dezelfde Python-versie is de uitvoer hierboven ook op jouw machine hetzelfde. Voor het schrijven van tests is dit erg belangrijk: om iets willekeurigs te testen, moet je het eerst herhaalbaar maken.
Dit is niet "willekeurig", het is "willekeurig ogend".randommaakt getallen met een berekening, dus dezelfde seed geeft altijd dezelfde reeks. Gebruik het niet voor wachtwoorden of tokens — daarvoor bestaat desecrets-module.
Waar Python zoekt
Voor import pricing zoekt Python in deze volgorde: eerst de modules die in de interpreter zijn ingebouwd, dan de map van het script dat wordt uitgevoerd, dan de standaardbibliotheek, en daarna geïnstalleerde packages.
Die volgorde heeft een gevolg, en daar trappen mensen in.
Er staat een bestand genaamd random.py in je map, en je schrijft:
import random
print(random.choice([1, 2, 3]))AttributeError: module 'random' has no attribute 'choice'Jouw bestand kwam vóór de echte random, dus jouw bestand is geïmporteerd — en het heeft geen choice erin.
De melding is verwarrend, want er staat dat random geen choice heeft, wat onmogelijk lijkt. Maar het gaat over jouw random.
De regel: noem nooit een bestand naar een module. random.py, string.py, json.py, csv.py — het zijn onschuldig klinkende namen, en juist daarom werkt de valkuil.
Eén uitzondering, en die verklaart de regel. Op veel installaties (bijvoorbeeld het Linux-systeem waarop de voorbeelden in dit hoofdstuk zijn gedraaid) maak je een bestandmath.py, en dan geeftimport mathje nog steeds de echte, niet jouw bestand. Dat komt doordatmathdaar in de interpreter is ingebouwd, en ingebouwde modules staan eerst in de zoekvolgorde. Welke modules ingebouwd zijn, hangt af van hoe Python is gebouwd. De valkuil slaat dus bij sommige namen wel toe en bij andere niet — en dat is lastiger te onthouden dan simpelweg het bestand hernoemen.
Bestanden in mappen zetten
Naarmate er meer bestanden komen, kun je ze in mappen groeperen. Zet een bestand __init__.py in de map — leeg is prima — en de map wordt een package.
shop/
__init__.py
pricing.py
main.pyHet pad kan dan met punten geschreven worden:
from shop.pricing import line_total
print(line_total(15.0, 3))51.75Dat is voor nu genoeg. Voor de rest van dit hoofdstuk blijven we bij platte bestanden.
Een compleet voorbeeld
Drie bestanden, met afhankelijkheden die maar in één richting lopen.
pricing.py:
"""Prices and tax. Nothing here prints, and nothing here asks for input."""
TAX_RATE = 0.15
def line_total(price, quantity):
"""One line of an order, tax included."""
return round(price * quantity * (1 + TAX_RATE), 2)
def order_total(lines):
"""`lines` is a list of (name, price, quantity) tuples."""
return round(sum(line_total(price, qty) for _, price, qty in lines), 2)report.py:
"""Turning numbers into lines of text. It imports pricing; pricing imports nothing."""
import pricing
def receipt(lines):
rows = [f"{name:<8} {pricing.line_total(price, qty):>8.2f}"
for name, price, qty in lines]
rows.append("-" * 17)
rows.append(f"{'total':<8} {pricing.order_total(lines):>8.2f}")
return rows
if __name__ == "__main__":
print("report.py has no data of its own to show")main.py:
"""The only file that is meant to be run."""
import statistics
from report import receipt
ORDER = [
("pen", 15.0, 3),
("bag", 850.0, 1),
("ink", 120.0, 2),
]
def main():
for row in receipt(ORDER):
print(row)
print()
quantities = [qty for _, _, qty in ORDER]
print("mean quantity:", statistics.mean(quantities))
if __name__ == "__main__":
main()Uitgevoerd als python main.py:
pen 51.75
bag 977.50
ink 276.00
-----------------
total 1305.25
mean quantity: 2Vier dingen die het bekijken waard zijn.
De afhankelijkheden lopen één kant op. main kent report, report kent pricing, en pricing kent niemand. Zo ontstaat een keten, en het bestand aan het eind ervan — pricing — kun je helemaal op zichzelf testen. Als pricing report terug had moeten importeren, dan hadden ze geen van beide nog op zichzelf kunnen staan.
pricing.py print niets en vraagt niets. Het neemt waarden en geeft waarden terug — de zuivere functies uit hoofdstuk eenentwintig. Omdat importeren betekent dat het bestand wordt uitgevoerd, is die terughoudendheid geen beleefdheid maar een vereiste.
report.py heeft ook een if __name__ == "__main__":, en die doet niets nuttigs — het bestand heeft geen eigen data, dus het heeft niets om te tonen. Die regel had weggelaten kunnen worden. Hij staat er zodat het bestand, als het ooit direct wordt uitgevoerd, tenminste iets zegt in plaats van stilletjes niets te doen.
Geen enkel bestand behalve main.py doet op zichzelf iets. Het hele programma heeft precies één startpunt, en als je het leest, weet je wat er gaat gebeuren.
Wanneer het breekt
ModuleNotFoundError: No module named 'priceing' Een tikfout, of het bestand staat in een andere map. Controleer of het naast het script staat dat je uitvoert.
ImportError: cannot import name 'line_totl' from 'pricing' De module is gevonden, maar de naam erin niet. De melding eindigt met het volledige bestandspad van de module tussen haakjes, en vaak doet hij een suggestie voor de bedoelde naam — Did you mean: 'line_total'?
AttributeError: module 'random' has no attribute 'choice' Bijna altijd een eigen bestand dat de echte module verbergt. Zoek die naam in je map en hernoem het bestand. Dat is genoeg — zodra het .py-bestand weg is, gebruikt Python de oude kopie in __pycache__ niet meer.
ImportError: cannot import name 'rows' from partially initialized module 'table' (most likely due to a circular import) Twee bestanden importeren elkaar, en minstens één van beide gebruikt from ... import ... om een naam op te vragen die in het andere bestand nog niet bestaat — omdat dat bestand zelf nog niet van boven naar beneden is uitgevoerd. De gebruikelijke oplossing is om wat beide nodig hebben te verplaatsen naar een derde bestand dat geen van beide importeert.
Er werd iets geprint, of om invoer gevraagd, op het moment dat ik het importeerde Er staat code op het hoogste niveau van de module. Zet die in een functie en roep die aan vanuit if __name__ == "__main__":.
Ik heb het bestand aangepast, maar ik zie nog steeds het oude gedrag Voer het programma opnieuw uit — een draaiend programma leest de module niet opnieuw in. In een notebook herstart je de kernel.
Stap 4 van 6 — Voorspellen
Test je begrip
line_total is gevraagd, TAX_RATE niet. Wat gebeurt er?
# pricing.py
TAX_RATE = 0.15
def line_total(price, quantity):
return round(price * quantity * (1 + TAX_RATE), 2)
# main.py
from pricing import line_total
print(line_total(15.0, 3))
print(TAX_RATE)- A`51.75` print, dan `None`
- BZowel `51.75` als `0.15` printen
- C`51.75` print, dan een `NameError`
- DEen `ImportError`
import tools staat er twee keer. Wat wordt geprint?
# tools.py
print("tools is being set up")
def ping():
return "pong"
# main.py
print("main starts")
import tools
import tools
print(tools.ping())
print("main ends")- Atools is being set up main starts pong main ends
- Bmain starts pong main ends
- Cmain starts tools is being set up pong main ends
- Dmain starts tools is being set up tools is being set up pong main ends
python main.py wordt uitgevoerd. Wat wordt geprint?
# tools.py
print("name:", __name__)
if __name__ == "__main__":
print("direct")
# main.py
import tools
print("done")- Aname: tools done
- Bname: main done
- Cname: __main__ direct done
- Dname: tools direct done
Voor antwoorden heb je een account nodig
Log in om je antwoorden te controleren
De vragen staan hierboven, en ze in je hoofd uitwerken is waar het om draait. Log in om de antwoorden, de uitleg en de hints in drie niveaus te zien.
Jouw beurt
Maak drie bestanden in één map.
textutil.py — twee functies: initials(name), die "rafi ahmed" omzet in "RA", en shorten(text, limit), die lange tekst inkort en ... aan het eind toevoegt. Gebruik nergens in dit bestand een print.
table.py — importeert textutil, en biedt rows(people) aan, die een lijst namen neemt en een lijst opgemaakte regels teruggeeft.
main.py — importeert table, roept die aan met een paar namen, en print. Zet in alle drie de bestanden if __name__ == "__main__":.
Daarna vier experimenten, die het allemaal waard zijn om met eigen ogen te zien:
- Zet
print("loaded")helemaal bovenaantextutil.pyen voerpython main.pyuit. Verschijnt die regel? Vóór of na de eersteprintvanmain.py? - Schrijf
import tabletwee keer inmain.py. Hoe vaak wordtloadedgeprint? - Maak een leeg bestand genaamd
statistics.pyin de map, importeer dan inmain.pystatisticsen roepstatistics.mean([1, 2])aan. Lees de melding — over welkestatisticsgaat die? - Zet
from table import rowshelemaal bovenaantextutil.py, zodat de twee elkaar importeren, en voer danpython main.pyuit. De laatste regel van de melding begint zo:ImportError: cannot import name 'rows' from partially initialized module 'table'. (Een gewoneimport tablegeeft geen fout: dan krijgttextutilalleen een naam voor de half opgebouwde module, en wordtrowspas later opgezocht, wanneer een functie draait.from table import rowsvraagt de naam nu op, terwijltable.pynog vastzit op zijn eerste regel.)
De laatste twee zijn fouten die uren kunnen kosten als de melding niet bekend is, en seconden zodra je ze zelf hebt veroorzaakt.
Stap 6 van 6
Uitdaging — de hoofdstukquiz
Tien vragen van makkelijk naar moeilijk. De laatste zijn met opzet lastig.
Log in om de quiz te maken