Pythonprojecten met uv¶
In de vorige hoofdstukken heb je gewerkt met een eigen virtual environment per project, zodat je jouw pythonomgeving mooi gescheiden kan houden van andere projecten waar je aan werkt. Dit is echt de oplossing voor alle problemen waarbij volledige Pythoninstallaties onbruikbaar kunnen worden — waarna je alles opnieuw moet installeren. Dit kan gebeuren als je — vanwege al je verschillende projecten — zoveel packages installeert dat die met elkaar in conflict komen.
Voor ieder project nieuwe environments aanmaken heeft wel een nadeel: je moet alle packages die je nodig hebt opnieuw installeren. Welke waren dat ook alweer? Vast numpy
, en matplotlib
, en…? Niet handig. Als je code gaat delen met elkaar krijg je regelmatig te maken met een ImportError
of ModuleNotFoundError
omdat je niet precies weet wat er nodig is, waarna je weer één of ander package moet installeren.
Nu pythondaq netjes is uitgesplitst in een MVC-structuur en de wijzigingen met Git worden bijgehouden, ga je er een package van maken zodat je het ook met anderen kunt delen. In deze package staan alle benodigdheden duidelijk omschreven, zodat gebruikers daar verder niet over hoeven na te denken.
Packages op PyPI — de standaardplek waar Python packages gepubliceerd worden — geven altijd hun dependencies op. Dat zijn de packages die verder nog nodig zijn om alles te laten werken. Installeer je matplotlib
, dan krijg je er six, python-dateutil, pyparsing, pillow, numpy, kiwisolver, cycler
automatisch bij. Maar alleen de namen van de packages als dependencies opgeven is niet genoeg. Welke versies van numpy
werken met de huidige versie van matplotlib
? Allemaal zaken die je — als je een package schrijft — zelf moet bijhouden. Als je dat netjes doet, dan hoeven jouw gebruikers alleen maar jouw package te installeren — de rest gaat vanzelf.
En… hoe test je je package, zodat je zeker weet dat je package het bij een ander ook doet? Heel vaak werkt het bij jou wel, maar vergeet je een bestand mee te sturen dat wel echt nodig is.1 Of: bij jou werkt import my_new_cool_app.gui
wel, maar bij een ander geeft dat een ImportError
of ModuleNotFoundError
. De bestanden zijn er wel, maar worden verkeerd geïmporteerd.
Hoe krijg je eigenlijk je code bij iemand anders? Liefst als één bestand, of zelfs met uv pip install my_new_cool_app
; dat zou wel mooi zijn.
uv helpt je ook hier om al deze problemen op te lossen.
Info
Voorgaande jaren leerden we studenten om Poetry te gebruiken. Heel populair, maar uv is de afgelopen anderhalf jaar nog veel populairder geworden. En terecht.
Er zijn meerdere tools ontwikkeld om dezelfde problemen op te lossen. uv is in korte tijd heel populair geworden. Het richt zich op het officiële ecosysteem: standaard Python packages, ofwel PyPI en pip
; niet conda
(zie meer hierover in paragraaf pip vs conda). Dit zorgt er voor dat iedereen mét of zónder Anaconda je package kan installeren. Omdat uv ook in staat is zelf verschillende versies van Python te installeren hebben we Anaconda niet meer nodig. De installer van Anaconda is bijna 1 Gb groot en bevat heel veel Python packages die je nooit gebruikt. De installer van uv is nog geen 20 Mb en kun je gebruiken om precies te installeren wat je nodig hebt.
Werken in een terminal
uv is een tool die je enkel en alleen in de terminal kunt gebruiken. Het heeft alleen een command-line interface (CLI). De Terminal Adventure Game helpt je om met meer gemak te navigeren in een terminal.
Projecttraject
- Werken in een terminal
Je gaat uv bedienen door commando's te geven in de terminal van Visual Studio Code. Je laat de terminal weten welk programma je wilt gaan besturen door uv
in te typen. En daarachter wat je wilt dat uv gaat doen. Je kunt bijvoorbeeld kijken welke commando's allemaal beschikbaar zijn met uv help
. Dat geeft een vrij lange lijst die je terug kunt scrollen in de terminal, maar je kunt ook uv help | more
intypen om de tekst per pagina weer te geven.2
> uv help | more
An extremely fast Python package manager.
Usage: uv [OPTIONS] <COMMAND>
Commands:
auth Manage authentication
run Run a command or script
init Create a new project
add Add dependencies to the project
remove Remove dependencies from the project
version Read or update the project's version
sync Update the project's environment
lock Update the project's lockfile
export Export the project's lockfile to an alternate format
tree Display the project's dependency tree
format Format Python code in the project
tool Run and install commands provided by Python packages
python Manage Python versions and installations
pip Manage Python packages with a pip-compatible interface
venv Create a virtual environment
build Build Python packages into source distributions and wheels
publish Upload distributions to an index
cache Manage uv's cache
self Manage the uv executable
generate-shell-completion Generate shell completion
help Display documentation for a command
Cache options:
-n, --no-cache Avoid reading from or writing to the cache, instead using a temporary directory for the
-- More --
Info
Zoals je ziet heeft uv
heel veel verschillende commando's. uv is een Zwitsers zakmes: het bevat heel veel tools voor wie dat nodig heeft. Jij hebt voor nu lang niet alles nodig, dus laat je niet uit het veld slaan door deze lange lijst. In de rest van dit hoofdstuk vertellen we je precies wat je wel nodig hebt. Als je meer wilt weten kun je het beste de documentatie lezen.
Nieuw uv project¶
Info
Je gaat werken met modules en packages. Ben je daar nog niet zo bekend mee, zorg dan dat je paragraaf Modules en paragraaf Packages gemaakt hebt.
Stel je wilt een package schrijven met wat handige functies om veelgebruikte statistische berekeningen makkelijk uit te voeren. Je noemt het package easystat
. Het doel is eerst om het in al je eigen analyses makkelijk te kunnen gebruiken (import easystat
), maar je wilt het ook op GitHub zetten en wie weet vinden anderen het ook handig! Je wilt het dus ook netjes doen. En niet later van anderen horen: leuk, maar bij mij werkt het niet!
Easystat: uv project aanmaken
- Open Github Desktop en ga naar Menu > File. Kies hier voor
New repository ...
. Geef de repository de naameasystat
en zet de repository in de mapECPC
. VinkInitialize this repository with a README
aan en kies bijGit ignore
voor Python. - Open de repository
easystat
in Visual Studio Code (Menu > Repository > Open in Visual Studio Code). - Open een terminal in je Visual Studio Code-omgeving (Menu > Terminal > New Terminal). Maak het uv project aan met:
-
Je bekijkt de nieuw gemaakte mappenstructuur en ziet dat het overeenkomt met de mappenstructuur zoals hieronder weergegeven:
ECPC
├──oefenopdrachten
├──pythondaq
├──easystat
├──src
└──easystat
└──__init__.py
├──.gitattributes
├──.gitignore
├──.python-version
├──pyproject.toml
└──README.md
└── ••• -
Commit in GitHub Desktop de wijzigingen die
uv init
heeft gedaan.
src-layout
Door het project in een source layout (src-layout) te bouwen — easystat
zit in een map src
— staat al je Pythoncode netjes bij elkaar weggestopt. Dit maakt het makkelijker om te testen of het installeren goed werkt, zodat je zeker weet dat andere mensen met jouw code aan de slag kunnen.
Testcode
(ECPC) > uv init --package
Initialized project `easystat`
Checkpunten
- De projectmap
easystat
staat in de mapECPC
. - In de projectmap
easystat
staat een mapsrc
. - In de map
src
staat een package mapeasystat
Projecttraject
- Easystat: uv project aanmaken
- Easystat: virtual environment synchroniseren
- Easystat: benodigde scripts aanmaken
- Easystat: testen
- Easystat: dependency toevoegen
- Easystat: opnieuw testen
- Easystat: imports aanpassen
- Easystat: absolute imports
- Easystat: voorbeeldscript bekijken
- Easystat: functie
main()
toevoegen - Easystat: commando toevoegen
- Easystat: commando testen
- Easystat: applicatie runnen in terminal
Laten we één voor één kijken welke mappen en bestanden uv heeft aangemaakt. Je had al een README.md
in de projectmap staan. Hierin komt een algemene beschrijving van het project.3
Dan komt de src
-map. Daarin komt het nieuwe package easystat
4 te staan. Er is alvast een __init__.py
aangemaakt. Handig! De bestanden .gitattributes
en .gitignore
bewaren wat instellingen voor Git. En .python-version
bewaart het versienummer van Python dat uv gebruikt. Vul je daar 3.12 in? Dan installeert uv Python 3.12 in je virtual environment.
En als laatste… een pyproject.toml
5 waarin alle informatie over je project wordt bijgehouden. Ook staat er in dit bestand informatie over de verschillende tools die je kunt gebruiken. De inhoud van het bestand ziet er ongeveer zo uit:
[project]
name = "easystat"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = [
{ name = "David Fokkema", email = "davidfokkema@icloud.com" }
]
requires-python = ">=3.13"
dependencies = []
[project.scripts]
easystat = "easystat:main"
[build-system]
requires = ["uv_build>=0.8.4,<0.9.0"]
build-backend = "uv_build"
Het bestand is in het TOML-formaat.7 Tussen de vierkante haken staan de koppen van de verschillende secties in dit configuratiebestand. In de eerste sectie staat informatie over het project. Je kunt daar bijvoorbeeld een beschrijving toevoegen of het versienummer aanpassen. Ook bevat die sectie de dependencies. Dit zijn alle Pythonpackages die het project nodig heeft. Op dit moment is dat nog niets. Ook het versienummer van Python is belangrijk. Hier is dat groter of gelijk aan 3.13. Dit kan belangrijk zijn. Gebruikers met een iets oudere versie van Python — bijvoorbeeld versie 3.11 — kunnen nu de package niet installeren. Als je niet per se de nieuwste snufjes van Python 3.13 nodig hebt, kun je aangeven dat een iets oudere versie van Python ook prima is. Op moment van schrijven — zomer 2025 — is Python 3.13 de nieuwste versie. Het is dus prima om minimaal 3.12 te vragen — deze versie is inmiddels bijna twee jaar oud.
Pythonversie in het project
Het is heel handig om, als je requires-python = ">=3.12"
invult, ofwel 'minstens 3.12', dat je dan in .python-version
ook 3.12 invult. Omdat je anders niet zeker weet of je code ook echt werkt met 3.12 (omdat jijzelf dan bijvoorbeeld met 3.13 werkt en je de code dus nooit getest hebt met 3.12).
De sectie [project.scripts]
zorgt ervoor dat je het script kunt aanroepen door easystat
in de terminal in te typen. De sectie [build-system]
zorgt ervoor dat je een package kunt maken en uploaden naar de Python Package Index (PyPI). De sectie [build-system]
is voor nu nog niet belangrijk.
Easystat: virtual environment synchroniseren
- Hoewel we hierboven beweerden dat je
easystat
kunt intypen in de terminal en dat er dan een script draait, werkt dat hier (nog) niet. Probeer maar eens! Het werkt ook niet als je een nieuwe terminal opent. En... er staat niets tussen haakjes aan het begin van de opdrachtprompt. Blijkbaar is er nog geen virtual environment actief. - Open het bestand
src/eaystat/__init__.py
. Rechtsonderin zie je inderdaadSelect Interpreter
. Als je daar op klikt, zie je alleen nietPython 3.x.x (easystat)
in het rijtje staan... Druk op Esc om het menu te verlaten. - Type in de terminal van Visual Studio Code:
Wat dit gedaan heeft is het automatisch aanmaken van het virtual environment op basis van je projectinstellingen. Dus de Pythonversie die in> uv sync Using CPython 3.13.5 Creating virtual environment at: .venv Resolved 1 package in 5ms Installed 1 package in 47ms + easystat==0.1.0 (from file:///C:/Users/David/Documents/ECPC/easystat)
.python-version
staat en eventuele dependencies die gedefinieerd zijn in jepyproject.toml
. - Kies de nieuwe virtual environment.
- Open een nieuwe terminal en type
easystat
. Als het goed is werkt het nu wél! - Commit in GitHub Desktop de wijzigingen die
uv sync
heeft gedaan (eenuv.lock
file, zie later).
Checkpunten
- In Visual Studio Code staat rechtsonderin je Python environment geselecteerd (3.12.x (easystat)).
- In de terminal staat (easystat) vooraan de opdrachtprompt.
- Het commando
easystat
runt zonder problemen.
Projecttraject
- Easystat: uv project aanmaken
- Easystat: virtual environment synchroniseren
- Easystat: benodigde scripts aanmaken
- Easystat: testen
- Easystat: dependency toevoegen
- Easystat: opnieuw testen
- Easystat: imports aanpassen
- Easystat: absolute imports
- Easystat: voorbeeldscript bekijken
- Easystat: functie
main()
toevoegen - Easystat: commando toevoegen
- Easystat: commando testen
- Easystat: applicatie runnen in terminal
Maken van de package¶
Nu ga je starten met de package. Je gaat een aantal keer de foutmelding ModuleNotFoundError
tegenkomen, maar dit los je stap voor stap ook weer op. Stel, je berekent vaak de standaarddeviatie van het gemiddelde en je maakt daarvoor een handige shortcut
in shortcuts.py
. Nu wil je deze shortcut ook in een ander script, measurements.py
, gebruiken, die op basis van een aantal metingen het gemiddelde mét een onzekerheid geeft. Dit kun je doen door de oorspronkelijke module te importeren in het nieuwe script, zodat je de functie stdev_of_mean
ook daar kunt gebruiken. Je maakt een script try_measurements.py
om dit allemaal te testen, en dit script zet je expres niet in het package, maar in een nieuwe map tests
. Het testscript hoort immers niet bij de code van de easystat
package.
Easystat: benodigde scripts aanmaken
Maak de bestanden shortcuts.py
, measurements.py
en try_measurements.py
aan. Kopieer onderstaande code in de betreffende bestanden. In welke map moet elk bestand staan? Je moet in ieder geval nog één map zelf aanmaken.
"""Easy shortcut functions for statistical calculations.
Currently only provides the `stdev_of_mean()` function which calculates the
standard deviation of the mean.
"""
import numpy as np
def stdev_of_mean(values):
"""Calculate the standard deviation of the mean"""
return np.std(values) / np.sqrt(len(values))
easystat
├──
src
├──
easystat
├──
__init__.py
├──
measurements.py
└──
shortcuts.py
├──
tests
└──
try_measurements.py
├──
pyproject.toml
└──
readme.md
Import numpy could not be resolved
Misschien valt het je op dat Visual Studio Code een oranje kringeltje onder numpy
zet in de eerste regels van twee scripts. En dit gebeurt ook onder shortcuts
en measurements
. Als je hier je muiscursor op plaatst, krijg je een popup met de melding Import numpy could not be resolved
. Daar moet je misschien wat mee... In de volgende opdrachten ga je deze problemen één voor één oplossen.
Checkpunten
- De map
easystat/src/easystat
bevat het bestandmeasurements.py
. - De map
easystat/src/easystat
bevat het bestandshortcuts.py
. - In de projectmap
easystat
staat een maptests
. - De map
easystat/tests
bevat het bestandtry_measurements.py
.
Projecttraject
- Easystat: uv project aanmaken
- Easystat: virtual environment synchroniseren
- Easystat: benodigde scripts aanmaken
- Easystat: testen
- Easystat: dependency toevoegen
- Easystat: opnieuw testen
- Easystat: imports aanpassen
- Easystat: absolute imports
- Easystat: voorbeeldscript bekijken
- Easystat: functie
main()
toevoegen - Easystat: commando toevoegen
- Easystat: commando testen
- Easystat: applicatie runnen in terminal
In de eerste regel van try_measurements.py
importeer je de functie uit het nieuwe package om deze uit te proberen. In de eerste print
-regel gebruik je een handige functie van f-strings.6
Easystat: testen
Je bent heel benieuwd of je package al werkt. Je runt als eerste het bestand shortcuts.py
en... je krijgt een foutmelding.
Testcode
import numpy as np
def stdev_of_mean(values):
"""Calculate the standard deviation of the mean"""
return np.std(values) / np.sqrt(len(values))
(easystat) > python .\src\easystat\shortcuts.py
Traceback (most recent call last):
File "C:\Users\David\Documents\ECPC\easystat\src\easystat\shortcuts.py", line 1, in
import numpy as np
ModuleNotFoundError: No module named 'numpy'
Checkpunten
- Je hebt de juiste virtual environment geactiveerd.
- Je krijgt een foutmelding
ModuleNotFoundError: No module named 'numpy'
als je het bestandshortcuts.py
runt.
Projecttraject
- Easystat: uv project aanmaken
- Easystat: virtual environment synchroniseren
- Easystat: benodigde scripts aanmaken
- Easystat: testen
- Easystat: dependency toevoegen
- Easystat: opnieuw testen
- Easystat: imports aanpassen
- Easystat: absolute imports
- Easystat: voorbeeldscript bekijken
- Easystat: functie
main()
toevoegen - Easystat: commando toevoegen
- Easystat: commando testen
- Easystat: applicatie runnen in terminal
De beloofde ModuleNotFoundError
! Het package heeft numpy
nodig en dat heb je nog niet geïnstalleerd in de virtual environment. Dat is ook de reden voor de kringeltjes onder numpy
in het bestand shortcuts.py
. Het installeren van numpy
kun je handmatig doen, maar dan hebben straks andere gebruikers een probleem. Veel beter is het om netjes aan te geven dat het package numpy
nodig heeft — als dependency.
Dependencies toevoegen¶
Om een dependency aan te geven vertel je uv dat hij deze moet toevoegen met:
(easystat) > uv add numpy
Resolved 2 packages in 453ms
Built easystat @ file:///C:/Users/David/Documents/ECPC/easystat
Prepared 1 package in 82ms
Uninstalled 1 package in 9ms
Installed 2 packages in 798ms
~ easystat==0.1.0 (from file:///C:/Users/David/Documents/ECPC/easystat)
+ numpy==2.3.2
Easystat: dependency toevoegen
Je voegt numpy
als dependency toe aan het project easystat
met het commando uv add numpy
. Je kijkt in de pyproject.toml
en warempel daar staat numpy
nu bij de dependencies! Je vraagt je af of numpy
nu ook in het virtual environment easystat
is geïnstalleerd en controleert dit met uv pip list
. En waarachtig numpy
staat in de lijst . Weer ga je shortcuts.py
draaien en ditmaal krijg geen foutmelding! Commit de wijzigingen.
Checkpunten
- Je hebt de juiste virtual environment geacitveerd.
- Je hebt
numpy
als dependency toegevoegd. - Je krijgt geen foutmelding meer als je het bestand
shortcuts.py
runt.
Projecttraject
- Easystat: uv project aanmaken
- Easystat: virtual environment synchroniseren
- Easystat: benodigde scripts aanmaken
- Easystat: testen
- Easystat: dependency toevoegen
- Easystat: opnieuw testen
- Easystat: imports aanpassen
- Easystat: absolute imports
- Easystat: voorbeeldscript bekijken
- Easystat: functie
main()
toevoegen - Easystat: commando toevoegen
- Easystat: commando testen
- Easystat: applicatie runnen in terminal
Fijn! uv heeft numpy
nu toegevoegd aan de virtual environment easystat
. Gewone package managers als Pip en Conda zullen geen packages toevoegen aan je uv project als je pip/conda install PACKAGE
aanroept. Gebruik daarom altijd uv add PACKAGE
als je met uv aan een project werkt. Sterker nog, als je met Pip handmatig packages extra installeert zal uv sync
deze packages als overbodig herkennen en ze prompt weer verwijderen. Heb je iets verkeerds toegevoegd? Het verwijderen van een dependency gaat met uv remove PACKAGE
.
Info
Als je de code in het package aanpast, dan hoef je het virtual environment niet opnieuw te synchroniseren met uv sync
. Maar als je met de hand iets wijzigt in de pyproject.toml
, dan moet dat wel. Als je een ImportError
of ModuleNotFoundError
krijgt voor je eigen package — bijvoorbeeld als je nieuwe mappen of bestanden hebt aangemaakt — probeer dan eerst voor de zekerheid uv sync
.
uv.lock
uv.lock¶
Na het toevoegen van NumPy is er ook een grote wijziging in het bestand uv.lock
bijgekomen. In dit bestand staan de exacte versies van alle geïnstalleerde packages. Vaak wordt dit bestand gecommit zodat collega-ontwikkelaars van hetzelfde project exact dezelfde versies installeren zodra ze uv sync
aanroepen. Ook als er nieuwere versies van NumPy bijkomen, blijven alle ontwikkelaars precies dezelfde NumPy-versie gebruiken totdat uv.lock
geüpdatet wordt. Niets is zo vervelend als "oh, bij mij werkt het wel". Dus hoe meer dingen precies hetzelfde zijn, hoe minder problemen je tegenkomt. Updaten naar nieuwere versies kan natuurlijk wel, maar alleen op het moment dat je er klaar voor bent om te testen of dan alles nog netjes werkt.
Upgrade uv.lock
- Clone de repository
NatuurkundePracticumAmsterdam/upgrade-uv-lock
. - Open de repository in Visual Studio Code en open een nieuwe terminal.
- Maak in één keer een virtual environment aan en installeer de dependencies met
uv sync
. - Waarvoor gebruikt uv de lockfile (
uv.lock)
? Welke versies van NumPy en matplotlib worden geïnstalleerd? - Sinds het maken van de repository zijn er nieuwere versies van NumPy en matplotlib uitgekomen. Die worden nu nog niet geïnstalleerd, hoewel er in
pyproject.toml
staat:dependencies = ["matplotlib>=3.10.3", "numpy>=2.2.6"]
(het mag dus wel!). Controleer dat inpyproject.toml
. - Nu wil je toch de nieuwe versies hebben. Je kunt de lockfile updaten met
uv lock --upgrade
. De nieuwere versies worden genoemd en verwerkt in de lockfile. Controleer in GitHub Desktop datuv.lock
gewijzigd is. - Update je virtual environment met
uv sync
en controleer dat de nieuwere versies inderdaad geïnstalleerd worden.
Upgrade dependencies
De stappen uv lock --upgrade
en uv sync
kunnen in één keer uitgevoerd worden met uv sync --upgrade
. Grote kans dat je die vaker zult gebruiken.
Absolute imports¶
Je hebt nu een uv project en dependencies toegevoegd, maar je hebt nog niet alle code getest. Dat ga je nu doen!
Easystat: opnieuw testen
Je probeert nog een keer het bestand shortcuts.py
te runnen en ziet dat dat gewoon werkt. Daarna probeer je het bestand measurements.py
te runnen. Dat werkt ook. Maar wel gek dat er kringeltjes onder from shortcuts import stdev_of_mean
staan, hij doet het toch gewoon? Je kijkt even welke waarschuwing gegeven wordt door je muiscursor op de kringeltjes te plaatsen. Daarna probeer je het bestand try_measurements.py
. Hier gaan dingen mis: daarom zette Visual Studio Code kringeltjes onder measurements
(en onder shortcuts
vanwege een vergelijkbare reden).
Testcode
(easystat) > python tests/try_measurements.py
Traceback (most recent call last):
File "c:\Users\David\Documents\ECPC\easystat\tests\try_measurements.py", line 1, in
from measurements import result_with_uncertainty
ModuleNotFoundError: No module named 'measurements'
Checkpunten
- Je kunt zonder problemen het bestand
shortcuts.py
runnen. - Je kunt het bestand
measurements.py
runnen. - Je krijgt de foutmelding
ModuleNotFoundError
als je het bestandtry_measurements.py
runt.
Projecttraject
- Easystat: uv project aanmaken
- Easystat: virtual environment synchroniseren
- Easystat: benodigde scripts aanmaken
- Easystat: testen
- Easystat: dependency toevoegen
- Easystat: opnieuw testen
- Easystat: imports aanpassen
- Easystat: absolute imports
- Easystat: voorbeeldscript bekijken
- Easystat: functie
main()
toevoegen - Easystat: commando toevoegen
- Easystat: commando testen
- Easystat: applicatie runnen in terminal
Je wilt dus de module measurements
importeren, maar Python kan deze module niet vinden. Dat is ook wel een klein beetje logisch, want try_measurements.py
staat in de map tests
terwijl measurements.py
in de map src/easystat
staat. Je moet Python daarom vertellen wáár hij die module kan vinden, namelijk in jouw nieuwe package easystat
. Doordat je een package gemaakt hebt, hoef je niet precies te vertellen in welke map alles te vinden is, je gebruikt alleen de naam van het package. Dus niet map.op.computer.easystat.src.easystat
, maar gewoon easystat
. Wel zo makkelijk.
Easystat: imports aanpassen
Je past from measurements ...
aan naar from easystat.measurements ...
. Je test de code opnieuw. Verdorie, weer een error. Overleg met je buurmens wat deze error betekent. Waarom kreeg je die error niet toen je het bestand measurements.py
testte?
Testcode
(easystat) > python tests/try_measurements.py
Traceback (most recent call last):
File "c:\Users\David\Documents\ECPC\easystat\tests\try_measurements.py", line 1, in
from easystat.measurements import result_with_uncertainty
File "C:\Users\David\Documents\ECPC\easystat\src\easystat\measurements.py", line 2, in
from shortcuts import stdev_of_mean
ModuleNotFoundError: No module named 'shortcuts'
Checkpunten
- Je kunt nog steeds zonder problemen het bestand
shortcuts.py
runnen. - Je kunt ook nog steeds het bestand
measurements.py
runnen. - Bij het runnen van het bestand
try_measurements.py
krijg je geen foutmelding meer voor de modulemeasurements
. - Bij het runnen van het bestand
try_measurements.py
krijg je wel een nieuwe foutmelding.
Projecttraject
- Easystat: uv project aanmaken
- Easystat: virtual environment synchroniseren
- Easystat: benodigde scripts aanmaken
- Easystat: testen
- Easystat: dependency toevoegen
- Easystat: opnieuw testen
- Easystat: imports aanpassen
- Easystat: absolute imports
- Easystat: voorbeeldscript bekijken
- Easystat: functie
main()
toevoegen - Easystat: commando toevoegen
- Easystat: commando testen
- Easystat: applicatie runnen in terminal
Het probleem is dat wanneer je met Python een script runt en je importeert iets, dat Python eerst in de map kijkt waar het script staat (in dit geval tests
) en daarna pas zoekt in de lijst met geïnstalleerde packages. De module shortcuts
staat niet in tests
. Toen je measurements.py
draaide kon Python de module shortcuts
wél vinden, want measurements.py
en shortcuts.py
staan in dezelfde map. Dus afhankelijk van welk script je draait kan Python de modules soms wel vinden en soms niet. Dat is natuurlijk niet zo handig. De oplossing: absolute imports. Geef bij alle imports van je eigen package altijd de naam van je package op.
Easystat: absolute imports
Je past in het bestand measurements.py
de regel from shortcuts ...
aan door de naam van het package toe te voegen en ziet dat de oranje kringeltjes ook verdwijnen. Je test de code in try_measurements.py
opnieuw. Gelukt!
Testcode
(easystat) > python tests/try_measurements.py
measurements=[1, 2, 2, 2, 3]
Result of measurements is: 2.00 +- 0.28.
Checkpunten
- Je hebt de import in het bestand
try_measurements.py
aangepast. - Je hebt de import in het bestand
measurements.py
aangepast. - Je krijgt geen foutmelding meer als je het bestand
try_measurements.py
runt.
Projecttraject
- Easystat: uv project aanmaken
- Easystat: virtual environment synchroniseren
- Easystat: benodigde scripts aanmaken
- Easystat: testen
- Easystat: dependency toevoegen
- Easystat: opnieuw testen
- Easystat: imports aanpassen
- Easystat: absolute imports
- Easystat: voorbeeldscript bekijken
- Easystat: functie
main()
toevoegen - Easystat: commando toevoegen
- Easystat: commando testen
- Easystat: applicatie runnen in terminal
Wheels
Wheels¶
Wanneer je klaar bent om jouw package te delen met andere gebruikers gebruik je het commando build
om zogeheten wheels te bouwen. Wheels zijn de bestanden die uv en pip downloaden en installeren wanneer je zegt pip install PACKAGE
. Het is een soort ingepakte installer met alles wat er nodig is om het package te gebruiken.
Easystat: wheel
- Bouw het wheel van
easystat
metuv build
. - Bekijk de namen van de bestanden in de nieuwe map
easystat/dist
. Welke extensie hebben ze?
(easystat) > uv build
Building source distribution (uv build backend)...
Building wheel from source distribution (uv build backend)...
Successfully built dist\easystat-0.1.0.tar.gz
Successfully built dist\easystat-0.1.0-py3-none-any.whl
Een .tar.gz
-bestand is een soort zipbestand met daarin de broncode van het package (een source distribution). De tests worden daar niet in meegenomen. Zogenaamde pure-python wheels bevatten alleen Pythoncode — en geen C-code die gecompileerd moet worden voor verschillende besturingssystemen of hardwareplatforms. Je herkent ze aan none-any
in de bestandsnaam. None
voor niet-OS-specifiek
en any
voor draait op elk hardwareplatform
. Je kunt dit bestand als download neerzetten op een website of aan anderen mailen. Zij kunnen het dan installeren met pip install
.
Easystat: test wheel
Tijd om het wheel uit te proberen. Je gaat straks een nieuw virtual environment aanmaken, installeert het wheel en probeert het testscript te runnen — één keer vóór het installeren van het wheel en één keer ná het installeren. Volg hiervoor de volgende stappen:
- Maak een nieuw — leeg — virtual environment aan.
- Run het bestand
test/try_measurements.py
en bekijk de foutmelding. - Installeer het wheel met
uv pip install .\dist\easystat-0.1.0-py3-none-any.whl
. - Run het bestand
test/try_measurements.py
opnieuw en bekijk de uitkomst.
Het werkt! Je ziet dat pip install
niet alleen jouw package easystat
installeert, maar ook de dependency numpy
. Dat is precies wat je wilt.
Het is belangrijk om de wheels niet in je GitHub repository te committen. Je repository is voor broncode, waarmee wheels gebouwd kunnen worden. Als je de stappen voor het aanmaken van de repository netjes gevolgd hebt, dan heb je een .gitignore
toegevoegd met Python-specifieke bestandsnamen en directories die genegeerd worden door Git en GitHub.
uv gebruiken voor een bestaand project¶
Natuurlijk willen we uv ook gaan gebruiken bij pythondaq
. Je maakt nu alleen geen nieuw project, maar je gaat uv toevoegen aan een bestaand project. Daarvoor moet je twee dingen doen: als eerste ga je uv initialiseren in de repository pythondaq
en daarna moet je de code in de src
-structuur plaatsen.
Pythondaq: uv project
- Je project
pythondaq
is zo tof aan het worden dat je het met uv gaat beheren, zodat jij en anderen het gemakkelijk kunnen installeren en gebruiken. Als eerste open je de repository in GitHub Desktop en daarna in Visual Studio Code. Open nu een nieuwe terminal. Je test voor de zekerheidrun_experiment.py
nog even uit, zodat je zeker weet dat alles nu nog werkt. Vervolgens maak je een uv project aan . Dan geef je op twee plaatsen aan dat je Python 3.12 wilt gebruiken (in welke bestanden moet je dit aangeven?). Daarna synchroniseer je je virtual environment en commit je je wijzigingen. - Test nu
run_experiment.py
opnieuw. Voeg alle benodigde dependencies toe , net zolang totdat alles werkt en je opnieuw de LED ziet branden en de resultaten van je experiment krijgt. Commit je wijzigingen.
Checkpunten
- Je hebt uv geïnitialiseerd in de projectmap
pythondaq
. - Na het initialiseren van uv zijn de bestanden
pyproject.toml
en.python-version
in de projectmap aangemaakt. - Wanneer je met
uv sync
een nieuw virtual environment aanmaakt, staat hier Python 3.12 in. - Je hebt alle dependencies toegevoegd, zodat
run_experiment.py
in de nieuwe environment naar behoren werkt.
Projecttraject
- Pythondaq: docstrings
- Pythondaq: uv project
- Pythondaq: src-layout
- Pythondaq: test imports
- Pythondaq: applicatie
Pythondaq: src-layout
run_experiment.py
opnieuw en je zorgt er voor dat je ook nu de LED ziet branden en de resultaten van je experiment krijgt.
pythondaq
├──
src
└──
pythondaq
├──
__init__.py
├──
arduino_device.py
├──
diode_experiment.py
└──
run_experiment.py
├──
.gitattributes
├──
.gitignore
├──
.python-version
├──
pyproject.toml
├──
README.md
└──
uv.lock
Checkpunten
- Al je 'oude' code staat nu in
src/pythondaq
. -
run_experiment.py
runt zonder problemen.
Projecttraject
- Pythondaq: docstrings
- Pythondaq: uv project
- Pythondaq: src-layout
- Pythondaq: test imports
- Pythondaq: applicatie
Model, view en controller packages
In grotere projecten is het gebruikelijk om model, view en controller niet alleen uit te splitsen in verschillende scripts, maar ook in aparte packages te zetten.
- Maak drie extra packages in de package
pythondaq
aan:models
,views
encontrollers
. - Zet de modules in de juiste packages.
- Test je code zodat alle imports weer werken.
Van script naar applicatie¶
Om code te testen heb je tot nu toe waarschijnlijk op de run
-knop in Visual Studio Code gedrukt. Of je hebt in de terminal aan python
gevraagd om het script.py
te runnen:
run
-knop gebruikt, moet wel het bestand dat je wilt runnen openstaan. Kortom, best een beetje gedoe. Maar als je programma's zoals uv, Conda of Python wilt gebruiken hoef je helemaal niet het juiste bestand op te zoeken en te runnen. Je hoeft alleen maar een commando in de terminal te geven — bijvoorbeeld python
of uv
— en de computer start automatisch het juiste programma op.
Dat willen wij ook voor onze programma's! En met uv kun je dat heel eenvoudig doen. Voordat je doorgaat met jouw package pythondaq
ga je dit eerst een keer testen. Je gaat daarvoor terug naar je easystat
repository.
Je kunt uv niet vragen om een script te runnen, maar wel om een functie in een module uit te voeren. Een nieuw uv project krijgt automatisch al een voorbeeldscript.
Easystat: voorbeeldscript bekijken
Je opent je easystat
repository in Visual Studio Code. Je opent een terminal en voert de opdracht easystat
uit. De code die nu gerund wordt staat in het bestand src/easystat/__init__.py
. Dit is overigens niet de beste plek, maar werkt prima als eenvoudig voorbeeld en is daarom automatisch aangemaakt door uv. Je bekijkt de code en ziet dat de bewuste code in een functie main()
staat.
Testcode
(easystat) > easystat
Hello from easystat!
Projecttraject
- Easystat: uv project aanmaken
- Easystat: virtual environment synchroniseren
- Easystat: benodigde scripts aanmaken
- Easystat: testen
- Easystat: dependency toevoegen
- Easystat: opnieuw testen
- Easystat: imports aanpassen
- Easystat: absolute imports
- Easystat: voorbeeldscript bekijken
- Easystat: functie
main()
toevoegen - Easystat: commando toevoegen
- Easystat: commando testen
- Easystat: applicatie runnen in terminal
Als je wilt dat er andere code gerund wordt als de opdracht easystat
gegeven wordt, dan moet je er voor zorgen dat je naar een functie in het betreffende bestand verwijst. Dit doe je net zoals dat gedaan is in het bestand src/easystat/__init__.py
.
Easystat: functie main()
toevoegen
Voor het vervolg heb je wat voorbeeldcode nodig. Dat kan van alles zijn dus je kopieert de code in het bestand tests/try_measurements.py
naar src/easystat/app.py
en zet de body
van de module in een functie main()
. Ook voeg je aan het eind toe:
main()
wordt aangeroepen, terwijl als je code importeert dat niet direct gebeurt. Zie de sectie Modules voor meer informatie. Als je beide regels weglaat gebeurt er helemaal niets als je het script runt. Er wordt dan wel een functie gedefinieerd maar hij wordt niet uitgevoerd.
Testcode
(easystat) > python src/easystat/app.py
measurements=[1, 2, 2, 2, 3]
Result of measurements is: 2.00 +- 0.28.
Checkpunten
- Er staat een functie
main()
in het bestandapp.py
. - In het bestand
app.py
is een statementif __name__ == '__main__'
toegevoegd. - Het runnen van het bestand
app.py
geeft als laatste regelResult of measurements is: 2.00 +- 0.28.
Projecttraject
- Easystat: uv project aanmaken
- Easystat: virtual environment synchroniseren
- Easystat: benodigde scripts aanmaken
- Easystat: testen
- Easystat: dependency toevoegen
- Easystat: opnieuw testen
- Easystat: imports aanpassen
- Easystat: absolute imports
- Easystat: voorbeeldscript bekijken
- Easystat: functie
main()
toevoegen - Easystat: commando toevoegen
- Easystat: commando testen
- Easystat: applicatie runnen in terminal
In pyproject.toml
kun je nu het commando toevoegen om een specifieke functie te runnen. In de sectie [project.scripts]
kun je aangeven met welk commando een functie uit een module wordt uitgevoerd. In pyproject.toml
staat deze sectie al:
naam_commando
het commando dat je in moet typen in de terminal. package
is de naam van het Python package waar de code staat en module
is de naam van de module waar de code staat. naam_functie
is de naam van de functie waarin de code staat. Als je module
weglaat, dan kijkt uv in __init__.py
. Vandaar dat uv met het commando easystat
op het moment de functie main()
in __init__.py
draait. Maar dit kun je natuurlijk aanpassen!
Om de wijzigingen aan pyproject.toml
door te voeren, moet je je virtual environment wel opnieuw synchroniseren. uv installeert dan jouw package ook opnieuw.
Easystat: commando toevoegen
Je past in de pyproject.toml
in de sectie [project.scripts]
het commando easystat
aan. Deze verwijst naar de functie main()
welke in de module app.py
staat, die ondergebracht is in de package easystat
. Omdat je handmatig de pyproject.toml
hebt aangepast, synchroniseer je je virtual environment opnieuw .
Checkpunten
- De naam van het commando is
easystat
. - De verwijzing na het = teken begint met twee aanhalingstekens gevolgd door het package
easystat
gevolgd door een punt. - Na de punt staat de naam van de module
app.py
zonder de extensie.py
gevolgd door een dubbele punt. - Na de dubbele punt staat de naam van de functie
main()
zonder haakjes()
. - Achter de functie staan weer dubble aanhalingstekens om de verwijzing te sluiten.
- Na het opslaan van de
pyproject.toml
is de virtual environment gesynchroniseerd.
Projecttraject
- Easystat: uv project aanmaken
- Easystat: virtual environment synchroniseren
- Easystat: benodigde scripts aanmaken
- Easystat: testen
- Easystat: dependency toevoegen
- Easystat: opnieuw testen
- Easystat: imports aanpassen
- Easystat: absolute imports
- Easystat: voorbeeldscript bekijken
- Easystat: functie
main()
toevoegen - Easystat: commando toevoegen
- Easystat: commando testen
- Easystat: applicatie runnen in terminal
Easystat: commando testen
Nu je het commando easystat
hebt aangemaakt ga je deze testen in een terminal. Je ziet de verwachte uitvoer verschijnen, met als laatste regel Result of measurements is: 2.00 +- 0.28.
Je vraagt je af of het commando ook werkt als de terminal in een andere map zit. Met het commando cd..
ga je naar een bovenliggende map. Je test het commando easystat
en ziet weer de tekst Result of measurements is: 2.00 +- 0.28.
verschijnen. Je concludeert dat het commando nu overal werkt zolang het juiste virtual environment is geactiveerd. Dat test je uit door het virtual environment te deactiveren en het commando easystat
nogmaal te proberen. Je krijgt een error en hebt daarmee je vermoeden bewezen. Tevreden ga je door naar de volgende opdracht.
Pseudo-code
(easystat) > easystat
measurements=[1, 2, 2, 2, 3]
Result of measurements is: 2.00 +- 0.28.
Checkpunten
- Het commando
easystat
werkt als het juiste virtual environment is geactiveerd. - Het commando
easystat
werkt nog steeds nadat je met het commandocd..
naar een bovenliggende map bent gegaan. - Het commando
easystat
werkt niet als het virtual environment is gedeactiveerd.
Projecttraject
- Easystat: uv project aanmaken
- Easystat: virtual environment synchroniseren
- Easystat: benodigde scripts aanmaken
- Easystat: testen
- Easystat: dependency toevoegen
- Easystat: opnieuw testen
- Easystat: imports aanpassen
- Easystat: absolute imports
- Easystat: voorbeeldscript bekijken
- Easystat: functie
main()
toevoegen - Easystat: commando toevoegen
- Easystat: commando testen
- Easystat: applicatie runnen in terminal
Error analysis
Maak deze opdracht alleen als je extra wilt oefenen. Je gaat met uv een commando maken om een ander script uit te laten voeren. De package is al aangemaakt, maar werkt nog niet naar behoren. Los in de volgende opdrachten de errors op om het script data_analysis.py
te laten runnen met een commando.
- Ga naar GitHub en clone
NatuurkundepracticumAmsterdam/erroranalysis
in GitHub Desktop en open de repository daarna in Visual Studio Code. - Natuurlijk synchroniseer je meteen een virtual environment , voordat je dit package gaat testen.
- Snuffel door de bestanden en mappen. Open
src/erroranalysis/data_analysis.py
. Dit is het script wat moet kunnen runnen. - Run het script
data_analysis.py
en los de errors één voor één op. - Om erachter te komen of de problemen die je eerder had écht zijn opgelost, maak je een nieuw — leeg(!) — virtual environment aan en test je dat het script niet werkt. Daarna synchroniseer je het environment en run je het script opnieuw. Werkt alles? Mooi!
- Dan ga je nu een commando aanmaken om de functie
table()
aan te roepen. Openpyproject.toml
en zoek de sectiescripts
. Maak een commando die de functietable()
aanroept insrc/erroranalysis/data_analysis.py
. Je mag de naam van het commando zelf kiezen. Het formaat voor een commando is: - Ga naar de terminal en kijk of het commando werkt. Als je onderstaande output krijgt, dan is het gelukt!
(erroranalysis) > naam_commando Area of the kitchen table is: 1.8386 ± 0.0049 m
Pythondaq: test imports
tests
-map met test_imports.py
in de repository pythondaq
. Voeg aan test_imports.py
onderstaande code toe:
Run het bestand test_imports.py
en los de errors op. Nu werkt je package ook als je het aanroept van buiten de map met broncode. Je repository pythondaq
is nu een volledig project dat je met andere gebruikers van Python kunt delen, bijvoorbeeld via een wheel.
pythondaq
├──
src
└──
pythondaq
├──
__init__.py
├──
arduino_device.py
├──
diode_experiment.py
└──
run_experiment.py
├──
tests
└──
test_imports.py
├──
.python-version
├──
pyproject.toml
├──
README.md
└──
uv.lock
Pseudocode
Testcode
(ECPC) > python test_imports.py
Traceback (most recent call last):
File "c:\Users\David\Documents\ECPC\pythondaq\tests\test_imports.py", line 1, in
import pythondaq.run_experiment
File "C:\Users\David\Documents\ECPC\pythondaq\src\pythondaq\run_experiment.py", line 5, in
from diode_experiment import DiodeExperiment, list_resources
ModuleNotFoundError: No module named 'diode_experiment'
Checkpunten
- Er staat een map
tests
in de repositorypythondaq
. - De import-statements in de modules in het package
pythondaq
zijn aangepast, zodat het bestandtest_imports.py
runt zonder problemen.
Projecttraject
- Pythondaq: docstrings
- Pythondaq: uv project
- Pythondaq: src-layout
- Pythondaq: test imports
- Pythondaq: applicatie
Easystat: applicatie runnen in terminal
Inmiddels ben je misschien gewend dat je virtual environments altijd moet activeren voordat je applicaties kunt runnen die daarin geïnstalleerd zijn. In Visual Studio Code gaat dat automatisch als je het environment goed geselecteerd hebt. Maar... hoe moet dat zonder Visual Studio Code? Omdat uv virtual environments in je projectmap neerzet — de map .venv
— moet je met je terminal wel in de goede map zitten. Daarna kun je ervoor kiezen om het commando te runnen met uv, dan is activeren van het virtual environment niet nodig, of zonder uv, dan is activeren van het virtual environment wel nodig. Voer de volgende opdrachten uit om dit te proberen.
- Open in GitHub Desktop nogmaals je repository
easystat
. - Klik op Menu > Repository > Open in Command Prompt om een nieuwe terminal te openen, in je projectmap.
- Run het commando
easystat
. Dit werkt niet. - Run het commando
uv run easystat
. Dit werkt wel. - Activeer het environment door het volgende commando te runnen:
.venv\Scripts\activate
. - Controleer dat
(easystat)
aan het begin van de opdrachtprompt staat. - Run het commando
easystat
. Dit werkt nu wel.
Je mag zelf kiezen welke methode je fijn vindt.
Projecttraject
- Easystat: uv project aanmaken
- Easystat: virtual environment synchroniseren
- Easystat: benodigde scripts aanmaken
- Easystat: testen
- Easystat: dependency toevoegen
- Easystat: opnieuw testen
- Easystat: imports aanpassen
- Easystat: absolute imports
- Easystat: voorbeeldscript bekijken
- Easystat: functie
main()
toevoegen - Easystat: commando toevoegen
- Easystat: commando testen
- Easystat: applicatie runnen in terminal
Pythondaq: applicatie
Je maakt een commando om het script run_experiment.py
uit de repository pythondaq
te starten . Wanneer je het commando aanroept gaat de LED branden en verschijnt er even later een $IU$-plot op het scherm. Je test of het commando ook buiten Visual Studio Code werkt door vanuit GitHub een nieuwe terminal te openen (Menu > Repository > Open in Command Prompt). Je test het commando met uv run COMMAND
en ook door het juiste virtual environment te activeren . Je ziet dat in beide gevallen het commando werkt. Wat een feest! Je hebt nu een applicatie geschreven die een Arduino aanstuurt om een LED te laten branden. En je kunt je applicatie gewoon vanuit de terminal aanroepen!
Pseudo-code
Checkpunten
- De functie in
run_experiment.py
bevat alle code die uitgevoerd moet worden om een meting te starten. - Het commando in de
pyproject.toml
verwijst op de correcte manier naar de functie inrun_experiment.py
. - Het aanroepen van het commando zorgt ervoor dat een meting gestart wordt.
- Het commando werkt ook in een terminal buiten Visual Studio Code, zolang het juiste virtual environment actief is óf
uv run
wordt gebruikt.
Projecttraject
- Pythondaq: docstrings
- Pythondaq: uv project
- Pythondaq: src-layout
- Pythondaq: test imports
- Pythondaq: applicatie
Versie 2.0.0
In de pyproject.toml
kan je ook de versie aangeven van je package. Maar wanneer hoog je nu welk cijfer op? Wanneer wordt iets versie 2.0.0? Daar zijn conventies voor. Bug fixes gaan op het laatste cijfer, wijzigingen en nieuwe features gaan op het middelste cijfer. Wanneer de applicatie dusdanig verandert dat bijvoorbeeld bestanden die je met de oude versie hebt gemaakt niet met de nieuwe versie geopend kunnen worden, dan verander je het eerste cijfer. Je start vaak met versie 0.1.0 en blijft tijdens het bouwen van je project ophogen naar 0.2.0 en soms zelfs 0.83.0. Wanneer je project min of meer klaar is voor het gebruik, dan kies je er vaak voor om versie 1.0.0 te releasen.
-
Echt gebeurd: meerdere studenten leverden hun grafische applicatie in voor een beoordeling. We konden het niet draaien, want er misten bestanden. Bij de studenten werkte het wel, maar bij ons echt niet. ↩
-
more
is een programma die aangeleverde tekst per pagina laat zien, waar je met Space een volgende pagina te zien krijgt. Met Enter krijg je maar één regel extra en met Q sluit je het programma meteen af. Het|
karakter stuurt output door. Dusuv help | more
stuurt de output vanuv help
door naar het programmamore
. ↩ -
Wanneer de repository op GitHub wordt geplaatst, wordt deze README automatisch op de hoofdpagina van de repository getoond, onder de code. ↩
-
Ja, er is een map
easystat
met daarin een mapsrc
met daarin weer een mapeasystat
— dat kan nog wel eens verwarrend zijn. Het is conventie om de projectmap dezelfde naam te geven als je package. Het pad is dus eigenlijkproject/src/package
en dat wordt dan, in dit geval,easystat/src/easystat
. ↩ -
Vroeger was er een
setup.py
, maar Python schakelt nu langzaam over naar dit nieuwe bestand. ↩ -
In f-strings kunnen tussen de accolades variabelen of functieaanroepen staan. Voeg daar het
=
-teken aan toe en je krijgt niet alleen de waarde, maar ook de variabele of aanroep zelf te zien. Bijvoorbeeld: als je definieertname = "Alice"
, dan geeftprint(f"{name}")
als uitkomstAlice
. Maar voeg je het=
-teken toe, zoals inprint(f"{name=")}
, dan wordt de uitvoername='Alice'
. Je ziet dan dus ook meteen de naam van de variabele en dat kan handig zijn. ↩ -
Tom Preston-Werner, Pradyun Gedam, and others. Tom's obvious, minimal language. URL: https://github.com/toml-lang/toml. ↩