From d2ec709eb6dc6c9b59c51eef9a10d3ac0091819d Mon Sep 17 00:00:00 2001
From: David Fokkema Lijst van benodigdhedenWe hebben voor deze cursus de volgende onderdelen gebruikt:
Welkom bij de Experiment Control with Python Course. Dit vak wordt aangeboden aan studenten van de joint degree natuur- en sterrenkunde van de Vrije Universiteit en de Universiteit van Amsterdam, en wordt gegeven door Annelies Vlaar (a.m.vlaar@vu.nl) en David Fokkema (d.b.r.a.fokkema@vu.nl).
Het doel van deze cursus is om jullie kennis te laten maken met het aansturen en uitlezen van een experiment. Bij heel eenvoudige experimenten kun je metingen \u00e9\u00e9n voor \u00e9\u00e9n verrichten en noteren in je labjournaal, bijvoorbeeld bij de trillingstijd van een slinger bij verschillende lengtes. Maar al snel wordt dat ondoenlijk, bijvoorbeeld wanneer je de hele beweging van de slinger wilt volgen met een ultrasoon afstandsdetector. In een gemiddeld lab worden alle experimenten via de computer aangestuurd en uitgelezen. Voor standaard handelingen zoals het bekijken van een sample onder een elektronenmicroscoop of het opnemen van een spectrum van een radioactieve bron kun je de door de fabrikant meegeleverde software gebruiken. Vaak echter is die software \u00f3f heel duur terwijl je maar een klein deel van de functionaliteit nodig hebt, of ongeschikt voor jouw doeleinden. En heel vaak voer je g\u00e9\u00e9n standaardexperiment uit, maar ben je een nieuw experiment aan het opzetten. In dat laatste geval is er helemaal geen software voorhanden. Je zult dus zelf aan de slag moeten.
We willen je in deze cursus niet alleen maar leren om een snelle meting uit te voeren, maar ook hoe je de code netjes schrijft. Als je een bachelorproject doet, een masterstage, of een promotieonderzoek, dan wil je code schrijven die nog steeds bruikbaar is nadat je je stage hebt afgerond of je proefschrift hebt verdedigd. Ook zullen andere onderzoekers aan dezelfde code werken. Het is dus belangrijk om te zorgen voor een duidelijke structuur waarmee de code overzichtelijk blijft maar ook makkelijk is aan te passen of uit te breiden.
Jullie gaan aan de slag met een Arduino. De Arduino bevat firmware waarmee het zich gedraagt als een meetinstrument en kan communiceren met de computer volgens een standaardprotocol dat ook ge\u00efmplementeerd wordt door onder andere functiegeneratoren en oscilloscopen.
"},{"location":"#werk-van-anderen","title":"Werk van anderen","text":"Over Python wordt veel geschreven en er zijn tal van (professionele) trainingen die je kunt volgen. Over de hele wereld zijn conferenties en er is vermoedelijk geen universiteit meer te vinden waar Python niet wordt gebruikt. Dit vak is niet in een vacu\u00fcm ontstaan. Voordat dit vak in studiejaar 2020-2021 voor het eerst gegeven werd is jarenlang het vak Experimentautomatisering met LabVIEW gegeven aan de Vrije Universiteit door Jan Mulder en Gerrit Kuik. Dit vak is de spirituele opvolger van het LabVIEW-vak. Een website (en boek!) met eenzelfde soort insteek is Python for the lab van Aquiles Carattino.2 Op de website is veel informatie te vinden over hoe je Python gebruikt (slots, singletons, multiprocessing) en met verschillende soorten hardware communiceert (van o.a. National Instruments en Basler). In het boek leer je hoe je een eenvoudig experiment opzet en aanstuurt vanuit Python, heel vergelijkbaar met het eerste deel van deze cursus. Zowel in deze cursus als in het boek is gekozen voor het diode-experiment (project 1 uit het Arduino Projects Book dat meegeleverd wordt met de Arduino Starter Kit). Een groot verschil is dat je in deze cursus leert over versiebeheer, command-line interfaces en project management met Poetry.
Voor het eindfeest automatiseren we een zonnecelexperiment. Het idee hiervoor is gebaseerd op het experiment dat we uitvoeren in het eerste jaar. Hoewel hetzelfde eerder gedaan is door o.a. Chenni et al.3 en Hammoumi et al.4 met LabVIEW, is de schakeling die in deze cursus gebruikt wordt door ons ontworpen om de noodzaak van een relatief dure stroomsterktesensor te vermijden. Ook wordt de schakeling daardoor fysisch gezien wat interessanter.
Deze cursus is oorspronkelijk opgezet door David Fokkema maar in de jaren daarna door Annelies Vlaar aanzienlijk verbeterd. De cursus heeft nu een veel sterkere focus (minder belangrijke zaken zijn verwijderd) en de opbouw is flink aangepakt. In 2024 heeft Annelies ook een subsidie toegekend gekregen om de cursus verder te verbeteren door een aantal studentassistenten in te huren. We zijn veel dank verschuldigd aan Olivier Swaak, Derk Niessink, Amin Rouan Serik, Thijs de Zeeuw en Fijke Oei.
"},{"location":"#notatie","title":"Notatie","text":"We zullen in deze handleiding vaak Engelse termen gebruiken, ook als Nederlandse termen voorhanden zijn. Bijvoorbeeld: list in plaats van lijst, class in plaats van klasse. Dit doen we omdat deze Engelse termen veel meer ingeburgerd zijn en omdat voor sommige van de Engelse termen geen goede vertalingen zijn. Liever wat consequenter Engelse termen gebruiken dan alles door elkaar!
In deze handleiding kom je twee verschillende dingen tegen. Pythoncode en systeemopdrachten. Voor pythoncode geldt dat je in Visual Studio Code een nieuw bestand moet aanmaken met de extensie .py
en dat je daarin de code kunt typen. Vervolgens kun je het script runnen en geeft Visual Studio Code de uitvoer terug. In deze handleiding zal dat als volgt worden weergegeven:
# I am a script. I am in a colored block\n# and the Python code has colored syntax.\n\ndef my_func(x):\n return x ** 2\n\nprint(my_func(2))\n
\n(ecpc) > python python_code.py\n4\n
Linksboven kun je op de -icoon klikken om de output van de python code te zien.
Rechtsboven in het blok staat een -icoon. Als je daar op klikt dan wordt de hele code gekopieerd naar het klembord en kun je het in Visual Studio Code weer plakken met Ctrl+V.
Ook zullen we soms systeemopdrachten moeten uitvoeren. We willen bijvoorbeeld nieuwe Pythonbibliotheken installeren of onze nieuw-gebouwde applicaties testen. Dit doen we vanuit de terminal. De terminal biedt een zogeheten command-line interface voor het systeem. Dit in tegenstelling tot een grafische interface.1
Met deze notatie laten we zien hoe je my-script.py
met python kunt runnen: Terminal
python my-script.py\n
Zoals je ziet hebben we de prompt (bijvoorbeeld >
) weggelaten zodat je makkelijker commando's kunt kopi\u00ebren en plakken. Wanneer we ook de uitvoer van commando's laten zien is het handiger om onderscheid te maken tussen het commando en de uitvoer. Nu geven we wel de prompt weer ((ecpc) >
). Door op het -icoon te klikken wordt de uitvoer zichtbaar. (ecpc) > python --version \nPython 3.10.9\n
We maken veel gebruik van conda environments. Hierboven zie je de naam van de conda environment tussen ronde haakjes staan, in dit geval heet de conda environment ecpc
. In de voorbeeldcode staat standaard de ecpc
conda environment, maar welk conda environment je moet gebruiken hangt van de opdracht af.
In de handleiding staan verschillende opgaves. De meeste zijn bedoeld als oefening, maar sommige moet je inleveren voor feedback en een beoordeling. Schrijf je code in het Engels, zo ben je voorbereid op het werken in een internationale onderzoeksgroep.
Info
In sommige programmeercursussen is het de bedoeling dat je een bepaald algoritme zelf schrijft. Je moet bijvoorbeeld een loop schrijven om een reeks berekeningen uit te voeren en mag dan niet gebruik maken van de NumPy-bibliotheek om dat in \u00e9\u00e9n regel te doen. Je kunt je voorstellen dat als je straks in een lab werkt dat je juist gebruik wilt maken van bibliotheken die het leven een stuk gemakkerlijker maken. Trek dus alles uit de kast. Kijk eens in de Python Standard Library,5 de Python Package Index6 of Awesome Python7.
Basisopdracht
opdrachtcodecheckDeze opgaves helpen je om het niveau te behalen wat van je verwacht wordt. Ze worden niet beoordeeld. Hierboven zie je 3 tabbladen opdracht, code en check. Klik op code om naar het volgende tabblad te gaan.
Pseudo-code
# hierin staat code om je verder op weg te helpen.\n
Testcode Testcode.py # gebruik (delen van) deze code om je opdracht te testen.\n# Bekijk de output van deze code\n# Ga daarna naar het tabblad \"check\"\n
\n(ecpc) > python Testcode.py\nKrijg je (ongeveer) dezelfde output?\n
Checkpunten:
Projecttraject
Inleveropdracht
Deze opgave moet worden ingeleverd voor feedback en een beoordeling. Je herkent ze aan de groene kleur. De opgaven bouwen voort op elkaar, dus er zijn meerdere opgaven. Je levert ze niet los in, maar als geheel. Kijk goed bij het projecttraject in het tabblad check welke groene opdrachten je gemaakt moet hebben voordat je het inlevert.
Vaak heb je kennis en/of vaardigheden nodig die je eerder heb geleerd. Zie je een lampje staan? Klik dan bovenin de blauwe balk (rechtsboven, naast de zoekbalk) op het lampje om de spiekbriefjes te openen.
Meer leren
Dit zijn verdiepende en verbredende opgaves om je te kunnen ontwikkelen tot een goed programmeur en een waardevolle aanwinst voor een onderzoeksgroep. Je kunt er geen extra punten mee verdienen wanneer je deze technieken toepast in je inleveropdrachten, maar het is wel een goede oefening. Doe deze opgaves alleen als je klaar bent met de rest.
Een basiskennis van Python is nodig om de opdrachten te kunnen maken. In de paragraaf Basiskennis Python vind je opdrachten om je kennis te testen. Het is handig om een uitgebreidere Python kennis te hebben, meer informatie vind je in de paragraaf Uitgebreidere Python kennis.
Er bestaan verschillende terminal emulators, meestal afhankelijk van het besturingssysteem \u2014 al heeft Windows zelf al drie verschillende prompts: de command prompt, de powershell prompt en tegenwoordig (voorkeur) de Windows Terminal. Een terminal ziet eruit als een tekstvenster. Hierbinnen verschijnt een prompt. Dit is een klein stukje tekst dat aangeeft waar je je opdrachten kunt intypen. In MacOS en Linux is de prompt vaak een $
-teken. In Windows ziet het er vaak uit als C:\\>
of PS>
. In veel documentatie op internet wordt de $
-prompt gebruikt.\u00a0\u21a9
Aquiles Carattino. Python for the lab. URL: https://pythonforthelab.com.\u00a0\u21a9
Rachid Chenni. Tracing current-voltage curve of solar panel based on labview arduino interfacing. Bili\u015fim Teknolojileri Dergisi, 8:, 09 2015. URL: https://www.researchgate.net/publication/283037607_Tracing_current-voltage_curve_of_solar_panel_Based_on_LabVIEW_Arduino_Interfacing.\u00a0\u21a9
A El Hammoumi, S Motahhir, A Chalh, A El Ghzizal, and A Derouich. Real-time virtual instrumentation of arduino and labview based pv panel characteristics. IOP Conference Series: Earth and Environmental Science, 161(1):012019, jun 2018. doi:10.1088/1755-1315/161/1/012019.\u00a0\u21a9
Python Software Foundation. The python standard library. URL: https://docs.python.org/3/library/.\u00a0\u21a9
Python Software Foundation. Python package index. URL: https://pypi.org.\u00a0\u21a9
Vinta Chen. Awesome python. URL: https://awesome-python.com.\u00a0\u21a9
Bij de cursus inleiding programmeren heb je de basis van het programmeren in Python geleerd. Bij inleiding programmeren mocht je kiezen om je code in het Nederlands of Engels te schrijven. Omdat wij jullie voorbereiden om in een onderzoeksgroep je bachelor project te gaan doen waar je hoogstwaarschijnlijk internationale collega's gaat treffen vragen we jou om bij ECPC alles in het Engels te schrijven. In deze paragraaf nemen we de hoofdlijnen van inleiding programmeren met je door in een aantal opdrachten.
"},{"location":"basis-python/#visual-studio-code","title":"Visual Studio Code","text":"Open VSCode en maak de map ECPC
f-strings, variabelen en input
diameter-ball.py
in de map ECPC
. ECPC
\u2514\u2500\u2500 diameter.py
diameter = input(\"Enter the diameter of the ball: \")\ndiameter = float(diameter)\nradius = diameter / 2\nprint(f\"A ball with a diameter of {diameter} m has a radius of {radius} m.\")\n
"},{"location":"basis-python/#if-statement-en-operatoren","title":"If-statement en operatoren","text":"if-statement en operatoren
Met een if
-statement kan je een conditie testen door operatoren te gebruiken.
...
de juiste condities in door gebruik te maken van and
, not
en or
. import random\n\nrain = random.choice([True, False])\numbrella = random.choice([True, False])\n\nprint(f\"{rain=}, {umbrella=}\")\n\nif ... :\n print(\"Lucky you have your umbrella with you since it's raining.\")\n\nif ... :\n if ... :\n print(\"You will get wet without an umbrella since it's raining.\")\n if ... :\n print(\"you can use your umbrella as a walking stick since it doesn't rain\")\n\nif ... :\n print(\"Without an umbrella there is no problem since it's not raining.\")\n
import random\n\nrain = random.choice([True, False])\numbrella = random.choice([True, False])\n\nprint(f\"{rain=}, {umbrella=}\")\n\nif rain and umbrella:\n print(\"Lucky you have your umbrella with you since it's raining.\")\n\nif rain or umbrella:\n if rain and not umbrella:\n print(\"You will get wet without an umbrella since it's raining.\")\n if not rain and umbrella:\n print(\"you can use your umbrella as a walking stick since it doesn't rain\")\n\nif not rain and not umbrella:\n print(\"Without an umbrella there is no problem since it's not raining.\")\n
"},{"location":"basis-python/#for-loop-while-loop-en-break","title":"For-loop, while-loop en break","text":"For-loop, while-loop en break
voltage = 0 # mV\nsteps = 50 # mV\nwhile voltage < 3300:\n voltage += steps\n
Bij het programmeren krijg je vaak errors. Bij het debuggen van een loop zijn twee dingen heel handig print
en break
.
print
om het voltage te printen in the while-loop, doe dit handig met f-strings zodat je weet wat je je print bijvoorbeeld: \"The voltage is set to 0 mV.\"break
om de loop maar een keer te laten lopen. for
-loop. print_and_break.py
voltage = 0 # mV\nsteps = 50 # mV\nwhile voltage < 3300:\n voltage += steps\n print(f\"The voltage is set to {voltage} mV.\")\n break\n
\n(ecpc) > python print_and_break.py\nThe voltage is set to 50 mV.\n
for_loop.py
for voltage in range(0, 3300, 50):\n print(f\"The voltage is set to {voltage} mV.\")\n
\n(ecpc) > python for_loop.py\nThe voltage is set to 0 mV.\nThe voltage is set to 50 mV.\nThe voltage is set to 100 mV.\nThe voltage is set to 150 mV.\n...\n
"},{"location":"basis-python/#functie","title":"Functie","text":"functies
Maak uit de onderstaande code de functie exponentiation werkend.
def exponentiation():\n solution =\n ...\n\n\nnumber_1 = 2\nnumber_2 = 8\n\nanswer = exponentiation(number_1, number_2)\nprint(f\"{number_1}^{number_2} = {answer}\")\n
In deze opdracht zijn 4 variabele solution
, number_1
, number_2
en answer
. Welk van deze variabele zijn globaal en welke zijn lokaal?
def exponentiation(base, exponent):\n solution = base**exponent\n return solution\n\n\nnumber_1 = 2\nnumber_2 = 8\n\nanswer = exponentiation(number_1, number_2)\nprint(f\"{number_1}^{number_2} = {answer}\")\n
De globale variabelen zijn number_1
, number_2
en answer
en de lokale variabele is solution
.
Het gevolg is dat number_1
, number_2
en answer
wel binnen de functie exponentiation()
gebruikt kunnen worden, maar solution
niet buiten de functie exponentiation()
gebruikt kan worden.
lijsten
lijsten.py
months = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n]\n\nninth_month = months[8]\n\nprint(f\"The ninth month is called {ninth_month}\")\n\nmonths.append(\"tr\u0113decimber\")\n\nprint(months)\n
\n(ecpc) > python lijsten.py\nThe ninth month is called September.\n['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', 'tr\u0113decimber']\n
"},{"location":"basis-python/#stijl","title":"Stijl","text":"Bij inleiding programmeren heb je ook geleerd hoe je code netjes opschrijft zodat het leesbaar en begrijpelijk is. Hieronder vind je een samenvatting, die een beetje aangevuld is met ECPC stijl.
def functie_namen_met_doel():
Namen van functies mogen lang zijn, maar geven duidelijk weer wat de functie doet.korte_variabele = 28
de namen van variabele houd je kort en duidelijk. Gebruik alleen afkortingen waarneer deze door veel mensen gekend zijn.#commentaar-kopjes
om een stukje code samen te vatten, een waarschuwing te geven, uitleg van complexe algoritmen te doen, voor bronvermelding, uitleg van een variabele te geven en zet dit altijd boven het stukje code waar het omgaat. Ook heb je geleerd om functies uit andere (python) modules te importeren, meer hierover vind je in de paragraaf Modules.
"},{"location":"basis-python/#plotten","title":"Plotten","text":"Grafieken
Gebruik matplotlib om een scatterplot te maken van twee lijsten die hieronder zijn weergegeven. Zet de grootheden en eenheden bij beide assen en sla het figuur op als .png-bestand.
time = [0, 0.5, 1, 1.5, 2, 2.5, 3] #seconds\ndistance = [0, 15, 50, 100, 200, 300, 400] #meters\n
Uitwerkingen import matplotlib.pyplot as plt\n\ntime = [0, 0.5, 1, 1.5, 2, 2.5, 3] # seconds\ndistance = [0, 15, 50, 100, 200, 300, 400] # meters\n\nplt.plot(time, distance, \"o\")\nplt.xlabel(\"Time (s)\")\nplt.ylabel(\"Distance (m)\")\nplt.savefig(\"plot.png\")\n
"},{"location":"basis-python/#bestanden-inlezen","title":"Bestanden inlezen","text":"txt-bestanden lezen
Hieronder vind je een verhaal, kopieer de inhoud naar een .txt-bestand en sla deze op een handige plek op.
\"Do you have a favourite\nsaying?\" asked the boy.\n\"Yes\" said the mole\n\"What is it?\"\n\"If at first you don't \nsucceed have some cake.\"\n\"I see, does it work?\"\n\"Every time.\"\nFrom: The Boy, the mole, the fox and the Horse - Charlie Mackesy\n
Schrijf een script om het .txt-bestand te lezen en regel voor regel te printen.
Uitwerkingentxt_bestanden_lezen.py
with open(\"story.txt\", \"r\") as file:\n for line in file:\n print(line)\n
\n(ecpc) > python txt_bestanden_lezen.py\n\"Do you have a favourite\nsaying?\" asked the boy.\n\"Yes\" said the mole\n\"What is it?\"\n\"If at first you don't \nsucceed have some cake.\"\n\"I see, does it work?\"\n\"Every time.\"\nFrom: The Boy, the mole, the fox and the Horse - Charlie Mackesy\n
"},{"location":"basisscript/","title":"Basisscript voor het experiment","text":"Het experiment wat we gaan uitvoeren is het bepalen van de $I,U$-karakteristiek van een LED. Omdat de Arduino alleen getallen tussen 0 en 1023 kan sturen en ontvangen moeten we nadenken over de Analoog-digitaalconversie voordat we een zinnige $I,U$-karakteristiek kunnen maken.
"},{"location":"basisscript/#analoog-digitaalconversie-adc","title":"Analoog-digitaalconversie (ADC)","text":"We hebben tot nu toe gewerkt met getallen van 0-1023 sturen en ontvangen. Wat is precies de betekenis van deze getallen? Daarvoor moeten we dieper ingaan op hoe de Arduino \u2014 en computers in het algemeen \u2014 getallen omzet in een spanning en hoe spanningen door de Arduino worden gemeten.
Een analoog signaal is continu in zowel de tijd als de waardes die het signaal aan kan nemen. Een digitaal signaal is echter discreet: op vaste tijdstippen is er een waarde bekend en het signaal kan maar een beperkt aantal verschillende waardes aannemen.1
Bemonsteren of sampling is het proces waarbij een analoog signaal wordt uitgelezen en wordt omgezet in een digitaal signaal. Zo wordt een audiosignaal al sinds eind jaren '70 van de vorige eeuw gewoonlijk bemonsterd met een frequentie van 44.1 kHz en een resolutie van 16 bits. Dus 44100 keer per seconde wordt er gekeken wat de waarde van het geluidssignaal is en dat wordt opgeslagen als een getal van 16 bits en kan dus $2^{16} = 65536$ verschillende waardes aannemen. Dit is nauwkeuriger dan het menselijk gehoor kan onderscheiden.
0 3.3 V 0 15 63 1023ADC resolutie
De schuifjes hierboven zijn aan elkaar gekoppeld. Het bovenste schuifje laat de analoge waarde zien. Het onderste schuifje is de bijbehorende digitale waarde.
De conversie van een analoog signaal naar een digitaal signaal (en andersom!) is de reden dat de spanningen die we kiezen en de metingen die we doen niet alle mogelijke waardes kunnen aannemen, maar stapjes maken.
De omzetting van een analoog signaal naar een digitaal signaal gebeurt als volgt. De ADC (analog-to-digital converter) in dit voorbeeld ondersteunt 16 niveau's (4-bits) in een bereik van 0 V tot 3.3 V (groen gearceerd). Lagere of hogere spanningen kunnen niet gemeten worden (rood gearceerd). Op gezette tijden wordt een meting gedaan (rode punten) waarbij de uitkomst van de meting het discrete niveau is dat het dichtst bij de analoge waarde ligt. Als het signaal te groot wordt kan de ADC als het ware vastlopen op het hoogste niveau. In de rechterflank is waar te nemen dat als het analoge signaal langzaam verandert dat het digitale signaal duidelijk sprongsgewijs verandert. Hoe meer niveau's een ADC heeft en hoe vaker het signaal bemonsterd kan worden, hoe nauwkeuriger het digitale signaal het analoge signaal benadert.
De digitale metingen die je programma krijgt van de ADC is hierboven weergegeven. De onzekerheid is gelijk aan de halve afstand tot het volgende niveau. In lichtgrijs zie je het oorspronkelijke analoge signaal. De meting benadert het signaal dus maar gedeeltelijk. De Arduino die we gebruiken heeft een bereik van 0 V tot 3.3 V en \u2014 in tegenstelling tot het voorbeeld hierboven \u2014 een resolutie van 10 bits, dus $2^{10} = 1024$ stapjes. Als je een experiment ontwerpt is het dus van belang te weten dat je nooit kunt meten met een nauwkeurigheid kleiner dan de stapgrootte. Voor ons is deze resolutie prima.
Volt naar ADC
opdrachtcodecheckJe hebt gezien dat de Arduino werkt met getallen van 0 t/m 1023 en dat de Arduino een bereik heeft van 0 V tot 3.3 V. Je schrijft de formule op om de ruwe ADC waarde naar een spanning in Volt om te rekenen en omgekeerd. Je controleert of je formules logische antwoorden geven door de spanning te berekenen die bij een ruwe waarde van 700 hoort en de ruwe waarde die hoort bij 2.28 V.
Pseudo-code
# raw_value to voltage\n# voltage = something with raw_value\n\n# voltage to raw_value\n# raw_value = something with voltage\n
Checkpunten:
Projecttraject:
Wij schrijven onze getallen op in een decimaal (tientallig) talstelsel. We hebben tien verschillende cijfers (0 t/m 9) en plakken bij grotere getallen de tientallen, honderdtallen, etc. aan elkaar. Computers werken met binaire getallen \u2014 een tweetallig talstelsel. Dat betekent dat computers het getal 0 en 1 zonder problemen kunnen opslaan, maar bij het getal 2 wordt het al lastig. Zij moeten dan al met tientallen werken en schrijven het getal 2 op als 10. Het getal 3 is dan 11. Voor 4 zijn de cijfers alweer op en moeten we overschakelen naar honderdtallen, dus 4 is 100, 5 is 101, enz. Zie onderstaande tabel voor nog een paar voorbeelden. De cijfers noem je bits en het getal 5 (101 binair) bestaat dus uit 3 bits. Als je maar 3 bits tot je beschikking hebt kun je $2^3 = 8$ verschillende getallen opslaan, dus 0 t/m 7. Een groepje van 8 bits (256 mogelijkheden) bleek een handige hoeveelheid en kun je op computers individueel opslaan. Zo'n groepje noem je een byte. Bestanden bestaan uit bytes, kilobytes (duizend bytes), megabytes (miljoen bytes) of gigabytes (miljard bytes). Wanneer je een signaal nauwkeurig wilt verwerken met een computer dan is het belangrijk om zoveel mogelijk bits tot je beschikking te hebben. Hoe meer bits, hoe meer verschillende waardes je kunt opslaan en hoe nauwkeuriger je signaal wordt bewaard.
Voorbeelden van het binair talstelsel:
decimaal getal binair getal 0 0 1 1 2 10 3 11 4 100 5 101 6 110 7 111 8 1000 9 1001 \u2026 \u2026 205 11001101"},{"location":"basisscript/#de-iu-karakteristiek-van-een-led","title":"De I,U-karakteristiek van een LED","text":"Je hebt op de middelbare school ongetwijfeld de $I,U$-karakteristiek van een ohmse weerstand onderzocht. Je neemt een gewone weerstand en zet daar een steeds hogere spanning op. Je meet de stroomsterkte en ook die neemt toe \u2014 rechtevenredig zelfs! Door $I$ tegen $U$ uit te zetten in een grafiek en de beste lijn door je metingen te trekken vind je met de richtingsco\u00ebffici\u00ebnt de inverse van de weerstand $R^{-1}$:
Een LED is een lichtgevende diode \u2014 en een diode gedraagt zich heel anders. Met de schakeling die we hebben gebouwd kunnen we de $I,U$-karakteristiek van een LED bepalen. Voor meer informatie over de fysica achter diodes, zie de appendix Diodes.
I,U-karakteristiek van een LED
Maak een schets van hoe je denkt dat de grafiek van de stroom tegen de spanning van een LED eruit zal zien.
Arduino heeft geen stroommeter
Schrijf op hoe je de spanning over de LED en de stroom door de LED berekent in termen van de spanningsmeters U1 en U2 en de bekende weerstand R.
Arduino pins
Kijk aan de onderkant van de Arduino4 of je de pinnentjes A0, A1, A2 en GND kan vinden.
Klik hier als je geen Arduino bij de hand hebt
Breadboard
Kijk terug naar de theoretische schakeling, welke lijnen komen daar overeen met de vier draadjes (rood, blauw, groen, oranje) in de echte schakeling?
Channels
Bekijk de documentatie over de firmware en schrijf het commando op om de maximale uitvoerspanning op kanaal 0 te zetten. Schrijf daarna de commando's op om de waardes van U1 en U2 uit te lezen.
Pythondaq: repository
Omdat we met een belangrijk project aan de slag gaan, namelijk een inleveropdracht, gaan we gelijk goed beginnen door een repository aan te maken.
pythondaq
) en kies onderstaande locatie. Let er op dat je mappenstructuur er als volgt uit ziet: ECPC \u251c\u2500\u2500 pythondaq \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022 Initialize this repository with a README
aan.Git ignore
voor Python.Pythondaq: start script
opdrachtcodecheck Je maakt het bestand diode-experiment.py
aan in de nieuwe pythondaq
repository, waarin je de spanning over de LED laat oplopen van nul tot de maximale waarde. Tijdens het oplopen van de spanning over de LED lees je de spanning over de weerstand uit. Je print steeds een regel met: ruwe waarde spanning op LED, voltage op LED, ruwe waarde spanning over weerstand, voltage weerstand. ECPC
\u251c\u2500\u2500 oefenopdrachten
\u2514\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 diode-experiment.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code diode-experiment.py
# connect to Arduino\n\n# set output voltage from 0 to max\n # measure voltages\n # calculate voltage LED \n # calculate voltage resistor\n # print LED: raw_voltage_LED (voltage_LED V) Resistor: raw_voltage_resistor (voltage_resistor V)\n
Checkpunten:
Projecttraject:
Je kunt de meetgegevens kopi\u00ebren en plakken naar een tekstbestand, spreadsheetprogramma of Python notebook o.i.d. Maar dat is wel veel werk, zeker als je metingen wilt herhalen. Op dit moment hebben we ook alleen nog maar ruwe metingen. We gaan hier voorbij aan het feit dat we graag de stroomsterkte door de LED $I$ zouden willen uitzetten tegen de spanning over de LED $U_\\mathrm{LED}$.
Info
In de volgende opdracht gaan we een grafiek maken. Installeer Matplotlib in je conda environment (zorg dat die geactiveerd is! ): Terminal
conda install --channel conda-forge matplotlib\n
Pythondaq: Quick 'n dirty meting
opdrachtcodecheckJe code berekent de spanning over en de stroomsterkte door de LED terwijl de spanning over het cirquit oploopt van nul tot de maximale waarde. De resultaten worden geprint en in een grafiek weergegeven.
Pseudo-code diode-experiment.py
# connect to Arduino\n\n# set output voltage from 0 to max\n # measure voltages\n # calculate LED voltage\n # calculate LED current\n # print voltage: raw_voltage_LED (voltage_LED V) current: raw_current_LED (current_LED V)\n\n# plot current vs voltage\n
Checkpunten:
Projecttraject:
Het is fijn dat je script de meetgegevens op het scherm kan printen en een grafiek maakt, maar als je echt bezig bent met een onderzoek is een grafiek niet genoeg. Je wilt dat de data bewaard blijft zodat je die later nog kunt gebruiken voor nieuwe analyses. Ook is het zo dat data steeds vaker beschikbaar moet zijn voor andere wetenschappers die jouw onderzoek willen controleren. Steeds meer wetenschappelijke tijdschriften vragen auteurs niet alleen hun grafieken, maar ook hun onderliggende data beschikbaar te maken en te publiceren. Op die manier is het veel moeilijker om fraude te plegen; iets dat in de wetenschap helaas soms nog voor komt.
Er zijn ontzettend veel verschillende bestandsformaten waarin je data kunt bewaren. Er zijn grofweg twee categori\u00ebn: tekstbestanden en binaire bestanden. De eerste zijn te lezen met ieder willekeurig programma. Sommige zijn heel eenvoudig (b.v. CSV), andere kunnen complexe datastructuren en extra informatie opslaan (b.v. JSON, XML). Binaire bestanden bevatten alle mogelijke karakters \u2014 niet alleen letters, cijfers, leestekens, maar ook stuurcodes zoals carriage return en de line feed, oorspronkelijk opdrachten voor bijvoorbeeld printers. Ze hebben vaak een strak formaat: zoveel bytes voor dit stukje informatie, zoveel bytes voor dat stukje, enzovoort. Met binaire karakters hoef je je dus niet te beperken tot letters, cijfers en leestekens en kunnen de bestanden wat kleiner zijn. Ook zorgen de vaste afspraken ervoor dat de lees- en schrijfroutines eenvoudiger kunnen zijn. Getallen worden in het interne geheugen van de computers ook binair opgeslagen dus het is vaak copy/paste vanuit of naar het bestand. Wel leiden kleine fouten vaak tot onbruikbare bestanden. Voor grote databestanden wordt vrijwel altijd gekozen voor een binair formaat, of het nou gaat om audio/video, databases of klimaatmodellen. Het uitwisselen van kleinere bestanden gebeurt echter vaak in een tekstformaat.
"},{"location":"basisscript/#comma-separated-values-csv","title":"Comma-separated values (CSV)","text":"Het CSV-bestand is het werkpaard van de wetenschap. Als je data van het ene in het andere programma moet krijgen of je download wetenschappelijke gegevens van een website dan is het CSV-bestand vaak de beste keuze. Het formaat bestaat uit kolommen met getallen, gescheiden door een komma. De eerste regels kunnen commentaar bevatten (uitleg over de kolommen, bijvoorbeeld) en de namen van de kolommen bevatten. Een voorbeeld voor de afstand die een vallend voorwerp aflegt in 10 s, gegeven door $s = \\frac{1}{2} g t^2$, is hieronder weergegeven:
t,s\n0.0,0.0\n1.0,4.9\n2.0,19.6\n3.0,44.1\n4.0,78.4\n5.0,122.50000000000001\n6.0,176.4\n7.0,240.10000000000002\n8.0,313.6\n9.0,396.90000000000003\n10.0,490.00000000000006\n
Het CSV-bestand heeft kolommen $t$ en $s$. De getallen hebben een punt als decimaal scheidingsteken en de komma wordt gebruikt om de kolommen te scheiden. Je kunt CSV-bestanden schrijven en lezen met de modules csv
, numpy
of pandas
. De eerste is altijd meegeleverd met Python en is speciaal geschreven voor het bestandsformaat,9 maar NumPy1011 en Pandas1213 bevatten veel meer functionaliteit op het gebied van wiskunde en data-analyse. Als je die modules toch al gebruikt hoef je niet te kiezen voor de kale csv module.
zip()
","text":"Het viel je misschien op dat in bovenstaand CSV-bestand iedere regel een $t$-waarde en een $s$-waarde heeft. Als je een lijst met $t$'s en een lijst met $s$'en hebt dan bevat de eerste regel het eerste element uit beide lijsten, de tweede regel het tweede element, etc. Je kunt dan een for-loop schrijven die Python's indexnotatie gebruikt: t[i]
, s[i]
, etc. Het kan \u00f3\u00f3k, makkelijker, met de zip()
-functie. Beide methodes kun je als volgt gebruiken in het geval van twee5 lijsten A en B: ```
with_zip.py
A = [1, 2, 3, 4]\nB = [1, 4, 9, 16]\n\nfor a, b in zip(A, B):\n print(a, b)\n
\n(ecpc) > python with_zip.py\n1 1\n2 4\n3 9\n4 16\n
with_indexing.py
A = [1, 2, 3, 4]\nB = [1, 4, 9, 16]\n\nfor i in range(len(A)):\n print(A[i], B[i])\n
\n(ecpc) > python with_indexing.py\n1 1\n2 4\n3 9\n4 16\n
Vergelijk beide methodes goed. In het geval van zip()
hoef je niet de lengte van de lijst op te zoeken en krijg je meteen de losse elementen zonder dat je ze zelf uit de lijst moet plukken met indexnotatie.
oefenen met zip
opdrachtcodecheckJe hebt een lijst met krachten en een lijst met afstanden. Loop over de lijsten en print voor iedere iteratie de kracht $F$, de afstand $s$ en de arbeid $W$. Je hebt een lijst met spanningen en een lijst met stroomsterktes. Je loopt over de lijsten en print voor iedere iteratie de spanning $U$, de stroomsterkte $I$ en de weerstand $R$.
Pseudo-code
F = [1.2, 1.8, 2.4, 2.7, 3.1] # N\ns = [0.3, 0.4, 0.6, 0.8, 1.0] # m \n\n# repeat\n# print F, s, W\n
Checkpunten:
zip()
om de elementen uit de lijst op te vragena
en b
Projecttraject:
csv
-module","text":"Wanneer je de csv
-module wilt gebruiken moet je \u00e9\u00e9rst een bestand openen om in te schrijven, daarna een writer object aanmaken, en dat object gebruiken om regels te schrijven. Daarna moet het bestand netjes afgesloten worden zodat het ook echt naar schijf weggeschreven wordt. Het openen en sluiten van een bestand kun je Python het beste laten doen met het with
-statement:6
with open('metingen.csv', 'w', newline='') as csvfile:\n # csvfile is nu een bestandsobject\n ...\n # na dit blok sluit Python automatisch het bestand\n
Bij open()
geef je eerst de naam van een bestand, dan 'w'
om aan te geven dat het bestand writeable moet zijn (gebruik 'r'
om te lezen) en newline=''
om Python niet zelf regeleindes te laten schrijven; dat doet de csv
-module. Op de volgende manier schrijven we dan de CSV-data weg:
import csv\n\nwith open('metingen.csv', 'w', newline='') as csvfile:\n writer = csv.writer(csvfile)\n writer.writerow(['t', 's'])\n writer.writerow([0.0, 0.0])\n writer.writerow([1.0, 4.9])\n writer.writerow([2.0, 19.6])\n ...\n
Je kunt het wegschrijven van de regels vervangen door een for-loop. Pythondaq: CSV
opdrachtcodecheckJe code schrijft de metingen weg als csv-bestand door gebruik te maken van de zip()
-functie en de csv
-module.
Pseudo-code diode-experiment.py
# connect to Arduino\n\n# set output voltage from 0 to max\n # measure voltages\n # calculate LED voltage\n # calculate LED current\n\n# plot current vs voltage\n# create csv-file\n
Checkpunten:
Projecttraject:
Het kan wenselijk zijn om niet alle bestanden mee te nemen voor versiebeheer in je repository. Soms wil je een bestand uitsluiten, of bepaalde bestand-types. Om GitHub te laten weten welke bestanden niet gecommit hoeven te worden is er een bestand .gitignore
. Let op de punt voor de bestandsnaam, dit betekent dat het een verborgen bestand is en mogelijk zie je het niet in je repository staan.
Stel je wilt alle csv-bestanden uitsluiten van versiebeheer, dat kan als volgt:
Pas de code zodanig aan dat een CSV-bestand nooit wordt overschreven. Je kunt bijvoorbeeld controleren of het bestand al bestaat en aan de bestandsnaam een oplopend getal toevoegen (data-001.csv
, data-002.csv
, etc.) totdat je uitkomt bij een bestandsnaam die nog niet bestaat. Controleer dat je programma ook echt geen data overschrijft.
Een vallende bal is een continu proces. De bal heeft op elk willekeurig moment een positie. Je zou de positie kunnen meten op het tijdstip $t$ = 2.0 s, maar ook op $t$ = 2.1, 2.01, 2.001 of 2.0001 s. Ook kun je de positie net zo nauwkeurig bepalen als je wilt.2 De natuur is analoog,3 maar moderne computers zijn digitaal en dus discreet. Als je een foto op je computer te ver inzoomt zie je blokjes. Je kunt verder inzoomen, maar je gaat niet meer detail zien. De hoeveelheid informatie is beperkt.\u00a0\u21a9
Uiteraard afhankelijk van de nauwkeurigheid van je meetinstrument.\u00a0\u21a9
Totdat je het domein van de kwantummechanica betreedt, dan blijkt de natuur ook een discrete kant te hebben.\u00a0\u21a9
Dit model bevat een 3D model die is gecre\u00eberd door AppliedSBC en is gedeeld onder CC-BY-SA licentie. Het originele model is te vinden via Arduino Nano 33 IoT. Het model is voorzien van een Arduino texture. Dit 3D model heeft een CC-BY-SA licentie.\u00a0\u21a9
Je kunt net zoveel lijsten in zip()
gooien als je wilt: for a, b, c, d, e in zip(A, B, C, D, E)
is geen probleem.\u00a0\u21a9
hier is open()
een zogeheten context manager, een functie die je kunt gebruiken met een with
-statement en dat bij de start iets doet \u2014 hier een bestand openen \u2014 en bij het eind iets doet \u2014 hier het bestand weer netjes afsluiten. Je kunt zelf ook context managers schrijven, als je wilt.\u00a0\u21a9
Hierarchical Data Format Version 5, in gebruik bij bijvoorbeeld de LOFAR radiotelescoop, het IceCube neutrino-observatorium en de LIGO zwaartekrachtsgolvendetector.\u00a0\u21a9
Lees bijvoorbeeld deze korte blog post over het gebruik van HDF5.\u00a0\u21a9
Python Software Foundation. Csv \u2013 csv file reading and writing. URL: https://docs.python.org/3/library/csv.html.\u00a0\u21a9
The NumPy Development Team. Numpy \u2013 the fundamental package for scientific computing with python. URL: https://numpy.org.\u00a0\u21a9
Charles R. Harris, K. Jarrod Millman, St'efan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, Robert Kern, Matti Picus, Stephan Hoyer, Marten H. van Kerkwijk, Matthew Brett, Allan Haldane, Jaime Fern'andez del R'\u0131o, Mark Wiebe, Pearu Peterson, Pierre G'erard-Marchant, Kevin Sheppard, Tyler Reddy, Warren Weckesser, Hameer Abbasi, Christoph Gohlke, and Travis E. Oliphant. Array programming with NumPy. Nature, 585(7825):357\u2013362, September 2020. URL: https://doi.org/10.1038/s41586-020-2649-2, doi:10.1038/s41586-020-2649-2.\u00a0\u21a9
The pandas development team. Pandas-dev/pandas: pandas 1.0.5. June 2020. URL: https://doi.org/10.5281/zenodo.3898987, doi:10.5281/zenodo.3898987.\u00a0\u21a9
Wes McKinney. Data Structures for Statistical Computing in Python. In St\u00e9fan van der Walt and Jarrod Millman, editors, Proceedings of the 9th Python in Science Conference, 56 \u2013 61. 2010. doi:10.25080/Majora-92bf1922-00a.\u00a0\u21a9
The HDF Group. The hdf5 library and file format. URL: https://www.hdfgroup.org/solutions/hdf5/.\u00a0\u21a9
Ivan Vilata Francesc Alted and others. PyTables: hierarchical datasets in Python. URL: http://www.pytables.org/.\u00a0\u21a9\u21a9
Een populair binair formaat in de wetenschappelijke wereld is HDF5.7 14 Je kunt hiermee verschillende datasets bewaren in \u00e9\u00e9n bestand. Je kunt een soort boomstructuur aanbrengen en zo verschillende datasets groeperen en er ook nog extra informatie (metadata) aanhangen zoals datum van de meting, beschrijving van de condities, etc. Je kunt een meetserie opslaan als reeks die in \u00e9\u00e9n keer in en uit het bestand wordt geladen maar ook als tabel. Die laatste biedt de mogelijkheid om \u2014 net als in een database \u2014 data te selecteren en alleen die data in te laden uit het bestand. Op die manier is het mogelijk om met datasets te werken die groter zijn dan het geheugen van je computer.8 Meer informatie lees je in de tutorial van PyTables15.
PyTables15 is een Python bibliotheek die het werken met HDF5-bestanden makkelijker maakt. Er zijn uiteraard functies om de bestanden aan te maken en uit te lezen maar ook om queries uit te voeren. Pandas kan \u2014 via PyTables \u2014 ook werken met HDF5-bestanden.
HDF5 tutorial
Download de HDF5 tutorial. Open de tutorial in Visual Studio Code en bestudeer de stappen die daar staan beschreven nauwkeurig.
PyTables
Pas je script aan zodat de meetserie van de LED wordt opgeslagen in een HDF5-bestand. Vraag hulp als je uitleg wilt over wat een UInt16
voor een ding is. Gebruik \u00e9\u00e9n bestand en maak daarin een nieuwe dataset voor iedere meetserie. Bewaar ook wat metadata (bijvoorbeeld tijdstip van de meting). Iedere keer dat je je script runt wordt er aan hetzelfde databestand een nieuwe dataset toegevoegd.
We hebben voor deze cursus de volgende onderdelen gebruikt:
Voor eindfeest zonnecel:
Voor eindfeest Morse:
Voor eindfeest afstandsensor:
conda create --name NAME PACKAGES\n
environment activeren conda activate NAME\n
pakket installeren (NAME) > conda install PACKAGE\n
GitHub Lege repository aanmaken NAME
en locatie (let op! de project map mag niet in een andere repository staan!)Initialize this repository with a README
aanGit ignore
: \"Python\"projectmap
(let op! de project map mag niet in een andere repository staan!)Create repository
.Initialize this repository with a README
aan.Git ignore
voor \"Python\".Create Repository
.poetry new --src NAME\n
Poetry toevoegen aan bestaand project poetry init --no-interaction\n
Poetry project installeren poetry install\n
Dependencies toevoegen poetry add PACKAGE\n
Dependencies verwijderen poetry remove PACKAGE\n
commando toevoegen pyproject.toml
een extra kopje toe: [tool.poetry.scripts]\nnaam_commando = \"package.module:naam_functie\"\n
Voor een snelle meting is het script dat je geschreven hebt bij opdracht quick 'n dirty meting en opdracht Pythondaq: CSV prima! Maar als de meetapparatuur ingewikkelder wordt (meer verschillende commando's) of je wilt meer aanpassingen doen, dan is het wel lastig dat je op allerlei plekken de commando's opnieuw moet programmeren \u2014 en eerst moet opzoeken. Als je een nieuw script schrijft moet je opnieuw goed opletten dat je de goede terminator characters gebruikt, etc. Het is wat werk, maar toch heel handig, om je code op te splitsen en een class te schrijven.
Een class is eigenlijk een groep functies die je bij elkaar pakt en die met elkaar gegevens kunnen delen. Zodra een programma wat complexer wordt merk je dat het fijn kan zijn om variabelen op te sluiten in ge\u00efsoleerde omgevingen.
"},{"location":"classes/#aanmaken-van-een-class","title":"Aanmaken van een class","text":"Een class is een verzameling functies. Hieronder staat een versimpelde weergave van de class Turtle
. Een class maak je aan met de regel class Turtle:
1 Daaronder komt ingesprongen de inhoud van de class. De class bestaat uit een collectie van functies \u2014 de zogeheten methods van de class. De eerste method __init__()
is speciaal (voor meer informatie zie: dunder methods), dit is de initializer waarin alle taken staan die uitgevoerd worden zodra de class gebruikt wordt.
class Turtle:\n def __init__(self, shape):\n # transform turtle into shape\n\n def forward(self, distance):\n # move turtle by distance\n\n def left(self, angle):\n # turn turtle counterclockwise\n # by angle in degrees\n
De eerste parameter van de __init__()
-method en van alle andere methods, is self
, daarna komen \u2014indien nodig\u2014 andere parameters die in de method nodig zijn. Later meer over de speciale parameter self, eerst gaan we kijken hoe je een class gebruikt.
Het aanroepen van een class lijkt veel op het aanroepen van een functie:
\ndef calculate_squares_up_to(max_number):\n squares = []\n for number in range(max_number):\n squares.append(number ** 2)\n return squares\n\nresult = calculate_squares_up_to(5)\n
\nclass Turtle:\n def __init__(self, shape):\n # transform turtle into shape\n\n def forward(self, distance):\n # move turtle by distance\n\n def left(self, angle):\n # turn turtle counterclockwise\n # by angle in degrees\n\nmaster_oogway = Turtle(\"turtle\") \n
Stel we hebben de functie calculate_squares_up_to(max_number)
. Dan roep je die aan met result = calculate_squares_up_to(5)
. Hierbij is calculate_squares_up_to(5)
de naam van de functie en result
de variabele waar de uitkomst heen gaat. Bij het aanroepen van een class doe je iets soortgelijks. In de variabele master_oogway
gaat de 'uitkomst' van de class, dat is in dit geval een collectie van methods (en variabelen). De variabele master_oogway
noemen we een instance van de class Turtle
. Net zoals je een functie vaker kunt aanroepen, kan je ook meerdere instances van een class aanmaken. Achter de naam van de class: Turtle
, komen tussen ronde haakjes de variabelen die worden meegegeven aan de __init__()
-method (self
niet meegerekend), de parameter shape
krijgt dus de variabele \"turtle\"
toegewezen.
Je kunt meerdere instances hebben van dezelfde class, bijvoorbeeld voor verschillende schildpadden:
class Turtle:\n ...\n\nturtle_1247 = Turtle()\n...\nturtle_1428 = Turtle()\n...\n
__init__(self)
Stel dat de init-method geen extra parameters mee krijgt, zoals in het volgende geval:
class Turtle:\n def __init__(self):\n # initialiseer class\n\n def forward(self, distance):\n # move turtle by distance\n\n def left(self, angle):\n # turn turtle counterclockwise\n # by angle in degrees\n
hoe maak je dan een instance aan van de class? Uitwerkingen master_oogway = Turtle()\n
Omdat de instance master_oogway
alle methods bevat kunnen we deze methods aanroepen:
master_oogway = Turtle(\"turtle\")\n\nmaster_oogway.forward(50)\nmaster_oogway.left(30)\nmaster_oogway.forward(50)\n
turtle
opdrachtcodecheck Je bent inmiddels nieuwsgierig geworden naar de schildpad. De class Turtle
zit standaard in Python, daarom kan je die importeren met from turtle import Turtle
. Maak een bestand turtles.py
waarin je een schildpad met de instancenaam master_oogway
laat lopen en draaien. ECPC
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 turtles.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022
Schildpad verdwijnt
Na het uitvoeren van het script sluit Python het scherm van de schildpad. Voeg de regel master_oogway.screen.mainloop()
toe om het scherm te laten staan en handmatig af te sluiten.
Pseudo-code
from turtle import Turtle\n\n# create instance of class Turtle\nmaster_oogway = Turtle(\"turtle\")\n\n# move turtle forward with 50 steps\n...\n# turn turtle left with 30 degrees\n...\n
Checkpunten:
Turtle
wordt ge\u00efmporteerd uit de module turtle
.Turtle
met hoofdletter T.forward()
of left()
van de instance aan.Projecttraject:
self
","text":"Een class method is vrijwel gelijk aan een normale functie, behalve dat een class method als eerste de parameter self
verwacht. Aan deze parameter wordt de eigen instance van de class meegegeven wanneer je de method aanroept.
Laten we kijken naar wat die instance van de class eigenlijk is. De instance van een class is de collectie van methods (en variabelen).
\ndef calculate_squares_up_to(max_number):\n squares = []\n for number in range(max_number):\n squares.append(number ** 2)\n return squares\n\nresult = calculate_squares_up_to(5)\n
\nclass Turtle:\n def __init__(self, shape):\n # transform turtle into shape\n\n def forward(self, distance):\n # move turtle by distance\n\n def left(self, angle):\n # turn turtle counterclockwise\n # by angle in degrees\n\nmaster_oogway = Turtle(\"turtle\") \n
Als we de functie calculate_squares_up_to(max_number)
aanroepen met result = calculate_squares_up_to(5)
, dan komt hetgeen we teruggeven, squares
, in de variabele result
terecht. Bij een class is er geen return
-statement maar komt de hele inhoud van de class alle methods (en variabelen) in de instance master_oogway
terecht.
Gelukkig hoef je de instance niet steeds zelf mee te geven aan een method. Wanneer je een method aanroept wordt impliciet de instance als eerste parameter meegegeven. Maar waarom zou je die instance meegeven aan een method als je die aanroept? Omdat de instance alle methods en variabele bevat, kan je de informatie die daarin is opgeslagen in elke method gebruiken.
Stel we maken een nieuwe method do_kungfu_move
waarin we forward()
en left()
willen gebruiken:
class Turtle:\n def __init__(self, shape):\n # transform turtle into shape\n\n def forward(self, distance):\n # move turtle by distance\n\n def left(self, angle):\n # turn turtle counterclockwise\n # by angle in degrees\n\n def do_kungfu_move(self):\n # Do kungfu move\n self.forward(130)\n self.left(350)\n self.forward(60)\n
Als we de method do_kungfu_move
aanroepen met master_oogway.do_kungfu_move()
geeft python automatisch de instance master_oogway
mee aan de method. De parameter self
is dus nu gelijk aan de instance master_oogway
, daarmee doet self.forward(130)
hetzelfde als master_oogway.forward(130)
.
De instance van een class bevat niet alleen alle methods, maar kan ook variabele hebben. In het voorbeeld hieronder voegen we de variabele quote
toe in de init-method aan de instance, daarmee wordt het een instance attribute.
class Turtle:\n def __init__(self, shape):\n # transform turtle into shape\n self.quote = \"Yesterday is history, Tomorrow is a mystery, but Today is a gift. That is why it is called the present\"\n\n ...\n
De instance attribute quote
is nu onderdeel van de instance. We kunnen die oproepen binnen elke method met self.quote
maar ook buiten de class: turtles.py ...\nmaster_oogway = Turtle(\"turtle\")\n\nprint(master_oogway.quote)\n
\n(ecpc) > python turtles.py\n\"Yesterday is history, Tomorrow is a mystery, but Today is a gift. That is why it is called the present\"\n
Opbouw van een class
Wat is nu het praktisch nut van classes en methods gebruiken in plaats van functies? Want in plaats van
forward(master_oogway, distance=50)\n
hebben we nu master_oogway.forward(distance=50)\n
en dat is even lang. Het grote voordeel ontstaat pas wanneer de class ingewikkelder wordt en meer data gaat bewaren. Ook kun je de class in een ander pythonbestand (bijvoorbeeld animals.py
) zetten en alle functionaliteit in \u00e9\u00e9n keer importeren met: from animals import Turtle\n\nmaster_oogway = Turtle()\n...\n
Op deze manier kun je code ook makkelijker delen en verspreiden. Zodra je een class definieert zal Visual Studio Code tijdens het programmeren je code automatisch aanvullen. Zodra je typt master_oogway.f
hoef je alleen maar op Tab te drukken en VS Code vult de rest aan. Class Particle
opdrachtcodecheck Je hebt een class Particle
gemaakt in een niew bestand particle.py
. Als je een instance aanmaakt van de class Particle
kun je de naam van het deeltje meegeven en de spin (bijvoorbeeld: 0.5). De instance attributes van deze class zijn 'name' en 'spin'. Er is ook een method is_up_or_down()
om terug op te vragen wat de spin van het deeltje op dat moment is (spin omhoog/positief of spin omlaag/negatief). Door de method flip()
op te roepen wordt de spin van het deeltje omgekeerd. ECPC
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 particle.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code
# class Particle:\n # def __init__(self, name, spin):\n # make instance attribute from name\n # make instance attribute from spin\n # def is_up_or_down\n # print up when spin is positive\n # print down when spin is negative\n ...\n # def flip\n # Make spin positive if spin is negative\n # Make spin negative if spin is positive\n ...\n
Testcode particle.py proton = Particle('mooi proton', 0.5)\nproton.is_up_or_down()\nproton.flip()\nproton.is_up_or_down()\nprint(proton.spin)\nprint(proton.name)\n
\n(ecpc) > python particle.py\nup\ndown\n-0.5\nmooi proton\n
Checkpunten:
is_up_or_down()
print 'up' als de spin positief is en 'down' als het negatief is.flip()
maakt de spin positief als de spin negatief is, en negatief als de spin positief is.Projecttraject:
Class ProjectileMotion
opdrachtcodecheck Je gaat een waterraket een aantal keer wegschieten met steeds een andere beginsnelheid en lanceerhoek. Je hebt een instance aangemaakt van de class ProjectileMotion
. De beginsnelheid en de lanceerhoek bewaar je steeds met de method add_launch_parameters()
. Om in een keer alle beginsnelheden op te vragen gebruik je de method get_initial_velocities()
. Om alle lanceerhoeken op te vragen gebruik je de method get_launch_angles()
. Op basis van de gegevens (en door de luchtweerstand te verwaarlozen) bepaal je de vluchtduur en het bereik van de raket. Je kunt de vluchtduur van alle vluchten opvragen met de method get_time_of_flights()
en het bereik van alle vluchten met get_flight_ranges()
. ECPC
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u251c\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 projectile-motion
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 water_rocket.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code
# class ProjectileMotion\n ...\n # __init__\n ...\n # add_launch_parameters\n ...\n # get_initial_velocities\n ...\n # get_launch_angles\n ...\n # get_time_of_flights\n ...\n # get_flight_ranges\n ...\n
Testcode water_rocket.py speedy = ProjectileMotion()\nspeedy.add_launch_parameters(v=28, angle=68)\nspeedy.add_launch_parameters(v=11, angle=15)\n\nv = speedy.get_initial_velocities()\nangles = speedy.get_launch_angles()\nx = speedy.get_flight_ranges()\nt = speedy.get_time_of_flights()\n\nprint(f\"{v=}\")\nprint(f\"{angles=}\")\nprint(f\"{x=}\")\nprint(f\"{t=}\")\n
\n(ecpc) > python water_rocket.py\nv=[28, 11]\nangles=[68, 15]\nx=[55.51602063607072, 6.167176350662587]\nt=[5.292792645845066, 0.5804300705663054]\n
Checkpunten:
add_launch_parameters
verwacht een beginsnelheid in meter per seconde en een lanceerhoek in graden.get_initial_velocities
geeft een lijst terug met beginsnelheden van alle ingevoerde parameters.get_launch_angles
geeft een lijst terug met alle lanceerhoeken van de ingevoerde parameters.get_time_of_flights
geeft een lijst terug met de vluchtduur in seconden corresponderend met de ingevoerde parameters. get_flight_ranges
geeft een lijst terug met het bereik in meters die correspondeerd met de ingevoerde parameters.Projecttraject:
Het is niet logisch als de lanceerhoek boven een bepaalde hoek uitkomt of als een negatieve beginsnelheid wordt ingevoerd. Zorg dat in die gevallen een error afgegeven wordt. Meer informatie hierover vind je in de paragraaf Exceptions.
SubclassWanneer je de Google Style Guide2 volgt schrijf je de naam van de class in CapWords of CamelCase.\u00a0\u21a9
Google. Google python style guide. URL: https://google.github.io/styleguide/pyguide.html.\u00a0\u21a9
Je kunt behalve een class ook een subclass aanmaken. De class Turtle
heeft hele handige methods maar je kunt een specifiekere class GiantTortoise
maken.
class GiantTortoise(Turtle):\n def __init__(self):\n super().__init__()\n self.shape(\"turtle\")\n self.color(\"dark green\")\n self.turtlesize(5)\n self.speed(1)\n\n def move(self, distance):\n steps = range(0, distance, 5)\n i = 1\n for step in steps:\n self.tiltangle(i * 5)\n self.forward(step)\n time.sleep(1)\n i = i * -1\n
Door de parentclass Turtle
tussen ronde haakjes mee te geven aan de nieuwe subclass GiantTortoise
krijgt de subclass alle functionaliteit mee van de parentclass, waaronder alle methods zoals forward()
. Als je in de init-method van de subclass methods of attributes wilt gebruiken van de parentclass, moet je ervoor zorgen dat de parentclass is ge\u00efnitialiseerd . Dit doe je met super().__init__()
hierbij verwijst super()
naar de parentclass en met __init__()
voer je de init-method van de parentclass uit. Nadat we in de init-method van de subclass de eigenschappen van de Reuzenschildpad hebben gedefinieerd, kunnen we extra functionaliteit gaan toevoegen bijvoorbeeld de manier van bewegen met de method move()
.
super().__init__()
GiantTortoise
aanmaakt.t = GiantTortoise()\nt.move(50)\n
super().__init__()
weglaat?Hawksbill turtle
self.screen.bgcolor(\"cyan\")
.swim()
die de schildpad over het scherm laat bewegen. Vanaf de jaren '60 van de vorige eeuw werden computers interactief. Het was mogelijk om via een terminal commando's aan de computer te geven en te wachten op een antwoord. In tegenstelling tot moderne gebruikersomgevingen waren deze volledig op tekst gebaseerd. Hoewel moderne besturingssystemen \u2014 of het nu computers, tablets of mobiele telefoons betreft \u2014 volledig grafisch zijn ingericht, is de tekstuele interface nooit verdwenen. Opdrachten geven door te typen is gewoon best wel handig en snel. Ook is het veel eenvoudiger om applicaties te ontwikkelen zonder grafische interface.
Op ieder besturingssysteem \u2014 Linux, MacOS, Windows \u2014 is een shell, terminal of command prompt te vinden. Als je die opstart kun je op de zogeheten command line opdrachten intypen. Veelal zijn dit commando's om het bestandssysteem te navigeren en programma's op te starten.
Wanneer je in Visual Studio Code een Python script start dan opent het een terminal onderin het scherm.
"},{"location":"cli/#commandos","title":"Commando's","text":"Je hebt tot nu toe al heel wat commando's in de terminal getypt. Laten we een paar voorbeelden bestuderen: Terminal
PS> python script.py\n
Als eerste vertel je welke applicatie je wilt gaan starten; in dit geval: python
. Daarna geef je met het argument script.py
aan welk Pythonscript je wilt uitvoeren. Vaak kun je ook opties meegeven zoals in: (ecpc) > python -V \nPython 3.10.13\n
Hiermee vraag je Python om het versienummer weer te geven. Soms kunnen opties zelf weer een argument meekrijgen. Bijvoorbeeld: Terminal
PS> python -m antigravity\n
Met deze regel geef je Python de optie -m
en die importeert een module (hier antigravity
) en voert die uit. Probeer maar eens zelf wat er gebeurt als je dat commando uitvoert. Als applicaties veel verschillende functionaliteit hebben dan krijg je regelmatig te maken met een lange regel met een combinatie van argumenten en opties: Terminal
PS> conda create --name pythondaq --channel conda-forge python pyvisa-py\n
Uitgesplitst in argumenten en opties, met vierkante haken [] om aan te geven welke onderdelen bij elkaar horen, is dat: conda create [--name pythondaq] [-channel conda-forge] [python pyvisa-py]
Poetry argumenten
conda create
heb je ook met andere argumenten gewerkt zoals activate
en install
. Welke argumenten ken je al van de applicatie poetry
?poetry list
, hoeveel kende je nog niet?Conda opties en argumenten
Anaconda Prompt
conda -h
)conda -V
)conda activate -h
)Als we gebruik willen maken van commando's in onze eigen applicatie moeten we weten wat de gebruiker in de terminal typt. Dit is mogelijk met sys.argv
.1 Waarbij alles wat we in de terminal typen aan input wordt meegegeven:
import sys\n\nprint(sys.argv)\n
\n(ecpc) > python cli.py test 123\n['cli.py', 'test', '123']\n
Met if-statements kunnen we acties verbinden aan bepaalde argumenten: cli.py
import sys\n\nargs = sys.argv\nprint(args)\n\nif args[1] == \"test\":\n print(f\"This is a test: {args[2]}\")\nelse:\n print(f\"CommandNotFoundError: No command '{args[1]}'.\")\n
Als je meerdere opties en argumenten meegeeft dan wordt het veel werk om die in je script uit elkaar te plukken en ze goed te interpreteren. Om dat makkelijker te maken zijn er verschillende bibliotheken beschikbaar \u2014 waaronder een paar in de standard library van Python. Een hele handige \u2014 die n\u00edet in de standard library van Pythhon zit maar w\u00e9l meegeleverd is met de base environment van Anaconda \u2014 is Click.5
Info
Click maakt gebruik van decorators (@decorator
). Om decorators te gebruiken, hoef je niet per se te weten hoe ze werken. Als je meer wilt weten over de werking ervan kijk dan de calmcode tutorial of lees de Primer on Python Decorators.
Als kort voorbeeld \u2014 ge\u00efnspireerd op de documentatie van Click \u2014 nemen we het volgende script: hello.py
def hello():\n print(\"Hello physicist!\")\n\nif __name__ == \"__main__\":\n hello()\n
Dit script print de uitdrukking \"Hello physicist!\". We gaan dit aanpassen en maken het mogelijk om de naam en het aantal begroetingen te kiezen. Hiervoor gebruiken we Click. Allereerst moeten we click
importeren en aangeven dat we de hello()
-functie willen gebruiken als commando:
import click\n\n@click.command()\ndef hello():\n print(\"Hello physicist!\")\n\nif __name__ == \"__main__\":\n hello()\n
Dit levert ons nog niet zoveel op, maar op de achtergrond is click wel degelijk aan het werk. De @click.command()
houdt in de gaten wat er in de command line wordt ingetypt. Zo kunnen we de helpfunctie aanroepen door --help
achter de naam van het script te zetten.
python hello.py --help\n
Help functie
opdrachtcodecheck Je neemt het script hello.py
over. Je vraagt de helpfunctie van het script op. Je ziet een helptekst verschijnen. Je vraagt je af wat er gebeurt als je @click.command()
weg haalt en dan de helpfunctie opvraagt. Je krijgt gewoon de output van de functie hello()
een geen help tekst. ECPC
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 hello.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u251c\u2500\u2500 pythondaq
\u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code hello.py
import click\n\n# make function Click command\n# function\n # print hello physicist!\n\n# when run this script:\n # run function\n
Testcode (ecpc) > python hello.py --help \nUsage: hello.py [OPTIONS] \nOptions:\n --help Show this message and exit.\n\n
Checkpunten:
--help
achter de bestandsnaam te zetten in de terminal.@click.command()
verschijnt er geen helptekst, maar de output van de functie.Projecttraject:
In de code hieronder geven we met de regel @click.argument(\"name\")
aan dat we van de gebruiker een argument verwachten. Zorg dat het argument ook gebruikt wordt in de functie hello
:
import click\n\n@click.command()\n@click.argument(\"name\")\ndef hello(name):\n print(f\"Hello {name}!\")\n\nif __name__ == \"__main__\":\n hello()\n
Argument toevoegen
opdrachtcodecheckJe runt het bestand hello.py
en geeft achter de bestandsnaam de naam Alice
mee. Er verschijnt Hello Alice!
als output in de terminal.
Pseudo-code hello.py
import click\n\n# make function Click command\n# make argument name\n# function, parameter name\n # print hello <name>!\n\n# when run this script:\n # run function\n
Testcode (ecpc) > python hello.py \nUsage: hello.py [OPTIONS] NAME\nTry 'hello.py --help' for help.\nError: Missing argument 'NAME'.\n\n
Checkpunten:
hello.py
zonder een argument: python hello.py
geeft een foutmelding.hello.py
met een argument: python hello.py Alice
werkt zoals verwacht.Projecttraject:
Warning
Let er op dat je bij @click.argument
de naam meegeeft die overeenkomt met de namen van de parameters van je functie. In ons geval hebben we een argument \"name\"
. Dit moet overeenkomen met de functiedefinitie def hello(name)
.
Argumenten zijn altijd verplicht en moeten in een vaste volgorde staan. Bij opties is dat anders. Je geeft met mintekens aan dat je een optie meegeeft. Veel opties hebben een lange naam en een afkorting (bijvoorbeeld --count
en -c
). Opties kunnen zelf weer een argument hebben (bijvoorbeeld --count 3
). Het is handig om een standaardwaarde te defini\u00ebren. In dat geval mag de gebruiker de optie weglaten. We voegen een for-loop2 toe om de begroeting te herhalen.
import click\n\n@click.command()\n@click.argument(\"name\")\n@click.option(\n \"-c\",\n \"--count\",\n default=1,\n)\ndef hello(name, count):\n for _ in range(count):\n print(f\"Hello {name}!\")\n\nif __name__ == \"__main__\":\n hello()\n
5 keer hello
opdrachtcodecheckJe runt het bestand hello.py
en geeft achter de bestandsnaam de naam van je assistent mee en geeft aan dat je deze 5 keer wilt printen. Er verschijnt vijf keer Hello <assistent>!
als output in de terminal.
Pseudo-code hello.py
import click\n\n# make function Click command\n# make argument name\n# make option count with default value 1\n# function, parameter name and count\n # repeat count times\n # print hello <name>!\n\n# when run this script:\n # run function\n
Testcode (ecpc) > python hello.py David -c 5 \nHello David!\nHello David!\nHello David!\nHello David!\nHello David!\n
Checkpunten:
-c
.--count
.count
wordt weggelaten wordt de naam 1 keer geprint. Wanneer er geen argument wordt meegegeven met count
volgt een foutmelding:
(ecpc) > python hello.py David -c \nError: Option '-c' requires an argument.\n
Projecttraject:
Warning
Let er op dat je bij @click.option
de afkorting met 1 minteken meegeeft en de lange naam met 2 mintekens. De lange naam moet overeenkomen met de paramater van je functie. In ons geval hebben we een optie \"--count\"
\u2014 de lange naam telt. Dit moet overeenkomen met de functiedefinitie def hello(name, count)
.
Het is handig om een korte helptekst toe te voegen. Dit gaat als volgt:
hello.pyimport click\n\n@click.command()\n@click.argument(\"name\")\n@click.option(\n \"-c\",\n \"--count\",\n default=1,\n help=\"Number of times to print greeting.\",\n show_default=True, # show default in help\n)\ndef hello(name,count):\n for _ in range(count):\n print(f\"Hello {name}!\")\n\nif __name__ == \"__main__\":\n hello() \n
Helptekst toevoegen
Voeg de helptekst toe en vraag de helptekst op zoals in de opdracht Help functie.
Als je dit script gebruikt ziet dat er zo uit:
(ecpc) > python hello.py --help \nUsage: hello.py [OPTIONS] NAME\n\nOptions:\n -c, --count INTEGER Number of times to print greeting. [default: 1]\n --help Show this message and exit.\n \n\n(ecpc) > python hello.py Alice \nHello Alice!\n\n(ecpc) > python hello.py Alice -c 2 \nHello Alice!\nHello Alice!\n\n(ecpc) > python hello.py Alice --count 3 \nHello Alice!\nHello Alice!\nHello Alice!\n
Pauze optie
opdrachtcodecheckJe runt het bestand hello.py
en geeft achter de bestandsnaam de naam van je assistent mee en geeft aan dat je deze 5 keer wilt printen met een pauze van 2 seconde ertussen. Het duurt 8 seconden voordat er vijf keer Hello <assistent>!
als output in de terminal staat. Als je geen pauze-optie meegeeft wordt er ook geen pauze gehouden.
Info
Je kan hiervoor gebruik maken van de module time die standaard met Python meekomt3. Met de functie sleep()
kun je de executie van de volgende regel in het script met een aantal seconden uitstellen.
import time\n# wait 28 second\ntime.sleep(28)\n
Pseudo-code hello.py
import click\n\n# make function Click command\n# make argument name\n# make option count with default value 1\n# make option pause\n# function, parameter name and count\n # repeat count times\n # print hello <name>!\n # pause\n\n# when run this script:\n # run function\n
Testcode (ecpc) > python hello.py David -c 5 \nHello David!\nHello David!\nHello David!\nHello David!\nHello David!\n
Checkpunten:
Projecttraject:
Opties zonder argument werken als vlag \u2014 een soort aan/uitknop.4
Vlag
Gebruik een optie als vlag om de gebruiker te laten kiezen tussen het wel (tea) of niet (no tea) aanbieden van een kopje thee. Zorg dat er standaard tea wordt aangeboden.
boolean flags
Lees meer over boolean flags in de Click documentatie.
Argumenten en opties
opdrachtcodecheckJe opent met Github Desktop de just_count
in Visual Studio Code. Je hebt ooit een environment voor deze repository aangemaakt maar je hebt geen idee of die in de tussentijd niet per ongeluk stuk is gegaan. Daarom maak je een nieuwe environment just_count
met daarin Python en gebruik je Poetry om het pakket just_count
in de nieuwe omgeving te installeren . Je test of je de applicatie nog kunt aanroepen met het commando square
.
Je activeert het juiste conda environment en past de code aan zodat met het commando square 6
het kwadraat van 6 in de terminal wordt geprint.
Info
Click maakt van alle argumenten een string, tenzij je een default waarde of een type definieert. Gebruik type=int
, type=float
enzovoorts om aan te geven wat voor type object het argument moet worden
Pseudo-code count_count.py
import square\n\n# Add functionality to select a number via click and print its square\ndef main():\n print(f\"The square of 5 is {square.square(5)}\")\n\nif __name__ == '__main__':\n main()\n
Testcode (ecpc) > square 6 \nThe square of 6 is 36\n
Checkpunten:
poetry install
in een schone environment (met alleen Python) gedaan.square
kan je een getal meegeven en krijg je het verwachte antwoord terug.Projecttraject
Tot nu toe konden we maar \u00e9\u00e9n functie uitvoeren in onze applicatie. Maar het is ook mogelijk om subcommando's aan te maken zodat je met \u00e9\u00e9n programma meerdere taken kunt uitvoeren. Denk bijvoorbeeld aan conda
. Je installeert packages met conda install
, verwijdert ze met conda remove
, maakt een environment met conda create
en activeert het met conda activate
.
Subcommando's bedenken
Je gaat de pythondaq
applicatie straks verder uitbreiden zodat er veel meer mogelijk is dan nu. Wat zou je willen dat de applicatie allemaal kan? Welke subcommando's wil je gaan aanmaken? Overleg met elkaar om goede idee\u00ebn uit te wisselen.
Een eenvoudig voorbeeldscript waarin de conda commando's install
en remove
worden nagebootst leggen we hieronder uit. Eerst de code:
fake_conda.py
import click\n\n@click.group()\ndef cmd_group():\n pass\n\n@cmd_group.command()\n@click.argument(\"package\")\n@click.option(\n \"-c\",\n \"--channel\",\n default=\"defaults\",\n help=\"Additional channel to search for packages.\",\n show_default=True, # show default in help\n)\ndef install(package, channel):\n print(f\"Installing {package} from {channel}...\")\n\n@cmd_group.command()\n@click.argument(\"package\")\ndef remove(package):\n print(f\"Removing {package}...\")\n\nif __name__ == \"__main__\":\n cmd_group()\n
In (de laatste) regel 18 roepen we de hoofdfunctie aan die we enigszins willekeurig cmd_group()
genoemd hebben en die we bovenaan defini\u00ebren. In tegenstelling tot het hello.py
-script doet deze functie helemaal niets (pass
). We vertellen aan click dat we een groep van commando's aan gaan maken met de @click.group()
-decorator in regel 3. Vervolgens gaan we commando's binnen deze groep hangen door niet de decorator @click.command()
te gebruiken, maar @cmd_group.command()
\u2014 zie regels 7 en 12. De namen van de commando's die worden aangemaakt zijn de namen van de functies. Dus regel 7 en 9 maken samen het commando install
. Verder werkt alles hetzelfde. Dus een argument toevoegen \u2014 zoals in regel 8 \u2014 is gewoon met @click.argument()
. Hier hoef je geen cmd_group
te gebruiken. Fake conda
opdrachtcodecheck Nu je hebt geleerd om met Click subcommando's te maken wil je deze uittesten in combinatie met het commando wat je met Poetry kan aanmaken om een functie uit een script uit te voeren. Je maakt in de map ECPC
een nieuw Poetry project aan voor fake_conda
en zet daarin de code uit het bestand fake_conda.py
. Je maakt een nieuw conda environment fake_conda
met daarin de benodigde packages . Je installeert het Poetry project in de nieuwe conda environment . Je past de pyproject.toml
aan zodat je met het commando fake_conda install scipy
zogenaamd scipy
kunt installeren . ECPC
\u251c\u2500\u2500 fake_conda
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 src/fake_conda
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 fake_conda.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 pyproject.toml
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u251c\u2500\u2500 oefenopdrachten
\u251c\u2500\u2500 pythondaq
\u2514\u2500\u2500 \u2022\u2022\u2022
commando
Als je een commando met Poetry toevoegt dan heeft dat de opbouw naam_commando = \"package.module:naam_functie\"
, welke functie moet uitgevoerd worden als je het commando aanroept?
Pseudo-code pyproject.toml
[tool.poetry.scripts]\nnaam_commando = \"package.module:naam_functie\"\n
Testcode (ecpc) > fake_conda install scipy \nInstalling scipy from defaults....\n
Checkpunten:
pyproject.toml
is het Poetry project opnieuw ge\u00efnstalleerd.pyproject.toml
verwijst [tool.poetry.scripts]
naar een functie zodat install
en remove
subcommando's zijn.fake_conda install scipy
print de tekst Installing scipy...
als output in de terminal. Projecttraject
Smallangle (meer leren)
Met deze opdracht kun je testen hoe goed je het Python-jargon onder de knie hebt. Je zult het woord smallangle z\u00f3 vaak tegenkomen dat het je duizelt \u2014 maar jij weet precies over welk onderdeel we het hebben.
src
indeling) aan met de naam smallangle
.smallangle
, het moet dus een repository zijn (of worden). smallangle
heet met daarin alleen Python .smallangle
een module smallangle.py
.smallangle.py
: import numpy as np\nfrom numpy import pi\nimport pandas as pd\n\n\ndef sin(number):\n x = np.linspace(0, 2 * pi, number)\n df = pd.DataFrame({\"x\": x, \"sin (x)\": np.sin(x)})\n print(df)\n\n\ndef tan(number):\n x = np.linspace(0, 2 * pi, number)\n df = pd.DataFrame({\"x\": x, \"tan (x)\": np.tan(x)})\n print(df)\n\n\nif __name__ == \"__main__\":\n sin(10)\n
smallangle installeren
opdrachtcodecheck Je cloned het Poetry project smallangle
van AnneliesVlaar/smallangle
door de repository in GitHub desktop te openen. Daarna open je het project in Visual Studio Code. Na het installeren van het poetry project in een nieuwe conda environment run je het bestand smallangle.py
en krijg je een lijst van 10 punten tussen 0 en 2 $\\pi$ en de sinus van deze punten. ECPC
\u251c\u2500\u2500 pythondaq
\u2514\u2500\u2500 smallangle
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022
Testcode smallangle.py
import numpy as np\nfrom numpy import pi\nimport pandas as pd\n\ndef sin(number):\n x = np.linspace(0, 2 * pi, number)\n df = pd.DataFrame({\"x\": x, \"sin (x)\": np.sin(x)})\n print(df)\n\ndef tan(number):\n x = np.linspace(0, 2 * pi, number)\n df = pd.DataFrame({\"x\": x, \"tan (x)\": np.tan(x)})\n print(df)\n\nif __name__ == \"__main__\":\n sin(10)\n
\n(ecpc) > python smallangle.py\n x sin (x)\n0 0.000000 0.000000e+00\n1 0.698132 6.427876e-01\n2 1.396263 9.848078e-01\n3 2.094395 8.660254e-01\n4 2.792527 3.420201e-01\n5 3.490659 -3.420201e-01\n6 4.188790 -8.660254e-01\n7 4.886922 -9.848078e-01\n8 5.585054 -6.427876e-01\n9 6.283185 -2.449294e-16\n
Checkpunten:
Projecttraject:
smallangle aanpassen
opdrachtcodecheckJe kunt met het commando smallangle
en de subcommando's sin
en tan
een lijst genereren van getallen tussen de 0 en 2 $\\pi$ en de bijbehorende sinus dan wel tangens van deze getallen. Met de optie -n
kan je het aantal stappen (het aantal $x$-waardes tussen 0 en $2\\pi$) kiezen. Als je de optie -n
weglaat werkt de applicatie met een standaardwaarde.
TypeError: 'int' object is not iterable
Probeer je de code te draaien maar krijg je een foutmelding zoals deze: Terminal
Traceback (most recent call last):\nFile \"c:\\smallangle\\src\\smallangle\\smallangle.py\", line 28, in <module>\n sin(10)\nFile \"C:\\click\\core.py\", line 1157, in __call__ \n return self.main(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^\nFile \"C:\\click\\core.py\", line 1067, in main\n args = list(args)\n ^^^^^^^^^^\nTypeError: 'int' object is not iterable\n
Dan komt dat doordat je sin(10)
probeert uit te voeren, terwijl de functie al verClickt is. De functie verwacht een argument vanuit de terminal en geen integer vanuit het pythonscript. Pas je script aan zodat if __name__ == \"__main__\":
naar de juiste functie verwijst en Click aanroept; niet sin(10)
.
(ecpc) > smallangle sin -n 9 \n x sin (x)\n0 0.000000 0.000000e+00\n1 0.785398 7.071068e-01\n2 1.570796 1.000000e+00\n3 2.356194 7.071068e-01\n4 3.141593 1.224647e-16\n5 3.926991 -7.071068e-01\n6 4.712389 -1.000000e+00\n7 5.497787 -7.071068e-01\n8 6.283185 -2.449294e-16\n
Checkpunten:
sin
en tan
.Projecttraject:
Met het commando approx
en een argument $\\epsilon$ moet het script de grootste hoek geven waarvoor nog geldt dat $\\lvert x - \\sin(x) \\rvert \\leq \\epsilon$, ofwel de grootste hoek waarvoor de kleine-hoekbenadering nog geldt met de opgegeven nauwkeurigheid. Doe dit op drie cijfers nauwkeurig (loop over .000, .001 en .002, etc. totdat de vergelijking niet meer geldt). N.B. besteed geen tijd aan het analytisch oplossen van de vergelijking. Een voorbeeld van de uitvoer:
(ecpc) > smallangle approx .1 \nFor an accuracy of 0.1, the small-angle approximation holds\nup to x = 0.854.\n
"},{"location":"cli/#docstrings-en-click-help","title":"Docstrings en Click --help
","text":"Docstrings werken ook heel handig samen met Click want ze worden gebruikt als we de helpfunctie aanroepen.
Info
We gebruiken bij click-functies niet de standaard structuur voor docstrings. Click breekt de docstrings standaard af waardoor het algauw een onogelijke brij aan informatie wordt. We kiezen daarom voor een samenvatting in een zin met daarin de PARAMETERS (argumenten) in hoofdletters en eventueel een korte toelichting daarop.
Uitgebreide documentatie en ClickIn de documentatie van Click vind je meer informatie over het afbreken van zinnen (en het voorkomen daarvan). Ook vind je daar een manier om een uitgebreide docstring te schrijven zonder dat het een bende wordt.
We voegen docstrings toe aan fake-conda:
fakeconda.py
import click\n\n\n@click.group()\ndef cmd_group():\n \"\"\"\n Fake the installation and removal of packages in fake conda environments.\n \"\"\"\n pass\n\n\n@cmd_group.command()\n@click.argument(\"package\")\n@click.option(\n \"-c\",\n \"--channel\",\n default=\"defaults\",\n help=\"Additional channel to search for packages.\",\n show_default=True, # show default in help\n)\ndef install(package, channel):\n \"\"\"Install a conda PACKAGE.\n\n PACKAGE is the name of the package.\n \"\"\"\n print(f\"Installing {package} from {channel}...\")\n\n\n@cmd_group.command()\n@click.argument(\"package\")\ndef remove(package):\n \"\"\"Remove a conda PACKAGE.\n\n PACKAGE is the name of the package.\n \"\"\"\n print(f\"Removing {package}...\")\n\nif __name__ == \"__main__\":\n cmd_group()\n
Als we vervolgens de help functie aanroepen zien we de eerste regel van de docstrings verschijnen voor alle subcommando's: (ecpc) > fake_conda --help \nUsage: fake_conda [OPTIONS] COMMAND [ARGS]...\n\nFake the installation and removal of packages in fake conda environments.\n\nOptions:\n --help Show this message and exit.\n\nCommands:\n install Install a conda PACKAGE.\n remove Remove a conda PACKAGE.\n
Daarna kun je uitleg vragen voor de subcommando's waarbij je de hele docstring te zien krijgt:
(ecpc) > fake_conda install --help \nUsage: fake_conda install [OPTIONS] PACKAGE\n\n Install a conda PACKAGE.\n\n PACKAGE is the name of the package.\n\nOptions:\n -c, --channel TEXT Additional channel to search for packages. [default:\n defaults]\n --help Show this message and exit.\n
Smallangle docstrings
opdrachtcodecheckJe gebruikt het commando smallangle --help
en leest de helptekst van de opdracht smallangle. De helptekst bevat zinvolle informatie die je in staat stelt om te begrijpen wat je met de applicatie kan doen. Je ziet dat er twee subcommando's zijn en bekijkt de helptekst van deze commando's met smallangle sin --help
en daarna smallangle tan --help
. Beide helpteksten stellen je in staat op de applicatie te begrijpen en te bedienen. Tevreden test je de applicatie verder uit.
Pseudo-code
\"\"\"Summary containing ARGUMENTs.\n\nARGUMENT description of the argument.\n\"\"\"\n
Testcode (ecpc) > smallangle --help \nUsage: smallangle [OPTIONS] COMMAND [ARGS] ...\n Options: \n --help Show this message and exit.\n Commands:\n Subcommand Summary containing ARGUMENTs.\n\n
Checkpunten:
smallangle --help
geeft zinvolle informatiesmallangle sin --help
geeft zinvolle informatiesmallangle tan --help
geeft zinvolle informatieProjecttraject
In hoofdstuk Model-View-Controller heb je pythondaq
uitgesplitst in model, view en controller. Wanneer we een command-line interface gaan bouwen dan is dat de softwarelaag tussen de gebruiker en de rest van de code. De command-line interface is dus een view. Het is helemaal niet gek om meerdere views te hebben, bijvoorbeeld een eenvoudig script zoals run_experiment.py
, een command-line interface en een grafische interface. Hier gaan we ons richten op een command-line interface. We gaan een nieuw bestand cli.py
aanmaken en dat langzaam opbouwen.
Pythondaq: commando's
opdrachtcodecheck Om de command-line interface voor pythondaq te maken ga je in een nieuw bestand src/pythondaq/cli.py
een opzetje maken waar je stap voor stap functionaliteit aan toevoegt. De oude run_experiment.py
maakte eerst een lijst van aangesloten apparaten en daarna werd een scan uitgevoerd. Daarom zet je in cli.py
de subcommando's list
en scan
. En zorg je dat ze voor nu alleen een stukje tekst printen. De gebruiker test de test-subcommmando's met de volgende handelingen. De gebruiker typt in de terminal het commando diode
met daarachter het subcommando list
en ziet een tekst verschijnen: Work in progress, list devices
. De gebruiker test vervolgens het subcommando scan
en ziet de tekst Work in progress, scan LED
verschijnen. ECPC
\u251c\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 src/pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 arduino_device.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 diode_experiment.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 run_experiment.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 cli.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022
Warning
Omdat de naam van een subcommando gelijk is aan de functienaam kan dat voor problemen zorgen wanneer je gereserveerde namen van python wilt gebruiken zoals: import
, return
, lambda
. Of wanneer je de naam van het subcommando graag hetzelfde wilt hebben als een ander pythonfunctie zoals sin
of list
. Een oplossing is om de functienaam aan te passen en de subcommando naam expliciet aan click mee te geven bij command
:
@cmd_group.command(\"import\")\n@click.argument(\"package\")\ndef import_package(package):\n print(f\"import {package}...\")\n
We hebben nu een commando import
aangemaakt \u2014 niet een commando import_package
. Pseudo-code cli.py
# subcommando list\n # print Work in progress, list devices\n# subcommando scan\n # print Work in progress, scan LED\n
Testcode (ecpc) > diode list \nWork in progress, list devices \n
(ecpc) > diode scan \nWork in progress, scan LED\n
Checkpunten:
diode
.list
print een stukje tekst.scan
print een ander stukje tekst.Projecttraject:
scan
list
info
--help
We gaan ons eerst richten op het uitvoeren van een volledige meetserie en het tonen van de resultaten daarvan aan de gebruiker.
Info
Bij het opgeven van argumenten en opties voor de spanning kan het belangrijk zijn om te controleren of de spanning \u00fcberhaupt wel een getal is tussen 0 en 3.3 V. Je kunt dit doen door de type
-parameter in @click.argument()
en @click.option()
. Je kunt een Pythontype opgeven (bijvoorbeeld: type=int
of type=float
) en Click heeft speciale types zoals type=click.FloatRange(0, 3.3)
voor een kommagetal tussen 0 en 3.3. Bekijk alle speciale types in de Click documentatie. Als je hiervan gebruik maakt hoef je niet zelf te controleren of de parameters kloppen. Click doet dat voor je.
Pythondaq: scan
Pas het subcommando scan
aan. De gebruiker test het subcommando scan
met de volgende handelingen. De gebruiker typt het commando diode scan
in de terminal. Aan de hand van de helptekst weet de gebruiker dat het met argumenten of opties mogelijk is om het spanningsbereik (in Volt) aan te passen. Er wordt een meting gestart die binnen het spanningsbereik blijft. De gebruiker ziet dat de stroomsterkte d\u00f3\u00f3r en de spanning \u00f3ver de LED in de terminal worden geprint. De gebruiker start een nieuwe meting en geeft ditmaal met de optie --output FILENAME
een naam voor een CSV-bestand mee. Dit keer worden de metingen ook opgeslagen als CSV-bestand onder de meegegeven bestandsnaam.
Pseudo-code
# subcommando scan with range in Volt and output CSV\n # start scan with range\n # print current and voltage\n # if output:\n # create csv\n
Checkpunten:
--output FILENAME
.Projecttraject:
scan
list
info
--help
Pythondaq: herhaalmetingen
opdrachtcodecheckPas het subcommando scan
aan zodat je met een optie het aantal herhaalmetingen kan kiezen. De gebruiker test de optie om het aantal herhaalmetingen te kiezen met de volgende handelingen. Met het subcommando scan
voert de gebruiker een meting uit in het bereik 2.8V tot 3.3V. Met een optie zet de gebruiker het aantal herhaalmetingen op 5. De gebruiker ziet dat het resultaat van de metingen met onzekerheden worden geprint in de terminal. De gebruiker bekijkt de grootte van de onzekerheden en voert nogmaals een scan uit maar dan met 10 metingen en daarna met 20 metingen. De gebruiker ziet dat de onzekerheden afnemen wanneer het aantal metingen toeneem.t
Pseudo-code
# subcommando scan with range in Volt, output CSV and repeat measurements\n # start scan with range and repeat measurements\n # print current, voltage and errors\n # if output:\n # create csv\n
Checkpunten:
Projecttraject:
scan
list
info
--help
Soms wil je snel een meting uitvoeren over het hele bereik, dan is het handig om minder punten te meten dan 1023 punten. Breid de applicatie uit zodat de gebruiker de stapgrootte kan aanpassen.
"},{"location":"cli/#het-meetinstrument-kiezen","title":"Het meetinstrument kiezen","text":"We kunnen de Arduino benaderen als we de naam weten die de VISA driver er aan heeft toegekend. Helaas kan \u2014 ook afhankelijk van het besturingssysteem \u2014 die naam veranderen als we de Arduino in een andere poort van onze computer steken of soms zelfs als we een andere Arduino op dezelfde poort koppelen. Met het commando list
laten we alle apparaten zien die gevonden worden door de VISA drivers.
Pythondaq: list
Pas het subcommando list
aan. De gebruiker test het subcommando list
met de volgende handelingen. De gebruiker typt het commando diode list
in de terminal. Daarna verschijnt in de terminal een lijst van aangesloten instrumenten.
(ecpc) > diode list \n('ASRL28::INSTR','ASRL5::INSTR')\n
Checkpunten:
diode list
de lijst met aangesloten devices opvragen.Projecttraject:
scan
list
info
--help
Pythondaq: info
Voeg een subcommando info
toe. De gebruiker test het subcommando info
met de volgende handelingen. Eerst heeft de gebruiker met het commando diode list
een lijst van aangesloten devices opgevraagd. De gebruiker wil weten wat de identificatiestring is van het apparaat dat aan een bepaalde poortnaam hangt. De gebruiker geeft daarom de poortnaam mee als argument aan het subcommando info
waarna de identificatiestring van het instrument in de terminal wordt geprint.
identificatiestring
De identificatiestring van onze Arduino was Arduino VISA firmware v1.0.0
. Je moet natuurlijk niet letterlijk deze string copy/pasten, maar de identificatie opvragen van het instrument. Welk firmwarecommando moest je daarvoor ook alweer gebruiken?
Pseudo-code
# subcommando info with device\n # print identificationstring of device\n
Testcode (ecpc) > diode info ASRL28::INSTR \nArduino VISA firmware v1.0.0\n
Checkpunten:
diode info DEVICE
op te vragen.Projecttraject:
scan
list
info
--help
Pythondaq: choose device
opdrachtcodecheckPas het subcommando scan
aan zodat je kan aangeven met welke Arduino je een meting wilt uitvoeren. De gebruiker test het subcommando scan
met de volgende handelingen. De gebruiker typt het commando diode scan
in de terminal en vergeet daarbij een poortnaam mee te geven. De gebruiker ziet een foutmelding verschijnen want een poortnaam opgeven is verplicht. De gebruiker vraagt met het subcommando list
een lijst van aangesloten instrumenten op. Met het subcommando info
is de gebruiker er achtergekomen wat de naam is van de poort waar de Arduino aanhangt. Vervolgens geeft de gebruiker deze poortnaam mee bij het subcommando scan
om een meting op de (juiste) Arduino uit te laten voeren. Tot slot leent de gebruiker een Arduino van een buurmens. De gebruiker sluit de tweede Arduino aan op de computer. Met list
en info
kijkt de gebruiker wat de poortnaam is van de tweede Arduino. Met het subcommando scan
voert de gebruiker een meting uit en ziet dat het lampje van de tweede Arduino gaat branden en niet het lampje van de eerste Arduino.
(ecpc) > diode scan \nerrorUsage: diode [OPTIONS] DEVICE\nTry 'diode --help' for help.\nError: Missing argument 'DEVICE'.\n
Checkpunten:
Projecttraject:
scan
list
info
--help
Pythondaq: Grafiek
opdrachtcheckPas het subcommando scan
aan zodat je met een boolean flag kan aangeven of er wel of niet een grafiek wordt getoond. De gebruiker test het subcommando scan
met de volgende handelingen. De gebruiker start een meting en geeft ook de optie --graph
na afloop ziet de gebruiker een grafiek met daarin de metingen. Daarna start de gebruiker opnieuwe een meting en geeft dit keer de optie --no-graph
mee, na afloopt van de meting ziet de gebruiker geen grafiek verschijnen. Tot slot start de gebruiker een meting en geeft daarbij geen van beide opties (`--graph/--no-graph) wederom ziet de gebruiker na afloop van de meting geen grafiek verschijnen.
Checkpunten:
--graph
wordt meegegeven.Projecttraject:
scan
list
info
--help
Pythondaq: --help
Voeg helpteksten toe. De gebruiker test de applicatie diode
met de volgende handelingen. De gebruiker typt diode --help
en bekijkt de helptekst. De gebruiker ziet dat er subcommando's zijn. Met subcommando --help
test de gebruiker de helpteksten een voor een uit. Ook bekijkt de gebruiker de helpteksten over de argumenten en otpies. De helpteksten stellen de gebruiker in staat om de applicatie te begrijpen en te bedienen.
Pseudo-code
\"\"\"Summary containing ARGUMENTs.\n\nARGUMENT description of the argument.\n\"\"\"\n
Testcode (ecpc) > diode --help \nUsage: diode [OPTIONS] COMMAND [ARGS] ...\n Options: \n --help Show this message and exit.\n Commands:\n Subcommand Summary containing ARGUMENTs.\n
Checkpunten:
diode --help
vertelt duidelijk welke subcommando's aanwezig zijn en wat ze doen.Projecttraject:
scan
list
info
--help
list --search
Breid het commando list
uit met een optie --search
waarmee je niet een lijst van alle instrumenten krijgt, maar alleen de instrumenten die de zoekterm bevatten. Dus bijvoorbeeld:
(ecpc) > diode list \nThe following devices are connected to your computer: \nASRL/dev/cu.SOC::INSTR\nASRL/dev/cu.MALS::INSTR\nASRL/dev/cu.AirPodsvanDavid-Wireles-1::INSTR\nASRL/dev/cu.Bluetooth-Incoming-Port::INSTR\nASRL/dev/cu.usbmodem143401::INSTR \n\n(ecpc) > diode list -s usbmodem \nThe following devices match your search string: \nASRL/dev/cu.usbmodem143401::INSTR \n
De lijst met instrumenten kan er op Windows heel anders uitzien. Sterker nog, op Windows is de lijst meestal vrij saai. Maar leen eens heel even een Arduino van iemand anders en je ziet dat er dan twee poorten in de lijst verschijnen.
Pas \u2014 na het uitbreiden van list
\u2014 de commando's scan
en info
aan zodat het niet nodig is om de volledige devicenaam mee te geven, maar alleen een zoekterm.
Op dit punt hebben we de functionaliteit van ons snelle script van het vorige hoofdstuk bereikt. Dit was veel meer werk, maar het is veel flexibeler. Als je wilt meten met een andere Arduino, een ander bereik, of een andere stapgrootte dan type je gewoon een iets ander commando in de terminal. Je hoeft geen scripts meer aan te passen. Als je na een tijdje niet meer precies weet hoe het ook alweer werkte allemaal kun je dat snel weer oppakken door --help
aan te roepen.
Alle subcommando's implementeren
Kijk nog eens terug naar het lijstje subcommando's die je in opdracht Subcommando's bedenken hebt opgeschreven. Heb je alles ge\u00efmplementeerd? Wat zou je willen dat je nog meer kan instellen? Als er tijd over is, kijk dan of dit lukt.
Rich Data-analyseargv staat voor: argument vector, een lijst met argumenten\u00a0\u21a9
Merk op in de code hieronder: _
is de weggooivariabele in Python. Het gaat ons erom dat de loop een aantal keer doorlopen wordt en we hoeven niets te doen met de loop index.\u00a0\u21a9
Zie ook: The Python Standard Library \u21a9
Zie voor meer informatie over flags de Click documentatie.\u00a0\u21a9
Pallets. Click. URL: https://click.palletsprojects.com/.\u00a0\u21a9
Will McGugan. Rich. URL: https://github.com/willmcgugan/rich.\u00a0\u21a9
Will McGugan. Rich documentation. URL: https://rich.readthedocs.io/en/latest/.\u00a0\u21a9
Ook command-line interfaces gaan met hun tijd mee. Vroeger waren ze per definitie zwart/wit en statisch, maar tegenwoordig worden interfaces vaak opgeleukt met kleur, emoji's en bewegende progressbars. Rich6 is een project dat in recordtijd heel populair is geworden. Het bestaat pas sinds november 2019 en heeft precies twee jaar later meer dan 31000 verzameld. Dat is veel \u2014 en de populariteit is sindsdien nog verder toegenomen.
Rich is ontzettend uitgebreid en heeft heel veel mogelijkheden. Voor ons project kan het handig zijn om een progressbar te gebruiken of met Rich een tabel weer te geven. De documentatie7 van Rich is best goed, maar kan lastig zijn om een mooi overzicht te krijgen. Een serie van korte video tutorials kun je vinden bij calmcode. Iedere video duurt maar \u00e9\u00e9n tot twee minuten en laat mooi de mogelijkheden zien. Voor de functies die je wilt gebruiken kun je dan meer informatie opzoeken in de documentatie van Rich zelf.
Rich
Verrijk je interface met Rich. Doe dit naar eigen wens en inzicht.
"},{"location":"cli/#data-analyse","title":"Data-analyse","text":"Door de $I,U$-karakteristiek van de (lichtgevende) diode te analyseren is het mogelijk om de constante van Boltzmann te bepalen. De stoomsterkte door een diode wordt gegeven door de Shockley diodevergelijking. Zie ook hoofdstuk diode.
Lukt het, om binnen de te bepalen onzekerheid, overeenkomst te vinden met de literatuurwaarde? Een LED is helaas geen ideale diode dus dit kan lastig zijn.
Model fitten
Fit het model van Shockley aan je $I,U$-karakteristiek. Welke parameters kun je bepalen? Overleg met je begeleider!\n
"},{"location":"communicatie/","title":"Communicatie met een meetinstrument","text":"Het hart van ieder experiment wordt gevormd door de metingen die worden uitgevoerd. Meetinstrumenten vervullen daarom een belangrijke rol bij het automatiseren van een experiment. De eerste stap die we zullen zetten tijdens het ontwikkelen van een applicatie is het communiceren met ons meetinstrument. We hebben gekozen voor een Arduino Nano 33 IoT,9 een zeer compact stukje elektronica rondom een ARM-microcontroller. Naast het uitvoeren van analoge spanningsmetingen kan dit model ook analoge spanningen afgeven dat voor ons heel nuttig gaat blijken. We hebben, speciaal voor dit vak, een stukje firmware1 ontwikkeld.10
"},{"location":"communicatie/#microcontrollers","title":"Microcontrollers","text":"Computers \u2014 zoals de meesten van ons die kennen \u2014 zijn zeer krachtig en ontworpen om zo flexibel mogelijk te zijn. Ze draaien games, e-mail of rekenen klimaatmodellen door. Ze komen in veel vormen: desktops, laptops, tablets en telefoons. Ze bevatten daarom veel losse componenten: snelle processor (CPU), veel geheugen (RAM), veel permanente opslag (SSD), complexe interfaces (HDMI, USB) en een besturingssysteem waarmee je verschillende programma's kunt opstarten en de computer kunt beheren. Computers zijn behoorlijk prijzig.
Een microcontroller daarentegen is veel eenvoudiger. Ze zijn ontworpen voor een beperkte en specifieke taak. Ze hebben veel verschijningsvormen \u2014 de meeste onherkenbaar. Je vindt microcontrollers in de vaatwasser, de magnetron, een draadloos toetsenbord en auto's (letterlijk tientallen verspreid over de hele auto). Ze hebben dan een beperkte taak: ze reageren op de knopjes op je dashboard om het klimaat te regelen of een raam te openen en ze sturen de kleppen in een verbrandingsmotor aan. Microcontrollers bevatten CPU, RAM en SSD vaak in \u00e9\u00e9n chip en hebben beperkte interfaces (vaak letterlijk losse pinnetjes die je moet verbinden). De CPU is relatief gezien traag en de hoeveelheid geheugen klein. Voor de beperkte taak is dat niet erg. Een besturingssysteem is niet nodig: als je hem aanzet draait hij meteen het enige programma dat ooit ingeladen is (dit heet dan firmware). Microcontrollers zijn goedkoop en daarom ook uitermate geschikt voor hobbyprojecten.
Een Arduino is zo'n microcontroller. Vaak wordt een Arduino vergeleken met een Raspberry Pi \u2014 een andere goedkope computer. Maar een Raspberry Pi is \u00e9cht een computer (en daarmee ook complex). Daarmee is een Raspberry Pi veel veelzijdiger, maar ook duurder en is het complexer om een eenvoudig programma te draaien. Apparatuur zoals frequentiegeneratoren en oscilloscopen hebben vaak een microcontroller ingebouwd, maar soms ook een microcomputer analoog aan een Raspberry Pi. Dat maakt voor ons weinig verschil zolang we maar weten hoe we het instrument kunnen aansturen.
"},{"location":"communicatie/#communicatieprotocol","title":"Communicatieprotocol","text":"Hoe praat je eigenlijk met hardware? Voor fabrikanten zijn er een paar opties:
De VISA-standaard is veelgebruikt, maar helaas komen proprietary protocollen veel voor. Dat is jammer, want in het laatste geval moet je het doen met de software die geleverd wordt door de fabrikant. Als die jouw besturingssysteem of favoriete programmeertaal niet ondersteunt heb je simpelweg pech.
Wij gaan gebruik maken van de VISA-standaard. VISA staat voor Virtual Instrument Software Architecture en is h\u00e9\u00e9l breed en definieert protocollen om te communiceren via allerlei verouderde computerpoorten en kabels. Hieronder zie je een voorbeeld van verschillende poorten zoals RS232 en GPIB aan de achterkant van een Tektronix TDS210 oscilloscoop.
Bron: Wikimedia Commons.
Maar gelukkig ook via internet en USB, waarvan wij gebruik zullen maken. Onderdeel van VISA is de SCPI standaard 15, wat staat voor Standard Commands for Programmable Instruments. Dit onderdeel definieert een bepaald formaat voor commando's die we naar ons instrument zullen sturen. De lijst met commando's die door de firmware van onze Arduino worden ondersteund is gegeven in de appendix.
"},{"location":"communicatie/#eerste-stappen","title":"Eerste stappen","text":"Waarschuwing
Let op dat je de weerstand van 220 \u03a9 gebruikt! Een te grote weerstand zorgt ervoor dat je nauwelijks iets kunt meten, maar een te kleine weerstand zorgt ervoor dat de stroomsterkte door de Arduino te groot wordt. In dat geval zul je de Arduino onherstelbaar beschadigen. De kleurcodes voor weerstanden vind je in de appendix.
Schakeling bouwen
opdrachtbouwtekeningcheckJe maakt een schakeling om de spanning over en de stroom door een LED te meten. Hiervoor maak je gebruik van een Arduino en een breadboard. Om de stroomsterkte te beperken zet je de LED in serie met een weerstand van 220 \u03a9. Je sluit twee spanningsmeters aan, spanningsmeter 1 staat over de LED en de weerstand samen. Spanningsmeter 2 staat over de weerstand.
Theoretische schakeling
Het circuit zoals je dat zou bouwen met twee losse voltmeters is hieronder weergegeven. De cijfers 0, 1 en 2 bij $U_0$, $U_1$ en $U_2$ zijn de kanalen waarmee de Arduino spanningen kan sturen of uitlezen. Dat wordt zometeen belangrijk.
Praktische schakeling
In het 3D-model5 hieronder is een Arduino Nano 33 IoT op een 400-punt breadboard geschakeld met een LED en een weerstand van 220 \u03a9. In een breadboard zijn in iedere rij alle kolommen A t/m E met elkaar verbonden (zo ook kolommen F t/m J). Draadjes die naast elkaar zijn geprikt zijn dus met elkaar verbonden. De Arduino is geprikt in kolom D t/m H en van rij 1 t/m 15. De pin van de Arduino in rij 4 is verbonden middels het rode draadje (De kleur van de draden is niet belangrijk. Kies altijd draden met een handige lengte.) met het pootje van de LED. De platte zijde in de onderste ring van de LED wordt richting aarde geschakeld. Het ander pootje van de LED is verbonden met de weerstand. De kleurcodes voor weerstanden vind je in de appendix. Van de weerstand loopt een draadje naar de aarde van de Arduino (rij 12, kolom H). Zo kan de Arduino een variabele spanning zetten over de LED en de weerstand.
De pin van de Arduino in rij 5 is verbonden met de LED en meet de spanning over de LED en de weerstand. De pin van de Arduino in rij 6 is verbonden met de weerstand en meet alleen de spanning over weerstand.
3D besturing
Door de linkermuisknop ingedrukt te houden en te slepen kan je de het 3D model draaien, met rechtermuisknop kan je hem verplaatsen en door te scrollen kan je in- en uitzoomen.
Checkpunten:
Projecttraject:
list
en open
query
Info
Om met Python via het VISA-protocol te kunnen communiceren met apparaten hebben we specifieke packages nodig. Die gaan we installeren in een conda environment. Voor meer informatie over conda environments zie paragraaf Conda environments.
Environment aanmaken
Open een Anaconda Prompt
die je kunt vinden via de zoekbalk van Windows. Maak de environment en installeer de juiste packages door in te typen:
Terminal
conda create --name pythondaq --channel conda-forge python pyvisa-py\n
Om de conda environment daadwerkelijk te gebruiken moet je die altijd eerst activeren met: Terminalconda activate pythondaq\n
Pyvisa in terminal
opdrachtcodecheckJe sluit de Arduino met een USB-kabel aan op de computer. In een Anaconda Prompt
open je het goede conda environment en open je een pyvisa-shell
met een python backend. Om erachter te komen hoe de pyvisa-shell
werkt type je het commando help
. Je ziet een reeks aan commando's en bekijkt de helptekst van de commando's waarmee je denkt de pyvisa-shell
te kunnen afsluiten. Wanneer je het afsluit commando hebt gevonden sluit je daarmee de pyvisa-shell
af.
Pseudo-code Terminal
# open pyvisa-shell with python backend\n# check help of pyvisa-shell\n# check help of exit command\n# shut down the pyvis-shell\n
Testcode (ecpc) > pyvisa-shell -b py \n\nWelcome to the VISA shell. Type help or ? to list commands. \n(visa)\n\n
(visa) help \n\nDocumented commands (type help <topic>
):\n========================================\nEOF attr close exit help list open query read termchar timeout write\n\n
Checkpunten:
pyvisa-shell
staat er (visa) tussen haakjes.help
intypt verschijnt een heel rijtje met commando's.help exit
intypt krijg je de hulpvaardige tekst: Exit the shell session.
Projecttraject:
list
en open
query
Info
We maken hier gebruik van de optie -b py
, wat staat voor gebruik backend: python. Het kan namelijk dat er, naast pyvisa-py
, ook andere backends, of drivers, ge\u00efnstalleerd staan op het systeem die de VISA-communicatie kunnen verzorgen. Als je bijvoorbeeld LabVIEW ge\u00efnstalleerd hebt, dan heb je de drivers van National Instruments. Maar de verschillende backends geven de aangesloten apparaten andere namen. Ook ondersteunen niet alle drivers alle types apparaten en moet je ze apart downloaden en installeren. Daarom maken we liever gebruik van de beschikbare Python drivers.
Pyvisa list
en open
Je bekijkt het lijstje met aangesloten apparaten door in de pyvisa-shell
het commando list
te typen. Je haalt de USB-kabel waarmee de Arduino aan de computer is aangesloten uit de computer en vraagt nogmaals de lijst met aangesloten apparaten op. Nu weet je welke poort de Arduino is. Je bekijkt de help tekst van het commando open
, daarna open je de communicatie met de Arduino.
Pseudo-code Terminal
# open pyvisa-shell\n# list\n# help open\n# open Arduino\n
Testcode (visa) list \n( 0) ASRL3::INSTR\n( 1) ASRL5::INSTR\n( 2) ASRL28::INSTR\n
(visa) help open \nOpen resource by number, resource name or alias: open 3\n
Checkpunten:
list
verschijnt er een lijst met een of meerdere apparaten.ASRL?::INSTR has been opened.\nYou can talk to the device using \"write\", \"read\" or \"query\".\nThe default end of message is added to each message.\n
Projecttraject:
list
en open
query
Pyvisa query
Je stuurt een commando naar de Arduino met query COMMANDO
. In de documentatie van de firmware heb je het commando opgezocht om de identificatiestring uit te lezen. Nadat je dit commando naar de Arduino stuurt krijg je een error. Je leest de handleiding rustig verder om erachter te komen hoe je dit moet oplossen.
Pseudo-code Terminal
# send query to get identificationstring \n
Testcode (open) query gappie \nResponse: ERROR: UNKNOWN COMMAND gappie\n
Checkpunten:
query
goed geschreven en met kleine letters.query
volgt een spatie.*
en dat ?
horen er ook bij!).Response: ERROR: UNKNOWN COMMAND .....
Projecttraject:
list
en open
query
Niet helemaal wat we hadden gehoopt! Als je goed kijkt in de documentatie van de firmware dan zie je dat er bepaalde terminator characters nodig zijn. Dit zijn karakters die gebruikt worden om het einde van een commando te markeren. Het is, zogezegd, een enter aan het eind van een zin. Dit mag je heel letterlijk nemen. Oude printers voor computeruitvoer gebruikten een carriage return (CR) om de wagen met papier (typemachine) of de printerkop weer aan het begin van een regel te plaatsen en een line feed (LF) om het papier een regel verder te schuiven. Nog steeds is het zo dat in tekstbestanden deze karakters gebruikt worden om een nieuwe regel aan te geven. Jammer maar helaas, verschillende besturingssystemen hebben verschillende conventies. Windows gebruikt nog steeds allebei: een combinatie van carriage return + line feed (CRLF).
Carriage Return Line Feed: Typewriter Demonstration
Maar MacOS/Linux/Unix gebruiken enkel een line feed (LF), want hoeveel meer heb je nodig? Af en toe is dat lastig, vooral wanneer er elektronica in het spel is want dan willen de regeleindes voor schrijven en lezen nog wel eens verschillend zijn.6
Terminator characters demo
opdrachtcheckJe vraagt je misschien af wat het betekent dat er bij het schrijven en lezen regeleindes gebruikt worden. Daarom open je de Termchar-demo. Je gaat naar de Basic tab, daar zie je twee inputvelden voor de client (dat ben jij) en de server (dat is de Arduino).
Je schrijft een commando measure_voltage
naar de Arduino (druk op Write). In het Input veld van de Arduino verschijnt jouw commando maar het staat nog niet in de Application Log, het is dus nog niet door de Arduino verwerkt. Want de Read Termination Characters van de Arduino zijn \\n
(LF), die gaat dus pas lezen als die tekens zijn verstuurd. Je verstuurt \\n
en ziet dat het commando wordt verwerkt en jij een antwoord krijgt.
Steeds \\n
handmatig versturen is onhandig daarom voer je bij de Client als Write Termination Characters \\n
in. Je verstuurt nog een commando measure_current
, drukt op Write en ziet dat het bericht direct door de Arduino wordt verwerkt en een antwoord terugstuurt.
In het Input veld van de Client staan twee antwoorden van de Arduino, als je nu op Read drukt blijven de termination characters in de antwoorden staan en moet je ze handmatig uit elkaar gaan halen. Dat is niet handig, daarom vul je bij de Read Termination Characters van de Client \\r\\n
(CRLF) in. Daarna druk je op Read en merk je dat de twee antwoorden apart uitgelezen worden, super handig!
Checkpunten:
\\n
.\\r\\n
.Projecttraject
list
en open
query
Ga opnieuw naar de Termchar-demo en lees de laatste stappen van de Introduction tab. Open de Advanced tab en voer de stappen uit.
We gaan nu het gebruik van de karakters instellen in Pyvisa:
Pyvisa regeleindes
opdrachtcodecheckJe gaat weer terug naar de Anaconda prompt. Je gebruikt het commando termchar
om de regeleindes in te stellen. Om erachter te komen hoe je dit moet instellen vraag je de helptekst op met help termchar
. Je vraagt eerst de huidige regeleinde instellingen op en ziet dat deze niet goed staan. Daarna stel je de read in op CRLF en de write op LF. Je bekijkt nog een keer de regeleinde instellingen om te controleren of ze nu wel goed staan. Je gaat terug naar de opdracht Pyvisa query
en krijgt een response in plaats van een error.
\\r\\n en CRLF
Bij de Termination Characters demo maakten we gebruik van \\r\\n
dat is de programmeertaal equivalent van CRLF
.
Pseudo-code Terminal
# get help text of termchar\n# what are the termchar settings?\n# make read = CRLF and write = LF\n# check if termchar settings are correct\n# send query to get identificationstring\n
Testcode (open) help termchar \nGet or set termination character for resource in use.\n<termchar>
can be one of: CR, LF, CRLF, NUL or None.\nNone is used to disable termination character\nGet termination character:\n termchar\nSet termination character read or read+write:\n termchar <termchar>
[<termchar>
]\n
Checkpunten:
termchar
daarna een spatie, vervolgens de karakters voor de read thermchar, dan weer een spatie en daarachter de karakters voor de write termchar. termchar
de instellingen van de regeleindes opvraag staat er:Termchar read: CRLF write: LF\nuse CR, LF, CRLF, NUL or None to set termchar\n
query
het commando om de identificatiestring uit te lezen naar de Arduino verstuurt verschijnt de tekst:Response: Arduino VISA firmware v1.0.0\n
Projecttraject:
list
en open
query
Onzichtbare regeleindes
Omdat de Arduino nu weet wanneer het commando voorbij is (door de LF aan het eind van de zin) krijgen we antwoord! Dat antwoord heeft dan juist weer een CRLF aan het eind dus pyvisa-shell
weet wanneer het kan stoppen met luisteren en print het antwoord op het scherm. De karakters CRLF en LF zelf blijven onzichtbaar voor ons.
Pyvisa LED laten branden
opdrachtcodecheckJe zoekt in de documentatie van de firmware op hoe je een spanning op het uitvoerkanaal zet. Je leest dat er een maximale waarde is voor de spanning en zet deze waarde op het uitvoerkanaal. Je ziet dat het LEDje brandt en er verschijnt een glimlach op je gezicht. Je bent benieuwd naar wat er gebeurt als je over de maximale spanning heen gaat en zet de maximale waarde + 1 op het uitvoerkanaal. Je denkt na over een verklaring voor wat je ziet gebeuren. Je weet dat een LED een drempelspanning nodig heeft om te branden, je vult een paar waardes in tussen de minimale en maximale waarde om erachter te komen wat deze drempelspanning is.
Pseudo-code Terminal
# set max voltage\n# set max voltage + 1\n# set threshold voltage\n
Testcode Zie documentatie van de firmware. Checkpunten:
query
.query
goed geschreven en met kleine letters.0
.Projecttraject:
list
en open
query
We hebben via de shell contact gelegd met de hardware. Nu wordt het tijd om, met de documentatie16 in de aanslag, hetzelfde vanuit Python te doen. Als je met een nieuw project begint is het helemaal geen gek idee om een kort script te schrijven waarin je wat dingen uitprobeert. Als alles lijkt te werken kun je het netjes gaan maken en gaan uitbreiden. We beginnen hier met een eenvoudig script en zullen dat daarna gaan verfijnen.
We lopen het voorbeeldscript eerst regel voor regel door en geven het volledige script aan het eind. Allereerst importeren we de pyvisa
-bibliotheek met
import pyvisa\n
Binnen pyvisa wordt alles geregeld met behulp van een Resource Manager. Die krijgen we met rm = pyvisa.ResourceManager(\"@py\")\n
Die kunnen we bijvoorbeeld gebruiken om een lijst van alle beschikbare poorten te krijgen: ports = rm.list_resources()\n\n# Bijvoorbeeld: (\"ASRL28::INSTR\",)\n
Om nu daadwerkelijk verbinding te gaan maken met de Arduino moeten we die openen. Daarvoor geven we de poortnaam op en vertellen we meteen wat de instellingen moeten zijn voor de regeleindes bij het lezen (CRLF, \"\\r\\n\"
) en het schrijven (LF, \"\\n\"
): device = rm.open_resource(\n \"ASRL28::INSTR\", read_termination=\"\\r\\n\", write_termination=\"\\n\"\n)\n
Ten slotte sturen we een query naar de Arduino: device.query(\"*IDN?\")\n
Het volledige script \u2014 met een paar print
-statements \u2014 ziet er dan als volgt uit: test_arduino.py import pyvisa\n\nrm = pyvisa.ResourceManager(\"@py\")\nports = rm.list_resources()\nprint(ports)\n\ndevice = rm.open_resource(\n \"ASRL28::INSTR\", read_termination=\"\\r\\n\", write_termination=\"\\n\"\n)\nidentification = device.query(\"*IDN?\")\nprint(identification)\n
\n(ecpc) > python test_arduino.py\n('ASRL28::INSTR',)\nArduino VISA firmware v1.0.0\n
De output van het script is afhankelijk van het systeem en het aantal apparaten dat verbonden is.
Vergelijk pyvisa-shell met Python code
Je hebt nu precies hetzelfde gedaan in Python als in de pyvisa shell. Vergelijk de verschillende stappen hieronder met elkaar door met de muis over de tekst heen te gaan.
\nPS> pyvisa-shell -b py\n\nWelcome to the VISA shell. Type help or ? to list commands.\n\n(visa) list\n( 0) ASRL3::INSTR\n( 1) ASRL5::INSTR\n( 2) ASRL28::INSTR\n(visa) open 2\nASRL28::INSTR has been opened.\nYou can talk to the device using \"write\", \"read\" or \"query\".\nThe default end of message is added to each message.\n(open) termchar CRLF LF\nDone\n(open) query *IDN?\nResponse: Arduino VISA firmware v1.0.0\n
\nimport pyvisa\n\nrm = pyvisa.ResourceManager(\"@py\")\nports = rm.list_resources()\nprint(ports)\n\ndevice = rm.open_resource(\n \"ASRL28::INSTR\", \n read_termination=\"\\r\\n\", \n write_termination=\"\\n\"\n)\nidentification = device.query(\"*IDN?\")\nprint(identification)
Pyvisa in pythonscript
opdrachtcodecheck Je gaat de gegeven Python code testen daarom open je in Visual Studio Code de map ECPC
en maakt een bestand test_arduino.py
aan. Je kopieert de Python code in het bestand. Je ziet dat de code gebruikt maakt van de package pyvisa
daarom selecteer je de environment die je bij opdracht Environment aanmaken hebt gemaakt. Je slaat het bestand op en runt het bestand. ECPC
\u251c\u2500\u2500test_arduino.py
\u2514\u2500\u2500\u2022\u2022\u2022
could not open port 'COM28': FileNotFoundError
Krijg je een FileNotFoundError
? Dan kan het zijn dat het script een poort probeert te openen die bij jou een andere naam heeft. Probeer met het lijstje instrumenten te raden welke de Arduino is en pas het script aan totdat het werkt.7
could not open port 'COM3': PermissionError
Krijg je een PermissionError
? Dan heb je vast nog een terminal openstaan waarin pyvisa-shell
actief is.
Pseudo-code
# import pyvisa package\n# create resourcemanager\n# get list resources\n# open device\n# send query\n
Testcode test_arduino.py ...\n\nprint(ports)\nprint(identification)\n
\n(ecpc) > python test_arduino.py\n('ASRL28::INSTR',)\nArduino VISA firmware v1.0.0\n
Checkpunten:
Projecttraject:
LED laten branden
opdrachtcodecheck Omdat je straks de IU-karakteristiek van de LED wilt gaan bepalen ga je een reeks aan spanningen naar de LED sturen waardoor de LED gaat branden. Je maakt daarvoor een bestand test_LED.py
aan in de map ECPC
. Je schrijft eerst een regel code waarmee je een commando naar de Arduino stuurt waardoor de LED gaat branden. Daarna schrijf je de code om zodat de spanning oploopt van de minimale waarde tot aan de maximale waarde. ECPC
\u251c\u2500\u2500test_arduino.py
\u251c\u2500\u2500test_LED.py
\u2514\u2500\u2500\u2022\u2022\u2022
f-strings
Het sturen van commando's naar de Arduino waar een variabele spanning in staat gaat gemakkelijk met f-strings. Voor meer informatie zie de paragraaf f-strings.
Pseudo-code
# import pyvisa package\n# create resourcemanager\n# get list resources\n# open device\n#\n# for value in min to max\n# send query set output channel to value\n
Testcode test_LED.py ...\nfinal_value = device.query(\"OUT:CH0?\")\nprint(final_value)\n
\n(ecpc) > python test_LED.py\n1023\n
Checkpunten:
OUT:CH0?
krijg je 1023
terug.Projecttraject:
flashingLED
opdrachtcodecheck Om bekend te raken met de code maak je een nieuw bestand flashingLED.py
aan in de map ECPC
waarin je code schrijft die de LED in een regelmatig tempo laat knipperen. ECPC
\u251c\u2500\u2500test_arduino.py
\u251c\u2500\u2500test_LED.py
\u251c\u2500\u2500flashingLED.py
\u2514\u2500\u2500\u2022\u2022\u2022
Info
Je kan hiervoor gebruik maken van de module time die standaard met Python meekomt8. Met de functie sleep()
kun je de executie van de volgende regel in het script met een aantal seconden uitstellen.
import time\n# wait 28 second\ntime.sleep(28)\n
Pseudo-code
# import pyvisa package\n# create resourcemanager\n# get list resources\n# open device\n#\n# repeat:\n# send query set output channel to max\n# wait\n# send query set output channel to min\n# wait\n
Testvoorbeeld Checkpunten:
Projecttraject:
Meer knipperritmes
Breid het bestand flashingLED.py
uit met meer knipperritmes, bijvoorbeeld:
Firmware is software die in hardware is geprogrammeerd. Bijvoorbeeld het computerprogramma dat ervoor zorgt dat je magnetron reageert op de knoppen en je eten verwarmd.\u00a0\u21a9
Proprietary betekent dat een bedrijf of individu exclusieve de rechten heeft over het protocol of de software en anderen geen toegang geeft tot de details.\u00a0\u21a9
Niet zelden zijn dergelijke bibliotheken maar op een paar besturingssystemen beschikbaar als driver. Gebruik je MacOS in plaats van Windows en het wordt alleen op Windows ondersteund? Dan kun je je dure meetinstrument dus niet gebruiken totdat je overstapt.\u00a0\u21a9
Die overigens op vrijwel alle platforms en voor veel programmeertalen bibliotheken leveren.\u00a0\u21a9
Dit model bevat twee 3D modellen die zijn gecre\u00eberd door Lara Sophie Sch\u00fctt en AppliedSBC en zijn gedeeld onder respectievelijk een CC-BY en CC-BY-SA licentie. De originele modellen zijn te vinden via [CC0] Set of Electronic Components en Arduino Nano 33 IoT. De modellen zijn samengevoegd en Voorzien van een Arduino texture een aangepaste LED texture en draden. Dit 3D model heeft een CC-BY-SA licentie.\u00a0\u21a9
De regeleindes voor de Arduinofirmware zijn verschillend voor lezen en schrijven. Dit heeft een oninteressante reden: bij het ontvangen van commando's is het makkelijk om alles te lezen totdat je \u00e9\u00e9n bepaald karakter (LF) tegenkomt. Bij het schrijven gebruikt de standaard println
-functie een Windows-stijl regeleinde (CRLF).\u00a0\u21a9
Tip: als je de Arduino loshaalt en weer aansluit is het de nieuwe regel in het lijstje.\u00a0\u21a9
Zie ook: The Python Standard Library \u21a9
Arduino AG. Arduino nano 33 iot. URL: https://store.arduino.cc/arduino-nano-33-iot.\u00a0\u21a9
David B.R.A. Fokkema. Arduino visa firmware. 2020. URL: https://github.com/davidfokkema/arduino-visa-firmware.\u00a0\u21a9
IVI Foundation. Vpp-4.3: the visa library. 2018. URL: https://www.ivifoundation.org/downloads/Architecture Specifications/IVIspecstopost10-22-2018/vpp43_2018-10-19.pdf.\u00a0\u21a9
Tektronix, Inc. URL: https://www.tek.com/.\u00a0\u21a9
National Instruments Corp. URL: https://www.ni.com/.\u00a0\u21a9
Pico Technology Limited. URL: https://www.picotech.com/.\u00a0\u21a9
SCPI Consortium. Standard commands for programmable instruments (scpi). 1999. URL: https://www.ivifoundation.org/docs/scpi-99.pdf.\u00a0\u21a9
PyVISA Authors. Pyvisa: control your instruments with python. URL: https://pyvisa.readthedocs.io/en/latest/.\u00a0\u21a9
In het introductie-experiment meten we de $I,U$-karakteristiek van een LED, een lichtgevende diode. In dit hoofdstuk gaan we iets dieper in op het aspect diode.
Uiteindelijk is een diode een eenrichtingsweg voor stroom. Dat wil zeggen: er kan maar in \u00e9\u00e9n richting stroom door de diode lopen. De diode heeft een lage weerstand, maar als de polariteit wordt omgedraaid dan is de weerstand plots zeer groot en loopt er nauwelijks stroom. Diodes kunnen bijvoorbeeld gebruikt worden als gelijkrichter waarbij een wisselspanning met een stelsel diodes wordt omgezet in een gelijkspanning. Ook zijn er dus diodes die licht geven. Het fysisch principe achter een diode zorgt ervoor dat er, in sommige gevallen, zeer energiezuinig licht geproduceerd kan worden.
"},{"location":"diodes/#halfgeleiders","title":"Halfgeleiders","text":"Metalen zijn geleiders. Ze hebben in de buitenste schil \u00e9\u00e9n of enkele valentie-elektronen die vrij kunnen bewegen in het kristalrooster. Een potentiaalverschil (veroorzaakt door bijvoorbeeld een batterij) zorgt voor een stroom van elektronen. Bij een isolator zitten alle elektronen vast in het rooster. Bij een halfgeleider is dat eigenlijk ook zo \u2014 de valentie-elektronen zijn nodig voor de bindingen tussen de atomen \u2014 maar door trillingen in het rooster is het relatief eenvoudig om af en toe een elektron-gat-paar te cre\u00ebren: een elektron ontsnapt en kan door het kristalrooster bewegen, maar de achtergebleven atomen missen nu een bindingselektron (het gat). Naburige elektronen kunnen heel eenvoudig in dit gat springen, maar laten dan weer een gat achter. Op deze manier kunnen gaten ook vrij door het rooster reizen. Een gat heeft effectief een positieve lading.
"},{"location":"diodes/#p-type-en-n-type-halfgeleiders","title":"p-type en n-type halfgeleiders","text":"Halfgeleiders kunnen gedoteerd worden met andere stoffen. Feitelijk worden onzuiverheden in het kristalrooster ingebracht. Als je elementen uit de stikstofgroep (vijf valentie-elektronen) toevoegt aan de halfgeleider silicium (vier valentie-elektronen) dan worden de vreemde atomen in het kristalrooster gedwongen tot het aangaan van vier covalente bindingen met naburige siliciumatomen. Hierdoor blijft een elektron over. Dit elektron kan vrij door het rooster bewegen en op deze manier zijn er dus veel extra vrije elektronen aan het materiaal toegevoegd. Dit is een n-type halfgeleider omdat de ladingsdragers negatief geladen zijn.
Op eenzelfde manier kunnen elementen uit de boorgroep (drie valentie-elektronen) worden toegevoegd aan silicium. Het is alleen niet mogelijk om vier covalente bindingen aan te gaan en er ontstaat plaatselijk een gat, een positieve ladingsdrager. Dit wordt dus een p-type halfgeleider genoemd. Hoewel gaten zich vrij door het rooster kunnen bewegen gaat dit wel trager dan bij elektronen.
Merk op dat hoewel n-type en p-type halfgeleiders beschikken over respectievelijk negatieve en positieve ladingsdragers ze als geheel neutraal zijn. De onzuiverheden in het rooster blijven wel achter als ion als er geen vrije ladingsdrager in de buurt is. Immers, stikstof is neutraal met vijf valentie-elektronen dus als het vijfde elektron vrij door het rooster is gaan bewegen blijft een positief ion achter. Een booratoom blijft achter als een negatief ion als het gat vertrokken is (en een extra elektron de binding heeft opgevuld).
"},{"location":"diodes/#p-n-overgangen","title":"p-n-overgangen","text":"Wanneer een p-type en een n-type halfgeleider elektrisch contact maken1 dan kunnen de elektronen en de gaten elkaar in het midden tegenkomen. Immers, door diffusie verplaatsen de elektronen en gaten zich willekeurig door het materiaal.
Elektronen (zwart) en gaten (wit) zijn de vrije ladingsdragers in respectievelijk n-type en p-type halfgeleiders. Wanneer beide typen elektrisch contact maken kunnen elektronen en gaten de grenslaag oversteken en recombineren.
Het extra elektron kan een tekort aanvullen en alle naburige atomen kunnen zo vier covalente bindingen aangaan. Het elektron en het gat heffen elkaar dus op recombinatie. Dit is energetisch voordelig, maar er ontstaat in het midden een sperlaag2 waar geen vrije ladingsdragers meer aanwezig zijn. Dit betekent echter wel dat de onzuiverheden (ionen!) ervoor zorgen dat het materiaal niet langer neutraal is, maar elektrisch geladen. Waar de elektronen verdwenen zijn blijven positieve ionen achter en omgekeerd voor de gaten. Er ontstaat zo een elektrisch veld dat de elektronen en gaten tegenhoudt. Buiten de sperlaag is er geen elektrisch veld, net als bij een condensator.3 Elektronen en gaten kunnen niet langer de sperlaag oversteken.
Na recombinatie van elektronen (zwart) en gaten (wit) ontstaat er een sperlaag waar geen vrije ladingsdragers meer aanwezig zijn. De gedoteerde atomen vormen ionen in het rooster en er onstaat een postief geladen gebied en een negatief geladen gebied. Buiten de sperlaag is geen veld aanwezig, net als bij een condensator. Het resulterende elektrisch veld remt eerst en stopt uiteindelijk de diffusie van de elektronen en gaten. Er ontstaat een evenwicht waarbij vrije ladingsdragers de grenslaag niet meer kunnen oversteken.
"},{"location":"diodes/#sperrichting","title":"Sperrichting","text":"Wanneer een diode wordt verbonden met een elektrisch circuit is de richting van het potentiaalverschil van belang. Wanneer het p-type halfgeleider verbonden wordt met de negatieve pool en het n-type met de positieve pool dan ontstaat er een elektrisch veld in de zelfde richting als dat van de sperlaag. Hierdoor wordt het veld sterker en zullen vrije ladingsdragers de sperlaag zeker niet kunnen oversteken. De diode staat in sperrichting en er zal nauwelijks4 stroom lopen. Een andere manier om dit in te zien is dat de gaten naar de negatieve pool worden getrokken en het gebied rond de sperlaag verlaten. Daar blijven nu dus nog meer negatieve ionen achter; de sperlaag wordt dikker. Idem aan de andere zijde van de sperlaag.
"},{"location":"diodes/#doorlaatrichting","title":"Doorlaatrichting","text":"Wanneer we de polariteit omdraaien en de p-typezijde verbinden aan de positieve pool en de n-typezijde aan de negatieve pool dan ontstaat er een elektrisch veld van p-type naar n-type, tegengesteld aan het sperveld. Wanneer het potentiaalverschil op de diode \u2014 ten gevolge van een externe spanningsbron \u2014 lager is dan het potentiaalverschil over de sperlaag, dan zal er nog steeds geen stroom kunnen lopen. De sperlaag wordt echter wel dunner. Wanneer het externe potentiaalverschil groter is, dan keert het netto elektrisch veld in de sperlaag om en kunnen gaten en elektronen de grenslaag oversteken. Er loopt een stroom, maar wel anders dan in een geleider. De gaten en elektronen recombineren in het gebied van de grenslaag, terwijl er aan de metaal/halfgeleider grenzen nieuwe elektron-gatparen worden gevormd. Er stromen aan de zijde van de n-type halfgeleider dus continu elektronen vanuit het metaal naar de grenslaag en aan de zijde van de p-type halfgeleider stromen continu gaten van het metaal naar de grenslaag. Aan de grenslaag metaal/p-type halfgeleider verlaten elektronen de halfgeleider (en zo ontstaan de gaten). Voor de vrije ladingsdragers is de weerstand in de halfgeleider vrij laag waardoor de stroomsterkte flink kan oplopen. Een diode geleidt dus in de doorlaatrichting, maar pas boven een minimale doorlaatspanning.
"},{"location":"diodes/#lichtgevende-diode","title":"Lichtgevende diode","text":"Wanneer een elektron en een gat elkaar tegenkomen is het energetisch gunstiger om te recombineren. Het elektron bindt zich aan de atomen in het rooster. Hierbij komt dus energie vrij. Meestal is dit in de vorm van roostertrillingen (warmte). Wanneer de materialen goed gekozen worden is het mogelijk om het energieverlies niet dominant via roostertrillingen te laten verlopen, maar via emissie van licht. Bij een doorzichtige halfgeleider kan het licht de grenslaag verlaten en uitgestraald worden. De LEDs die wij gebruiken bestaan uit een heel klein stukje halfgeleidermateriaal in een kegelvormige reflector om het licht naar boven te richten. Het geheel is ter bescherming in kunststof gegoten en de bolvorm zorgt voor een lenseffect om zoveel mogelijk licht in \u00e9\u00e9n richting uit te stralen.
"},{"location":"diodes/#de-iu-karakteristiek-van-een-diode","title":"De I,U-karakteristiek van een diode","text":"Shockley, \u00e9\u00e9n van de uitvinders van de transistor, ontwikkelde een model voor p-n-overgangen. Volgens dat model7 wordt de stroomsterkte gegeven door
\\begin{equation} I = I_\\mathrm{S} \\left(e^\\frac{V_\\mathrm{D}}{nV_\\mathrm{T}} - 1 \\right), \\end{equation} met $I$ de diodestroom, $I_\\mathrm{S}$ de diodelekstroom, $V_\\mathrm{D}$ de spanning over de diode, $n$ de kwaliteitsfactor van de diode en $V_\\mathrm{T}$ de thermal voltage gegeven door \\begin{equation} V_\\mathrm{T} = \\frac{kT}{q}, \\end{equation} met $k$ de constante van Boltzmann, $T$ de temperatuur van het materiaal en $q$ de elementaire lading. De diodelekstroom is de stroomsterkte ten gevolge van de minderheidsladingsdragers5 \u2014 het kleine aantal vrije elektronen in p-type halfgeleider en het kleine aantal gaten in n-type halfgeleider.
De stroom door een diode ten gevolge van de spanning over de diode.
In de praktijk worden er geen twee losse halfgeleiders aan elkaar verbonden maar wordt een enkel siliciumkristal zeer selectief plaatselijk verontreinigd: de ene helft om een p-type te maken, de andere helft om een n-type te maken.\u00a0\u21a9
Engels: depletion zone.\u00a0\u21a9
Zie o.a. Giancoli6 voor een beschrijving van het veld van twee vlakke en tegengesteld geladen schijven.\u00a0\u21a9
Bedenk dat in een niet-gedoteerde halfgeleider door roostertrillingen elektron-gat-paren worden gevormd waardoor de halfgeleider een beetje geleidt. Dit gebeurt \u00f3\u00f3k in een gedoteerde halfgeleider. In n-type komen dus ook (weinig) gaten voor, en in p-type ook (weinig) elektronen. Deze kunnen de sperlaag wel oversteken en er zal dus toch een zeer kleine stroom kunnen lopen.\u00a0\u21a9
Engels: minority charge carriers. \u21a9
D.C. Giancoli. Physics for Scientists and Engineers with Modern Physics. Pearson Education, 2008. ISBN 9780131495081. URL: https://books.google.nl/books?id=xz-UEdtRmzkC.\u00a0\u21a9
W. Shockley. The theory of p-n junctions in semiconductors and p-n junction transistors. Bell System Technical Journal, 28(3):435\u2013489, 1949. URL: https://onlinelibrary.wiley.com/doi/abs/10.1002/j.1538-7305.1949.tb03645.x, arXiv:https://onlinelibrary.wiley.com/doi/pdf/10.1002/j.1538-7305.1949.tb03645.x, doi:10.1002/j.1538-7305.1949.tb03645.x.\u00a0\u21a9
Documentatie is vaak een onderschoven kindje, maar is ontzettend belangrijk. Als je zelf informatie opzoekt over bijvoorbeeld een voor jou onbekende Pythonbibliotheek dan vind je het heel fijn als er een duidelijke tutorial is. Als je code schrijft die ook door andere mensen gebruikt moet worden is documentatie nodig. Als de code langer mee moet gaan dan zeg een paar weken, dan helemaal. Want over een paar weken ben jij zelf een ander persoon. Hoe vervelend het ook is, code die je nota bene zelf geschreven hebt is over een paar weken niet meer glashelder. Je zult dan moeten uitzoeken hoe je ook alweer iets hebt gedaan of wat de gedachte erachter was.
Tot nu toe heb je waarschijnlijk gebruik gemaakt van #stukjes commentaar
om duidelijk te maken wat je code doet. Maar als je de applicatie aan het gebruiken bent en je wilt weten wat een bepaalde functie eigenlijk doet, moet je dus de code induiken op zoek naar de betreffende functie. Met docstrings \u2014 documentatiestrings \u2014 is dat verleden tijd. De documentatie over een functie kan automatisch gegenereerd worden vanuit je code met behulp van de docstring. Docstrings staat tussen 3 dubbele aanhalingstekens en hebben doorgaans een vaste structuur:1
def integers_up_to(number):\n \"\"\"List integers up to a given number.\n\n Args:\n number (int): list integers up to this number\n\n Returns:\n list: containing the integers\n \"\"\"\n if number > 1:\n return list(range(1, number))\n else:\n return []\n\n\nhelp(integers_up_to)\n
\n(ecpc) > python integers_up_to.py\nHelp on function integers_up_to in module __main__:\n\nintegers_up_to(number)\n List integers up to a given number.\n\n Args:\n number (int): list integers up to this number\n\n Returns:\n list: containing the integers\n
De eerste regel geeft een korte samenvatting weer, na de witregel komt een langere samenvatting. Met Args:
worden alle argumenten opgesomd die aan de functie worden meegegeven en Returns:
geeft aan wat de functie teruggeeft. We kunnen de documentatie van deze functie opvragen met: help(integers_up_to)
. Dat geeft resultaat zoals hierboven gegeven (druk op ).
Je zult niet altijd de help()
functie gebruiken misschien, maar gebruik zoveel mogelijk docstrings \u2014 ze helpen ook enorm als je de code leest. Het is extra werk maar het verdient zich dubbel en dwars terug. Je hoeft geen proza te schrijven, maar wees duidelijk. Lees voor meer voorbeelden bijvoorbeeld de Google Python Style Guide.3
Om het gemakkelijker te maken om docstrings ook \u00e9cht te gaan schrijven, zijn er docstring generators ontwikkeld. Voor Visual Studio Code is er de extensie autoDocstring - Python Docstring Generator.4
Autodocstring
Kijk in Visual Studio Code bij extensions hoe je AutoDocstring kunt gebruiken. Kies daarvoor in de linkerkantlijn het goede icoon voor extensions en selecteer dan de autoDocstring
extensie. Zoek in de documentatie naar hoe je automatisch (een deel van) de docstring genereert.
Wanneer we voor de functie integers_up_to()
de docstring generator gebruiken, krijgen we het volgende:
def integers_up_to(number):\n\"\"\"_summary_\n\nArgs:\n number (_type_): _description_\n\nReturns:\n _type_: _description_\n\"\"\"\nif number > 1:\n return list(range(1, number))\nelse:\n return []\n
Zo kunnen we gemakkelijk alles gaan invullen. Zo lang je niet op Esc drukt maar gewoon je tekst typt kun je met Tab naar het volgende veld en zo de docstring snel invullen. Het is mooi als je daarna onder de summary nog een uitgebreidere uitleg geeft van een paar zinnen. Vergeet ook niet om de docstring zonodig weer bij te werken als je een functie aanpast.
Pythondaq: docstring
opdrachtcodecheckAlle code van je pythondaq
applicatie zijn voorzien van docstrings. Je bent aan het werk in je model script en ziet dat er gebruik wordt gemaakt van een method get_input_voltage()
die in de controller staat. Je vraagt je ineens af wat deze method ook al weer doet. Voorheen ging je dan naar de controller en scrolde je naarget_input_voltage()
. Maar tegenwoordig heb je overal docstrings geschreven, je blijft in het model-script, houd je muis bij get_input_voltage()
en ziet daar je fantastische omschrijving van de method die in de controller staat!
Pseudo-code
# class ArduinoVISADevice\n \"\"\"Summary of class here.\n\n Longer class information...\n Longer class information...\n \"\"\"\n ...\n
Testcode: arduino_device.py if __name__ == \"__main__\":\n help(ArduinoVISADevice)\n
\n(ecpc) > python arduino_device.py\nHelp on class ArduinoVISADevice in module main:\nclass ArduinoVISADevice(builtins.object)\n| Summary of class here.\n|\n| Longer class information...\n| Longer class information...\n|\n| Data descriptors defined here:\n|\n-- More --\n
Checkpunten:
Projecttraject:
Die vaste structuur wordt niet door Python afgedwongen, maar is een goed gebruik. Er worden verschillende stijlen gebruikt. E\u00e9n van de meest gebruikte stijlen is door programmeurs van Google bedacht.3 \u21a9
Sphinx is van oudsher de standaard documentatiegenerator voor Pythonprojecten. Maar Sphinx is al redelijk op leeftijd en gebruikt als tekstformaat niet het bekende en zeer populaire Markdown maar het steeds minder populaire Restructured Text. MkDocs wordt steeds meer gebruikt en Sphinx steeds minder. Toch zul je Sphinx nog veel tegenkomen bij projecten omdat het na al die jaren zeer veel features heeft en zeer stabiel is.\u00a0\u21a9
Google. Google python style guide. URL: https://google.github.io/styleguide/pyguide.html.\u00a0\u21a9\u21a9\u21a9
Nils Werner. Autodocstring - python docstring generator. URL: https://marketplace.visualstudio.com/items?itemName=njpwerner.autodocstring.\u00a0\u21a9
Accelerators and Beam Physics Computing Group. Accelerators and beam physics computing. URL: https://abpcomputing.web.cern.ch.\u00a0\u21a9
Will McGugan and others. Textual. URL: https://textual.textualize.io.\u00a0\u21a9
Martin Breuss. Build your python project documentation with mkdocs. URL: https://realpython.com/python-project-documentation-with-mkdocs/.\u00a0\u21a9
Een bijkomend voordeel van docstrings is dat ze gebruikt kunnen worden om automatisch documentatie te genereren voor een heel project met behulp van bijvoorbeeld MkDocs of Sphinx.2 MkDocs is een documentatie generator en Material for MkDocs is daar de meestgebruikte uitbreiding op. Het wordt veel gebruikt om documentatie te schrijven voor software projecten. Een paar voorbeelden zijn bijvoorbeeld de website van de Accelerators and Beam Physics Computing groep op CERN5 of de nieuwe Textual bibliotheek6 om zogenaamde terminal user interfaces te maken, een tegenhanger van grafische interfaces. Behalve dat je vrij eenvoudig uitgebreide documentatie kunt schrijven kan MkDocs alle docstrings gebruiken om een referentie op te bouwen. De website voor de ECPC cursus is ook gebouwd met Material for MkDocs.
Het voert tijdens deze cursus te ver om veel aandacht te besteden aan MkDocs. Maar aangezien documentatie zo belangrijk is wilden we het toch noemen! Voor een uitgebreide tutorial, zie Build Your Python Project Documentation With MkDocs.7
"},{"location":"eindfeest/","title":"Eindfeest","text":"Jullie hebben tijdens deze cursus heel veel geleerd. Nu wordt het tijd om dat toe te passen op een nieuw project! Jullie worden hiervoor niet beoordeeld. Dat geeft jullie de vrijheid om in een ontspannen sfeer dingen uit te proberen. Jullie hebben al een hele structuur opgebouwd en misschien hoef je alleen maar je model en view te kopi\u00ebren en wat dingen aan te passen; misschien heb je hele wilde plannen en begin je met een nieuw project. Het mag allemaal, zolang jullie wel een plan hebben en het te maken heeft met het aansturen van een experiment vanuit Python.
Hebben jullie een eigen plan? Overleg met de staf! Als we het plan goedkeuren kunnen jullie meteen aan de slag; anders kunnen we bespreken wat er allemaal w\u00e9l kan. We hebben een paar projecten ter suggestie. De meeste zijn behoorlijk open en we geven geen garantie dat je het werkend krijgt in twee dagdelen, maar dat kan ook juist een leuke uitdaging zijn! De meest 'veilige' optie die het meest lijkt op het experiment dat we tot nu toe gedaan hebben staat onderaan (de zonnecel).
"},{"location":"eindfeest/#michelson-interferometer","title":"Michelson interferometer","text":"E\u00e9n van de experimenten uit het eerste jaar is de Michelsoninterferometer. We hebben die in het klein als bouwpakket aangeschaft bij het Nikhef. Hij moet nog in elkaar worden gezet (niet heel moeilijk) maar het zou leuk zijn als de metingen (kies je eigen onderzoeksvraag!) geautomatiseerd kunnen worden. Daarvoor moet dan nog wel de opstelling uitgedacht worden en zijn er ongetwijfeld onderdelen nodig (motortje, sensor, ...?). Dus als dit je leuk lijkt, begin dan snel met idee\u00ebn bedenken zodat we voor het eindfeest onderdelen kunnen bestellen.
"},{"location":"eindfeest/#morse-code-communicatie","title":"Morse code communicatie","text":"Type op je computer een tekst, druk op 'verzend', en zie de tekst verschijnen bij je buurmens. We kennen dit allemaal van e-mail, Discord, Whatsapp, etc. Dat kan via communicatie over het internet, maar het kan ook met Morse. Schrijf een chat-applicatie waarmee je tekst kunt verzenden van de ene computer en kunt ontvangen op de andere computer. Gebruik daarvoor een LED om te verzenden en een light-dependent resistor (LDR) om te ontvangen. Hiervoor moet je nog een eenvoudige schakeling bouwen. Als het werkt kun je het ook uitbreiden naar bidirectionele communicatie: heen en weer chatten.
"},{"location":"eindfeest/#ocean-optics","title":"Ocean Optics","text":"Tijdens het eerste jaar heb je misschien het spectroscopie-experiment uitgevoerd. Hierbij heb je een spectroscoop gebouwd om gasontladingslampen te onderzoeken. Handmatig heb je spectraallijnen opgezocht, hoeken genoteerd en omgerekend naar golflengtes. Dat was een tijdrovende klus. Het bedrijf Ocean Optics brengt digitale spectroscopen op de markt. Je richt die op een lichtbron en krijgt een dataset van de lichtsterkte bij verschillende golflengtes. De modellen die wij hebben worden niet meer ondersteund en er is geen software meer voor beschikbaar. Maar: we hebben wel documentatie gevonden over hoe we ze aan kunnen sturen. Je kunt dus zelf een applicatie schrijven om een spectrum te meten! Binnen een seconde heb je dan een volledig spectrum gemeten; heel anders dan vorig jaar!
"},{"location":"eindfeest/#functiegenerator-oscilloscoop","title":"Functiegenerator / Oscilloscoop","text":"Functiegeneratoren en oscilloscopen behoren tot de standaarduitrusting van een natuurkundig laboratorium. Dat je ze met de hand kunt bedienen kan heel fijn zijn om snel dingen uit te proberen, maar is lastiger als je uitgebreidere experimenten wilt uitvoeren die deels met de computer bediend worden. Daarom zijn veel modellen met de computer aan te sturen en uit te lezen. Ontwikkel daarvoor een applicatie naar keuze.
"},{"location":"eindfeest/#picoscope","title":"PicoScope","text":"Omdat oscilloscopen nog vaak gebruikt worden voor experimenten komen er ook steeds meer modellen op de markt die je alleen met de computer aan kunt sturen. We gebruiken de PicoScope 5000 Series in ons eigen lab bijvoorbeeld voor het uitlezen van de scintillatordetectoren voor deeltjesdetectie. In hun eentje vervangen die een batterij aan oude apparatuur. De manier van aansturen gaat wel wat anders dan bij de Arduino. Als je dat leuk vindt kun je zelf een interface maken om de PicoScope aan te sturen en uit te lezen. Je kunt hiervoor een functiegenerator gebruiken om het signaal aan te leveren, maar als je wilt mag je bij voldoende toezicht ook op de zaal met radioactieve bronnen werken.
"},{"location":"eindfeest/#arduino-nano-33-iot","title":"Arduino Nano 33 IoT","text":"De Arduino die wij gebruiken heeft ook nog ingebouwde sensoren. De leukste is een versnellingsmeter / gyroscoop. Hiermee kun je de snelheid van rotaties meten of de stand van de Arduino ten opzichte van de lokale gravitatierichting. Door de firmware aan te passen met nieuwe firmwarecommando's kun je die ook uitlezen met een Pythonapplicatie. Je zou de stand van de Arduino kunnen gebruiken als een soort muiscursor of een joystick. E\u00e9n student heeft een keer een 3D-model op het scherm getoond die meedraaide met de echte Arduino.
"},{"location":"eindfeest/#de-iu-karakteristiek-van-een-zonnecel","title":"De $I,U$-karakteristiek van een zonnecel","text":"In het eerste jaar bepalen natuurkundestudenten een $I,U$-curve van een zonnepanneel. Zij vari\u00ebren met de hand de weerstand en lezen de stroom en spanning af. Wij gaan het experiment automatiseren zodat met \u00e9\u00e9n druk op de knop een $I,U$-curve getoond wordt.
Deze opdracht is vergelijkbaar met wat we tot nu toe hebben gedaan. We gaan wederom een $I,U$-curve bepalen, maar niet van een diode maar van een zonnepaneel. Een zonnepaneel gedraagt zich \u2014 afhankelijk van de belastingsweerstand van het circuit \u2014 soms als een spanningsbron en soms als een stroombron. Het geleverde vermogen is ook zeer afhankelijk van deze belasting. Voor de werking van de zonnecel en een beschrijving van de $I,U$- en $P,R$-curves, zie hoofdstuk Zonnecel.
"},{"location":"eindfeest/#de-schakeling","title":"De schakeling","text":"In de figuur hieronder is de equivalente schakeling die we gaan bouwen weergegeven.
We gebruiken een variabele weerstand $R_\\text{var}$ om de belasting van het zonnepaneel te vari\u00ebren. Deze is in serie geschakeld met een stroomsterktemeter om de stroomsterkte door het circuit te meten. Parallel is een spanningsmeter geschakeld waarmee we de spanning die geleverd wordt door het zonnepaneel kunnen meten.
Merk op dat onze Arduino geen stroomsterktemeter heeft. We zullen dus de spanning over een kleine weerstand moeten meten om zo \u2014 met behulp van de wet van Ohm \u2014 de stroomsterkte te bepalen. Een ander probleem is dat de spanning die geleverd wordt door het zonnepaneel groter kan zijn dan de 3.3 V die maximaal op de pinnen mag staan. Hiervoor gaan we gebruik maken van een 3:1 spanningsdeler zodat de spanning altijd onder de 3.3 V zal blijven \u2014 volgens de specificaties komt de maximale spanning van het zonnepaneel in de meest ideale omstandigheden uit op 10 V.3 Het laatste probleem is de variabele weerstand: er zijn variabele weerstanden te koop waarbij de weerstand zeer nauwkeurig kan worden gekozen. Helaas is de minimale weerstand, ten gevolge van de vrij ingewikkelde interne schakelingen, te groot om de maximale stroom van een zonnepaneel te meten. Daarom maken we gebruik van een type veldeffect transistor, de MOSFET. Een MOSFET is feitelijk een soort schakelaar. Afhankelijk van de spanning die op de gate gezet wordt, is de weerstand tussen de source (aarde, minpool) en de drain (pluspool)1 te vari\u00ebren tussen nul en oneindig. Er is maar een relatief klein gebied waarin de weerstand snel verandert van oneindig naar nul.
De schakeling voor onze Arduino is weergegeven in de figuur hieronder. Hier belasten we het zonnepaneel met een MOSFET. In serie hiermee staat een kleine weerstand van 4.7 \u03a9 waarover we de spanning meten ten behoeve van de bepaling van de stroomsterkte. De pin van de Arduino die verbonden is met de gate van de MOSFET is beschermd met een weerstand van 1 k\u03a9. Dit is belangrijk, want wanneer er een spanning gezet wordt op de gate kan er kortdurend een vrij grote stroom lopen. De gate gedraagt zich als een kleine capaciteit. Parallel aan de MOSFET + weerstand is een 3:1 spanningsdeler geschakeld met weerstanden van 2 M\u03a9 en 1 M\u03a9.
In de 3D-model2 hieronder is een Arduino Nano 33 IoT op een 400-punt breadboard geschakeld. Aan de linkerkant van het breadboard is de serieschakeling van de MOSFET met de kleine weerstand geplaatst. De pinnen van de MOSFET zijn van boven naar beneden de gate, de drain (+) en de source (-). De rechterkant bevat de spanningsdeler. Het zonnepaneel zelf wordt aan de $+$ en $-$ power rails aan de rechterkant van het bord geschakeld.
De namen source en drain verwijzen hier naar de elektronenstroom. Elektronen worden geleverd door de source (aard, minpool) en stromen dan naar de drain (pluspool).\u00a0\u21a9
Dit model bevat twee 3D modellen die zijn gecre\u00eberd door Lara Sophie Sch\u00fctt en AppliedSBC en zijn gedeeld onder respectievelijk een CC-BY en CC-BY-SA licentie. De originele modellen zijn te vinden via [CC0] Set of Electronic Components en Arduino Nano 33 IoT. De modellen zijn samengevoegd en Voorzien van een Arduino texture, mosfet, zonnecel en draden. Dit 3D model heeft een CC-BY-SA licentie.\u00a0\u21a9
Seeed Studio. Small solar panel 55x70mm 0.5w. URL: https://www.seeedstudio.com/0-5W-Solar-Panel-55x70.html.\u00a0\u21a9
For full documentation visit mkdocs.org.
"},{"location":"faq/#commands","title":"Commands","text":"mkdocs new [dir-name]
- Create a new project.mkdocs serve
- Start the live-reloading docs server.mkdocs build
- Build the documentation site.mkdocs -h
- Print help message and exit.mkdocs.yml # The configuration file.\ndocs/\n index.md # The documentation homepage.\n ... # Other markdown pages, images and other files.\n
"},{"location":"faq/#ideeen","title":"Idee\u00ebn","text":"Schakeling bouwen Als je geen kant-en-klare schakeling bij je werkplek hebt liggen, druk de Arduino in het breadboard en bouw een schakeling met een LED op de manier die is weergegeven in fig:arduino-LED-breadboard. De weerstand heeft een waarde van 220 \u03a9. De LED heeft aan \u00e9\u00e9n zijde een platte kant in de dikkere ring onderaan de plastic behuizing (goed kijken!); schakel die aan de kant van de aarde. Als de pootjes van de LED niet afgeknipt zijn, dan zit het korte pootje aan de platte zijde van de LED. Het heeft geen zin om naar het plaatje te kijken hoe het er \u00edn de LED uitziet \u2014 dat verschilt per type LED.
Bestand: docs/index.md
en ook pythondaq/models/diode.py
. Die vind je1 ook in de repository davidfokkema/tailor
. Folder: oefenopdrachten
.
Eenheden: 220 \u03a9 m/s of ms-1 of $220\\,ms^{-1}$ en $220\\,\\Omega$. We doen het eerste!!
Voor menu's gaan we het zo doen: Menu > Code > Add repository en voor toetsen Ctrl+F.
Een referentie naar een opdracht of figuur maak je aan door <div id=\"label\"></div>
blokje als label neer te zetten. Verwijzen gaat dan met [opdracht _label_](bronbestand.md#label)
waarbij je dus ook het bestand moet weten waarin het label gedefinieerd wordt.
Voor vergelijkingen: \\begin{equation} f(x) \\sin x, \\end{equation} met $f(x)$ een functie van $x$.
Voor code: hier een print
-statement, maar meer code met: Titel
print(\"Hello, world!\")\n
oefenopdracht
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa
Inlever opdracht
Deze opdracht lever je in.
Meer leren
Met deze opdracht kan je meer leren
en dit is dus een voetnoot\u00a0\u21a9
Waarschuwing
Let op dat dit ook kan.
Info
Of niet.
En zo verder.
"},{"location":"firmware/","title":"Firmware","text":""},{"location":"firmware/#firmware","title":"Firmware","text":"De firmware bestaat uit een gedeeltelijke implementatie van het VISA-protocol.1 Het voornaamste verschil bestaat uit het feit dat VISA voor ieder commando zowel een korte als een lange versie heeft. Zo zou je in de documentatie van een instrument het commando MEASure
kunnen vinden. Je kunt dan zowel MEAS
als MEASURE
gebruiken om het commando te geven. In deze implementatie is het slechts mogelijk om de korte vorm te gebruiken.
De nummering van de kanalen volgt de nummering van de Arduino hardware. Dus kanaal 0 is pin A0 op de Arduino, kanaal 1 is pin A1, enz. De digitale resolutie is ingesteld op 10 bits ($2^{10}$~stappen, ofwel waardes tussen 0 en 1023) en het analoge bereik is 0 V tot 3.3 V.
Bij het versturen van opdrachten naar het apparaat, moet je afsluiten met een linefeed ('\\n'). Het apparaat sluit zijn antwoorden af met een carriage return gevolgd door een linefeed ('\\r\\n').
De code is terug te vinden in de repository /davidfokkema/arduino-visa-firmware
.2 Deze documentatie is voor versie~1.0.0. De commando's die geaccepteerd worden door de firmware zijn weergegeven in de tabel hieronder.
*IDN?
Geeft de identificatiestring van de firmware. OUT:CH<ch> <value>
Zet een specifieke spanning <value>
op uitvoerkanaal <ch>
. Waardes mogen liggen tussen 0 (minimale spanning) en 1023 (maximale spanning). Voorbeeld: OUT:CH0 1023
OUT:CH<ch>?
Geef de huidige instelling voor de spanning terug op uitvoerkanaal <ch>
in het bereik 0 tot 1023. Voorbeeld: OUT:CH0?
MEAS:CH<ch>?
Meet de spanning op invoerkanaal <ch>
in het bereik 0 tot 1023. Voorbeeld: MEAS:CH1?
IVI Foundation. Vpp-4.3: the visa library. 2018. URL: https://www.ivifoundation.org/downloads/Architecture Specifications/IVIspecstopost10-22-2018/vpp43_2018-10-19.pdf.\u00a0\u21a9
David B.R.A. Fokkema. Arduino visa firmware. 2020. URL: https://github.com/davidfokkema/arduino-visa-firmware.\u00a0\u21a9
Zodra je scripts wat ingewikkelder worden begin je tegen hele praktische problemen aan te lopen. Het werkt nu, maar je wilt een flinke aanpassing gaan doen. Werkt het straks nog wel? Hoe ingewikkelder het script, hoe ingewikkelder de wijzigingen en hoe minder het vertrouwen dat het in \u00e9\u00e9n keer gaat lukken. Misschien heb je wel eens de ervaring gehad dat het maar niet wil werken en dat je niet goed weet wat je precies had veranderd ten opzichte van toen het nog wel werkte. Veel mensen hebben de neiging om naast een script.py
een script-v1.py
, script-v2.py
, enz. aan te maken. Soms zelfs een script-eindversie.py
en met wat pech dan toch nog een script-eindversie-definitief.py
. Niet heel fijn. Je ziet dan nog steeds niet goed wat er veranderd is (dat blijft naast elkaar leggen en zoeken) en je map loopt vol met overbodige scripts. Dit kan beter\u2026 met versiebeheer!
Versiebeheer (Engels: version control) stelt je in staat om af en toe een momentopname te maken van al je bestanden in een bepaalde map, inclusief alle submappen. Dit doe je niet na iedere regel code, maar bijvoorbeeld wel als je een stukje code af hebt en getest hebt dat het werkt. Zo'n momentopname heet een commit. Hoe vaak je commit is aan jou; maar wacht niet te lang \u2014 dan is het geen versiebeheer meer.
Je versiebeheersysteem geeft ondertussen duidelijk al je wijzigingen weer ten opzichte van de laatste commit. Ook kun je de wijzigingen tussen oudere versies bekijken. Alles is relatief: je kunt zien wat er veranderd is tussen twee weken terug en gisteren, of gisteren en vandaag; iedere commit kun je vergelijken met willekeurig iedere andere commit. Heb je iets verprutst en wil je een oude versie terughalen? Prima! Commit die ook, dan kun je zelfs dat weer terugdraaien later. Je verliest nooit meer je werk. En stukmaken mag!1
"},{"location":"github/#git","title":"Git","text":"Ruim tien jaar geleden werden er nog vele concurrerende systemen gebruikt. Die tijd is grotendeels voorbij. E\u00e9n van de nieuwste systemen, Git,2 wordt tegenwoordig door bijna iedereen gebruikt of ondersteund. Git is ontwikkeld door Linus Torvalds als alternatief voor het commerci\u00eble systeem dat gebruikt werd voor de ontwikkeling van de Linux kernel.6 Het begon als een zeer eenvoudig \u2014 en volkomen ongebruiksvriendelijk \u2014 programma. Later is het in een veel gebruiksvriendelijker jasje gestoken.
Git werkt in principe via de command-line. Je geeft opdrachten in de map waar je broncode staat: toevoegen van wijzigingen aan de staging area, bekijken van de meest recente wijzigingen, committen van je code, teruggaan en werken met oudere versies, aanmaken van branches,3 je wijzigingen uploaden naar internet, enz. Het geheel van map met broncode en versiegeschiedenis wordt een repository genoemd.
In deze cursus zullen we gebruik maken van een grafische applicatie die eenvoudiger werkt. Je kunt daarna \u2014 als je dat wilt \u2014 de stap maken naar de command-line waarmee je nog veel meer mogelijkheden tot je beschikking krijgt. Voor meer informatie over Git en het gebruik via de command-line, zie het boek Pro Git.7
"},{"location":"github/#github","title":"GitHub","text":"Git is een distributed version control system (DVCS) wat wil zeggen dat er geen centrale server hoeft te zijn. Je kunt volledig offline werken in je eigen repository en je wijzigingen af en toe committen. Als je daar zin in hebt kun je je wijzigingen naar een collega sturen (pushen) of je kunt een collega toestemming geven om de wijzigingen op te halen (pullen). Je bouwt dan aan \u00e9\u00e9n grote versiegeschiedenis met kopie\u00ebn op meerdere computers. Je bent zo volledig onafhankelijk van bedrijven die servers in de lucht houden of bepalen wie er wel en niet toegang krijgt. Dat is fijn, maar een centrale plek om repositories neer te zetten heeft weer het grote voordeel dat je de wereld kunt laten zien wat voor moois je gemaakt hebt \u00e9n dat het samenwerking wel vermakkelijkt. Als iedereen uit je team regelmatig commits pusht naar een centrale server en daar vandaan ook ophaalt dan is iedereen altijd up-to-date.
Er zijn tegenwoordig veel websites die een plek bieden voor Git repositories. De bekendste zijn GitHub, GitLab, Bitbucket en SourceForge. GitHub, aangekocht door Microsoft, is op dit moment het bekendste en grootste platform. Veel bekende softwareprojecten vinden daar hun thuis.
Wij gaan werken met GitHub, je moet dan wel een (gratis) account aanmaken. Als student kom je ook nog in aanmerking voor een educatiekorting op een pro-account. Je betaalt dan nog steeds niets.
Account aanmaken
Ga naar https://github.com/ en klik rechtsboven op Sign Up
. Maak een account aan onder je priv\u00e9-emailadres. Op deze manier blijf je toegang houden tot je account ook nadat je afgestudeerd bent.
Om het programmeurs makkelijker te maken met GitHub te werken heeft GitHub een desktop applicatie ontwikkeld met de naam GitHub Desktop
. We gaan GitHub Desktop gebruiken om een repository te maken van een map met oefenopdrachten.
Van bestaande map repository maken
opdrachtcheck Je gaat een repository maken van een bestaande map. Als je van de ECPC
een repository maakt, kun je daar geen andere repositories inzetten. Dus maak je in de map ECPC
een nieuwe map oefenopdrachten
aan en zet daarin alle Python-bestandjes die je hebt gemaakt om te oefenen zoals de opdrachten Pyvisa in pythonscript en KnipperLED. Je gaat naar GitHub Desktop en logt in met je eigen account. Je ziet bij File drie opties staan, New
, Add local
en Clone
repository. Hoewel New repository
een goede optie lijkt, wordt daarmee een nieuwe map aangemaakt en dat is niet nodig. Dus kies je voor Add local
repository. Je geeft de map oefenopdrachten
op als locatie en krijgt in rode tekst een waarschuwing. De waarschuwing geeft aan dat de map wel bestaat maar dat het geen Git repository
is, daarom klik je op de blauwe tekst create a repository
. Je vinkt Initialize this repository with a README
aan en kiest bij Git ignore
voor Python. Je bevestigt dan met de blauwe knop Create Repository
. Vanaf nu duiden we een repository aan met het -symbool. De repository oefenopdrachten
is in GitHub Desktop geopend en als je op het tabblad 'History' klikt dan zie je dat er een Initial commit
is met wat git
-bestanden en de Pythonscripts die je in de map hebt gezet. Vanaf nu staat oefenopdrachten
in versiebeheer en houdt Git je wijzigingen bij, maar je moet wel zelf committen! ECPC
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 test_arduino.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 flashingLED.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022
Git ignore Python
Waarom zou je bij Git ignore voor Python kiezen, we gaan toch juist Python bestanden maken? De Git Ignore zorgt ervoor dat allerlei hulpbestanden van Python niet bewaard worden als commit. Maar de Python code wordt wel bewaard.
Checkpunten:
oefenopdrachten
zit in de map ECPC
.oefenopdrachten
is een bestand README.md
oefenopdrachten
is een bestand .gitattributes
.oefenopdrachten
is een bestand .gitignore
.Projecttraject
Alle wijzigingen aan bestanden in de repository kun je vanaf nu bijhouden door regelmatig een commit te maken. Met een commit maak je als het ware een snapshot van alle bestanden en hang je daar een labeltje aan. Dit kan in GitHub Desktop, maar ook direct vanuit Visual Studio Code. Elke commit geef je een begeleidend schrijven mee. Je hoopt dat jij, maar ook je collega, na het lezen van het berichtje snel begrijpt wat er veranderd is \u00e9n waarom. Wanneer je bepaalde wijzigingen ongedaan wilt maken, kan je snel vinden bij welke commit je dan moet zijn. En als je dan je applicatie gaat uitbrengen op Github kun je de commit messages gebruiken om snel op te sommen wat de nieuwste versie van jou app kan!
Commit
GitHub DesktopVisual Studio CodecheckVoer de volgende opdrachten uit:
Source Control
die laat weten dat er wijzigingen zijn ten opzichte van de vorige commit. Klik op Source Control
.Changes
staat een lijst met bestanden waar wijzigingen in aan zijn gebracht. Kies welke bestanden je wilt committen door rechts op het +je te klikken. Deze bestanden komen nu op het podium te staan onder Staged Changes
. Je kunt ook alle bestanden in een keer op het podium zetten door naast het kopje Changes
op het +je te klikken.Geen blauw bolletje
Zie je geen bolletje verschijnen? Kijk of je het bestand zeker weten hebt opgeslagen. Nog steeds geen blauw bolletje? Ga naar GitHub Dekstop en ga verder met stap 5.
Checkpunten:
History
tabblad een nieuw commit message.Projecttraject
In GitHub Desktop zie je nu bij history de commit staan, met in een oogopslag alle wijzigingen.
Info
Als je wilt opzoeken hoe iets werkt bij GitHub Desktop, kijk dan in de documentatie: https://docs.github.com/en/desktop.
Hieronder zie je een aantal voorbeelden van commit messages. De titels zijn kort en krachtig, in de description staat specifieke en uitgebreidere informatie.
Enabled initialization of the measurement instrument and calibration of sensors for accurate data collection. Commit to main Resolved calibration errors caused by incorrect sensor configurations. Commit to main Upgraded matplotlib and click to the latest versions to enable error bar functionality. Commit to main Fetched all measurements prior to analysis to enhance performance and efficiency. Commit to main Updated README to include information about the new plot button and save feature. Commit to main Commit to mainPush en pull
opdrachtcheckDe repository die je in opdracht Repository toevoegen hebt aangemaakt bestaat alleen nog maar op de computer. Als de computerkabouters 's nachts langskomen kan het zijn dat de computer daarna is gewist en je alles kwijt bent. Daarom is het fijn om de repository ook in de cloud te hebben op GitHub.com. In GitHub desktop is een knop Publish repository; Publish this repository to GitHub
, als je daar op drukt kun je nog een andere naam aan de repository geven (dat bepaalt de url op github.com), een description en of de code priv\u00e9 moet zijn. Daarna klik je op de blauwe knop Publish repository
. Als je nu naar GitHub.com gaat zie je bij jouw repositories de zojuist gepubliceerde repository staan.
Om je wijzigen ook in de cloud op te slaan kun je commits pushen
naar Github.com met de knop Push origin
. Als je op een andere computer gaat werken kun je de repository vanuit de cloud naar de computer halen door op Fetch origin
te klikken en daarna op Pull origin
.
Checkpunten:
Projecttraject
Om makkelijk je Git repository te delen met vrienden, collega's en de rest van de wereld kan je er voor kiezen om deze op GitHub te zetten. Je kunt dan je commits pushen naar GitHub en wijzigingen die je vrienden hebben gemaakt pullen zodat jij er ook weer aan verder kan werken. Van alle repositories die op GitHub staan \u00e9n openbaar zijn kun je de broncode clonen en zelf mee aan de slag! Laten we eens een kijkje nemen op GitHub.
Tailor
Als je nog nooit op GitHub bent geweest dan kunnen de pagina's nogal intimiderend overkomen. De informatiedichtheid is nogal hoog. Na een paar bezoeken weet je meestal wel waar je dingen kunt vinden. David heeft een data-analyse app geschreven dat Tailor heet en dat gebruikt wordt bij natuurkundepractica voor studenten medische natuurwetenschappen (MNW) en science, business and innovation (SBI). Laten we eens kijken wat daar allemaal opstaat.
/davidfokkema/tailor
op github.com op.About
. Een uitgebreidere beschrijving vind je als je naar beneden scrollt onder Readme
.code
is de hoofdpagina met de mappenstructuur. Navigeer door de mappen, wat staat er op regel 14 van plot_tab.py
?code
. Hoeveel commits zijn er gemaakt? Klik op commits en daarna op een commit-message. Hoeveel regels zijn er weggehaald of bijgekomen?pyproject.toml
en klik rechtsboven op History
. Wat is er aangepast in pyproject.toml
bij de commit Release v2.0.0? Je ziet ook welke bestanden nog meer zijn gewijzigd in deze commit, welk bestand is nog meer gewijzigd bij Release v2.0.0?Issues
. Hoeveel bugs zijn er gerapporteerd? En hoeveel enhancements?Pull requests
, klik op Closed
en bekijk welke pull requests zijn ge\u00efmplementeerd.Insights
geeft je, tegen alle verwachtingen in, inzicht. Je kan zien door hoeveel mensen er aan gewerkt wordt. Kijk bij Code frequency
, in welke periode is er het meest aan de code veranderd?code
knop. Met die knop kan je de repository als zip-bestand downloaden of openen met GitHub desktop.Clone de LMfit-py repository op GitHub:
lmfit/lmfit-py
)examples
README.txt
. Verander in de eerste paragraaf Below are examples
in Below are different examples
en sla het bestand op.Commit
-knop.History
. Bovenaan staat jouw wijziging. Daaronder kun je alle wijzigingen van anderen bekijken.Aangezien je geen schrijfrechten hebt voor LMfit kun je niet kiezen voor Push origin
\u2014 de knop die rechtsboven verschijnt. Met die knop duw je je wijzigingen naar GitHub zodat iedereen ze kan zien. Dat is mooi, maar je mag niet zomaar de repository van iemand anders wijzigen.
Tot nu toe heb je Visual Studio Code of GitHub Desktop gebruikt om te committen. Maar je kan Git ook bedienen via de terminal. De mogelijkheden van Git zijn in de terminal ook veel groter dan in de grafische applicaties die we gebruikt hebben.
git log
. Scroll door de commit messages met spatie.Stukmaken mag, maar het terughalen van een oude versie is niet met een druk op de knop gebeurt. Vraag om hulp als je terug wilt naar een oude versie, wij helpen je graag!\u00a0\u21a9
https://initialcommit.com/blog/How-Did-Git-Get-Its-Name\u00a0\u21a9
Een branch is een splitsing in je versiegeschiedenis. Je kunt het gebruiken om over een langere tijd een grote wijziging uit te testen terwijl je af en toe heen en weer springt tussen je main branch en de nieuwe branch. Commits in de nieuwe branch blijven gescheiden. Later kun je ervoor kiezen om de wijzigingen in de nieuwe branch te mergen met je main branch, maar dat hoeft niet.\u00a0\u21a9
Je kunt je commit message opdelen in een titel (of summary) en een beschrijving. Dit doe je dit door een witregel toe te voegen tussen de titel en de beschrijving.\u00a0\u21a9
Als je vergeten bent waar je de repository ook alweer bewaard had kun je met Repository > Show in Finder de folder openen.\u00a0\u21a9
Linus Torvalds and others. Git. 2005. URL: https://git-scm.com.\u00a0\u21a9
Scott Chacon and Ben Straub. Pro git: Everything you need to know about Git. Apress, second edition, 2014. URL: https://git-scm.com/book/en/v2.\u00a0\u21a9
Soms wil je je code flink onder handen nemen of iets heel nieuws eraan toevoegen. Terwijl je bezig bent ga je natuurlijk eerst van alles stuk maken voordat je het weer werkend hebt gekregen. Maar ondertussen kan je oude functionaliteit van je code niet gebruiken. Of je bent samen met een vriend aan een package bezig en om de haverklap werkt jouw stukje code niet meer omdat ergens anders de code verbouwd wordt. Dan is het handig dat je vanaf het punt dat je code werkt een zijweg kan inslaan. Daarom zijn branches uitgevonden. Je kunt vanuit Github Desktop, vanuit Visual Studio Code en natuurlijk via de terminal een branch aanmaken.
Branches
Als je een grafische applicatie schrijft roep je functies aan van het besturingssysteem om vensters, knoppen, menu's e.d. te laten tekenen en te reageren op muisklikken en het toetsenbord. Het lastige daaraan is dat een applicatie voor MacOS heel anders geschreven moet worden dan \u00e9\u00e9n voor Linux of Windows. Om die reden zijn er verschillende cross-platform bibliotheken ontwikkeld die als het ware tussen het besturingssysteem en je applicatie komen te staan. Je kunt dezelfde applicatie maken voor alle besturingssystemen en de bibliotheek kiest welke functies aangeroepen moeten worden om een venster te tekenen. Het voordeel is duidelijk: je hoeft maar \u00e9\u00e9n applicatie te schrijven die overal werkt. Het nadeel is dat je niet \u00e9cht gebruik kunt maken van alle functies en opties die het besturingssysteem biedt. Hier kiezen we voor de voordelen en gaan we gebruik maken van misschien wel de meest populaire optie: Qt.1 De bibliotheek PySide6
is de offici\u00eble Pythonbibliotheek.
Info
Maak voor de oefeningen een nieuw conda environment test-qt
met: Terminal
conda create --name test-qt python=3.12\nconda activate test-qt\npip install pyside6 pyqtgraph\n
Selecteer het nieuwe test-qt
conda environment in Visual Studio Code en sluit alle oude terminals met het -icoon.2 Een minimale Qt-applicatie ziet er als volgt uit:
import sys\n\nfrom PySide6 import QtWidgets\n\n\nclass UserInterface(QtWidgets.QMainWindow):\n pass\n\n\ndef main():\n app = QtWidgets.QApplication(sys.argv)\n ui = UserInterface()\n ui.show()\n sys.exit(app.exec())\n\n\nif __name__ == \"__main__\":\n main() \n
Eerst importeren we een paar bibliotheken. Het draait uiteindelijk om de UserInterface
class. De naam mag je zelf kiezen, zolang je maar aangeeft dat de class een afgeleide is van QtWidgets.QMainWindow
, het hoofdvenster van je applicatie. In het hoofdgedeelte van het programma (gedefinieerd in de functie main()
) maak je eerst een instance van QtWidgets.QApplication
.3 Ook maken we een instance van onze eigen class en we roepen de show()
method aan. Die hebben we niet zelf geprogrammeerd; die zit in de parent class QMainWindow
. Als laatste roepen we de exec()
method aan van onze QApplication
en de uitvoer daarvan (een exit code) geven we mee aan de functie sys.exit()
. Dat betekent dat als het programma afsluit met een foutmelding, dat een foutcode wordt meegegeven aan het besturingssysteem. Iemand anders die een script schrijft kan die code afvangen en daar iets mee doen. Een aantal elementen uit dit programma (sys.argv
, sys.exit()
) zijn strikt genomen niet noodzakelijk, maar wel good practice. Ook het schrijven van een main()
functie is niet strikt noodzakelijk, maar het maakt het wel makkelijk om straks een zogeheten entry point te hebben als we weer een applicatie willen schrijven. In de pyproject.toml
geven we dan aan dat we de main()
functie willen aanroepen. Dat komt later.
Minimale GUI
opdrachtcodecheck Je gaat de gegeven Python code voor een een minimale GUI testen. In de map ECPC
maak je een example-gui.py
aan en zet daarin de Python code. Je activeert de test-qt
conda environment en runt het bestand example-gui.py
. Er verschijnt een leeg venster in beeld met als venstertitel python
en drie knoppen. Een streepje (minimize), een vierkant (maximize) en een kruis (close). Je drukt op het kruisje en het venster sluit. ECPC
\u251c\u2500\u2500 pythondaq
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 example-gui.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code
import sys\n\nfrom PySide6 import QtWidgets\n\n# create subclass of QtWidgets.QMainWindow\n\ndef main():\n # create instance of QtWidgets.QApplication with arguments from sys.argv\n # create instance of subclass\n # call show method of subclass\n # get exit code with exec method of QApplication instance and give exit code to sys.exit()\n\n# when run this script:\n # run main function \n
Checkpunten:
Projecttraject:
Elke keer als je een nieuwe Qt applicatie gaat schrijven kun je bovenstaand stukje code copy/pasten. Als we dit programma draaien hebben we echter een klein leeg venster op het scherm, zonder elementen. Die elementen kunnen we op twee manieren toevoegen: door ze te programmeren of door het gebruik van een visueel ontwerp met Qt Designer. Beide zullen in de volgende secties toegelicht worden.
"},{"location":"gui/#de-interface-programmeren","title":"De interface programmeren","text":"We gaan de eenvoudige interface programmeren die hieronder is weergegeven:
We doen dat door de class UserInterface
uit te breiden met widgets uit de QtWidgets
bibliotheek.
Het defini\u00ebren van layouts gebeurt in veruit de meeste opmaaksystemen met rechthoeken (Engels: boxes) die op verschillende manieren gestapeld worden \u2014 naast elkaar, boven elkaar, of op een rechthoekig grid bijvoorbeeld. Zulke systemen zijn ook hi\u00ebrarchisch: je stopt boxes in andere boxes.
De layout van bovenstaande screenshot is als volgt opgebouwd. Het hoofdelement van de grafische interface is de central widget
:
De central widget
krijgt een verticale layout die we vbox
noemen:
In de verticale layout plaatsen we een textbox
en een horizontale layout die we hbox
noemen:
In de horizontale layout plaatsen we twee button
s:
Het stuk programma om bovenstaande layout op te bouwen geven we hieronder weer. We bespreken straks de code regel voor regel.
from PySide6.QtCore import Slot\n\n\nclass UserInterface(QtWidgets.QMainWindow):\n def __init__(self):\n # roep de __init__() aan van de parent class\n super().__init__()\n\n # elk QMainWindow moet een central widget hebben\n # hierbinnen maak je een layout en hang je andere widgets\n central_widget = QtWidgets.QWidget()\n self.setCentralWidget(central_widget)\n\n # voeg geneste layouts en widgets toe\n vbox = QtWidgets.QVBoxLayout(central_widget)\n self.textedit = QtWidgets.QTextEdit()\n vbox.addWidget(self.textedit)\n hbox = QtWidgets.QHBoxLayout()\n vbox.addLayout(hbox)\n\n clear_button = QtWidgets.QPushButton(\"Clear\")\n hbox.addWidget(clear_button)\n add_text_button = QtWidgets.QPushButton(\"Add text\")\n hbox.addWidget(add_text_button)\n\n # Slots and signals\n clear_button.clicked.connect(self.textedit.clear)\n add_text_button.clicked.connect(self.add_text_button_clicked)\n\n @Slot()\n def add_text_button_clicked(self):\n self.textedit.append(\"You clicked me.\")\n
Allereerst defini\u00ebren we een __init__()
. Helaas gaat dat niet zomaar. We schrijven namelijk niet helemaal zelf een nieuwe class (class UserInterface
), maar breiden de QMainWindow
-class uit (class UserInterface(QtWidgets.QMainWindow)
). Door dat te doen zijn er heel veel methods al voor ons gedefinieerd. Daar hoeven we verder niet over na te denken, onze interface werkt gewoon. Het gaat mis als wij zelf nieuwe methods gaan schrijven die dezelfde naam hebben. Stel dat de parent class QMainWindow
een method click_this_button()
heeft. Als onze class ook een method click_this_button()
heeft, dan zal die worden aangeroepen in plaats van de method uit de parent class. Dat is handig als je de parent method wilt vervangen maar niet zo handig als je de parent method wilt aanvullen, zoals nodig is bij __init__()
. Immers, we willen onze eigen class initialiseren, maar we willen ook dat de parent class volledig wordt ge\u00efnitialiseerd. De oplossing is gelukkig vrij eenvoudig: we kunnen de __init__()
van de parent class gewoon aanroepen en daarna ons eigen ding doen. De Pythonfunctie super()
verwijst altijd naar de parent class, dus met super().__init__()
wordt de parent class volledig ge\u00efnitialiseerd. Dat is dus het eerste dat we doen in regel 10. Kijk voor meer informatie over super().__init__()
in de paragraaf subclasses.
In de volgende opdrachten ga je zelf de hele applicatie opbouwen, zodat je precies weet wat in de code hierboven staat.
Parent class initialiseren
opdrachtcodecheckJe hebt geleerd hoe je widgets aan de applicatie kunt toevoegen. Omdat het veel stappen in een keer zijn ga je de instructies stap voor stap volgen en steeds tussendoor testen. Je begint met het maken van een __init__()
method voor de class UserInterface
en zorgt ervoor dat de parent class (QtWidgets.QMainWindow
) volledig wordt ge\u00efnitialiseerd. Je runt example-gui.py
en ziet dat er nog steeds een leeg venster wordt gestart. Je bent benieuwd of het initialiseren \u00e9cht nodig is, daarom haal je de super()
-aanroep weg en kijkt wat er gebeurd als je example-gui.py
runt. Je zet super()
-aanroep heel gauw weer terug.
Pseudo-code
import sys\n\nfrom PySide6 import QtWidgets\n\n# create subclass of QtWidgets.QMainWindow\n # def __init__()\n # initialise the parent class Qtwidgets.QMainWindow\n\ndef main():\n # create instance of QtWidgets.QApplication with arguments from sys.argv\n # create instance of subclass\n # call show method of subclass\n # get exit code with exec method of QApplication instance and give exit code to sys.exit()\n\n# when run this script:\n # run main function \n
Checkpunten:
__init__()
method gemaakt voor de subclass UserInterface
.__init__()
method wordt de parent class ge\u00efnitialiseerd (regel 10).Projecttraject:
Verder heeft iedere applicatie een centrale widget nodig. Niet-centrale widgets zijn bijvoorbeeld een menubalk, knoppenbalk of statusbalk.
Central widget toevoegen
opdrachtcodecheckNu de parent class wordt ge\u00efnitialiseerd kan je een widget aanmaken met QtWidgets.QWidget()
, je noemt deze widget central_widget
. En stelt deze in als centrale widget met de method setCentralWidget()
van de class QtWidgets.QMainWindow
. Je runt example-gui.py
en ziet dat er nog steeds een leeg venster wordt gestart.
Pseudo-code
import sys\n\nfrom PySide6 import QtWidgets\n\n# create subclass of QtWidgets.QMainWindow\n # def __init__()\n # initialise the parent class Qtwidgets.QMainWindow\n # create central widget with QtWidgets.QWidget()\n # set central widget\n\ndef main():\n # create instance of QtWidgets.QApplication with arguments from sys.argv\n # create instance of subclass\n # call show method of subclass\n # get exit code with exec method of QApplication instance and give exit code to sys.exit()\n\n# when run this script:\n # run main function \n
Checkpunten:
QtWidgets.QWidget()
(regel 14).setCentralWidget()
(regel 15).setCentralWidget()
is afkomstig van de class QtWidgets.QMainWindow
welke ge\u00efnitialiseerd is, de method wordt daarom met self.setCentralWidget()
aangeroepen.Projecttraject:
Daarna gaan we layouts en widgets toevoegen. Layouts zorgen ervoor dat elementen netjes uitgelijnd worden. We willen het tekstvenster en de knoppen onder elkaar zetten en maken dus eerst een verticale layout. Aan die layout voegen we een textbox toe.
textbox toevoegen
opdrachtcodecheckOmdat je de textbox en de knoppen onder elkaar wilt uitlijnen voeg je een verticale layout toe. Door de central_widget
mee te geven tijdens het aanmaken van de verticale layout is de layout automatisch onderdeel van de central widget en zal deze in het venster verschijnen. Je maakt een textbox aan en voegt deze toe aan de verticale layout. Je runt example-gui.py
en ziet een venster met een textbox verschijnen, je typt een vrolijke tekst en sluit het venster.
Pseudo-code
import sys\n\nfrom PySide6 import QtWidgets\n\n# create subclass of QtWidgets.QMainWindow\n # def __init__()\n # initialise the parent class Qtwidgets.QMainWindow\n # create central widget with QtWidgets.QWidget()\n # set central widget\n\n # create vertical layout as part of central widget\n # create textbox\n # add textbox to vertical layout\n\ndef main():\n # create instance of QtWidgets.QApplication with arguments from sys.argv\n # create instance of subclass\n # call show method of subclass\n # get exit code with exec method of QApplication instance and give exit code to sys.exit()\n\n# when run this script:\n # run main function \n
Checkpunten:
central_widget
als parameter meegegeven (regel 18).QTextEdit
) is toegevoegd aan de verticale layout (regel 20).Projecttraject:
De knoppen zelf plaatsen we straks in een horizontale layout, dus die voegen we ook toe aan de vbox
. En we maken de layout compleet door knoppen toe te voegen aan de hbox
.
Knoppen toevoegen
opdrachtcodecheckOmdat de knoppen naast elkaar moeten komen te staan voeg je een horizontale layout toe aan de verticale layout. Je maakt een clear button
en een add button
en voegt deze toe aan de horizontale layout. Je runt example-gui.py
en ziet een venster met een textbox verschijnen met daaronder twee knoppen, je drukt verwoed op de knoppen maar er gebeurt niets4.
Pseudo-code
import sys\n\nfrom PySide6 import QtWidgets\n\n# create subclass of QtWidgets.QMainWindow\n # def __init__()\n # initialise the parent class Qtwidgets.QMainWindow\n # create central widget with QtWidgets.QWidget()\n # set central widget\n\n # create vertical layout as part of central widget\n # create textbox\n # add textbox to vertical layout\n\n # create horizontal layout\n # add horizontal layout to vertical layout\n\n # create clear_button\n # add clear button to horizontal layout\n # create add_text_button\n # add add_text_button to horizontal layout\n\ndef main():\n # create instance of QtWidgets.QApplication with arguments from sys.argv\n # create instance of subclass\n # call show method of subclass\n # get exit code with exec method of QApplication instance and give exit code to sys.exit()\n\n# when run this script:\n # run main function \n
Checkpunten:
clear_button
en add_text_button
aan gemaakt met daarop de tekst \"Clear\" en \"Add text\" respectievelijk (regels 24 en 26).Projecttraject
Info
Widgets zoals knoppen voeg je toe met addWidget()
. Layouts voeg je toe aan andere layouts met addLayout()
.
De horizontale layout (voor de knoppen) moeten we expliciet toevoegen aan de verticale layout zodat hij netjes verticaal onder het tekstvenster verschijnt. Merk op dat de verticale layout vbox
niet expliciet wordt toegevoegd (aan de centrale widget). De centrale widget (en alleen de centrale widget) krijgt een layout door bij het aanmaken van de layout de parent central_widget
op te geven, dus: QtWidgets.QVBoxLayout(central_widget)
. Alle andere widgets en layouts worden expliciet toegevoegd en daarvoor hoef je dus geen parent op te geven.
Als laatste verbinden we de knoppen aan functies. Zodra je op een knop drukt wordt er een zogeheten signal afgegeven. Die kun je verbinden met een slot. Er zijn ook verschillende soorten signalen. Het drukken op een knop zorgt voor een clicked signal, het veranderen van een getal in een keuzevenster geeft een changed signal. Wij verbinden \u00e9\u00e9n knop direct met een al bestaande method van het tekstvenster clear()
en de andere knop met een eigen method add_button_clicked()
. De naam is geheel vrij te kiezen, maar boven de functiedefinitie moet je wel de @Slot()
-decorator gebruiken (voor meer informatie over decorators zie paragraaf Decorators). PySide kan dan net wat effici\u00ebnter werken.
Slots en signals toevoegen
opdrachtcodecheckJe gaat functionaliteit aan de knoppen verbinden. Je verbint de clear_button
aan de clear()
method van textedit
. Je maakt een eigen Slot
met de naam add_text_button_clicked
die een tekst aan de textbox toegevoegd. Je vind de tekst \"You clicked me.\" maar suf en bedenkt zelf een andere leuke tekst. Je runt example-gui.py
en ziet een venster met een textbox verschijnen met daaronder twee knoppen. Je drukt op \"Add text\" en er verschijnt tekst in de textbox, daarna druk je op \"Clear\" en de tekst verdwijnt.
() ontbreken bij clear
en add_text_button_clicked
Bij het verbinden van het clicked
-signaal met clicked.connect()
geef je aan connect de methods clear
en add_text_button_clicked
mee zonder deze aan te roepen (dat gebeurt later). Concreet betekent dit dat je de haakjes weglaat (regel 30 en 31).
Pseudo-code
import sys\n\nfrom PySide6.QtCore import Slot\n\nfrom PySide6 import QtWidgets\n\n# create subclass of QtWidgets.QMainWindow\n # def __init__()\n # initialise the parent class Qtwidgets.QMainWindow\n # create central widget with QtWidgets.QWidget()\n # set central widget\n\n # create vertical layout as part of central widget\n # create textbox\n # add textbox to vertical layout\n\n # create horizontal layout\n # add horizontal layout to vertical layout\n\n # create clear_button\n # add clear button to horizontal layout\n # create add_text_button\n # add add_text_button to horizontal layout\n\n # connect clear_button to clear method of textedit\n # connect add_text_button to add_text_button_clicked\n\n # decorate method with Slot function\n # def add_text_button_clicked\n # add text to textedit\n\ndef main():\n # create instance of QtWidgets.QApplication with arguments from sys.argv\n # create instance of subclass\n # call show method of subclass\n # get exit code with exec method of QApplication instance and give exit code to sys.exit()\n\n# when run this script:\n # run main function \n
Checkpunten:
clicked
signaal van clear_button
is met connect
verbonden met de clear()
method van textedit
(regel 30). add_text_button
is met connect
verbonden met een eigen method add_text_button_clicked
(regel 31). add_text_button_clicked
is voorzien van een decorator @Slot()
met Slot met een hoofdletter en ronde haakjes erachter omdat Slot een functie is (regel 33).Slot
functie is ge\u00efmporteerd vanuit de PySide6.QtCore
.add_text_button_clicked
voegt met append
een tekst toe aan textedit
(regel 35). Projecttraject
Er zijn veel verschillende widgets met eigen methods en signals. Je vindt de lijst in de Qt for Python-documentatie. Qt zelf bestaat uit C++ code en PySide6 vertaalt alle methods e.d. letterlijk naar Python. Vandaar ook de methodnaam addWidget()
in plaats van add_widget()
. In C++ en Java is het wel gebruikelijk om functies CamelCase
namen te geven als kijkDitIsEenMooieFunctie()
, maar in Python zijn we snake_case
gewend, als in kijk_dit_is_een_mooie_functie()
.
De volgorde waarin je layout en widgets toevoegt bepaalt het uiterlijk van de grafische interface. Verander de code om de layout aan te passen (zet bijvoorbeeld de knoppen boven de textbox of zet de knoppen onder elkaar en naast de textbox).
'Hello world' en Quit knoppen toevoegen
opdrachtcodecheckNu de minimale GUI werkt wil je meer knoppen toevoegen. Je begint met een knop Hello, world
die de tekst \"Hello, world\" aan de textbox toevoegd. Je runt example-gui.py
en ziet dat de knop werkt. Daarna voeg je een Quit
-knop toe die onder de andere knoppen staat. Het signaal van deze knop verbind je met de method self.close()
zodat de applicatie wordt afgesloten. Je runt example-gui.py
drukt nog een paar keer op de Hello, world
-knop en daarna op de knop Quit
, het venster is gesloten de opdracht is voltooid .
Pseudo-code
# create hello_world button and add to layout\n# create Quit button and add to layout\n\n# connect hello_world button to add_hello_world_clicked method\n# connect Quit button to self.close()\n\n# decorate with Slot\n# def add_hello_world_clicked\n # add Hello World to textbox \n
Checkpunten:
Hello World
knop voegt de text \"Hello World\" toe aan de textbox.Quit
knop staat _ onder_ de andere knoppen.Quit
knop sluit het venster.Projecttraject
Info
Druk in de video's op het vierkant rechtsboven om ze in volledig scherm te bekijken.
Designer opstarten
Info
Qt Designer wordt ge\u00efnstalleerd met het qt
package, dat standaard aanwezig is in Anaconda \u00e9n ge\u00efnstalleerd wordt als je PySide6
installeert. Je start hem het makkelijkst op vanuit een terminal. Activeer je test-qt
conda environment als dat nog nodig is en type pyside6-designer
.
De GUI ontwerpen in Designer
Zodra interfaces wat ingewikkelder worden is het een hoop werk om ze te programmeren. Daarom kun je met Qt Designer de interface ook visueel ontwerpen. Laten we eerst kijken hoe we widgets toevoegen en positioneren in Designer:
En hoe je de eigenschappen van widgets aanpast:
De GUI vertalen naar Python
Je bewaart dat als een .ui
-bestand. Vervolgens vertaal je het .ui
-bestand naar een Pythonbestand dat je importeert in je eigen programma:
Info
In het filmpje wordt verwezen naar de Compacte Pyside6 Documentatie. Zie het overzicht hieronder.
De volledige class van het vorige voorbeeld kan dan vervangen worden door:
from ui_simple_app import Ui_MainWindow\n\n\nclass UserInterface(QtWidgets.QMainWindow):\n def __init__(self):\n super().__init__()\n\n self.ui = Ui_MainWindow()\n self.ui.setupUi(self)\n\n self.ui.clear_button.clicked.connect(self.ui.textedit.clear)\n self.ui.add_button.clicked.connect(self.add_button_clicked)\n\n @Slot()\n def add_button_clicked(self):\n self.ui.textedit.append(\"You clicked me.\")\n
Waarbij de gebruikersinterface geladen wordt uit het bestand en we alleen nog maar de signals aan de slots hoeven te koppelen. In deze code defini\u00ebren we niet self.ui.clear_button
of self.ui.add_button
; die namen geven we aan de knoppen die we maken in Designer. De namen van alle objecten in Designer zijn daarna beschikbaar in onze code om bijvoorbeeld de signalen te koppelen. Merk op dat we nu niet meer self.clear_button
gebruiken maar self.ui.clear_button
. Alle widgets komen op deze manier onder een .ui
-object te hangen. Designer gebruiken
MainWindow
. Klik dan op Create. Ontwerp de user interface van het screenshot en gebruik dezelfde namen voor de widgets als het voorbeeld. Dus een add_button
knop, een clear_button
knop en een textedit
tekstveld. Het is niet erg als je venster niet dezelfde grootte heeft. Qt Designer kiest een andere standaardafmeting.simple_app.ui
. ECPC
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 simple_app.ui
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 example-gui.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022 pyside6-uic simple_app.ui --output ui_simple_app.py \n
Deze stap moet je doen elke keer als je in Designer iets wijzigt. Gebruik de Up-toets om oude commando's terug te halen. Dat scheelt typewerk. Later, met Poetry, zullen we dit eenvoudiger maken. ECPC
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 simple_app.ui
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 simple_app.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 example-gui.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022 De documentatie van PySide6 is niet super-intu\u00eftief. Daarom hebben we speciaal voor jullie een Compacte PySide6 documentatie\ud83d\udcc4 geschreven. Daarin kan je een lijst van widgets vinden met de meest handige methods en signals. De documentatie is dus niet compleet maar genoeg voor een simpele GUI. Een overzicht van alle classes gedocumenteerd in de compacte documentatie vind je hieronder.
Classes:
QApplication
: Beheert de controleflow en hoofdinstellingen van de GUI-applicatie.QLayout
: Basisclass van alle layout-objecten in QtWidgets
. QWidget
: Basisclass van alle widget-objecten in QtWidgets
.
Subclasses van QLayout
:
QHBoxLayout
: Beheert een horizontale indeling van widgets. QVBoxLayout
: Beheert een verticale indeling van widgets. QGridLayout
: Beheert een roosterindeling waarbij de ruimte wordt verdeeld in rijen en kolommen.QFormLayout
: Beheert een indeling waarbij de ruimte wordt verdeeld in een linker kolom met labels en een rechter kolom met widgets. Subclasses van QWidget
:
QMainWindow
: Biedt een framework voor het bouwen van de gebruikersinterface van een applicatie.QGroupBox
: Biedt een frame, een titel erboven, en kan verschillende andere widgets binnen zichzelf weergeven.QTextEdit
: Geeft tekst weer en stelt de gebruiker in staat om deze te bewerken.QCheckBox
: Schakelknop met een checkbox-indicator. QLabel
: Een widget die tekst weergeeft.QComboBox
: Een widget waarmee de gebruiker een keuze kan maken uit een lijst met opties.QSpinBox
: Een widget waarmee de gebruiker een geheel nummer kan kiezen uit een bereik.QDoubleSpinBox
: Een widget waarmee de gebruiker een komma getal kan kiezen uit een bereik.QPushButton
: Een knop die door de gebruiker kan worden ingedrukt.QLineEdit
: Een widget waarmee de gebruiker een enkele regel platte tekst kan invoeren en bewerken.QFileDialog
: Biedt een dialoogvenster waarmee de gebruiker bestanden of mappen kan selecteren.Je hebt nu twee manieren gezien om een interface te bouwen: programmeren of Designer gebruiken. Let er wel op dat er dus een subtiel verschil is in het benaderen van de widgets. Je kunt bij zelf programmeren bijvoorbeeld self.add_button
gebruiken, maar als je Designer gebruikt moet dat self.ui.add_button
zijn.
In de eindopracht willen we data weergeven op een scherm. We zullen dus nog moeten plotten. In de volgende opdrachten gaan we daarmee aan de slag.
Je bent bekend met matplotlib en dat kan ook ingebouwd worden in Qt-applicaties. Helaas is matplotlib voor het gebruik in interactieve interfaces nogal traag zodra we te maken krijgen met meer data. We kiezen daarom voor een populair alternatief: PyQtGraph. E\u00e9n nadeel: de documentatie is niet fantastisch. Het geeft dus niets als je ergens niet uitkomt en je hulp nodig hebt van de assistent of een staflid.
"},{"location":"gui/#de-plotter-als-script","title":"De plotter als script","text":"Om PyQtGraph te importeren en globale opties in te stellen moeten we bovenaan ons programma het volgende schrijven:
import pyqtgraph as pg\n\n\n# PyQtGraph global options\npg.setConfigOption(\"background\", \"w\")\npg.setConfigOption(\"foreground\", \"k\")\n
Dit geeft zwarte letters op een witte achtergrond. Je kunt de opties weglaten en dan krijg je de standaardinstelling: grijze letters op een zwarte achtergrond. Het is maar wat je fijn vindt. Info
Als je je GUI het liefst programmeert, gebruik dan de volgende regel om een plot widget te krijgen in de __init__()
:
self.plot_widget = pg.PlotWidget()\n
Als je je GUI het liefst ontwerpt met Designer voegen we als volgt een plot widget toe: PlotWidget
en bij Header file vul je in pyqtgraph
(zonder .h
aan het eind);De stappen zijn weergegeven in onderstaand screenshot. Bij de rode pijl vind je Graphics View en in het rode kader staat wat je moet invullen om te promoten:
Nu je dit een keer gedaan hebt kun je voortaan op een Graphics View meteen kiezen voor Promote to > PlotWidget en hoef je niets meer in te typen. Vergeet niet je widget nog even een handige naam te geven, bijvoorbeeld plot_widget
.
Om daadwerkelijk een functie te plotten kun je deze code aanpassen:
import numpy as np\n\nclass UserInterface(QtWidgets.QMainWindow):\n\n ...\n\n def plot(self):\n x = np.linspace(-pi, pi, 100)\n self.plot_widget.plot(x, np.sin(x), symbol=None, pen={\"color\": \"m\", \"width\": 5})\n self.plot_widget.setLabel(\"left\", \"y-axis [units]\")\n self.plot_widget.setLabel(\"bottom\", \"x-axis [units]\")\n
Je kunt uiteraard spelen met de instellingen zoals symbol
en pen
om te zien wat ze doen. Leeg maken kan met self.plot_widget.clear()
. Functionplotter: plot
opdrachtcodecheck We gaan een nieuwe repository aanmaken in de ECPC
map (zie hiernaast). Maak een Poetry project functionplotter
, voeg die toe aan GitHub Desktop en open hem in Visual Studio Code. Bekijk pyproject.toml
en zorg dat er een commando is aangemaakt om de applicatie te starten. Je maakt een nieuw conda environment aan met alleen Python daarin . Gebruik poetry install
om het project te installeren en voer het commando uit om de applicatie te starten. Als je applicatie af is verschijnt er een scherm met een plot waarin de functie $\\sin(x)$ plot in het domein $(0, 2\\pi)$ is weergegeven. Een golfje van trots gaat door je heen en je gaat door naar de volgende opdracht. ECPC
\u251c\u2500\u2500 pythondaq
\u251c\u2500\u2500 functionplotter
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 pyproject.toml
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code
# import statements\n\n# class UserInterface(QtWidgets.QMainWindow):\n ...\n # when app starts plot sin function\n\n# create application\n# show UserInterface\n# close properly\n
Checkpunten:
functionplotter
poetry install
in een nieuwe conda environment.Projecttraject
Functionplotter: widgets
opdrachtcodecheckVoer opnieuw het commando uit om de applicatie functionplotter
te starten. Dit keer zorg je dat de applicatie de mogelijkheid krijgt om het domein van de plot aan te passen. Je ziet dan de sinusplot veranderen wanneer je de startwaarde verhoogd. Je kunt de startwaarde ook naar beneden aanpassen. Hetzelfde geldt voor de stopwaarde. Dan maak je nog een widget om het aantal punten (num
) te kiezen waarmee de sinus wordt geplot. Speel eens met de widget en zie de sinus van hoekig naar mooi glad veranderen. Steeds als je een waarde aanpast moet de functie automatisch opnieuw geplot geworden.
Pseudo-code
# import statements\n\n# class UserInterface(QtWidgets.QMainWindow):\n ...\n # when app starts plot sin function\n\n # create widgets for start, stop and num in designer or in the script\n # connect widgets to methods\n\n # methods for start stop and num\n\n# create application\n# show UserInterface\n# close properly\n
Checkpunten:
Projecttraject
Gebruik een QComboBox
om de functie te kunnen kiezen. Je moet hem leeg toevoegen aan je interface en vult hem vanuit je programma. Zoek de widget op in de documentatie om uit te zoeken welke functie je moet gebruiken om keuzemogelijkheden toe te voegen en welk signaal je moet koppelen om te zorgen dat de plot opnieuw wordt uitgevoerd als je de functie aanpast. Geef de gebruiker de keuzes $\\sin(x)$, $\\cos(x)$, $\\tan(x)$ en $\\exp(x)$.
Voeg aan de functiekiezer de functies $x$, $x^2$, $x^3$, en $\\frac{1}{x}$ toe. Je kunt daarvoor lambda functions gebruiken, maar dat is niet per se nodig.
Functieplotter: functies typenVervang de functiekiezer door een tekstveld waarin de gebruiker zelf functies kan typen zoals x ** 2
, sin(x)
of 1 / sqrt(x + 1)
. Gebruik daarvoor het asteval
package.12 Documentatie vind je op https://newville.github.io/asteval/.
Waarschuwing
Gebruik nooit zomaar eval()
op een string die iemand anders aanlevert. Anders kan iemand met typen in een tekstveld of het inlezen van een tekstbestand je computer wissen bijvoorbeeld, of malware installeren. Als je eval()
wilt gebruiken, lees dan de sectie Minimizing the Security Issues of eval() in Python eval(): Evaluate Expressions Dynamically.13 Maar veel makkelijker is om asteval
te gebruiken.
In het vorige hoofdstuk hebben we een tekst-interface geschreven voor ons experiment. We gaan nu een grafische interface schrijven voor hetzelfde experiment.
We hebben tot nu toe veel moeite gedaan om onze code te splitsen volgens het MVC-model: werken in laagjes, goed nadenken over wat waar hoort. Als dat netjes gelukt is kunnen we relatief makkelijk \u00e9\u00e9n van die laagjes vervangen. We kunnen de ArduinoVISADevice
vervangen door een RaspberryPiDevice
of een PicoScopeDevice
6. Ook kunnen we een nieuwe applicatie schrijven voor ons bestaande experiment. We hoeven dan alleen een extra view te schrijven (de interface met de gebruiker) en de rest kunnen we hergebruiken. Misschien dat we hier en daar iets willen aanpassen maar zorg er dan voor dat je oude applicatie nog steeds werkt!
We gaan nu \u2014 in stapjes \u2014 een grafische applicatie schrijven voor ons experiment.
Info
Je mag zelf kiezen of je de grafische interface gaat ontwerpen met Designer of dat je hem volledig programmeert.
Info
Als je Designer gaat gebruiken voor de grafische interface dan is het lastig dat je steeds pyside-uic
moet aanroepen en moet zorgen dat je in de goede directory staat. We kunnen met Poetry taken aanmaken die je met een eenvoudig commando kunt laten uitvoeren. Die taken zijn alleen beschikbaar tijdens het ontwikkelen van je applicatie. Doe dit als volgt:
poetry add --group dev poethepoet\n
We geven hiermee aan dat we dit package nodig hebben voor de ontwikkeling van onze applicatie, maar dat deze niet meegeleverd hoeft te worden als we de applicatie gaan delen met anderen.pyproject.toml
het volgende toe \u2014 uitgaande van de mappenstructuur in de pythondaq
package en mainwindow.ui
als naam van je .ui
-bestand: [tool.poe.tasks.compile]\nshell = \"\"\"\npyside6-uic src/pythondaq/mainwindow.ui --output src/pythondaq/ui_mainwindow.py\n\"\"\"\ninterpreter = [\"posix\", \"powershell\"]\n
Je kunt binnen de driedubbele aanhalingstekens meerdere regels toevoegen als je meerdere .ui
-bestanden hebt \u2014 voor ieder bestand een regel.tool.poe.tasks
de naam van de taak \u2014 in dit geval dus compile
. Je kunt die naam zelf kiezen en vervolgens gebruiken om de taak uit te voeren in de terminal: Terminalpoe compile\n
En dat gaat een stuk sneller dan die lange pyside-uic
-regel onthouden en intypen!Pythondaq: leeg venster
opdrachtcodecheck Je wilt een toffe GUI maken voor de pythondaq
applicatie. Je gaat dit in stapjes opbouwen zodat je tussendoor nog kunt testen of het werkt. Je maakt een gui.py
aan waarin een leeg venster wordt gemaakt. Het lege venster wordt getoond zodra je een commando in de terminal intypt. Je sluit het venster. Om te testen of dit bij andere mensen ook zou werken maak je een nieuwe conda environment aan met Python , installeer je de package met Poetry en test je opnieuw het commando, er verschijnt opnieuw een leeg venster. ECPC
\u251c\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 src/pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 gui.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code
# create empty window\n
Checkpunten:
pythondaq
met Poetry in een nieuwe conda environment met Python. Projecttraject
Pythondaq: plot scan
opdrachtcodecheckAls het commando wordt uitgevoerd start de applicatie een scan en laat de metingen vervolgens zien in een plot binnen het venster. Voor het gemak heb je de poortnaam, start- en stopwaardes e.d. hard coded in het script gezet. Later ga je er voor zorgen dat een gebruiker die kan instellen, maar dat komt straks wel.
Foutenvlaggen plotten
Foutenvlaggen toevoegen aan een pyqtgraph is helaas iets minder intuitief dan bij matplotlib. Met breedte en hoogte geef je aan hoe groot de vlaggen zijn, de vlag is 2 keer zo hoog of breed als de onzekerheid. Samen met de $x$ en $y$ data maak je dan een ErrorBarItem
aan die je expliciet toevoegt aan de plot. Let op: x
, y
, x_err
en y_err
moeten NumPy arrays zijn of, en dat geldt alleen voor de errors, een vast getal. Gewone lijsten werken helaas niet.
def plot(self):\n \"\"\"Clear the plot widget and display experimental data.\"\"\"\n\n # Genereer wat data als demo.\n # **Let op:** `x`, `y`, `x_err` en `y_err` *moeten* NumPy arrays zijn *of*,\n # en dat geldt alleen voor de errors, een vast getal.\n x = np.linspace(0, 2 * np.pi, 20)\n y = np.sin(x)\n x_err = 0.1\n y_err = np.random.normal(0, 0.2, size=len(x))\n\n # Maak eerst een scatterplot\n self.plot_widget.plot(x, y, symbol=\"o\", symbolSize=5, pen=None)\n\n # nu de foutvlaggen, met 'breedte' en 'hoogte' in plaats van x errors en y\n # errors.\n error_bars = pg.ErrorBarItem(x=x, y=y, width=2 * x_err, height=2 * y_err)\n # we moeten de error_bars expliciet toevoegen aan de plot\n self.plot_widget.addItem(error_bars)\n
Pseudo-code
# when application starts\n # ask model to do scan\n # plot results in application window\n
Checkpunten:
Projecttraject
Pythondaq: widgets
opdrachtcodecheckNa het uitvoeren van het commando start de pythondaq
applicatie waarin een lege plot en een aantal widgets zijn te zien waarmee de start- en stopwaardes, het aantal metingen kunnen worden ingesteld. Ook is er een startknop waarmee een (nieuwe) meting wordt uitgevoerd. Je vult verschillende (logische en niet logische) waardes in voor de start- en stopwaardes en het aantal metingen en ziet dat de applicatie naar verwachting werkt.
Pseudo-code
# create widgets in designer or in the script\n# for start, stop, measurements and start scan\n\n# connect widgets to methods\n\n# methods for start, stop, measurements and start scan\n
Checkpunten:
Projecttraject
Je zou na iedere meting de gegevens automatisch kunnen wegschrijven naar bestanden zonder dat de gebruiker nog iets kan kiezen, maar je kunt ook gebruik maken van een Save
-knop en dialoogvensters. Je kunt de knop koppelen aan een method save_data()
en daarin de volgende regel opnemen:
filename, _ = QtWidgets.QFileDialog.getSaveFileName(filter=\"CSV files (*.csv)\")\n
De functie getSaveFileName()
opent een dialoogvenster om een bestand op te slaan. Vanwege het filter argument geeft het venster (op sommige besturingssystemen) alleen CSV-bestanden weer. In elk geval geldt op alle besturingssystemen dat als de gebruiker als naam metingen
intypt, dat het filterargument ervoor zorgt dat er automatisch .csv
achter geplakt wordt.7 De functie geeft twee variabelen terug: filename
en filter
, die je zelf hebt meegegeven in bovenstaande aanroep. Die laatste kenden we dus al en gooien we weg met behulp van de weggooivariabele _
.
Het enige dat het dialoogvenster doet is de gebruiker laten kiezen waar en onder welke naam het bestand moet worden opgeslagen. Je krijgt echt alleen een pad en bestandsnaam terug, de data is niet opgeslagen en het bestand is niet aangemaakt. De variabele filename
is echt niets anders dan een bestandsnaam, bijvoorbeeld: /Users/david/LED-rood.csv
. Nadat je die bestandsnaam gekregen hebt moet je dus zelf nog code schrijven zodat het CSV-bestand wordt opgeslagen onder die naam.
Pythondaq: save
opdrachtcodecheckBreid je code zodanig uit dat het volgende werkt: Je opent de applicatie en start een scan. Dan valt je oog op een Save
-knop, wanneer je op deze knop drukt wordt er een dialoogvenster geopent. Je kiest een locatie en typt een bestandsnaam, je klikt op Save
(of Opslaan
). Daarna ben je nieuwsgierig of het gelukt is. Via File Explorer
(of Verkenner
) navigeer je op de computer naar de locatie waar je het bestand hebt opgeslagen. Je opent het bestand en ziet de metingen staan. Tevreden sluit je het bestand af en ga je door naar de volgende opdracht.
Pseudo-code
# create widget for save in designer or in the script\n\n# connect widget to method\n\n# methods save\n # open dialog\n # save measurements as csv in given filename\n
Checkpunten:
Save
opent een dialoogvenster.Projecttraject
Je kunt je grafische applicatie volledig optuigen met menu's of taakbalken. Ook kun je onderin je applicatie met een statusbalk weergeven wat de status is: gereed, aan het meten, foutcode, etc. Dat valt buiten het bestek van deze cursus, maar een mooie referentie is PySide6 Toolbars & Menus \u2014 QAction.14 Als je vaker grafische applicaties wilt gaan maken dan moet je dat zeker eens doornemen!
Pythondaq: statusbalk
Maak een statusbalk die aangeeft wat de identificatiestring is van het device dat geselecteerd is. Maak ook een menu waarmee je een CSV-bestand kunt opslaan en een nieuwe meting kunt starten. Let op: je hebt dan een menu-item \u00e9n een knop die dezelfde method aanroepen. Je hoeft geen dubbele code te schrijven, maar moet de save_data()
-method wel twee keer verbinden.
Je hebt nu waarschijnlijk nog de poortnaam van de Arduino in je code gedefinieerd als vaste waarde. Dat betekent dat als je de code deelt met iemand anders \u2014 bijvoorbeeld wanneer je de code inlevert op Canvas of wanneer je je experiment op een labcomputer wilt draaien \u2014 je het risico loopt dat je applicatie crasht omdat de Arduino aan een andere poort hangt. Zeker bij de overstap van Windows naar MacOS of Linux, of andersom! Je kunt dit op twee manieren oplossen:
Je kunt je voorstellen dat mogelijkheid 2 de voorkeur heeft! Helaas is dit moeilijker dan gedacht. Zodra je andere devices gaat openen en commando's gaat sturen om te ontdekken wat voor apparaat het is kunnen er gekke dingen gebeuren. Onder MacOS bijvoorbeeld kunnen Bluetooth luidsprekers en koptelefoons opeens ontkoppelen. We gaan dus toch voor keuze 1. Bijkomend voordeel van deze keuze is dat je meerdere Arduino's aan je computer kunt hangen en kunt schakelen \u2014 vooral handig als je meerdere experimenten vanaf \u00e9\u00e9n computer wilt aansturen.
Pythondaq: selecteer Arduino
opdrachtcodecheckJe opent de applicatie en ziet een keuzemenu (QComboBox
) waarmee je de Arduino kunt selecteren. Je selecteert de juiste Arduino, start een meting en ziet het LED lampje branden. Je sluit de applicatie af en bent benieuwd wat er gebeurt als je meerdere Arduino's aansluit. Dus vraag je een (of twee, of drie) Arduino('s) van je buren, sluit deze aan op je computer en start opnieuw de applicatie. Je ziet dat er meerdere apparaten in het keuzemenu staan. Je kiest een Arduino, start een meting en ziet een lampje branden. Daarna selecteer je een andere Arduino, start een meting en ziet een ander lampje branden, hoe leuk .
Arduino afsluiten
Als je met meerdere Arduino's werkt kan het handig zijn om na afloop van de scan de communicatie met de Arduino weer te sluiten. In de opdracht Pyvisa in terminal heb je al eens gewerkt met het commando close
. Dit werkt ook voor pyvisa in een script. Je hebt in de controller de communicatie geopend met self.device = rm.open_resource(port, read_termination=\"\\r\\n\", write_termination=\"\\n\")
, je kunt de communicatie met self.device
in de controller sluiten met self.device.close()
. Je kunt een method in de controller toevoegen die de communicatie sluit. Via het model kun je deze method aanroepen in de gui.
Pseudo-code
# create widget for select Arduino\n\n# method start scan\n # open communication with selected Arduino\n # start scan\n # close communication with selected Arduino\n
Checkpunten:
Projecttraject
Uitspraak: het Engelse cute.\u00a0\u21a9
Of in \u00e9\u00e9n keer met View > Command Palette > Terminal: Kill All Terminals \u21a9
Die kun je eventuele command-line arguments meegeven die door Python in sys.argv
bewaard worden. Meestal zijn die leeg, maar we geven ze gewoon door aan Qt.\u00a0\u21a9
Waarom doen de knoppen niets als je er op klikt?\u00a0\u21a9
Overleg met elkaar of met de assistent als je niet weet hoe dat moet.\u00a0\u21a9
Je moet dan wel eerst nieuwe controllers schrijven (of krijgen van een collega) om deze nieuwe instrumenten aan te sturen. Maar als je die hebt kun je vrij eenvoudig wisselen.\u00a0\u21a9
Het eerste deel van het argument (CSV files
) is vrij te kiezen en geeft alleen informatie aan de gebruiker. Het deel tussen haakjes (*.csv
) is het gedeelte dat echt van belang is. Het geeft de extensie die achter alle bestandsnamen geplakt wordt.\u00a0\u21a9
Er is een subtiliteit. In Python draaien threads niet tegelijk, maar om de beurt. In de praktijk merk je daar niet veel van: threads worden z\u00f3 vaak per seconde gewisseld dat het lijkt alsof ze tegelijk draaien. Terwijl de ene thread steeds even tijd krijgt voor een meting kan de andere thread steeds even de plot verversen. In het geval van zwaar rekenwerk schiet het alleen niet op. Er draait maar \u00e9\u00e9n berekening tegelijkertijd dus threads of niet, het is even snel. Wil je echt parallel rekenen, dan moet je kijken naar de multiprocessing
module om meerdere processen te starten in plaats van threads.\u00a0\u21a9
Variabelen die we in een class defini\u00ebren door ze aan te maken met self.
ervoor zijn instance attributes.\u00a0\u21a9
Hier zie je een probleem met threads. Het k\u00e1n \u2014 in uitzonderlijke situaties \u2014 voorkomen dat de plot-functie n\u00e9t wil gaan plotten als de $x$-waardes al langer gemaakt zijn, maar de $y$-waardes nog niet. Die kans is heel klein en wij accepteren het risico. Schrijf je software voor een complex experiment dat drie dagen draait, dan is dit iets waar je echt rekening mee moet houden. Je moet dan gebruik gaan maken van zogeheten locks of semaphores maar dat valt buiten het bestek van deze cursus.\u00a0\u21a9
Door een beetje ons best te doen kunnen we ervoor zorgen dat zowel de command-line interface als de grafische interface allebei gebruikt kunnen worden.\u00a0\u21a9
Matt Newville. Asteval: minimal python ast evaluator. URL: https://newville.github.io/asteval.\u00a0\u21a9
Leodanis Pozo Ramos. Python eval(): evaluate expressions dynamically. 2020. URL: https://realpython.com/python-eval-function/.\u00a0\u21a9
Martin Fitzpatrick. Pyside6 toolbars & menus \u2014 qaction. 2021. URL: https://www.pythonguis.com/tutorials/pyside6-actions-toolbars-menus/.\u00a0\u21a9
Afhankelijk van de instellingen die we gekozen hebben kan een meting best lang duren. In ieder geval moeten we even wachten tot de meting afgelopen is en pas daarna krijgen we de resultaten te zien in een plot. Als een meting langer duurt dan een paar seconden kan het besturingssysteem zelfs aangeven dat onze applicatie niet meer reageert. En inderdaad, als we ondertussen op knoppen proberen te drukken dan reageert hij nergens op. Onze applicatie kan helaas niet twee dingen tegelijk. Kon hij dat wel, dan zouden we zien hoe de grafiek langzaam opbouwt tot het eindresultaat.
De manier waarop besturingssystemen meerdere dingen tegelijk doen is gebaseerd op processes en threads. Een process is, eenvoudig gezegd, een programma. Als je meerdere applicaties opstart zijn dat allemaal processen. Besturingssystemen regelen dat ieder proces een stuk geheugen krijgt en tijd van de processor krijgt toegewezen om zijn werk te doen. Processen zijn mooi gescheiden en kunnen dus eenvoudig naast elkaar draaien. Het wordt iets lastiger als een proces meerdere dingen tegelijk wil doen. Dat kan wel, met threads. Het besturingssysteem zorgt dat meerdere threads naast elkaar draaien.8
Threads geven vaak problemen omdat ze in zekere zin onvoorspelbaar zijn. Je weet niet precies hoe snel een thread draait, dus je weet niet zeker wat er in welke volgorde gebeurt. Dit kan leiden tot problemen waarvan de oorzaak maar lastig te vinden is. Google maar eens op thread problems in programming
. We moeten dus voorzichtig zijn! Ook is het ombouwen van code zonder threads naar code met threads een klus waar makkelijk iets fout gaat. Het is dus belangrijk dat je in kleine stapjes je code aanpast en vaak test of het nog werkt.
Info
We gaan in het volgende stuk een kleine applicatie ombouwen van no-threads naar threads. We raden je ten zeerste aan om de code te copy/pasten en dan stapje voor stapje aan te passen zoals in de handleiding gebeurt. Probeer alle stappen dus zelf! Pas na stap 4 ga je aan de slag om je eigen code om te bouwen. Samenvattend: doorloop dit stuk handleiding twee keer. De eerste keer doe je de opdrachten met het demoscript, de tweede keer met je eigen code voor pythondaq
.
import sys\n\nimport numpy as np\n\nfrom PySide6 import QtWidgets\nimport pyqtgraph as pg\n\nfrom model import Experiment\n\n\nclass UserInterface(QtWidgets.QMainWindow):\n def __init__(self):\n super().__init__()\n\n central_widget = QtWidgets.QWidget()\n self.setCentralWidget(central_widget)\n\n vbox = QtWidgets.QVBoxLayout(central_widget)\n self.plot_widget = pg.PlotWidget()\n vbox.addWidget(self.plot_widget)\n start_button = QtWidgets.QPushButton(\"Start\")\n vbox.addWidget(start_button)\n\n start_button.clicked.connect(self.plot)\n\n # Maak een instance aan van Experiment\n self.experiment = Experiment()\n\n def plot(self):\n \"\"\" Clear the plot widget and display experimental data. \"\"\"\n self.plot_widget.clear()\n x, y = self.experiment.scan(0, np.pi, 50)\n self.plot_widget.plot(x, y, symbol=\"o\", symbolSize=5, pen=None)\n\ndef main():\n app = QtWidgets.QApplication(sys.argv)\n ui = UserInterface()\n ui.show()\n sys.exit(app.exec())\n\n\nif __name__ == \"__main__\":\n main() \n
import time\nimport numpy as np\n\nclass Experiment:\n def scan(self, start, stop, steps):\n \"\"\" Perform a scan over a range with specified steps and return the scanned values. \"\"\"\n x = np.linspace(start, stop, steps)\n y = []\n for u in x:\n y.append(np.sin(u))\n time.sleep(0.1)\n return x, y\n
In regels 15--24 bouwen we een kleine user interface op met een plot widget en een startknop. We koppelen die knop aan de plot()
-method. In regel 27 maken we ons experiment (het model) aan en bewaren die. In regels 30--34 maken we de plot schoon, voeren we een scan uit en plotten het resultaat. model.py
vormt ons experiment. Eerst wordt een rij $x$-waardes klaargezet en dan, in een loop, wordt punt voor punt de sinus uitgerekend en toegevoegd aan een lijst met $y$-waardes. De time.sleep(.1)
wacht steeds 0.1 s en zorgt hiermee voor de simulatie van trage metingen. En inderdaad, als we deze code draaien dan moeten we zo'n vijf seconden wachten voordat de plot verschijnt.
In de volgende opdrachten gaan we de code stap voor stap ombouwen naar threads. Als we daarmee klaar zijn worden de metingen gedaan binnen de scan()
-method van de Experiment()
-class en verversen we ondertussen af en toe de plot. De plot()
-method van onze user interface wordt regelmatig aangeroepen terwijl de meting nog loopt en moet dus de hele tijd de huidige metingen uit kunnen lezen. Dat kan, als de metingen worden bewaard in instance attributes.9
Threads 0
Neem view.py
en model.py
over en test de applicatie.
We maken in de scan()
-method lege lijsten self.x
en self.y
. Hier komen de meetgegevens in en die staan dus los van de lijst met $x$-waardes die je klaarzet. Met andere woorden: de variabele x
is niet hetzelfde als de variabele self.x
:
class Experiment:\n def scan(self, start, stop, steps):\n x = np.linspace(start, stop, steps)\n self.x = []\n self.y = []\n for u in x:\n self.x.append(u)\n self.y.append(np.sin(u))\n time.sleep(0.1)\n return self.x, self.y\n
We zorgen er zo voor dat de lijst met meetgegevens voor zowel de $x$- als de $y$-waardes steeds even lang zijn. Dit is nodig voor het plotten: hij kan geen grafiek maken van 50 $x$-waardes en maar 10 $y$-waardes.10 Ook moeten we er voor zorgen dat er altijd (lege) meetgegevens beschikbaar zijn \u2014 ook als de meting nog niet gestart is. Anders krijgen we voordat we een meting hebben kunnen doen een foutmelding dat self.x
niet bestaat. We doen dat in de __init__()
:
class Experiment:\n def __init__(self):\n self.x = []\n self.y = []\n\n ...\n
We laten self.x = []
(en idem voor self.y
) ook staan in de scan()
-methode zodat bij iedere nieuwe scan de oude meetgegevens worden leeggemaakt.
Threads I
Pas de code aan zodat de meetgegevens altijd beschikbaar zijn. Test je code, de applicatie moet nog steeds werken.
"},{"location":"gui/#stap-2-plot-de-meetgegevens-vanuit-het-experiment","title":"Stap 2: plot de meetgegevens vanuit het experiment","text":"Nu we de meetgegevens bewaren als instance attributes van de Experiment
-class kunnen we die ook plotten. We geven ze nog steeds terug als return value vanuit de scan()
-method voor ouderwetse code,11 maar wij gaan nu de nieuwerwetse instance attributes gebruiken:
class UserInterface(QtWidgets.QMainWindow):\n\n ...\n\n def plot(self):\n \"\"\" Clear the plot widget and display experimental data. \"\"\"\n self.plot_widget.clear()\n self.experiment.scan(0, np.pi, 50)\n self.plot_widget.plot(\n self.experiment.x, self.experiment.y, symbol=\"o\", symbolSize=5, pen=None\n )\n
De code wordt hier niet sneller van \u2014 hij maakt nog steeds pas een grafiek als de meting helemaal is afgelopen \u2014 maar we bereiden de code wel voor op het gebruik van de instance attributes.
Threads II
Pas de code aan zodat je instance attributes gebruikt voor het plotten. Test je code, het moet nog steeds werken als vanouds.
"},{"location":"gui/#stap-3-threads","title":"Stap 3: threads","text":"We gaan nu met threads werken. Je importeert daarvoor de threading
module en maakt voor iedere thread een threading.Thread()
instance. Deze heeft twee belangrijke parameters: target
waarmee je de functie (of method) aangeeft die in de thread moet worden uitgevoerd, en args
waarmee je argumenten meegeeft voor die functie of method. We maken een nieuwe method start_scan()
waarmee we een nieuwe thread starten om een scan uit te voeren. We doen dit als volgt:
import threading\n\nclass Experiment:\n def start_scan(self, start, stop, steps):\n \"\"\"Start a new thread to execute a scan.\"\"\"\n self._scan_thread = threading.Thread(\n target=self.scan, args=(start, stop, steps)\n )\n self._scan_thread.start()\n\n def scan(self, start, stop, steps):\n \"\"\" Perform a scan over a range with specified steps and return the scanned values. \"\"\"\n x = np.linspace(start, stop, steps)\n self.x = []\n self.y = []\n for u in x:\n self.x.append(u)\n self.y.append(np.sin(u))\n time.sleep(0.1)\n return self.x, self.y\n
In plaats van dat onze plotfunctie de scan()
-method aanroept, moeten we nu de start_scan()
-method aanroepen. Maar: die method start een scan en sluit meteen af, terwijl de daadwerkelijke meting op de achtergrond wordt uitgevoerd. De plotfunctie moet \u2014 in deze stap nog even \u2014 wachten tot de scan klaar is. Er is een manier om op een thread te wachten. Je moet daartoe de join()
method van de thread aanroepen. In bovenstaande code hebben we de thread bewaard in de variabele _scan_thread
, dus hij is voor ons beschikbaar:
class UserInterface(QtWidgets.QMainWindow):\n\n ...\n\n def plot(self):\n \"\"\" Clear the plot widget and display experimental data. \"\"\"\n self.plot_widget.clear()\n self.experiment.start_scan(0, np.pi, 50)\n self.experiment._scan_thread.join()\n self.plot_widget.plot(\n self.experiment.x, self.experiment.y, symbol=\"o\", symbolSize=5, pen=None\n )\n
Threads III
self.experiment._scan_thread.join()
uit te commentari\u00ebren (hekje ervoor). Niet vergeten het hekje weer weg te halen.We zijn er nu bijna. We gebruiken threads om de metingen op de achtergrond uit te voeren maar we wachten nog steeds tot de metingen klaar zijn voordat we \u2014 eenmalig \u2014 de grafiek plotten. In deze laatste stap doen we dat niet meer. Als je straks op de startknop drukt dan start de meting op de achtergrond. Ondertussen wordt er regelmatig geplot. Je ziet dan tijdens de metingen de plot opbouwen. We doen dat door het scannen en plotten van elkaar los te koppelen \u2014 niet meer samen in \u00e9\u00e9n functie \u2014 en door met een QTimer
de plotfunctie periodiek aan te roepen. Kijk de code goed door.
from PySide6 import QtWidgets, QtCore\n\nclass UserInterface(QtWidgets.QMainWindow):\n def __init__(self):\n super().__init__()\n\n ...\n\n start_button.clicked.connect(self.start_scan)\n\n ... \n\n # Plot timer\n self.plot_timer = QtCore.QTimer()\n # Roep iedere 100 ms de plotfunctie aan\n self.plot_timer.timeout.connect(self.plot)\n self.plot_timer.start(100)\n\n def start_scan(self):\n \"\"\"Starts a scanning process with specified parameters.\"\"\"\n self.experiment.start_scan(0, np.pi, 50)\n\n def plot(self):\n \"\"\" Clear the plot widget and display experimental data. \"\"\"\n self.plot_widget.clear()\n # Twee regels code zijn verwijderd\n self.plot_widget.plot(\n self.experiment.x, self.experiment.y, symbol=\"o\", symbolSize=5, pen=None\n )\n
Hiermee zijn we klaar met de implementatie van threads. De gebruiker hoeft niet langer in spanning te wachten maar krijgt onmiddelijke feedback.
Threads IV
Pas de code op dezelfde manier aan zodat de metingen op de achergrond worden uitgevoerd terwijl je de plot ziet opbouwen. De code werkt nu niet als vanouds, en voelt veel sneller!
Pythondaq: threads in je eigen code
Doorloop nu opnieuw stappen 1 t/m 4 maar dan voor je eigen pythondaq
-applicatie.
Wanneer je op de startknop drukt, even wacht en dan w\u00e9\u00e9r op de startknop drukt, dan kun je zien dat er twee metingen tegelijk worden uitgevoerd op de achtergrond. Dat wil je voorkomen. Ook is het wel aardig om metingen tussentijds te kunnen stoppen. Dat is vooral handig als je merkt dat een meting veel te lang gaat duren. Verder is het ook nog zo dat we er nu met onze timer voor gezorgd hebben dat de plotfunctie meerdere keren per seconde wordt uitgevoerd \u2014 of er nu een meting loopt of niet.
Je kunt dit oplossen met threading.Event()
objecten. Dit zijn objecten met set()
, clear()
en wait()
methods om gebeurtenissen aan te geven of er op te wachten. Zo kun je een event is_scanning
aanmaken die je set()
zodra een meting begint en clear()
zodra de meting is afgelopen. Je controleert bij de start van de meting dan bijvoorbeeld eerst of de meting al loopt met is_scanning.is_set()
en start alleen een meting als dat nog niet zo is.
Ook kun je in de grafische interface na het starten van een meting de startknop onbeschikbaar maken met start_button.setEnabled(False)
en weer beschikbaar maken met start_button.setEnabled(True)
. De knop wordt dan tussendoor grijs. Dat kan handig zijn om duidelijk te maken dat een meting al loopt en dat je niet nogmaals op de startknop kunt drukken.
Vergrendelen
Pas je code aan zodat je niet meerdere metingen tegelijk kunt starten. Zorg er ook voor dat de grafiek alleen geplot wordt tijdens de metingen (of tot kort daarna), maar niet de hele tijd.\n
"},{"location":"kleurcodes/","title":"Kleurcodes voor weerstanden","text":"Kleur Cijferwaarde Vermenigvuldigingsfactor Tolerantie (%) Zilver --- 10-2 10 Goud --- 10-1 5 Zwart 0 100 --- Bruin 1 101 1 Rood 2 102 2 Oranje 3 103 0.05 Geel 4 104 0.02 Groen 5 105 0.5 Blauw 6 106 0.25 Paars 7 107 0.1 Grijs 8 108 0.01 Wit 9 109 --- Helaas is het niet altijd mogelijk om de linkerkant van de weerstand van de rechterkant te onderscheiden. In dat geval moet je de weerstand beide kanten oplezen en vergelijken met je materialenlijst of de overige weerstanden om zeker te weten dat je de goede hebt gevonden. Bovenstaande weerstand heeft de waarde 220\u22c5100 \u03a9 \u00b1 1 %, en niet de waarde 100\u22c5102 \u03a9 \u00b1 2 %.
"},{"location":"mvc/","title":"Model-View-Controller","text":""},{"location":"mvc/#mvc-en-het-gebruik-van-packages","title":"MVC en het gebruik van packages","text":"MVC staat voor Model-View-Controller en is een belangrijk, maar wat diffuus concept in software engineering en is vooral van toepassing op gebruikersinterfaces. Het belangrijkste idee is dat een programma zoveel mogelijk wordt opgesplitst in onderdelen. Het model bevat de onderliggende data en concepten van het programma (een database, meetgegevens, berekeningen, etc.); de controller praat met de fysieke omgeving en reageert bijvoorbeeld op invoer van een gebruiker en past het model aan; de view is een weergave van de data uit het model en vormt de gebruikersinterface zelf. Vaak praten alle onderdelen met elkaar, maar een gelaagd model is makkelijker te overzien en dus eenvoudiger te programmeren. In het geval van een natuurkunde-experiment is dit vaak mogelijk. Daarmee krijgt MVC bij ons een andere betekenis dan bijvoorbeeld bij het bouwen van websites. Het gelaagd MVC-model dat wij gaan gebruiken is hieronder weergegeven:
De controllers communiceren met de apparatuur, bevat informatie en berekeningen die apparatuur afhankelijk zijn; het model bevat de meetgegevens, berekeningen over - en de opzet van - het experiment; de view zorgt voor een gebruikersinterface met weergave van de data.
Het scheiden van je programma in deze lagen kan enorm helpen om ervoor te zorgen dat je geen spaghetticode schrijft \u2014 ongestructureerde en moeilijk te begrijpen code. Wanneer het drukken op een knop in de code van de grafische omgeving direct commando's stuurt naar de Arduino of dat de code voor het doen van een enkele meting meteen de $x$-as van een grafiek aanpast, sla je lagen over in ons model en knoop je delen van het programma aan elkaar die niet direct iets met elkaar te maken hebben. De knop moet een meting starten, ja, maar hoe dat precies moet is niet de taak van de gebruikersinterface. En de meting zelf moet zich niet bemoeien met welke grafiek er precies getekend wordt. Je zult merken dat het heel lastig wordt om overzicht te houden en later aanpassingen te doen als je alles door elkaar laat lopen. Je zult dan door je hele code moeten zoeken als je \u00f3f de aansturing van de Arduino, \u00f3f de grafische interface wilt aanpassen. En dus gaan we alles netjes structureren.
De verschillende onderdelen in het model kunnen we voor ons experiment als volgt beschrijven:
View Het startpunt van je applicatie. Geeft de opdracht om een meting te starten en geeft na afloop de resultaten van de meting weer op het scherm. Model De code die het experiment uitvoert door verschillende metingen te doen en instellingen aan te passen, zoals de spanning over de LED. Het model weet hoe het experiment in elkaar zit en dat er bijvoorbeeld een weerstand van 220 \u03a9 aanwezig is. Geeft opdrachten aan de controller. Controller De code die via pyvisa praat met de Arduino. Opdrachten worden omgezet in firmwarecommando's en doorgestuurd naar het apparaat.Het opsplitsen van je programma hoeft niet in \u00e9\u00e9n keer! Dit kan stapsgewijs. Je kunt starten met een eenvoudig script \u2014 zoals we hierboven gedaan hebben \u2014 en dat langzaam uitbreiden. Je begint klein, verdeelt je code in lagen en bouwt vervolgens verder.
"},{"location":"mvc/#implementeren-van-mvc","title":"Implementeren van MVC","text":"Het opsplitsen van het diode-experiment.py
in MVC gaan we stapsgewijs doen. We gaan een class maken voor de aansturing van de Arduino, deze class valt in de categorie controller.
Pythondaq: open de repository
Open in GitHub Desktop de repository van pythondaq
en open de repository in Visual Studio Code. In de volgende opdrachten ga je het diode-experiment.py
uitbreiden en opsplitsen in MVC.
Pythondaq: controller bouwen
opdrachtcodecheckJe schrijft een script waarmee je de Arduino aanstuurt. Een gebruiker test de door jou geschreven controller met de volgende handelingen. De gebruiker vraag een lijst met beschikbare poorten op met de functie list_resources()
. De gebruiker weet aan welke poort de Arduino hangt en gebruikt deze poortnaam om een instance aan te maken van de class ArduinoVISADevice
. Met deze class kan de gebruiker met de Arduino communiceren. Met de method get_identification()
vraagt de gebruiker de identificatiestring op. De gebruiker zet met de method set_output_value()
om een waarde van 828 op het uitvoerkanaal 0, de gebruiker zit de LED branden en weet daarom dat de method werkt. De gebruiker vraag met de method get_input_value()
de spanning op kanaal 1 op, dit herhaald de gebruiker vervolgens voor kanaal 2. Met de method get_input_voltage()
vraagt de gebruiker de spanning op in volt. De gebruiker rekent de gegeven waarde van get_input_value()
op kanaal 1 om naar volt en ziet dat deze overeenkomt met de gegeven spanning door de method get_input_voltage()
op kanaal 1.
Pseudo-code test-controller.py
# def list_resources\n# return list of available ports\n\n# class ArduinoVISADevice\n# def init (ask port from user)\n ...\n# def get_identification\n# return identification string of connected device\n#\n# def set_output_value\n# set a value on the output channel\n#\n# def get_output_value\n# get the value of the output channel\n# \n# def get_input_value\n# get input value from input channel\n#\n# def get_input_voltage\n# get input value from input channel in Volt\n
Testcode: test-controller.py # get available ports\nprint(list_resources())\n\n# create an instance for the Arduino on port \"ASRL28::INSTR\"\ndevice = ArduinoVISADevice(port=\"ASRL28::INSTR\")\n\n# print identification string\nidentification = device.get_identification()\nprint(identification)\n\n# set OUTPUT voltage on channel 0, using ADC values (0 - 1023)\ndevice.set_output_value(value=828)\n\n# measure the voltage on INPUT channel 2 in ADC values (0 - 1023)\nch2_value = device.get_input_value(channel=2)\nprint(f\"{ch2_value=}\")\n\n# measure the voltage on INPUT channel 2 in volts (0 - 3.3 V)\nch2_voltage = device.get_input_voltage(channel=2)\nprint(f\"{ch2_voltage=}\")\n\n# get the previously set OUTPUT voltage in ADC values (0 - 1023)\nch0_value = device.get_output_value()\nprint(f\"{ch0_value=}\")\n
\n(ecpc) > python test-controller.py\n('ASRL28::INSTR', ) \nArduino VISA firmware v1.0.0\nch2_value=224\nch2_voltage=0.7774193548387097\nch0_value=828\n
Checkpunten:
list_resources()
is een functie die buiten de class staat.__init__()
method moet een poortnaam worden meegeven.__init__()
method opent de communicatie met de meegegeven poortnaam.get_identification()
die de identificatiestring teruggeeft.set_output_value()
en get_output_value()
communiceren standaard met kanaal 0.get_input_value
en get_input_voltage
moet een kanaal opgegeven worden.Projecttraject:
Je hebt nu een werkende controller, maar je gebruikt het nog niet in je experiment.
Pythondaq: Controller implementeren
opdrachtcodecheckZet je controller code (zonder de testcode) in het bestand diode-experiment.py
. Pas de code die de meting uitvoert aan zodat deze gebruikt maakt van de class ArduinoVISADevice
en de bijbehorende methods.
Pseudo-code diode-experiment.py
# def list_resources\n# ...\n\n# class ArduinoVISADevice\n ...\n\n# get list resources\n# connect to Arduino via ArduinoVISADevice\n\n# set output voltage from 0 to max\n # measure voltages\n # calculate LED voltage\n # calculate LED current\n\n# plot current vs voltage\n# create csv-file\n
Checkpunten:
list_resources()
, ArduinoVISADevice()
en de code om de LED te laten branden, metingen te doen en het resultaat te laten zien.ArduinoVISADevice()
uit het script wordt geknipt, werkt diode-experiment.py
niet meer.Projecttraject:
Als je de vorige opdracht succesvol hebt afgerond maakt het niet meer uit wat de precieze commando's zijn die je naar de hardware moet sturen. Als je de Arduino in de opstelling vervangt voor een ander meetinstrument moet je de class aanpassen, maar kan alle code die met het experiment zelf te maken heeft hetzelfde blijven.
Nu we de controller hebben gemaakt die de Arduino aanstuurt, blijft er nog een stukje code over. Het laatste stuk waar de plot en het CSV-bestand gemaakt worden kunnen we beschouwen als een view en de rest van de code \u2014 waar de metingen worden uitgevoerd en de stroomsterkte $I$ wordt berekend \u2014 is een model. We gaan de code nog wat verder opsplitsen om dat duidelijk te maken \u00e9n onderbrengen in verschillende bestanden \u2014 dat is uiteindelijk beter voor het overzicht.
Pythondaq: Controller afsplitsen
opdrachtcodecheck In latere opdrachten ga je een command-line interface en een grafische user interface maken voor het experiment. Daarom is het handig om alvast overzicht cre\u00ebren door de verschillende onderdelen in aparte scripts te zetten. Het bestand arduino_device.py
bevat de class ArduinoVISADevice
en de functie list_resources()
. Deze class en functie importeer je in het bestand diode-experiment.py
. ECPC
\u251c\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 arduino_device.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 diode-experiment.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022
error
Waarschijnlijk krijg je nog een of meerdere errors als je diode-experiment.py
runt. Lees het error bericht goed door, om welk bestand gaat het arduino_device.py
of diode-experiment.py
? Wat is er volgens het error bericht niet goed?
Pseudo-code arduino_device.py
# def list_resources\n# ...\n\n# class ArduinoVISADevice\n ...\n
diode-experiment.pyfrom arduino_device import ArduinoVISADevice, list_resources\n\n# get list resources\n# connect to Arduino via ArduinoVISADevice\n\n# set output voltage from 0 to max\n # measure voltages\n # calculate LED voltage\n # calculate LED current\n\n# plot current vs voltage\n# create csv-file\n
Checkpunten:
diode-experiment.py
zorgt ervoor dat een meting start.Projecttraject:
if __name__ == '__main__'
opdrachtcodecheck Later wil je de functie list_resources()
netjes in het hele model-view-controller systeem vlechten zodat je als gebruiker de lijst kunt opvragen, maar voor nu wil je af en toe even zien aan welke poort de Arduino hangt. Wanneer je het script arduino_device.py
runt wordt er een lijst geprint met poorten. Dit gebeurt niet wanneer het bestand diode-experiment.py
wordt gerund.
modules
Nog niet bekend met if __name__ == '__main__'
? kijk dan voor meer informatie in de paragraaf modules.
Pseudo-code arduino_device.py
# def list_resources\n# ...\n\n# class ArduinoVISADevice\n ...\n\n# print list ports if arduino_device.py is the main script \n# print list ports not if arduino_device.py is imported as a module in another script\n
diode-experiment.pyfrom arduino_device import ArduinoVISADevice, list_resources\n\n# connect to Arduino via ArduinoVISADevice\n\n# set output voltage from 0 to max\n # measure voltages\n # calculate LED voltage\n # calculate LED current\n\n# plot current vs voltage\n# create csv-file\n
Checkpunten:
arduino_device.py
wordt gerund.diode-experiment.py
wordt gerund.Pythondaq: Model afsplitsen
opdrachtcodecheck Omdat je in latere opdrachten een command-line interface en een grafische user interface gaat maken voor het experiment is het handig om alvast overzicht cre\u00ebren door de verschillende onderdelen in aparte scripts te zetten. Maak een bestand run_experiment.py
waarin de gebruiker een paar dingen kan aanpassen. De gebruiker test de door jou geschreven applicatie (view, model, controller) met de volgende handelingen. Het runnen van het bestand run_experiment.py
geeft een lijst van aangesloten instrumenten. De gebruiker past in het bestand run_experiment.py
de poortnaam aan naar een poort waarop een Arduino is aangesloten. De instance van de class DiodeExperiment
die uit het model wordt ge\u00efmporteerd gebruikt deze poortnaam om de communicatie met de Arduino te openen. De gebruiker roept de method scan()
aan van de class DiodeExperiment
waarna een meting wordt gestart. Om gegevens van het naar de Arduino te sturen maakt het model gebruik van de controller. De gegevens die het model terugkrijgt van de Arduino worden volgens de fysische relaties verwerkt tot de benodigde gegevens en doorgestuurd naar de view. De resultaten worden in een plot getoond en naar een CSV-bestand weggeschreven. De gebruiker past het bereik van de meting aan door door de start- en stopparameters, die aan de method scan()
worden meegegeven, aan te passen. ECPC
\u251c\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 arduino_device.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 diode_experiment.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 run_experiment.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code arduino_device.py
# def list_resources\n# ...\n\n# class ArduinoVISADevice\n ...\n
diode_experiment.pyfrom arduino_device import ArduinoVISADevice, list_resources\n\n# class DiodeExperiment\n ...\n # connect to Arduino via ArduinoVISADevice\n ...\n # def scan with start, stop\n # set output voltage from 0 to max\n # measure voltages\n # calculate LED voltage\n # calculate LED current\n # return LED voltage, LED current\n
run_experiment.pyfrom diode_experiment import DiodeExperiment, list_resources\n\n# get list resources\n# connect to Arduino via DiodeExperiment\n\n# get current and voltage from scan(start, stop)\n\n# plot current vs voltage\n# create csv-file\n
Checkpunten:
DiodeExperiment
run_experiment.py
zorgt ervoor dat een meting start.Projecttraject:
Het oorspronkelijke script dat je gebruikte voor je meting is steeds leger geworden. Als het goed is gaat nu (vrijwel) het volledige script alleen maar over het starten van een meting en het weergeven en bewaren van de meetgegevens. In het view script komen verder geen berekeningen voor of details over welk kanaal van de Arduino op welke elektronische component is aangesloten. Ook staat hier niets over welke commando's de Arduino firmware begrijpt. Dit maakt het veel makkelijker om in de vervolghoofdstukken een gebruiksvriendelijke applicatie te ontwikkelen waarmee je snel en eenvoudig metingen kunt doen.
Pythondaq: Onzekerheid
opdrachtcodecheckOmdat je never nooit je conclusies gaat baseren op een enkele meetserie ga je de meting herhalen en foutenvlaggen toevoegen. Je moet weer even hard nadenken over hoe je dat bepaalt en hoe je dat in je code gaat verwerken. Daarom pak je pen en papier, stoot je je buurmens aan en samen gaan jullie nadenken over hoe jullie in dit experiment de onzekerheid kunnen bepalen. Daarna kijken jullie naar de opbouw van de code en maken jullie aantekeningen over wat er waar en hoe in de code aangepast moet worden. Je kijkt naar je repository en ziet dat je de nu-nog-werkende-code hebt gecommit vervolgens ga je stap voor stap (commit voor commit) aan de slag om de aanpassingen te maken. Als het klaar is run je run_experiment.py
met het aantal herhaalmetingen op 3 en ziet in de grafiek foutenvlaggen op de metingen voor stroom en spanningen staan. Je kijkt op het beeldscherm van je buurmens en ziet daar ook foutenvlaggen verschijnen. Met een grijns kijken jullie elkaar aan en geven een high five .
Pseudo-code arduino_device.py
# def list_resources\n# ...\n\n# class ArduinoVISADevice\n ...\n
diode_experiment.pyfrom arduino_device import ArduinoVISADevice, list_resources\n\n# class DiodeExperiment\n ...\n # connect to Arduino via ArduinoVISADevice\n ...\n def scan # with start, stop and number of measurements\n # set output voltage from 0 to max\n # measure voltages\n # calculate LED voltage\n # calculate LED current\n # return LED voltage, LED current and errors\n
run_experiment.pyfrom diode_experiment import DiodeExperiment\n\n# get current and voltage with errors from scan(start, stop, measurements)\n\n# plot current vs voltage with errorbars\n# create csv-file\n
Checkpunten:
Projecttraject:
De gebruiker moet in de view het script aanpassen om een andere meting te doen. Kun je input()
gebruiken om van de gebruiker input te vragen voor de start, stop en aantal metingen?
Als de gebruiker in de run_experiment.py
per ongeluk een negatieve startwaarde of negatieve aantal metingen invult gaat het niet goed. Gebruik Exceptions om dergelijke gevallen af te vangen en een duidelijke error af te geven.
In de vorige hoofdstukken heb je gewerkt met een eigen conda environment zodat je jouw pythonomgeving mooi gescheiden kan houden van andere studenten die op dezelfde computer werken en voor het isoleren van de verschillende projecten waar je aan werkt. Dit is echt de oplossing voor alle problemen waarbij volledige Pythoninstallaties onbruikbaar kunnen worden \u2014 waarna je alles opnieuw moet installeren.
Opnieuw beginnen of 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\u2026? Niet handig. Als je code gaat delen met elkaar krijg je regelmatig te maken met een ImportError
waarna je weer \u00e9\u00e9n 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 kan delen.
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 packages zijn niet genoeg. Welke versies van numpy
werken met de huidige versie van matplotlib
? Allemaal zaken die je \u2014 als je een package schrijft \u2014 zelf moet bijhouden. Het voordeel is dat jouw gebruikers alleen maar jouw pakket hoeven te installeren \u2014 de rest gaat vanzelf.
En\u2026 hoe test je je package zodat je zeker weet dat hij 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 hij een ImportError
. De bestanden zijn er wel, maar worden verkeerd ge\u00efmporteerd.
Hoe krijg je eigenlijk je code bij iemand anders? Liefst als \u00e9\u00e9n bestand, of zelfs met pip install my_new_cool_app
; dat zou wel mooi zijn.
En daar is Poetry.
Er zijn meerdere tools ontwikkeld om dezelfde problemen op te lossen. Poetry is heel populair geworden. Het richt zich op het offici\u00eble ecosysteem: standaard Python packages, ofwel PyPI en pip
; niet conda
(zie meer hierover in paragraaf pip vs conda). Jammer, maar dit zorgt er wel voor dat iedereen m\u00e9t of z\u00f3nder Anaconda je package kan installeren. Dat is dan wel weer fijn. Wij gaan Anaconda gebruiken om een virtual environment met alleen Python te maken. Vervolgens installeren we alles dat we nodig hebben met pip
. Dat werkt prima, want we mengen verder geen conda
met pip
packages. Het maken van conda packages valt daarmee buiten het bestek van deze cursus, al is dat een relatief kleine stap als je je standaard Python package af hebt.
Werken in een terminal
Poetry is een tool die je enkel en alleen in de terminal kunt gebruiken. Het heeft alleen een command-line interface (CLI). Ben je nog niet zo bekend met het navigeren in een terminal dan kun je als oefening de Terminal Adventure Game spelen.
Poetry installeren
Om Poetry te installeren gaan we gebruik maken van pipx
, zie voor meer informatie paragraaf pipx. Eerst moeten we pipx
installeren
conda create --name pipx python\n
Terminalconda activate pipx\n
Terminalpython -m pip install --user pipx\n
python -m pipx ensurepath\n
pipx\n
Nu kunnen we Poetry
installeren met pipx
.
pipx install poetry\n
poetry\n
Poetry doet het niet in Visual Studio Code
Werkt Poetry niet in een terminal in Visual Studio code? Gooi de oude terminals weg, sluit Visual Studio Code en GitHub Desktop af. Open Visual Studio Code weer via GitHub Desktop, open een nieuwe terminal en kijk of het nu wel werkt.
We gaan Poetry bedienen door commando's te geven in de terminal van Visual Studio Code. We laten de terminal weten welk programma wij willen gaan besturen, door poetry
in te typen. En daarachter wat we willen dat Poetry gaat doen. We kunnen informatie over Poetry opvragen met het commando about
.
(ecpc) > poetry about \nPoetry - Package Management for Python\n\nVersion: 1.8.4\nPoetry-Core Version: 1.9.1\n\nPoetry is a dependency manager tracking local dependencies of your projects and libraries.\nSee https://github.com/python-poetry/poetry for more information.\n
Poetry about
Open een terminal en vraag informatie over Poetry op met het commando poetry about
. Lees de tekst die Poetry aan je teruggeeft, waar kan je meer informatie vinden?
Info
We gaan 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 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 Poetry project aanmaken
opdrachtcodecheckEen project stop je altijd in een map , als je aan Poetry vraagt om een project te maken zal er een nieuwe (project)map worden aangemaakt. Je denkt na over een geschikte locatie en besluit dat de projectmap in de ECPC
map moet komen te staan. Je opent Visual Studio Code en opent de map ECPC
. Je opent een terminal en controleert dat de terminal ook in de map ECPC
is. Je geeft Poetry de opdracht om een nieuw project met de naam easystat
aan te maken in de src-layout10 met het commando poetry new --src easystat
. Je bekijkt de nieuw gemaakte mappenstructuur en ziet dat het overeenkomt met de mappenstructuur zoals hieronder weergegeven:
ECPC
\u251c\u2500\u2500 oefenopdrachten
\u251c\u2500\u2500 pythondaq
\u251c\u2500\u2500 easystat
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 src
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 easystat
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 tests
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 pyproject.toml
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 readme.md
\u2514\u2500\u2500 \u2022\u2022\u2022
src-layout
Door het project in een source layout (src-layout) te bouwen maken we het expres iets moeilijker om vanuit een script je package te importeren. Je kunt dat dan alleen nog maar doen door het package zelf ook te installeren (zoals andere gebruikers ook moeten doen) en daardoor loop je zelf tegen eventuele problemen aan. Werkt het uiteindelijk bij jou? Dan werkt het ook bij andere mensen.
Testcode
(ecpc) > poetry new --src easystat \nCreated package easystat in easystat\n
Checkpunten:
easystat
staat in de map ECPC
.easystat
staat een map src
.src
staat een package map easystat
Projecttraject
Bekijk nog eens de mappenstructuur. Allereerst is er een projectmap easystat
(waar de map src
in staat) aangemaakt . Je kunt nu in GitHub Desktop deze map easystat
toevoegen als nieuwe repository, zoals we gedaan hebben in opdracht Repository toevoegen.
Laten we \u00e9\u00e9n voor \u00e9\u00e9n kijken welke mappen en bestanden Poetry heeft aangemaakt. We zien een README.md
in de projectmap staan. Hierin komt een algemene beschrijving van ons project.2
Daarna is er een map tests
. Goede software wordt getest. In deze map komen bestanden te staan die delen van de code runnen en resultaten vergelijken met verwachte resultaten \u2014 zoals je kunt doen in opdracht Packages.3
Dan komt de src
-map. Daarin komt ons nieuwe package easystat
4 te staan. Er is alvast voor ons een __init__.py
aangemaakt. Handig!
En als laatste\u2026 een pyproject.toml
5 waarin alle informatie over je project wordt bijgehouden. Ook staat er in dit bestand informatie voor de verschillende tools die je kunt gebruiken. De inhoud van het bestand ziet er ongeveer zo uit:
[tool.poetry]\nname = \"easystat\"\nversion = \"0.1.0\"\ndescription = \"\"\nauthors = [\"David Fokkema <davidfokkema@icloud.com>\"]\nreadme = \"README.md\"\npackages = [{include = \"easystat\", from = \"src\"}]\n\n[tool.poetry.dependencies]\npython = \"^3.13\"\n\n\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n
Het bestand is in het TOML-formaat.11 Tussen de vierkante haken staan de koppen van de verschillende secties in dit configuratiebestand. Overal zie je poetry
terugkomen, want dat is de tool die wij gebruiken. In de eerste sectie staat informatie over ons project. Je kunt daar bijvoorbeeld een beschrijving toevoegen of het versienummer aanpassen. De tweede sectie bevat de dependencies. Dit zijn alle Pythonpackages die ons project nodig heeft. Op dit moment is dat alleen maar Python. Ook het versienummer van Python is belangrijk. Hier is dat 3.13 en het dakje geeft aan dat nieuwere versies 3.14, 3.15, enz. ook prima zijn, maar 3.12 (te oud) en 4.0 (te nieuw) niet. Dit kan belangrijk zijn. Gebruikers met een iets oudere versie van Python \u2014 bijvoorbeeld versie 3.11 \u2014 kunnen nu het 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 dit moment \u2014 herfst 2024 \u2014 is Python 3.13 de nieuwste versie. Het is dus prima om minimaal 3.12 te vragen \u2014 die versie is inmiddels een jaar oud.
Bij het schrijven van een nieuw package is het z\u00e9ker belangrijk om een conda environment te gebruiken. Anders loop je het risico dat je package lijkt te werken maar bij iemand anders crasht. Immers, het kan best zijn dat jij NumPy gebruikt en al eerder ge\u00efnstalleerd had. Bij iemand die NumPy nog niet ge\u00efnstalleerd had gaat het dan mis.
Easystat conda environment aanmaken
opdrachtcodecheckJe voegt de projectmap easystat
toe als existing/local repository in GitHub . Vanuit GitHub Desktop open je de repository easystat
in Visual Studio Code. Je maakt in Anaconda Prompt een nieuwe conda environment aan met de naam easystat
en daarin python=3.12
. Uiteraard selecteer je het nieuwe environment in Visual Studio Code.
Testcode
(easystat) > conda list \n# packages in environment at C:\\easystat:\n#\n# Name Version Build Channel\nbzip2 1.0.8 h2bbff1b_6\nca-certificates 2024.7.2 haa95532_0\nlibffi 3.4.4 hd77b12b_1\nopenssl 3.0.15 h827c3e9_0\npip 24.2 py310haa95532_0\npython 3.12.7 h99e199e_0\nsetuptools 72.1.0 py310haa95532_0\nsqlite 3.45.3 h2bbff1b_0\ntk 8.6.14 h0416ee5_0\ntzdata 2024a h04d1e81_0\nvc 14.40 h2eaa2aa_1\nvs2015_runtime 14.40.33807 h98bb1dd_1\nwheel 0.43.0 py310haa95532_0\nxz 5.4.6 h8cc25b3_1\nzlib 1.2.13 h8cc25b3_1\n
Checkpunten:
easystat
is geopend in Visual Studio Code.easystat
.easystat
geactiveerd.Projecttraject
conda-forge
Merk op dat we nu niet gebruik hoeven te maken van de conda-forge
channel. Python zelf staat in alle kanalen en we gaan verder geen software installeren met conda, dus ook niet uit conda-forge
.
We starten met ons package. Stel, we berekenen vaak de standaarddeviatie van het gemiddelde en maken daarvoor een handige shortcut in shortcuts.py
. Nu willen we deze shortcut ook in een ander script gebruiken. Dit kunnen we doen door package easystat
te importeren in dit nieuwe script zodat we de functie stdev_of_mean
daar ook kunnen gebruiken. We maken een script try_shortcuts.py
om dit te testen.
Easystat shortcuts.py en try_shortcuts.py aanmaken
Maak zoals hieronder aangegeven de bestanden shortcuts.py
en try_shortcuts.py
aan: shortcuts.py
import numpy as np \n\n\ndef stdev_of_mean(values):\n \"\"\"Calculate the standard deviation of the mean\"\"\"\n return np.std(values) / np.sqrt(len(values)) \n
try_shortcuts.pyfrom easystat.shortcuts import stdev_of_mean\n\nprint(f\"{stdev_of_mean([1, 2, 2, 2, 3])=}\")\n
easystat
\u251c\u2500\u2500 src
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 easystat
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 shortcuts.py
\u251c\u2500\u2500 tests
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 try_shortcuts.py
\u251c\u2500\u2500 pyproject.toml
\u2514\u2500\u2500 readme.md
Import numpy could not be resolved
Misschien is het je al opgevallen dat VS Code een oranje kringeltje onder numpy
zet in de eerste regel. Als je daar je muiscursor op plaatst krijg je een popup met de melding Import numpy could not be resolved
. Daar moeten we misschien wat mee en dat gaan we straks ook doen.
In de eerste regel van test_shortcuts.py
importeren we de functie uit het nieuwe package om uit te proberen. In de laatste regel gebruiken we een handige functie van f-strings.6
Easystat try_shortcuts.py testen
opdrachtcodecheckJe bent heel benieuwd of je package al werkt. Je runt het bestand try_shortcuts.py
en krijgt een foutmelding...
Testcode try_shortcuts.py
from easystat.shortcuts import stdev_of_mean\n\nprint(f\"{stdev_of_mean([1, 2, 2, 2, 3])=}\")\n
\n(easystat) > python try_shortcuts.py\nTraceback (most recent call last):\n File \"c:\\ECPC\\easystat\\tests\\try_shortcuts.py\", line 1, in < module >\n from easystat.shortcuts import stdev_of_mean\nModuleNotFoundError: No module named 'easystat'\n\n\n\nCheckpunten:
\n\n- Je hebt de juiste conda environment geactiveerd.
\n- Je runt het bestand
try_shortcuts.py
uit de map tests
. \n- Je krijgt een foutmelding
ModuleNotFoundError: No module named 'easystat'
\n
\nProjecttraject
\n\n- Easystat Poetry project aanmaken
\n- Easystat conda environment aanmaken
\n- Easystat shortcuts.py en try_shortcuts.py aanmaken
\n- Easystat try_shortcuts.py testen
\n- Easystat Poetry install
\n- Easystat dependencies toevoegen
\n
\n\n\n\n\nDit konden we verwachten. We hebben onze package immers nog niet ge\u00efnstalleerd. Als we onze package gaan delen met andere mensen verwachten wij dat zij onze package ook gaan installeren, door dezelfde stappen te doorlopen als andere gebruikers komen we erachter of alles wel goed werkt.
"},{"location":"poetry/#installeren-van-een-package","title":"Installeren van een package","text":"Het installeren van de package kan makkelijk met Poetry:
\n(easystat) > poetry install \nUpdating dependencies\nResolving dependencies... (0.1s)\n\nWriting lock file\n\nInstalling the current project: easystat (0.1.0)\n
\n\nPoetry is even bezig en ons package is ge\u00efnstalleerd.
\n\nEasystat Poetry install
\nopdrachtcodecheck\n\n\nJe opent een terminal in Visual Studio Code. Je gaat het project easystat
installeren in de conda environment easystat
met het commando poetry install
. Waarschijnlijk krijg je een error (zie info-blok hieronder) maar door rustig te lezen los je die op. Je installeert alsnog het project easystat
draai je opnieuw tests/try_shortcuts.py
en zie je een nieuwe error verschijnen ModuleNotFoundError: No module named 'numpy'
. Hoera de eerste error is met succes opgelost en je kunt door met de volgende opdracht.
\n\nCurrent Python version is not allowed by the project
\nWaarschijnlijk krijg je in dikke rode letters de error:\n
Current Python version (3.12.7) is not allowed by the project (^3.13).\nPlease change python executable via the \"env use\" command.\n
\nIn de pyproject.toml
staat bij de Python dependency dat er minstens versie 3.13 of hoger (^3.13) nodig is voor dit project7. En de conda environment easystat
heeft Python 3.12 ge\u00efnstalleerd. Je kunt nu twee dingen doen: \n\n- Je bedenkt dat voor dit project een lagere versie van Python ook voldoende is en past de Python versie dependency aan in de
pyproject.toml
naar ^3.12. \n- Je vindt dat het project minstens versie 3.13 moet gebruiken en upgrade Python in de
easystat
environment met conda install python=3.13
. \n
\n\n\n\nTestcode\n
(easystat) > conda list \n# packages in environment at C:\\easystat:\n#\n# Name Version Build Channel\nbzip2 1.0.8 h2bbff1b_6\nca-certificates 2024.7.2 haa95532_0\neasystat 0.1.0 pypi_0 pypi\nlibffi 3.4.4 hd77b12b_1\nopenssl 3.0.15 h827c3e9_0\npip 24.2 py310haa95532_0\npython 3.10.14 he1021f5_1\nsetuptools 72.1.0 py310haa95532_0\nsqlite 3.45.3 h2bbff1b_0\ntk 8.6.14 h0416ee5_0\ntzdata 2024a h04d1e81_0\nvc 14.40 h2eaa2aa_1\nvs2015_runtime 14.40.33807 h98bb1dd_1\nwheel 0.43.0 py310haa95532_0\nxz 5.4.6 h8cc25b3_1\nzlib 1.2.13 h8cc25b3_1\n
\n\n\nCheckpunten:
\n\n- Je hebt de juiste conda environment geactiveerd.
\n- Nadat je
poetry install
hebt gedaan krijg je de melding Installing the current project: easystat (0.1.0)
. \n- Je runt het bestand
tests/try_shortcuts.py
. \n- Je krijgt een foutmelding
ModuleNotFoundError: No module named 'numpy'
\n
\nProjecttraject
\n\n- Easystat Poetry project aanmaken
\n- Easystat conda environment aanmaken
\n- Easystat shortcuts.py en try_shortcuts.py aanmaken
\n- Easystat try_shortcuts.py testen
\n- Easystat Poetry install
\n- Easystat dependencies toevoegen
\n
\n\n\n\n\nAls we het testscript nu draaien krijgen we w\u00e9\u00e9r een foutmelding:\n
ModuleNotFoundError: No module named 'numpy'\n
\nOns package heeft NumPy nodig en dat hebben we nog niet ge\u00efnstalleerd. Dat kunnen we handmatig doen maar dan hebben andere gebruikers een probleem. Veel beter is het om netjes aan te geven dat ons package NumPy nodig heeft \u2014 als dependency."},{"location":"poetry/#dependencies-toevoegen","title":"Dependencies toevoegen","text":"Om een dependency aan te geven vertellen we Poetry dat hij deze moet toevoegen met:
\n(easystat) > poetry add numpy \nUsing version ^1.23.2 for numpy\n\nUpdating dependencies\nResolving dependencies...\n\nWriting lock file\n\nPackage operations: 1 install, 0 updates, 0 removals\n\n \u2022 Installing numpy (1.23.2)\n
\n\n\nEasystat dependencies toevoegen
\nopdrachtcodecheck\n\n\nJe voegt Numpy
als dependency toe aan het project easystat
met het commando poetry 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 de conda environment easystat
is ge\u00efnstalleerd en controleert dit met conda list
en waarachtig Numpy
staat in de lijst . Weer ga je tests/try_shortcuts.py
draaien en ditmaal krijg je een uitkomst!
\n\n\nTestcode\n try_shortcuts.py\n
from easystat.shortcuts import stdev_of_mean\n\nprint(f\"{stdev_of_mean([1, 2, 2, 2, 3])=}\")\n
\n\n(ecpc) > python try_shortcuts.py\nstdev_of_mean([1, 2, 2, 2, 3])=np.float64(0.282842712474619)\n
\n\n\nCheckpunten:
\n\n- Je hebt de juiste conda environment geacitveerd.
\n- Je hebt
Numpy
als dependency toegevoegd. \n- Je krijgt een uitkomst als je het bestand
tests/try_shortcuts.py
runt. \n
\nProjecttraject
\n\n- Easystat Poetry project aanmaken
\n- Easystat conda environment aanmaken
\n- Easystat shortcuts.py en try_shortcuts.py aanmaken
\n- Easystat try_shortcuts.py testen
\n- Easystat Poetry install
\n- Easystat dependencies toevoegen
\n
\n\n\n\n\nFijn! Het verwijderen van dependency PACKAGE
gaat met poetry remove PACKAGE
. Poetry heeft Numpy nu toegevoegd aan de environment easystat
.Gewone package managers als Pip en Conda zullen geen packages toevoegen aan je Poetry project als je pip/conda install package
aanroept. Gebruik daarom altijd poetry add package
als je met Poetry aan een package werkt.
\n\nInfo
\nAls we de code in ons package aanpassen dan hoeven we het niet opnieuw te installeren met Poetry, maar als we met de hand iets wijzigen in de pyproject.toml
dan moet dat wel. Als je een ImportError
krijgt voor je eigen package \u2014 bijvoorbeeld als je nieuwe mappen of bestanden hebt aangemaakt \u2014 probeer dan eerst voor de zekerheid poetry install
.
\n\n\nPoetry.lock\n\n\nWheels"},{"location":"poetry/#poetrylock","title":"Poetry.lock","text":"Na het toevoegen van Numpy is er ook een bestand poetry.lock
bijgekomen. Hierin staan de exacte versies van alle ge\u00efnstalleerde packages. Vaak wordt dit bestand gecommit zodat collega-ontwikkelaars exact dezelfde versies installeren zodra ze poetry install
aanroepen. Om dat te proberen maken we even een schone conda environment:
\n\nSchone environment
\n\n- Maak een schone conda environment met
conda create --name easystat python=3.12
\n- Kies voor ja als Conda een waarschuwing geeft dat deze environment al bestaat en vraagt of je het bestaande environment wilt verwijderen.
\n- Draai
tests/try_shortcuts.py
en bekijk de foutmelding. \n
\n\nWe krijgen meteen foutmeldingen. Immers, we hebben nog niets ge\u00efnstalleerd.
\n\nPoetry.lock
\n\n- Installeer de
easystat
package met poetry
. \n- Waarvoor gebruikt Poetry de lock file (
poetry.lock)
? \n- Draai
tests/try_shortcuts.py
en bekijk de uitkomst. \n
"},{"location":"poetry/#wheels","title":"Wheels","text":"Wanneer we klaar zijn om ons package te delen met andere gebruikers gebruiken we het commando build
om wheels te bouwen.
\n\nBouw een wheel
\n\n- Bouw het wheel van easystat met
poetry build
. \n- Bekijk de namen van de bestanden in de nieuwe map
easystat/dist
, welke extensie hebben ze? \n
\n\n(ecpc) > poetry build \nBuilding easystat (0.1.0)\n - Building sdist\n - Built easystat-0.1.0.tar.gz\n - Building wheel\n - Built easystat-0.1.0-py3-none-any.whl\n
\nEen sdist is een source distribution. Een .tar.gz
-bestand is een soort zipbestand met daarin de broncode van ons pakket. De tests worden daar niet in meegenomen. Een wheel is een soort bestand dat direct ge\u00efnstalleerd kan worden met pip
. Zogenaamde pure-python packages bevatten alleen Pythoncode \u2014 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. We kunnen dit bestand als download neerzetten op een website of aan anderen mailen.\n\nTest wheel
\nLaten we het wheel uitproberen. We gaan straks een nieuwe conda environment aanmaken, installeren het wheel en proberen het testscript te runnen \u2014 \u00e9\u00e9n keer v\u00f3\u00f3r het installeren van het wheel en \u00e9\u00e9n keer n\u00e1 het installeren, als volgt:
\n\n- Maak een nieuwe conda environment aan met de naam
test-wheel
en activeer deze.\n TerminalPS> conda create --name test-wheel python=3.12\n...\nPS> conda activate test-wheel\n
\n- Draai
tests/try_shortcuts.py
en bekijk de foutmelding. \n- Installeer het wheel met
pip install dist/easystat-0.1.0-py3-none-any.whl
. \n- Draai
tests/try_shortcuts.py
en bekijk de uitkomst. \n
\n\nHet werkt! Je ziet dat pip install
niet alleen ons package easystat
installeert, maar ook de dependency numpy
. Dat is precies wat we willen.
\nHet 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.
"},{"location":"poetry/#poetry-gebruiken-voor-een-bestaand-project","title":"Poetry gebruiken voor een bestaand project","text":"Met poetry new
start je een nieuw project en maakt Poetry voor jou bestanden en mappen aan waarmee je aan de slag kunt. Maar vaak ben je al bezig met een project en wil je dat niet overschrijven. Ook is het een gedoe om een nieuw project te maken en daar je bestaande code in te kopie\u00ebren. Gelukkig kun je Poetry ook vertellen dat je al bezig bent en dat Poetry alleen een pyproject.toml
-bestand moet aanmaken. Run dan in de map van je project:\nTerminal
poetry init --no-interaction\n
\nJe geeft met poetry init
de opdracht om Poetry alleen te initialiseren en --no-interaction
voorkomt je dat je eerst honderd vragen krijgt over je project. Meestal kies je toch de standaardantwoorden.8\n\nInfo
\nVergeet niet \u2014 waar nodig \u2014 de __init__.py
bestanden toe te voegen aan de packages. Meer informatie over de __init__.py
bestanden vind je in paragraaf packages.
\n\n\nInfo
\nAls je al bezig bent met een project dan werk je als het goed is al in een conda environment. Daar heb je dan met conda install
al packages ge\u00efnstalleerd die je nodig hebt. Het gebeurt dan makkelijk dat je vergeet om dat aan te geven met poetry add
. Dat betekent alleen dat als iemand anders je package installeert dat er dependencies missen en dat jouw code dus niet werkt! Dit is makkelijk op te lossen. Zodra je Poetry gaat gebruiken wis dan je environment en maak een nieuwe aan met alleen Python. Dat gaat het makkelijkst als volgt. Stel dat je bezig bent in het environment pythondaq
. We maken dan een nieuw environment met dezelfde naam:\n
(ecpc) > conda create --name pythondaq python=3.12 \nWARNING: A conda environment already exists at '/Users/david/opt/anaconda3/envs/pythondaq'\nRemove existing environment (y/[n])? y \n...\n
\nJe overschrijft dus je huidige environment met een nieuwe, lege. Je kunt daarna met poetry add
packages toevoegen net zo lang tot je geen ImportError
meer krijgt.
\n\n\nPoetry flashingLED
\nopdrachtcodecheck\n\n\n\n \n Je gaat een bestaand project maken zodat je kunt oefenen om daar Poetry aan toe te voegen. Omdat de opdracht flashingLED een oefenopdracht was voor Pythondaq
besluit je deze als oefenpackage te gebruiken. Je maakt een nieuwe repository flasher
aan en opent deze in Visual Studio Code. Je maakt zelf in de repository flasher
de src-layout van mappen en bestanden, zoals hier rechts is weergegeven. Het bestand flashingLED
heb je gekopieerd uit je repository oefenopdrachten
. \n \n \n Nu het oefenpackage klaar staat (commit) maak je een nieuwe conda environment met de naam flasher
met daarin python=3.12
. Je activeert de environment flasher
en voegt Poetry toe aan de bestaande projectmap flasher
. Je installeert het Poetry pakket in de flasher
conda environment en daarna voeg je de benodigde dependencies toe (in ieder geval pyvisa-py
maar wat nog meer?) net zolang tot het scriptje weer werkt . \n \n \n Tot slot wil je testen of het nu ook werkt in een nieuwe conda environment. Dus je maakt weer een nieuwe conda environment met de naam flasher
met daarin python=3.12
. Je installeert het Poetry pakket in de flasher
conda environment . Dan test je of het scriptje nog werkt.\n \n \n ECPC
\n \u251c\u2500\u2500 oefenopdrachten
\n \u251c\u2500\u2500 pythondaq
\n \u251c\u2500\u2500 flasher
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 src
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 flasher
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 flashingLED.py
\n \u2514\u2500\u2500 \u2022\u2022\u2022\n \n
\n\nNo module named 'serial'
\nWaarschijnlijk krijg je onder andere de foutmelding:\n
ValueError: Please install PySerial (>=3.0) to use this resource type.\n No module named 'serial'\n
\nSuper handig dat iemand daarboven heeft opgeschreven wat je moet doen om dit probleem op te lossen. Maar waarom moeten we nu ineens PySerial
installeren9? Dat komt omdat we eerst pyvisa-py
met conda uit de conda-forge channel installeerde en daar komt PySerial
als dependencie mee. Nu installeerd Poetry met behulp van pip pyvisa-py
en daar komt PySerial
niet automatisch mee. En dus moeten we het nu zelf handmatig toevoegen.\n\n\n\nTestcode\n flasherLED.py\n
import pyvisa\nimport numpy as np\nimport time\n\nrm = pyvisa.ResourceManager(\"@py\")\nports = rm.list_resources()\nprint(ports)\ndevice = rm.open_resource(\n \"ASRL3::INSTR\", read_termination=\"\\r\\n\", write_termination=\"\\n\"\n)\n\nfor value in np.arange(0, 10):\n device.query(f\"OUT:CH0 {0}\")\n time.sleep(1)\n device.query(f\"OUT:CH0 {1023}\")\n time.sleep(1)\n
\n\n(ecpc) > python flasherLED.py\n()\nTraceback (most recent call last):\n File \"c:\\ECPC\\flasher\\src\\flasher\\flashingLED.py\", line 8, in \n device = rm.open_resource(\n File \"C:\\envs\\flasher\\lib\\site-packages\\pyvisa\\highlevel.py\", line 3292, in open_resource\n res.open(access_mode, open_timeout)\n File \"C:\\envs\\flasher\\lib\\site-packages\\pyvisa\\resources\\resource.py\", line 281, in open\n self.session, status = self._resource_manager.open_bare_resource(\n File \"C:\\envs\\flasher\\lib\\site-packages\\pyvisa\\highlevel.py\", line 3217, in open_bare_resource\n return self.visalib.open(self.session, resource_name, access_mode, open_timeout)\n File \"C:\\envs\\flasher\\lib\\site-packages\\pyvisa_py\\highlevel.py\", line 168, in open\n sess = cls(session, resource_name, parsed, open_timeout)\n File \"C:\\envs\\flasher\\lib\\site-packages\\pyvisa_py\\sessions.py\", line 861, in init\n raise ValueError(self.session_issue)\nValueError: Please install PySerial (>=3.0) to use this resource type.\nNo module named 'serial'\n\n\n\nCheckpunten:
\n\n- Je hebt een repository
flasher
met daarin een src-layout. \n- Je hebt de juiste conda environment geactiveerd.
\n- Poetry is toegevoegd aan het project.
\n- Alle benodigde dependencies staan in het
pyproject.toml
en zijn ge\u00efnstalleerd in de conda environment. \n- Het runnen van
flashingLED.py
laat het LED knipperen. \n- Als het Poetry project wordt ge\u00efnstalleerd in een nieuwe conda environement met alleen Python=3.12 gaat het LED weer knipperen als
flashingLED.py
wordt uitgevoerd. \n
\nProjecttraject
\n\n- Communicatie met een meetinstrument: flashingLED
\n- Versiebeheer met GitHub: Repository toevoegen
\n- Poetry flashingLED
\n
"},{"location":"poetry/#poetry-gebruiken-voor-pythondaq","title":"Poetry gebruiken voor pythondaq","text":"Natuurlijk willen we Poetry ook gaan gebruiken bij pythondaq
. Daarvoor moeten we twee dingen doen. Als eerste gaan we de pythondaq
repository in een src
-structuur zetten en daarna gaan we Poetry initialiseren.
\n\nPythondaq: src-layout
\n\n \n Je project pythondaq
is zo tof aan het worden dat je het met Poetry gaat beheren zodat jij en andere het gemakkelijk kunnen installeren en gebruiken. Om te beginnen zet je de repository om in een src-layout zoals hiernaast:\n \n \n pythondaq
\n \u251c\u2500\u2500src
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u2514\u2500\u2500pythondaq
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u251c\u2500\u2500__init__.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u251c\u2500\u2500arduino_device.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u251c\u2500\u2500diode_experiment.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u2514\u2500\u2500run_experiment.py
\n \u251c\u2500\u2500.gitattributes
\n \u251c\u2500\u2500.gitignore
\n \u2514\u2500\u2500README.md
\n \n
\n\n\nPythondaq: poetry
\nopdrachtcodecheck\n\n\nNu de repository pythondaq
in de src-layout staat voeg je Poetry toe om het project te beheren . Nadat alles gelukt is test je het project door een nieuwe conda environment aan te maken met de naam pythondaq
met daarin alleen python=3.12
. Daarna installeer je het Poetry project en wanneer je run_experiment.py
runt zie je als vanouds een lampje branden en een plot verschijnen.
\n\n\nPseudo-code\n
(ecpc) > poetry install \nInstalling dependencies from lock file\n\nPackage operations: x installs, 0 updates, 0 removals\n\n- Installing xxx (1.2.3)\n- Installing xxx (1.2.3)\n- Installing xxx (1.2.3): Pending...\n- Installing xxx (1.2.3): Installing...\n- Installing xxx (1.2.3)\n\nInstalling the current project: pythondaq (0.1.0)\n
\n\n\nCheckpunten:
\n\n- Je hebt Poetry ge\u00efnitialiseerd in de Pythondaq project map.
\n- Na het initialiseren van Poetry is er een
pyproject.toml
in de projectmap aangemaakt. \n- Wanneer met
poetry install
in een nieuwe conda environment met alleen python=3.12 het pakket wordt ge\u00efnstalleerd werkt run_experiment.py
daarna in die nieuwe omgeving naar behoren. \n
\nProjecttraject
\n\n- Pythondaq: Docstring
\n- Pythondaq: src-layout
\n- Pythondaq: poetry
\n- Pythondaq: test imports
\n- Pythondaq: applicatie
\n
\n\n\n\n\n\nModel, view, controller packages\nIn grotere projecten is het gebruikelijk om model, view, controller niet alleen uit te splitsen in verschillende scripts, maar ook in aparte packages te zetten.
\n\n- Maak 3 extra packages in de
pythondaq
package. models
, views
en controllers
. \n- Zet de modules in de juiste packages.
\n- Test je code zodat alle imports weer werken.
\n
"},{"location":"poetry/#van-script-naar-applicatie","title":"Van script naar applicatie","text":"Om onze python 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:\nTerminal
python script.py\n
\nJe moet dan wel de juiste map geopend hebben zodat python het bestand kan vinden. En als je de run
-knop gebruikt moet wel het bestandje open hebben staan dat je wilt runnen. Kortom, best een beetje gedoe. Maar als we programma's zoals Poetry, Conda of Python willen gebruiken hoeven we helemaal niet het juiste bestandje op te zoeken en te runnen. We hoeven alleen maar een commando in de terminal te geven \u2014 bijvoorbeeld python
of conda
\u2014 en de computer start automatisch het juiste programma op.\nDat willen wij ook voor onze programma's! En omdat we Poetry gebruiken kunnen we dat heel eenvoudig doen. We gaan even in een andere test-repository een commando toevoegen om de module uit te voeren waarvan je de code in paragraaf Modules kunt vinden. De twee bestanden square.py
en count_count.py
hebben we voor jullie netjes in een package geplaats in de repository AnneliesVlaar/just_count
met de volgende structuur:
\njust_count/\n src/\n just_count/\n __init__.py\n square.py\n count_count.py\n tests/\n __init__.py\n pyproject.toml\n README.md\n
\nDe bestanden square.py
en count_count.py
zien er hetzelfde uit als in paragraaf Modules:
\nsquare.pycount_count.py\n\n\ndef square(x):\n return x**2\n\n\nif __name__ == \"__main__\":\n print(f\"The square of 4 is {square(4)}\")\n
\n\n\nimport square\n\nprint(f\"The square of 5 is {square.square(5)}\")\n
\n\n\n\nWe kunnen Poetry niet vragen om een script te runnen, maar wel om een functie uit te voeren.
\n\nMain functie toevoegen
\nopdrachtcodecheck\n\n\nJe cloned de repository just_count in GitHub desktop en opent het daarna vanuit GitHub Desktop in Visual Studio Code. Je ziet een pyproject.toml
in de repository staan. Dus installeer je het pakket met Poetry in een nieuwe conda environment (met alleen python=3.12) . Je opent het hoofdbestand count_count.py
en zet de body van de module in een functie main()
. Daarna pas je het bestand aan zodat de functie nog steeds wordt uitgevoerd wanneer je het bestand count_count.py
runt.
\n\n\nTestcode\n count_count.py\n
import square\n\ndef main():\n print(f\"The square of 5 is {square.square(5)}\")\n\nif __name__ == '__main__':\n main()\n
\n\n(ecpc) > python count_count.py\nThe square of 5 is 25\n
\n\n\nCheckpunten:
\n\n- Er is een functie
main()
in het bestand count_count.py
\n- Het runnen van het bestand
count_count.py
geeft de output The square of 5 is 25
\n
\nProjecttraject
\n\n- main functie toevoegen
\n- commando toevoegen
\n- commando testen
\n
\n\n\n\n\nIn pyproject.toml
kunnen we nu het commando toe gaan voegen. Met de scripts
-tool van Poetry kunnen we aangeven met welk commando een functie uit een script wordt uitgevoerd. Om een commando toe te voegen ga je naar pyproject.toml
en voeg je een extra kopje toe:\n
[tool.poetry.scripts]\nnaam_commando = \"package.module:naam_functie\"\n
\nOm de wijzigingen aan pyproject.toml
door te voeren moet je de package opnieuw installeren. Poetry 'kijkt' altijd vanuit de map src
, de package package
waar naar verwezen wordt moet dan ook direct in de map src
zitten (en niet in een submap).\n\ncommando toevoegen
\nopdrachtcodecheck\n\n\nJe voegt in de pyproject.toml
het kopje [tool.poetry.scripts]
toe. Je voegt vervolgens het commando square
toe. Deze verwijst naar de functie main()
welke in de module count_count.py
staat die ondergebracht is in de package just_count
. Omdat je handmatig het toml-bestand hebt aangepast installeer je het package opnieuw met Poetry .
\n\n\nPseudo-code\npyproject.toml
[tool.poetry.scripts]\nsquare = \"just_count.count_count:main\"\n
\n\n\nCheckpunten:
\n\n- De naam van het commando is
square
. \n- De verwijzing na het = teken begint met twee aanhalingstekens gevolgd door het package
just_count
gevolgt door een punt. \n- Na de punt staat de naam van de module
count_count.py
zonder de extensie .py
gevolgd door een dubbele punt. \n- Na de dubbele punt staat de naam van de functie
main()
zonder haakjes ()
. \n- Achter de functie staan weer dubble aanhalingstekens om de verwijzing te sluiten.
\n- Na het opslaan van de
pyproject.toml
is het pakket opnieuw ge\u00efnstalleerd. \n
\nProjecttraject
\n\n- main functie toevoegen
\n- commando toevoegen
\n- commando testen
\n
\n\n\n\n\n\n\n\nCommando testen
\nopdrachtcodecheck\n\n\nNu je het commando square
hebt aangemaakt ga je deze testen in een terminal. Er verschijnt een error ModuleNotFoundError: No module named 'square'
. Je leest het info-blokje hieronder.\n\n\nJe runt het commando square
opnieuw en je ziet de tekst The square of 5 is 25
verschijnen. 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 square
en ziet weer de tekst The square of 5 is 25
verschijnen. Je concludeert dat het commando nu overal werkt zolang het juiste conda environment is geactiveerd. Dat test je uit door een ander conda environment te activeren en het commando square
nogmaal te proberen. Je krijgt een error en hebt daarmee je vermoeden bewezen. Tevreden ga je door naar de volgende opdracht.
\n\nModuleNotFoundError: No module named 'square'
\nAls je de Traceback leest zie je dat het probleem ontstaat in de module count_count.py
. Omdat Poetry altijd begint met zoeken vanuit de map src
kan daar de module square.py
niet gevonden worden. Pas het import statement aan naar import just_count.square as square
.
\n\n\n\nPseudo-code\n
(ecpc) > square \nTraceback (most recent call last):\nFile \"/base/envs/just_count/bin/square\", line 3, in \n from just_count.count_count import main\nFile \"/just_count/src/just_count/count_count.py\", line 1, in \n import square\nModuleNotFoundError: No module named 'square'\n\n\n\nCheckpunten:
\n\n- Het import statement in
count_count.py
is genoteerd vanuit de map src
. \n- Het commando
square
werkt als het juiste conda environment is geactiveerd. \n- Het commando
square
werkt nog steeds nadat je met het commando cd..
naar een bovenliggende map bent gegaan. \n- Het commando
square
werkt niet als een andere conda environment is geactiveerd. \n
\nProjecttraject
\n\n- main functie toevoegen
\n- commando toevoegen
\n- commando testen
\n
\n\n\n\n\n\nError analysis\nAls extra oefening gaan we met Poetry 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.
\n\n- Ga naar GitHub en clone
AnneliesVlaar/erroranalysis
in GitHub Desktop en open de repository daarna in Visual Studio Code. \n- Natuurlijk maak je gelijk een nieuwe Conda environment aan , voordat we dit package gaan testen.
\n- Snuffel door de bestanden en mappen, en open
src/erroranalysis/data_analysis.py
. Dit is het script wat moet kunnen runnen. \n- Run het script
data_analysis.py
en los de errors \u00e9\u00e9n voor \u00e9\u00e9n op. \n
\nOm erachter te komen of de problemen die we hierboven hadden \u00e9cht zijn opgelost maak je een nieuwe Conda environment aan , installeer je het package en run je het script. Werkt alles? Mooi! Dan gaan we nu een commando aanmaken om de functie table()
aan te roepen.
\n\n- Open
pyproject.toml
en voeg een kopje toe voor scripts.\n [tool.poetry.scripts]\nnaam_commando = \"package.module:naam_functie\"\n
\n pas de regel aan zodat jouw commando de functie table()
aanroept in src/erroranalysis/data_analysis.py
. Je mag de naam van het commando zelf kiezen. \n- Ga naar de terminal en kijk of het werkt!\n
(ecpc) > naam_commando \nArea of the kitchen table is: 1.8386 \u00b1 0.0049 m\n
\n
\n\n\nPythondaq: test imports
\nopdrachtcodecheck\n\n\n\n \n Bij het uitbouwen van de applicatie ga je mogelijk onderdelen uit de pythonpackage importeren. Daarom is het verstandig om, net als met de opdracht Packages, het importeren uit de package te testen.\n Maak daarvoor een tests
-map met __init__.py
en test_imports.py
in de repository pythondaq
. \n test_imports.py
import pythondaq.view\n
\n Je runt het bestand test_imports.py
en lost de errors op. Daarna werkt je package ook als je het aanroept van buiten de map met broncode. Je pythondaq
-repository is nu een volledig project dat je met andere gebruikers van Python kunt delen, bijvoorbeeld via een wheel.\n \n \n pythondaq
\n \u251c\u2500\u2500src
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u2514\u2500\u2500pythondaq
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u251c\u2500\u2500__init__.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u251c\u2500\u2500arduino_device.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u251c\u2500\u2500diode_experiment.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u2514\u2500\u2500run_experiment.py
\n \u251c\u2500\u2500tests
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u251c\u2500\u2500__init__.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u2514\u2500\u2500test_imports.py
\n \u251c\u2500\u2500pyproject.toml
\n \u2514\u2500\u2500README.md
\n \n\n\n\nPseudocode\nrun_experiment.py
# define from which package the module diode_experiment should be imported\n...\n
\nTestcode\n test_imports.py\nimport pythondaq.view\n
\n\n(ecpc) > python test_imports.py\nTraceback (most recent call last):\n File \"c:\\pythondaq\\tests\\test_imports.py\", line 1, in < module >\n import pythondaq.view\n File \"C:\\pythondaq\\src\\pythondaq\\run_experiment.py\", line 4, in < module >\n from diode_experiment import DiodeExperiment\n ModuleNotFoundError: No module named 'diode_experiment'\n
\n\n\nCheckpunten:
\n\n- Er is een map
tests
in de repository pythondaq
. \n- Er is een bestand
__init__.py
in de map tests
. \n- De import statements in de modules in het package
pythondaq
zijn aangepast zodat het bestand test_imports
runt zonder problemen. \n
\nProjecttraject
\n\n- Pythondaq: Docstring
\n- Pythondaq: src-layout
\n- Pythondaq: poetry
\n- Pythondaq: test imports
\n- Pythondaq: applicatie
\n
\n\n\n\n\n\nPythondaq: applicatie
\nopdrachtcodecheck\n\n\nJe maakt een commando om het script run_experiment.py
uit de repository pythondaq
te starten . Wanneer je het commando aanroept gaat het LED-lampje branden, en verschijnt er even later een IU-plot op het scherm. Je test of het commando ook buiten Visual Studio Code werkt door een Anaconda prompt
te openen. Je activeert het juiste conda environment en ziet dat ook dan het commando werkt. Wat een feest! Je hebt nu een applicatie geschreven die een Arduino aanstuurt om een ledje te laten branden. En je kunt je applicatie gewoon vanuit de terminal aanroepen!
\n\n\nPseudo-code\nrun_experiment.py
# import statements\n\n# def function\n # code to start a measurement\n
\npyproject.toml[tool.poetry.scripts]\nnaam_commando = \"package.module:naam_functie\"\n
\n\n\nCheckpunten:
\n\n- De functie in
run_experiment.py
bevat alle code die uitgevoerd moet worden om een meting te starten. \n- Het commando in de
pyproject.toml
verwijst op de correcte manier naar de functie in run_experiment.py
. \n- Het aanroepen van het commando zorgt ervoor dat een meting gestart wordt.
\n- Het commando werkt ook in een
Anaconda prompt
zolang het juiste conda environment actief is. \n
\nProjecttraject
\n\n- Pythondaq: Docstring
\n- Pythondaq: src-layout
\n- Pythondaq: poetry
\n- Pythondaq: test imports
\n- Pythondaq: applicatie
\n
\n\n\n\n\n\nVersie 2.0.0\nIn de pyproject.toml
kan je ook de versie aangeven van je package. Maar wanneer hoog je nu welk cijfertje 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 je bijvoorbeeld bestanden die je met oude versie hebt gemaakt niet met de nieuwe versie kunt openen, 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 eerste gebruik, dan kies je er vaak voor om versie 1.0.0 te releasen.
\n\n\n\n\n- \n
Echt gebeurd: meerdere studenten leverden hun grafische applicatie in voor een beoordeling. We konden het niet draaien, want er misten bestanden. Bij de student werkte het wel, maar bij ons echt niet.\u00a0\u21a9
\n \n- \n
Wanneer de repository op GitHub wordt geplaatst wordt deze README automatisch op de hoofdpagina van de repository getoond, onder de code.\u00a0\u21a9
\n \n- \n
Python heeft een ingebouwde module unittest
die deze tests kan vinden, kan runnen en daarna een handige weergave geeft van welke tests geslaagd zijn en welke faalden. Ook het package pytest
is erg bekend. Op deze manier weet je altijd zeker dat wanneer je aanpassingen doet in je code, dat de rest van de code nog steeds is blijven werken \u2014 z\u00f3nder dat je zelf uitvoerig alles hebt hoeven uitproberen. Je draait gewoon even snel alle tests. Helaas, helaas \u2014 in deze cursus is te weinig tijd om het schrijven van tests te behandelen.\u00a0\u21a9
\n \n- \n
Ja er is een map easystat
met daarin een map src
met daarin weer een map easystat
\u2014 dat kan nog wel eens verwarrend zijn. Het is conventie om de projectmap dezelfde naam te geven als je package. Het pad is dus eigenlijk project/src/package
en dat wordt dan, in ons geval, easystat/src/easystat
.\u00a0\u21a9
\n \n- \n
Vroeger was er een setup.py
maar Python schakelt nu langzaam over naar dit nieuwe bestand.\u00a0\u21a9
\n \n- \n
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 definieert name = \"Alice\"
, dan geeft print(f\"{name}\")
als uitkomst Alice
. Maar voeg je het =
-teken toe zoals in print(f\"{name=\")}
wordt de uitvoer name='Alice'
. Je ziet dan dus ook meteen de naam van de variabele en dat kan handig zijn.\u00a0\u21a9
\n \n- \n
Dit is bij het aanmaken standaard ingevuld op basis van de Python versie die in de base environment zit, kijk maar met conda list
in de base environment welke versie van Python daarin zit.\u00a0\u21a9
\n \n- \n
Het is eenvoudig om zelf de pyproject.toml
te openen en daar wat in aan te passen voor zover nodig.\u00a0\u21a9
\n \n- \n
PySerial is een package die we gebruiken om te communiceren over USB poorten.\u00a0\u21a9
\n \n- \n
Hynek Schlawack. Testing & packaging. URL: https://hynek.me/articles/testing-packaging/.\u00a0\u21a9
\n \n- \n
Tom Preston-Werner, Pradyun Gedam, and others. Tom's obvious, minimal language. URL: https://github.com/toml-lang/toml.\u00a0\u21a9
\n \n
"},{"location":"software-tools/","title":"Gereedschap","text":""},{"location":"software-tools/#isolatie-virtual-environments","title":"Isolatie: virtual environments","text":"Je hebt het misschien al gemerkt: Anaconda neemt veel schijfruimte in beslag. Dat is gek, want Python is best klein. Anaconda bevat alleen veel meer dan Python. Anaconda is een Python-distributie en bevat een enorme verzameling aan packages. Je kunt zelf extra packages installeren met conda
of pip
. Je loopt dan mogelijk wel tegen problemen aan: packages hebben vaak zelf weer andere packages nodig. En regelmatig ook met een bepaalde versie. Dit kan een ingewikkeld netwerk worden waarbij het installeren van een nieuwe package \u00f3f heel lang duurt, \u00f3f niet kan vanwege een conflict,1 \u00f3f blind gedaan wordt waarna sommige dingen niet meer willen werken. Alledrie is op te lossen door virtual environments te gebruiken. Ge\u00efsoleerde omgevingen met een eigen \u2014 veelal kleine \u2014 collectie van packages. Soms zelfs met een eigen versie van Python. Je kunt environments aanmaken voor specifieke projecten bijvoorbeeld: een omgeving voor NSP1, een omgeving voor ECPC en een omgeving voor een hobbyproject. Wellicht heb je bij NSP1 een environment aangemaakt om Jupyter Notebooks en een verzameling packages te installeren voor de data-analyse.
"},{"location":"software-tools/#pip-vs-conda","title":"Pip vs Conda","text":"De package manager van Python is pip
. Je kunt hiermee alle Python packages installeren die bestaan uit Python code. NumPy bijvoorbeeld bevat echter ook veel code geschreven in C. Die code moet eerst gecompileerd worden. Dat kan pip
\u00f3\u00f3k doen, mits er een C compiler op je computer ge\u00efnstalleerd is. Via de Python package index kunnen gelukkig ook zogeheten binary packages verspreid worden waarin de code al is gecompileerd. Er zijn dan losse packages voor Windows, MacOS en Linux. Meestal gaat dit goed, maar helaas niet altijd. Historisch waren NumPy maar vooral ook SciPy een flink probleem. Ook het gebruik van grafische bibliotheken ging vaak moeizaam. Dan was het package wel ge\u00efnstalleerd, maar riep hij dat hij systeembibliotheken niet kon vinden. Heel vervelend.
Een ander probleem van pip
is dat deze \u2014 tot voor kort \u2014 geen controle deed op de versies van al ge\u00efnstalleerde pakketten. Je kon dus packages installeren die nieuwe versies binnenhaalden van andere packages, waarna al eerder ge\u00efnstalleerde packages soms stopten met werken.
Om die reden is conda
in het leven geroepen. Conda installeert alleen binary packages, kan naast Python packages ook systeembibliotheken installeren als dat nodig is \u00e9n doet een uitgebreide controle op alle versies van te installeren en al eerder ge\u00efnstalleerde packages zodat alles altijd blijft werken. Nadeel is dat die controle nogal lang kan duren als je al veel ge\u00efnstalleerd hebt. Omdat je met conda
dus wel heel makkelijk uitgebreide wetenschappelijke packages kon installeren met een mix van Python-, C-, of zelfs Fortrancode is conda
(en Anaconda, de distributie) heel populair geworden in de wetenschappelijke wereld. Omdat jullie bij vorige cursussen al gewerkt hebben met Anaconda zullen we dat deze cursus ook gebruiken, maar we gaan veel met pip
werken om packages te schrijven die door alle Pythongebruikers gebruikt kunnen worden.
"},{"location":"software-tools/#conda-environments","title":"Conda environments","text":"Er zijn verschillende tools voor het aanmaken van environments voor Python. Allemaal hebben ze hun voor- en nadelen. Langzamerhand blijven de populairste over. De offici\u00eble is venv
, maar op dit moment niet de meest populaire. Binnen een groot deel van de wetenschappelijke gemeenschap is conda
de standaardkeuze. Het voordeel van conda
ten opzichte van veel andere tools is dat je verschillende environments kunt maken met verschillende versies van Python. Ideaal om te testen of je code ook werkt met de allernieuwste Pythonversie of juist met wat oudere versies.
Je moet je realiseren dat het aanmaken (en weggooien) van een environment heel makkelijk is. Doe dat regelmatig zodat je scherp houdt welke packages je nu echt nodig hebt voor je analyse of voor de software die je schrijft. Hieronder geven we een overzicht van de meest gebruikte commando's om met conda environments te werken.
Info
Conda installeert packages vanuit verschillende channels. De defaults
channel bevat packages die af en toe door Anaconda worden getest en samengenomen tot een distributie (versie 2021.05
bijvoorbeeld). Er zijn weinig updates. De conda-forge
channel bevat alle nieuwste versies van die packages en bevat ook software die (nog) niet in de defaults
channel terecht is gekomen. De conda-forge channel is daarom erg populair, maar er gaat ook regelmatig iets stuk.
Hieronder volgen enkele voorbeelden van het gebruik van conda: Terminal
Leeg environment aanmaken met naam 'pythondaq' (leeg = zelfs geen Python)\nPS> conda create --name pythondaq\n\nNieuw environment aanmaken met Python versie 3.10\nPS> conda create --name pythondaq python=3.10\n\nPackages installeren vanuit de 'conda-forge' channel en nieuwste Python\nAls het environment al bestaat vraagt hij of hij die moet overschrijven met een nieuwe schone versie\nPS> conda create --name pythondaq --channel conda-forge python\n\nEnvironment activeren\nPS> conda activate pythondaq\n\nEnvironment deactiveren\nPS> conda deactivate\n\nEnvironment wissen\nPS> conda env remove --name pythondaq\n\nLijst van environments bekijken\nPS> conda env list\n\nNieuw pakket installeren vanuit de conda-forge channel in het ACTIEVE environment\nPS> conda install --channel conda-forge lmfit\n\nNieuw environment voor NSP2 met notebooks voor analyse en fits\nPS> conda create --name nsp2 --channel conda-forge notebook pandas matplotlib lmfit\n\nPackage pandas updaten naar nieuwe versie in het ACTIEVE environment\nPS> conda update --channel conda-forge pandas\n\nAlle packages updaten naar nieuwe versie in het ACTIEVE environment\nPS> conda update --channel conda-forge --all\n
Als je scripts schrijft in Visual Studio Code wil je dat ze ook runnen in de omgevingen die je net hebt aangemaakt. Als je in Visual Studio Code een python script opent dan geeft het rechtsonder, in de statusbalk, de huidige Pythonomgeving aan:
Als je daarop klikt2 kun je door de lijst met Pythonomgevingen scrollen. Kies de omgeving die je wilt gebruiken. Let op: als je het environment net hebt aangemaakt dan staat hij er nog niet tussen. Klik dan rechtsbovenin eerst op het Refresh Interpeter list-knopje. Bijvoorbeeld:
Sluit alle oude terminals met het -icoon als je je muis aan de rechterkant over de namen van de terminals beweegt of in \u00e9\u00e9n keer met View > Command Palette > Terminal: Kill All Terminals. Alle nieuwe terminals die je opent zullen de nieuw geselecteerde conda environment actief maken. Wanneer je nu je Pythoncode draait dan is dat binnen deze omgeving. Het kan wel zijn dat hij opeens klaagt over packages die niet ge\u00efnstalleerd zijn omdat je dat \u2014 in die omgeving \u2014 nog niet had gedaan. Geen probleem: installeer ze dan.
"},{"location":"software-tools/#pipx","title":"Pipx","text":"Pythonapplicaties, zoals conda
, worden ge\u00efnstalleerd als commando dat je kunt aanroepen vanaf de command-line. Maar het is een Pythonapplicatie. En dat betekent dat als je van omgeving wisselt, de applicatie niet meer beschikbaar is. Ook kan het gebeuren dat je packages update of verwijdert waardoor de applicatie niet meer werkt. Met pipx
is het mogelijk om dit soort applicaties in een eigen virtual environment te installeren. Je loopt geen risico dat je ze stukmaakt en ze zijn beschikbaar vanuit andere virtual environments. In plaats van: Terminal
pip install PACKAGE\n
doe je straks Terminalpipx install PACKAGE\n
Met pipx list
bekijk je dan een lijst van ge\u00efnstalleerde pakketten. Je installeert pipx
met: Terminalpython -m pip install --user pipx\npython -m pipx ensurepath\n
Herstart je terminal en test of het commando pipx
werkt. Als je in een terminal in Visual Studio Code werkt moet je dat ook herstarten en als je VS Code gestart hebt vanuit GitHub Desktop moet je \u00f3\u00f3k dat herstarten. Werkt het nog steeds niet, dan zul je volledig uit moeten loggen en weer in moeten loggen om de shellomgeving opnieuw te laden en/of vraag om hulp."},{"location":"software-tools/#coding-style-black","title":"Coding style: Black","text":"Code wordt veel vaker gelezen dan geschreven, is een veel geciteerd gezegde onder programmeurs. Je schrijft je code en zit vervolgens uren te puzzelen om een fout te vinden of hoe je de code het beste kunt uitbreiden. Je zoekt op internet naar voorbeeldcode, je helpt een medestudent of vraagt die om hulp. Heel vaak dus lees je niet je eigen code, maar die van iemand anders. Is dat relevant? Ja! Want die code ziet er anders uit. Iedereen programmeert toch op zijn eigen manier. Het scheelt enorm als de code er tenminste grotendeels hetzelfde uitziet. Het kost je dan minder energie om te lezen. Daarom ook dat de artikelen in wetenschappelijke tijdschriften bijvoorbeeld er allemaal hetzelfde uitzien en de auteur niet de vrijheid krijgt om z\u00e9lf lettertypes te kiezen. Net zo goed hebben grote organisaties vaak hun eigen coding style ontwikkeld waar alle werknemers zich zoveel mogelijk aan moeten houden.
Python heeft een eigen style guide die je vooral eens door moet lezen.3 Google heeft ook een hele mooie, met duidelijke voorbeelden.4
Fijn dat je code consistenter wordt, maar het moet nu ook weer niet zo zijn dat je uren kwijt bent met de style guides bestuderen of twijfelen waar je een regel code precies moet afbreken. Wel of niet een enter? Om daar vanaf te zijn zijn er verschillende pakketten die je code automatisch aanpassen aan de standaard. Als je de instelling Editor: Format On Save aan zet (staat standaard uit) dan wordt je code aangepast zodra je je bestand opslaat. Black is zo'n formatter en heeft een `eigen mening'. Als je je daar bij neerlegt hoef je bijna niet meer na te denken over hoe je je code precies vormgeeft. De Black website zegt5:
By using Black, you agree to cede control over minutiae of hand-formatting. In return, Black gives you speed, determinism, and freedom from pycodestyle nagging about formatting. You will save time and mental energy for more important matters.
Black is tegenwoordig immens populair en in Visual Studio Code kun je hem gebruiken door de Black Formatter-extensie van Microsoft te installeren. De code in deze handleiding is geformat met Black. In Visual Studio Code, ga naar File en dan naar Preferences > Settings > Editor: Format On Save en vink die aan. De eerste keer dat je je bestand opslaat zal hij vragen of hij Black moet gebruiken, daarna wordt je code altijd netjes gemaakt zodra je je Pythonbestand bewaart.
De volgende code:
s1 = 'Hello'\ns2 = \"World\"\nvalues = [1,2,3,4,5]\n\nf = a * x ** 2 + b * x + c\ng = a*x +b\nh = A*np.sin(2*pi*f*t+phi) + A2*np.sin(2*pi*f2*t+phi2) + A3*np.sin(2*pi*f3*t+phi3)\n
wordt door Black omgezet in:
s1 = \"Hello\"\ns2 = \"World\"\nvalues = [1, 2, 3, 4, 5]\n\nf = a * x**2 + b * x + c\ng = a * x + b\nh = (\n A * np.sin(2 * pi * f * t + phi)\n + A2 * np.sin(2 * pi * f2 * t + phi2)\n + A3 * np.sin(2 * pi * f3 * t + phi3)\n)\n
-
Stel package A heeft package B nodig met versie >= 1.1, maar package C heeft package B nodig met versie 1.0. Nu kunnen packages A en C dus niet tegelijkertijd ge\u00efnstalleerd worden.\u00a0\u21a9
-
Of: View > Command Palette > Python: Select Interpreter.\u00a0\u21a9
-
Guide van Rossum. Pep 8 \u2013 style guide for python code. URL: https://www.python.org/dev/peps/pep-0008/.\u00a0\u21a9
-
Google. Google python style guide. URL: https://google.github.io/styleguide/pyguide.html.\u00a0\u21a9
-
\u0141ukasz Langa. Black, the uncompromising code formatter. URL: https://black.readthedocs.io/en/stable/.\u00a0\u21a9
"},{"location":"terminal-adventure-game/","title":"Terminal Adventure Game","text":"To play the Terminal Adventure Game, clone the repository or download and unpack the zip. Open the folder suitable for your operating system and preferred language in explorer. Go into the forest folder and open/run the 'start' file. Let yourself be guided through the adventure.
Parrot Pokkie was taken from the girl by Snerk the Extremely Magnificent. Can you help her get Pokkie back?
"},{"location":"terminal-adventure-game/#credits","title":"credits","text":"This game was developed by Extra Nice in Leeuwarden in association with VU
"},{"location":"tui/","title":"Text-based user interfaces","text":"De text-based user intefaces (TUI) of terminal user interface zijn de voorlopers van de graphical user interfaces die wel nog in een terminal worden uitgevoerd. Een populair framework waarmee je in Python een TUI kunt bouwen is Textual.
Textual installeren Maak een nieuwe conda omgeving aan en installeer de benodigdheden voor Textual. Zie voor meer informatie de documentatie
Stopwatch Volg de tutorial van Textual om een stopwatch applicatie te maken.
Pythondaq: TUI Maak met behulp van Textual een TUI voor Pythondaq.
"},{"location":"vervolg-python/","title":"Uitgebreidere Python kennis","text":"Python is een batteries included taal. Dat betekent dat als je 'kaal' Python installeert er al heel veel functionaliteit standaard meegeleverd wordt. Allereerst omdat de taal zelf al behoorlijk krachtig is, maar ook omdat de standaardbibliotheek zeer uitgebreid is. Met een eenvoudig import
-statement haal je extra functionaliteit binnen, onder andere op het gebied van datatypes, wiskunde, toegang tot bestanden, een database, datacompressie, cryptografie, netwerktoegang, e-mail, multimedia, etc. Nog veel meer bibliotheken zijn beschikbaar via de Python Package Index17.
In dit hoofdstuk behandelen we de kennis die nuttig kan zijn voor de rest van deze cursus1. Een deel van wat we hier behandelen kan al bekend zijn uit eerdere cursussen. Een ander deel is nieuw.2
In de cursus gaan we bibliotheken (modules, packages) en een applicatie ontwikkelen. Dat betekent dat we verder gaan dan het schrijven van scripts en dat we dus meer gaan doen dan functies schrijven. Uiteindelijk moet het mogelijk zijn de software te verspreiden op een wat meer professionele manier. Dus niet alleen via een zipje met wat Pythonbestanden waar uiteindelijk verschillende versies van rondslingeren en die lastig zijn te updaten. Wat er nodig is voor een goede distributie van software en om het mogelijk te maken met meerdere mensen software te (blijven) ontwikkelen zal in deze cursus aan bod komen.
Een punt wat vaak onderschoven blijft is documentatie. Als je software schrijft die gebruikt (en doorontwikkeld) wordt in een onderzoeksgroep, dan is het heel belangrijk dat iedereen kan begrijpen wat je software doet en hoe die uitgebreid kan worden. Het is zonder hulp vaak heel moeilijk om de code van een iemand anders te begrijpen. En in de praktijk blijkt heel vaak dat als je code schrijft en daar een paar weken of maanden later op terugkijkt, jij z\u00e9lf die ander bent. Wat toen blijkbaar heel logisch leek, is dat later toch niet meer. Dus documentatie schrijf je heel vaak ook gewoon voor jezelf.
Als je niet zo heel veel in Python geprogrammeerd hebt kan het helpen om de paragraaf Basiskennis Python18 door te nemen. Een boek dat zeker bij natuurkundigen in de smaak kan vallen is Effective Computation in Physics19, maar deze is niet gratis verkrijgbaar. Een boek dat zowel op papier te bestellen is als in de vorm van een pdf of webpagina is te lezen is Think Python.20
"},{"location":"vervolg-python/#zen-of-python","title":"Zen of Python","text":"Python is niet C (of iedere willekeurige andere programmeertaal). Er zit een gedachte achter die op een gegeven moment verwoord is door Tim Peters21.
Je kunt het lezen middels een easter egg in Python zelf: import this
.
zen
- Open Visual Studio Code.
- Open de map
ECPC
). - Maak een bestand
zen-of-python.py
met daarin de onderstaande code: import this\n
ECPC
\u251c\u2500\u2500 zen-of-python.py
\u2514\u2500\u2500 \u2022\u2022\u2022 - Run het script en lees de output.
Deze tekst kan nog behoorlijk cryptisch overkomen, maar een paar dingen worden snel duidelijk: code moet mooi zijn (regel 1) en duidelijk (regels 2, 3 en 6). Er bestaan prachtige programmeertrucs in \u00e9\u00e9n of twee regels, maar onleesbaar is het wel. Een voorbeeld 22:
print('\\n'.join(\"%i bytes = %i bits which has %i possible values.\" %\n (j, j*8, 256**j) for j in (1 << i for i in range(4))))\n
Kun je zien wat de uitvoer van dit programma moet zijn? Misschien als we het op deze manier uitschrijven:
zen.py for num_bytes in [1, 2, 4, 8]:\n num_bits = 8 * num_bytes\n num_possible_values = 2 ** num_bits\n print(\n f\"{num_bytes} bytes = {num_bits} bits which has {num_possible_values} possible values.\"\n )\n
\n(ecpc) > python zen.py\n1 bytes = 8 bits which has 256 possible values.\n2 bytes = 16 bits which has 65536 possible values.\n4 bytes = 32 bits which has 4294967296 possible values.\n8 bytes = 64 bits which has 18446744073709551616 possible values.\n
De code is langer, met duidelijkere namen van variabelen en zonder bitshifts of joins.
Moraal van dit verhaal: we worden gelukkiger van code die leesbaar en begrijpelijk is, dan van code die wel heel slim in elkaar zit maar waar bijna niet uit te komen is. Overigens komt het regelmatig voor dat de programmeur z\u00e9lf een paar weken later al niet zo goed meer weet hoe de code nou precies in elkaar zat.
Als je samenwerkt aan software kan het andere Pythonprogrammeurs erg helpen om dingen 'op de Python-manier te doen'. Een C-programmeur herken je vaak aan het typische gebruik van lijsten of arrays in for
-loops. Als je een lijst hebt: names = ['Alice', 'Bob', 'Carol']
, doe dan niet:
names = ['Alice', 'Bob', 'Carol']\ni = 0\nwhile i < len(names):\n print(\"Hi,\", names[i])\n i = i + 1\n
en ook niet: names = ['Alice', 'Bob', 'Carol']\nfor i in range(len(names)):\n print(\"Hi,\", names[i])\n
waarbij je loopt over een index i
. Gebruik liever het feit dat een lijst al een iterator is: names = ['Alice', 'Bob', 'Carol']\nfor name in names:\n print(\"Hi,\", name)\n
Deze code is bovendien veel korter en gebruikt minder variabelen. Itereren op de python-manier
- Neem het onderstaande script over.
- Itereer over de lijst
voltages
op de python-manier. - Print voor elk item in de lijst de waarde in mV. Bijvoorbeeld: \"The voltage is set to 0 mV.\"
voltages = [0, 50, 100, 150, 200, 250, 300] #mV\n
Uitwerkingen iterator.py
voltages = [0, 50, 100, 150, 200, 250, 300] #mV\n\nfor voltage in voltages:\n print(f\"The voltage is set to {voltage} mV.\")\n
\n(ecpc) > python iterator.py\nThe voltage is set to 0 mV.\nThe voltage is set to 50 mV.\nThe voltage is set to 100 mV.\nThe voltage is set to 150 mV.\nThe voltage is set to 200 mV.\nThe voltage is set to 250 mV.\nThe voltage is set to 300 mV.\n
"},{"location":"vervolg-python/#enumerate","title":"Enumerate","text":"Soms is het nodig om de index te hebben, bijvoorbeeld wanneer je een namenlijstje wilt nummeren: Terminal
1. Alice\n2. Bob\n3. Carol\n
Dit kan dan in Python-code het makkelijkst als volgt:
for idx, name in enumerate(names, 1):\n print(f\"{idx}. {name}\")\n
Hier maken we gebruik van de enumerate(iterable, start=0)
-functie en f-strings. Er zijn dus veel manieren om programmeerproblemen op te lossen, maar het helpt om het op de `Pythonmanier' te doen. Andere programmeurs zijn dan veel minder tijd en energie kwijt om jouw code te begrijpen -- \u00e9n andersom wanneer jij zelf op internet zoekt naar antwoorden op problemen. Immers, je herkent dan veel makkelijker en sneller hoe andermans code werkt."},{"location":"vervolg-python/#datatypes","title":"Datatypes","text":"Gehele getallen, kommagetallen, strings: allemaal voorbeelden van datatypes. Veel zullen jullie al wel bekend voorkomen, zoals strings, lists en NumPy arrays. Andere zijn misschien alweer wat weggezakt, zoals dictionaries of booleans. Weer andere zijn misschien wat minder bekend, zoals complexe getallen of sets. En als laatste voegt Python af en toe nieuwe datatypes toe, zoals f-strings in Python 3.6 of data classes sinds Python 3.7.
Info
De python-standard-library documentatie 23 bevat een mooi overzicht van alle datatypes met een beschrijving van operaties en eigenschappen. Voor uitgebreidere tutorials kun je vaak terecht bij real-python 24. Het kan makkelijk zijn om in een zoekmachine bijvoorbeeld real python dict
te typen als je een tutorial zoekt over Python dictionaires.
Om nog even te oefenen met de datatypes volgt er een aantal korte opdrachten.
"},{"location":"vervolg-python/#list","title":"List","text":"list
Schrijf een kort scriptje.
- Maak een
list
van de wortels van de getallen 1 tot en met 10. Dus de rij $\\left(\\sqrt{1}, \\sqrt{2}, \\sqrt{3}, \\ldots, \\sqrt{10}\\right)$. - Print die rij onder elkaar (\u00e9\u00e9n getal per regel, met drie decimalen).
- Geef weer of het getal 3 voorkomt in die rij en geef weer of het getal 4 voorkomt in die rij.
Uitwerkingen list.py
import math\n\nsquares = []\nfor n in range(1, 11):\n squares.append(math.sqrt(n))\n\n# print the list below each other with three decimal places\nprint(\"Square of range 1 to 10 with three decimal places: \")\nfor square in squares:\n print(f\"{square:.3f}\")\n\n# State if number 3 or 4 appears in the list of squares\nfor number in [3, 4]:\n print(f\"does number {number} appears in the list of squares?\", number in squares)\n
\n(ecpc) > python list.py\nSquare of range 1 to 10 with three decimal places: \n1.000\n1.414\n1.732\n2.000\n2.236\n2.449\n2.646\n2.828\n3.000\n3.162\ndoes number 3 appears in the list of squares? True\ndoes number 4 appears in the list of squares? False\n
"},{"location":"vervolg-python/#numpy-array","title":"NumPy array","text":"Je kunt op verschillende manieren een NumPy array maken (na import numpy as np
):
- Door een Python lijst te converteren:
np.array([0, 0.5, 1, 1.5, 2])
. - Door een array aan te maken met een stapgroote:
np.arange(0, 3, 0.5) # start, stop, step
- Door een array aan te maken met getallen gelijkmatig verdeeld over een interval:
np.linspace(0, 2.5, 6) #start, stop, number
NumPy arrays NumPy arrays zijn vaak handiger dan lists. Als je een array hebt van 20 $x$-waardes in het domein $[0, \\pi]$ kun je in \u00e9\u00e9n keer alle waardes van $\\sin x$ uitrekenen. Bijvoorbeeld:
import numpy as np\nfrom numpy import pi\n\nx = np.linspace(0, pi, 20)\ny = np.sin(x)\n
NumPy voert de berekeningen uit binnen een C-bibliotheek3 en is daarmee veel sneller dan een berekening in Python zelf: import math\nx = [0.00, 1.05, 2.09, 3.14, 4.19, 5.24, 6.28]\ny = []\nfor u in x:\n y.append(math.sin(u))\n
Niet alleen is NumPy zo'n honderd keer sneller,4 het is ook veel korter op te schrijven. Het nadeel van NumPy arrays is dat je geen elementen kunt toevoegen.5 Python lijsten hebben dus voordelen, zeker als rekentijd geen probleem voor je is. Als je veel functies uit NumPy gebruikt is het handig \u2013 en gebruikelijk \u2013 om je import-statements kort te houden en duidelijk te maken dat je de sin()
-functie uit NumPy gebruikt en niet uit de math
module. Constantes worden wel vaak los ge\u00efmporteerd. Daarom is dit dus gebruikelijk:
import numpy as np\nfrom numpy import pi\n\nx = np.linspace(0, pi, 100)\ny = np.sin(x)\n
np.array
Doe hetzelfde als de vorige opdracht met lists, maar nu met NumPy arrays:
- Maak een
np.array
van de wortels van de getallen 1 tot en met 10. Dus de rij $\\left(\\sqrt{1}, \\sqrt{2}, \\sqrt{3}, \\ldots, \\sqrt{10}\\right)$. - Print die rij onder elkaar (\u00e9\u00e9n getal per regel, met drie decimalen).
- Geef weer of het getal 3 voorkomt in die rij en geef weer of het getal 4 voorkomt in die rij.
Uitwerkingen np_array.py
import numpy as np\n\n# Make an array from 1 to 10\nnumbers = np.arange(1, 11, 1)\n\n# Take the squareroot of each number\nsquareroot = np.sqrt(numbers)\n\n# Print the list of squareroots below each other with three decimal places\nfor root in squareroot:\n print(f\"{root:.3f}\")\n\n# State if number 3 or 4 appears in the list of squares\nprint(\"does number 3 appears in the list of squares?\", 3 in squareroot)\nprint(\"does number 4 appears in the list of squares?\", 4 in squareroot)\n
\n(ecpc) > python np_array.py\n1.000\n1.414\n1.732\n2.000\n2.236\n2.449\n2.646\n2.828\n3.000\n3.162\ndoes number 3 appears in the list of squares? True\ndoes number 4 appears in the list of squares? False\n
Dictionaries Tuples, * args, ** kwargs Set"},{"location":"vervolg-python/#dictionaries","title":"Dictionaries","text":"Dictionaries zijn een bijzonder handige manier om informatie op te slaan. Een dictionary bestaat uit een of meerdere key-value tweetallen. Met een handige gekozen naam voor de key kan je betekenis geven aan een value.
dict
Schrijf een kort scriptje:
- Maak een dictionary
constants
met de waardes van de (natuur)constantes $\\pi$, de valversnelling $g$, de lichtsnelheid $c$ en het elementaire ladingskwantum $e$. - Print de namen -- niet de waardes -- van de constantes die zijn opgeslagen in
constants
. - Bereken de zwaartekracht $F_\\text{z} = mg$ voor een voorwerp met een massa van 14 kg door gebruik te maken van de waarde van $g$ uit de dictionary.
- Maak een dictionary
measurement
die de resultaten van een meting bevat: een spanning van 1.5 V bij een stroomsterkte van 75 mA. - Bereken de weerstand van de schakeling op basis van de voorgaande meting en bewaar het resultaat in dezelfde dictionary.
Uitwerkingen dictionaries.py
import numpy as np\n\n# Dictionary of constants pi, gravitational acceleration (g), the speed of light (c) and elementary charge (e)\nconstants = {\"pi\": np.pi, \"g\": 9.81, \"c\": 3e8, \"e\": 1.6e-19}\n\n# print de names -not the values- of the constants in the dictionary\nprint(constants.keys())\n\n# Calculate gravity of an object with mass of 14 kg\nmass = 14 # kg\nF_z = mass * constants[\"g\"]\nprint(f\"Gravity of an object with {mass} kg is: {F_z} N\")\n\n# Dictionary with results of a measurement \nmeasurement = {\"U\": 1.5, \"I\": 75e-3} # U in V, I in A\n\n# Add resistance to dictionary\nmeasurement[\"R\"] = measurement[\"U\"] / measurement[\"I\"]\n\nprint(f'The resistance was: {measurement[\"R\"]:.2f} \\u03A9')\n
\n(ecpc) > python dictionaries.py\ndict_keys(['pi','g', 'c', 'e'])\nGravity of an object with 14kg is: 137.34 N\nThe resistance was: 20.00 \u03a9\n
"},{"location":"vervolg-python/#tuples-args-kwargs","title":"Tuples, * args, ** kwargs","text":"In Python zijn tuple
's een soort alleen-lezen list
's. Een tuple is een immutable6 object. Daarom worden ze vaak gebruikt wanneer lijstachtige objecten altijd dezelfde vorm moeten hebben. Bijvoorbeeld een lijst van $(x, y)$-co\u00f6rdinaten zou je zo kunnen defini\u00ebren:
coords = [(0, 0), (1, 0), (0, 1)]\n
Hier is coords[0]
gelijk aan (0, 0)
. Je kunt nu niet dit co\u00f6rdinaat uitbreiden naar drie dimensies met coords[0].append(1)
en dat is waarschijnlijk precies wat je wilt voor een lijst met tweedimensionale co\u00f6rdinaten. Ook is dit object veel compacter dan een dict
: coords = [{\"x\": 0, \"y\": 0}, {\"x\": 1, \"y\": 0}, {\"x\": 0, \"y\": 1}]\n
Hier zijn tuples dus best handig, al moet je dus wel onthouden in welke volgorde de elementen staan. Dat is voor $(x, y)$-co\u00f6rdinaten niet zo'n probleem maar kan in andere situaties lastiger zijn.7 Tuples ondersteunen tuple unpacking. Je kunt het volgende doen: (x, y, z) = (2, 3, 4)\n
Na deze operatie geldt $x = 2$, $y = 3$ en $z = 4$. Je mag zelfs de haakjes weglaten voor nog compactere notatie: x, y, z = 2, 3, 4\n
Op deze manier kan een functie ook meerdere argumenten teruggeven die je vervolgens uit elkaar plukt: def get_measurement():\n ... # perform measurement\n return voltage, current\n\n\nvoltage, current = get_measurement()\n
Het uit elkaar plukken van argumenten kan zelfs als je een functie aanroept: def power(a, b):\n return a ** b\n\n\n# regular function call\npower(2, 7)\n\n# function call with tuple unpacking\nargs = 2, 7\npower(*args)\n
Wat zelfs werkt is dictionary unpacking. Je kunt aan functies ook argumenten bij naam meegeven -- de volgorde maakt dan niet uit en je maakt in je programma expliciet duidelijk welke argumenten je meegeeft. Dat werkt zo: # regular function call\npower(b=7, a=2)\n\n# function call with dictionary unpacking\nkwargs = {\"b\": 7, \"a\": 2}\npower(**kwargs)\n
args
Gegeven de lijst odds = [1, 3, 5, 7, 9]
, print de waardes uit deze lijst op \u00e9\u00e9n regel, zoals hieronder weergegeven: Terminal
1 3 5 7 9\n
Je mag er niet vanuit gaan dat de lijst altijd 5 elementen bevat. Uitwerkingen odds = [1, 3, 5, 7, 9]\n\n# What is the difference between printing with and without a star?\nprint(*odds)\n\nprint(odds)\n
"},{"location":"vervolg-python/#set","title":"Set","text":"Als laatste willen we nog de aandacht vestigen op set
's: een unieke verzameling van objecten. Ieder element komt maar \u00e9\u00e9n keer voor in een set:
l = [1, 2, 2, 3, 5, 5]\nset(l)\n# {1, 2, 3, 5}\n
Je moet even oppassen: de {}
-haakjes worden gebruikt voor zowel sets als dictionaries. Omdat een dictionary (key: value) paren heeft en een set losse elementen kan Python het verschil wel zien: is_set = {1, 2, 3, 4}\nis_dict = {1: 1, 2: 4, 3: 9, 4: 16}\n
Dat gaat alleen mis als je een lege set wilt maken. Daarvoor zul je expliciet de set()
-constructor moeten gebruiken: is_dict = {}\nis_set = set()\n
Je kunt elementen toevoegen aan een set met .add()
en sets gebruiken om verzamelingen met elkaar te vergelijken. Komen er elementen wel of niet voor in een set? Is de ene set een subset van de andere set? Enzovoorts. Zie daarvoor verder de documentatie."},{"location":"vervolg-python/#comprehension","title":"Comprehension","text":"Door gebruik te maken van een list comprehension kun je de for-loop in \u00e9\u00e9n regel opschrijven:
from math import sin\nx = [0.00, 1.05, 2.09, 3.14, 4.19, 5.24, 6.28]\ny = [sin(u) for u in x]\n
Er is in veel gevallen tegenwoordig geen groot verschil met een for-loop qua snelheid. In andere gevallen is de list comprehension net wat sneller. Als je lijsten niet te lang zijn is het makkelijker (en sneller) om een list comprehension te gebruiken in plaats van je lijst \u00e9\u00e9rst naar een array te veranderen en er dan mee verder te rekenen. Als je lijst w\u00e9l lang is of je weet al dat je meerdere berekeningen wilt uitvoeren kan dat wel: import numpy as np\nx = [0.00, 1.05, 2.09, 3.14, 4.19, 5.24, 6.28]\nx = np.array(x)\ny = np.sin(x)\n
Kortom: berekeningen met arrays zijn sneller, maar for-loops (en list comprehensions) zijn veelzijdiger. Het is zelfs mogelijk om een if
-statement op te nemen in je list comprehension. Bijvoorbeeld:
pdf.py filenames = [\"test.out\", \"text.pdf\", \"manual.pdf\", \"files.zip\"]\npdfs = [name for name in filenames if name.endswith(\".pdf\")]\nprint(f\"{pdfs=}\")\n
(ecpc) > python pdf.py\npdfs=['text.pdf', 'manual.pdf']\n
In een for-loop heb je daar meer ruimte voor nodig. Naast list comprehensions heb je ook set comprehensions8 en dict comprehensions.
array, for-loops en comprehensions
Voer, door een script te schrijven, de volgende opdrachten uit:
- Maak een lijst van de getallen 1 tot en met 10.
- Gebruik een 'gewone' for-loop om een lijst te maken van de derdemachtswortel van de getallen.
- Maak nogmaals een lijst van de derdemachtswortel van de getallen maar gebruik nu list comprehension.
- Gebruik tot slot arrays om de lijst met derdemachtswortels van de getallen te maken.
Uitwerkingen for_loop.py
import numpy as np\n\nnumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n\n# use a for loop to create a list with cube root of numbers\ncube_root = []\nfor number in numbers:\n answer = number ** (1 / 3)\n cube_root.append(answer)\n\n\n# use list comprehension to create a list with cube root of numbers\ncube_root_comprehension = [n ** (1 / 3) for n in numbers]\n\n# use numpy arrays to create a list with cube root of numbers\nnumbers = np.array(numbers)\ncube_root_array = numbers ** (1 / 3)\n\nprint(cube_root)\nprint(cube_root_comprehension)\nprint(cube_root_array)\n
\n(ecpc) > python for_loop.py\n[1.0, 1.2599210498948732, 1.4422495703074083, 1.5874010519681994, 1.7099759466766968, 1.8171205928321397, 1.912931182772389, 2.0, 2.0800838230515904, 2.154434690031884]\n[1.0, 1.2599210498948732, 1.4422495703074083, 1.5874010519681994, 1.7099759466766968, 1.8171205928321397, 1.912931182772389, 2.0, 2.0800838230515904, 2.154434690031884]\n[1. 1.25992105 1.44224957 1.58740105 1.70997595 1.81712059 1.91293118 2. 2.08008382 2.15443469]\n
Lambda functions Generators Dunder methods Decorators"},{"location":"vervolg-python/#lambda-functions","title":"Lambda functions","text":"In Python zijn functies ook objecten. Je kunt ze bewaren in een lijst of dictionary, of je kunt ze meegeven als parameter aan een andere functie. Dat kan heel handig zijn! Stel je hebt een lijst met verschillende soorten fruit die je wilt sorteren op alfabet:
a = [\"kiwi\", \"banana\", \"apple\"]\nprint(sorted(a))\n
(ecpc) > python sort.py \n['apple', 'banana', 'kiwi']\n
Dat gaat heel makkelijk met de ingebouwde sorted()
-functie. Je kunt aan deze functie ook een key
-parameter meegeven; een \u00e1ndere functie die gebruikt wordt om te bepalen waarop gesorteerd moet worden. Zo kun je sorteren op de lengte van de fruitnamen door simpelweg de len()
-functie als parameter mee te geven: a = [\"kiwi\", \"banana\", \"apple\"]\n\nprint(len(\"apple\"))\nprint(sorted(a, key=len))\n
(ecpc) > python length.py \n5\n['kiwi', 'apple', 'banana']\n
Als je wilt sorteren op de tweede letter van de naam -- waarom niet? -- dan kun je zelf een functie defini\u00ebren en gebruiken: a = [\"kiwi\", \"banana\", \"apple\"]\n\ndef second_letter(value):\n return value[1]\n\nprint(second_letter(\"lemon\"))\nprint(sorted(a, key=second_letter))\n
(ecpc) > python second_letter.py \ne\n['banana', 'kiwi', 'apple']\n
Lambdafuncties zijn bedacht om je een hoop typewerk te besparen. Je kunt korte functies in \u00e9\u00e9n regel opschrijven en gebruiken, zolang het maar een geldige expression is. G\u00e9\u00e9n if-then-else, maar de meeste andere dingen mogen wel. Bijvoorbeeld: a = [\"kiwi\", \"banana\", \"apple\"]\n\nsquared = lambda x: x ** 2\nprint(squared(4))\n\nsecond_letter = lambda value: value[1]\nprint(sorted(a, key=second_letter))\n
(ecpc) > python lamda.py \n16\n['banana', 'kiwi', 'apple']\n
Aangezien de definitie van een lambdafunctie zelf ook een expression is kun je het sorteren op de tweede letter zelfs in \u00e9\u00e9n regel doen: a = [\"kiwi\", \"banana\", \"apple\"]\n\nprint(sorted(a, key=lambda value: value[1]))\n
(ecpc) > python one_line.py \n['banana', 'kiwi', 'apple']\n
Lambdafuncties kun je ook gebruiken om te fitten aan een bepaald model. Je definieert je model dan in \u00e9\u00e9n regel met een lambdafunctie:
# from lmfit import models\nf = lambda x, a, b: a * x + b\nmodel = models.Model(f)\nfit = model.fit(y, x=x)\n
Het is hierbij wel belangrijk dat lmfit
er vanuit gaat dat de eerste variabele in de functiedefinitie de onafhankelijke variabele ($x$-as) is. Dit is verder geen Pythonlimitatie. Je kunt de functies ook bewaren in een dictionary voor later gebruik.
lambda
Maak een dictionary models
met functies voor een lineaire functie linear
gegeven door $y = ax + b$, een kwadratische functie quadratic
gegeven door $y = ax^2 + bx + c$ en een sinusfunctie sine
gegeven door $a + b\\sin(cx + d)$. Hierna moet de volgende code werken:
f = models['linear']\nf(5, a=2, b=3)\n# 13\n
Maak een grafiek van de sinusfunctie op het domein $[0,\\, 2\\pi]$ met parameters $a=1$, $b=2$, $c=2$ en $d=\\frac{\\pi}{2}$. Uitwerkingen import numpy as np\nfrom numpy import pi\nimport matplotlib.pyplot as plt\n\n# dictionary with linear, quadratic and sine function\nmodels = {\n \"linear\": lambda x, a, b: a * x + b,\n \"quadratic\": lambda x, a, b, c: a * x ** 2 + b * x + c,\n \"sine\": lambda x, a, b, c, d: a + b * np.sin(c * x + d),\n}\n\n# test the next piece of code\nf = models[\"linear\"]\nprint(f(5, a=2, b=3))\n\n# Graph of sine function on domain [0, 2pi] with parameters a=1, b=2, c=2, d=0.5pi\nx = np.linspace(0, 2 * pi, 100)\nf = models[\"sine\"]\n\nplt.plot(x, f(x, a=1, b=2, c=2, d=0.5 * pi))\nplt.show()\n
"},{"location":"vervolg-python/#generators","title":"Generators","text":"Als een functie een serie metingen verricht kan het lang duren voordat de functie de resultaten teruggeeft. Laten we die functie even perform_measurements()
noemen. Het is soms lastig als de rest van het programma daarop moet wachten voordat een analyse kan worden gedaan, of een melding aan de gebruiker kan worden gegeven. Het kan dan gebeuren dat je je programma draait en je dan afvraagt: doet hij het, of doet hij het niet? Je kunt dit oplossen door print()
-statements in je programma op te nemen, maar dit is niet zo netjes. Als je perform_measurements()
inbouwt in een tekstinterface die ook stil moet kunnen zijn? Of als je de functie gaat gebruiken vanuit een grafisch programma waarin je geen tekst wilt printen, maar een grafiek wilt opbouwen? Je moet dan steeds perform_measurements()
gaan aanpassen. Een ander probleem kan optreden wanneer je langdurige metingen doet die ook veel geheugen innemen. Wachten op de hele meetserie betekent dat het geheugen vol kan lopen. Lastig op te lossen!
Of\u2026 je maakt gebruik van een generator function: een functie die tussendoor resultaten teruggeeft. Dat kan door gebruik te maken van yield
in plaats van return
. De rest gaat automatisch. Maar: je moet wel even weten hoe je omgaat met de generator. Stel, we willen de kwadraten berekenen van een reeks getallen tot een bepaald maximum:
def calculate_squares_up_to(max_number):\n \"\"\"Calculate squares of all integers up to a maximum number\"\"\"\n squares = []\n for number in range(max_number):\n squares.append(number ** 2)\n return squares\n\nprint(calculate_squares_up_to(5))\n
(ecpc) > python squares.py \n[0, 1, 4, 9, 16]\n
De functie berekent eerst alle kwadraten, voegt ze toe aan een lijst en geeft vervolgens de lijst met uitkomsten terug. Een generator definieer je als volgt:
def calculate_squares_up_to(max_number):\n \"\"\"Generate squares of all integers up to a maximum number\"\"\"\n for number in range(max_number):\n yield number ** 2\n
Lekker kort, want we hoeven geen lijst bij te houden! Als je de functie aanroept krijg je geen resultaat terug, maar een generator. Als je de waardes wil zien dan gebruik je next()
, als volgt: square_generator = calculate_squares_up_to(5)\nnext(square_generator)\n# 0\nnext(square_generator)\n# 1\n...\nnext(square_generator)\n# 16\nnext(square_generator)\n# StopIteration\n
Als de generator is uitgeput (de for-loop is afgelopen, de functie sluit af) dan geeft Python een StopIteration
exception en crasht het programma -- tenzij je de exception afvangt. Het werkt, maar het is niet helemaal ideaal. Makkelijker is om de generator te gebruiken in een loop: for square in calculate_squares_up_to(5):\n print(\"Still calculating...\")\n print(square)\n
(ecpc) > python squares.py \nStill calculating...\n0\nStill calculating...\n1\nStill calculating...\n4\nStill calculating...\n9\nStill calculating...\n16\n
Dit kan ook in list comprehensions. En als je toch wilt wachten op alle resultaten, dan kan dat eenvoudig met squares = list(calculate_squares_up_to(5))
.
generators
Schrijf een generator function die het vermoeden van Collatz illustreert. Dat wil zeggen: beginnend bij een getal $n$, genereer het volgende getal als volgt: is het getal even, deel het dan door twee; is het getal oneven, vermenigvuldig het met 3 en tel er 1 bij op. Enzovoorts. Sluit de generator af als de uitkomst gelijk is aan 1. Dat is het vermoeden van Collatz: ongeacht met welk geheel getal je begint, je komt altijd op 1 uit. Als voorbeeld, beginnend bij het getal 3 krijg je de reeks 3, 10, 5, 16, 8, 4, 2, 1.
Uitwerkingen collatz.py
def Collatz(x):\n \"\"\"Illustrates Collatz's conjecture\n\n Starts with x generates next number when x is not equal to 1.\n Next number is x devided by 2 if x is even and x times 3 + 1 if x is odd.\n Collatz suspects that you always end with number 1 despite of the starting value.\n\n Args:\n x (int): starting value\n\n Yields:\n int: next number in the sequence\n \"\"\"\n yield x\n while x != 1:\n if x % 2 == 0:\n # x is even and is divided by 2\n x = x // 2 # dubble // makes it an integer\n else:\n # x is odd, multiply by 3 and add 1\n x = 3 * x + 1\n yield x\n\n\nprint(\"print the values of generator with next:\")\ncollatz_generator = Collatz(3)\nprint(next(collatz_generator))\nprint(next(collatz_generator))\nprint(next(collatz_generator))\nprint(next(collatz_generator))\nprint(next(collatz_generator))\nprint(next(collatz_generator))\nprint(next(collatz_generator))\nprint(next(collatz_generator))\n# print(next(collatz_generator)) # gives StopIteration exception\n\nprint(\"print values of generator without next:\")\nfor number in Collatz(28):\n print(number)\n
\n(ecpc) > python collatz.py\nprint the values of generator with next:\n3\n10\n5\n16\n8\n4\n2\n1\nprint the values of generator without next:\n28\n14\n7\n22\n11\n34\n17\n52\n26\n13\n40\n20\n10\n5\n16\n8\n4\n2\n1\n
"},{"location":"vervolg-python/#dunder-methods","title":"Dunder methods","text":"Hoe weet Python eigenlijk wat de lengte is van een string? Of hoe je getallen optelt? Voor operatoren als + - * / **
wordt eigenlijk een method aangeroepen. bijvoorbeeld __add__()
voor +
, en __mul__()
voor *
. Een ingebouwde functie als len()
roept stiekem de method __len__()
aan en print()
print de uitvoer van __str__()
. Zulke methodes worden dunder methods9 of magic methods genoemd. We kunnen zelf bijvoorbeeld een vector introduceren waarbij we de operatoren voor onze eigen doeleinden gebruiken 25. We defini\u00ebren het optellen van vectoren en de absolute waarde (norm) van de vector:
class Vector:\n def __init__(self, x, y):\n self.x = x\n self.y = y\n\n def __add__(self, other):\n new_x = self.x + other.x\n new_y = self.y + other.y\n return Vector(new_x, new_y)\n\n def __abs__(self):\n return (self.x ** 2 + self.y ** 2) ** .5\n
De speciale __init__()
methode zorgt voor de initialisatie van de class en de eerste parameter die alle methodes meekrijgen verwijst naar zichzelf en wordt dus gewoonlijk self
genoemd.10 Met de regel self.x = x
wordt de parameter x
bewaard voor later gebruik. Je kunt de class gebruiken op de volgende manier: Terminal
>>> v1 = Vector(0, 1)\n>>> v2 = Vector(1, 0)\n>>> abs(v1)\n1.0\n>>> abs(v2)\n1.0\n>>> abs(v1 + v2)\n1.4142135623730951\n>>> (v1 + v2).x, (v1 + v2).y\n(1, 1)\n>>> v1 + v2\n<__main__.Vector object at 0x7fdf80b3ae10>\n>>> print(v1 + v2)\n<__main__.Vector object at 0x7fdf80b45450>\n
In de eerste regels maken we twee vectoren v_1 en v_2 en berekenen de lengtes11 ||v_1||, ||v_2|| en ||v_1 + v_2||. Ook kunnen we de co\u00f6rdinaten van de som bekijken. Het gaat mis als we de somvector willen printen of willen kijken wat voor object het is. We krijgen technisch juiste, maar totaal onbruikbare informatie terug. Dit lossen we op met het defini\u00ebren van __str__()
, gebruikt door str()
en dus ook print()
, en __repr__()
, gebruikt door repr()
en de Python interpreter.12 class Vector:\n ...\n def __repr__(self):\n return f\"Vector: ({self.x}, {self.y})\"\n\n def __str__(self):\n # roept __repr__ aan\n return repr(self)\n
Terminal>>> v1 + v2\nVector: (1, 1)\n>>> print(v1 + v2)\nVector: (1, 1)\n
We raden je aan altijd een zinnige __str__
en __repr__
te defini\u00ebren. Vaak hebben classes geen dunder methods nodig (behalve __repr__
en __str__
).
"},{"location":"vervolg-python/#decorators","title":"Decorators","text":"Functies zijn ook objecten in Python. Je kunt ze, zoals we eerder gezien hebben, meegeven als argument of bewaren in een dictionary. Ook kun je functies in functies defini\u00ebren en functies defini\u00ebren die functies teruggeven. Vaag13. Ik moet hier altijd weer even over nadenken en daarom mag je dit stukje overslaan. Om decorators te gebruiken, hoef je niet per se te weten hoe ze werken.
Decorators worden vaak gebruikt om het gedrag van een functie aan te passen.
Stel je hebt een functie die eenvoudig twee getallen vermenigvuldigd. Je wilt deze functie, zonder hem van binnen te veranderen, aanpassen zodat hij altijd het kwadraat van de vermenigvuldiging geeft. Dus niet $a\\cdot b$, maar $(a\\cdot b)^2$. Dat kan als volgt:
def f(a, b):\n return a * b\n\n\ndef squared(func, a, b):\n return func(a, b) ** 2\n\nf(3, 4)\n# 12\nsquared(f, 3, 4)\n# 144\n
Het werkt, maar we moeten er wel steeds aan denken om squared()
aan te roepen en dan \u00f3\u00f3k nog de functie f()
als eerste argument mee te geven. Lastig. Maar omdat functies objecten zijn kan dit ook: def squared_func(func):\n def inner_func(a, b):\n return func(a, b) ** 2\n\n return inner_func\n\n\ng = squared_func(f)\ng(3, 4)\n# 144\n
Hier gebeurt iets geks\u2026 Om te begrijpen wat hier gebeurt moeten we een beetje heen en weer springen. In regel 8 roepen we de functie squared_func(f)
aan. In regel 5 zien we dat die functie een andere functie teruggeeft -- die niet wordt aangeroepen! In regel 8 wordt die functie bewaard als g
en pas in regel 9 roepen we hem aan. De functie g()
is dus eigenlijk gelijk aan de functie inner_func()
die in regels 2--3 gedefinieerd wordt. De aanroep in regel 9 zorgt er uiteindelijk voor dat in regel 3 de oorspronkelijke functie f(a, b)
wordt aangeroepen en dat het antwoord gekwadrateerd wordt. Dit is echt wel even lastig.
In deze opzet moet de inner_func(a, b)
nog weten dat de oorspronkelijke functie aangeroepen wordt met twee argumenten a
en b
. Maar ook dat hoeft niet. We hebben immers argument (un)packing met *args
:
def squared_func(func):\n def inner_func(*args):\n return func(*args) ** 2\n\n return inner_func\n
En nu komt het: in Python kun je de decorator syntax gebruiken om je functie te vervangen door een iets aangepaste functie. In plaats van: f = squared_func(f)\n
op te nemen in je code kun je de functie meteen `decoraten' als volgt: @squared_func\ndef f(a, b):\n return a * b\n\nf(3, 4)\n# 144\n
Als je meer wilt weten over hoe decorators werken en hoe je je eigen decorators kunt maken, dan vind je een uitgebreide uitleg in Primer on Python Decorators 26. Deze tutorial heb je niet per se nodig voor de volgende opdracht.
decorators
Schrijf en test een decorator die werkt als een soort logboek. Als je een functie aanroept die gedecoreerd is print dan een regel op het scherm met het tijdstip van de aanroep, de parameters die meegegeven werden \u00e9n de return value van de functie.
Uitwerkingen decorators.py.py
import datetime\n\n\ndef log(func):\n def inner(*args, **kwargs):\n return_value = func(*args, **kwargs)\n\n print(40 * \"-\")\n print(f\"Logging function call at {datetime.datetime.now()}.\")\n print(f\"Function was called as follows:\")\n print(f\"Arguments: {args}\")\n print(f\"Keyword arguments: {kwargs}\")\n print(f\"And the return value was {return_value}\")\n print(40 * \"-\")\n\n return return_value\n\n return inner\n\n\n@log\ndef f(a, b):\n return a * b\n\n\nf(3, 4)\nf(3, b=4)\n
\n(ecpc) > python decorators.py.py\n----------------------------------------\nLogging function call at year-month-date hours:minutes:seconds\nFunction was called as follows:\nArguments: (3, 4)\nKeyword arguments: {}\nAnd the return value was 12 ---------------------------------------- ----------------------------------------\nLogging function call at year-month-date hours:minutes:seconds\nFunction was called as follows:\nArguments: (3,)\nKeyword arguments: {'b': 4}\nAnd the return value was 12\n----------------------------------------\n
"},{"location":"vervolg-python/#modules","title":"Modules","text":"Als je een nieuw script begint te schrijven staat alle code in \u00e9\u00e9n bestand. Dat is lekker compact, maar heeft ook nadelen. Als je je experiment of programma gaat uitbreiden kan het erg onoverzichtelijk worden. Ook zul je al je wijzigingen steeds in dit bestand moeten doen terwijl je je code van eerdere experimenten misschien wel wilt bewaren. Mogelijk kopieer je steeds je script naar een nieuw bestand, maar dat is niet erg DRY.14 Als je dan bijvoorbeeld een functie of class wilt aanpassen, moet dat nog steeds op heel veel plekken. Daarom is het handig om gebruik te maken van modules.
Eenvoudig gezegd is een module een stuk Python code dat je kunt importeren en gebruiken. Meestal worden er in een module handige functies en classes gedefinieerd:
math.py import math\nprint(math.sqrt(2))\nprint(math.pi)\nprint(math.sin(.5 * math.pi))\n
\n(ecpc) > python math.py\n1.4142135623730951\n3.141592653589793\n1.0\n
Door de math
module te importeren hebben we opeens de beschikking over het getal $\\pi$ en de sinus- en wortelfunties.
Je kunt je eigen code ook importeren, maar hier moet je wel even opletten. Stel, we hebben een bestand square.py
:
square.py def square(x):\n return x**2\n\n\nprint(f\"The square of 4 is {square(4)}\")\n
\n(ecpc) > python square.py\nThe square of 4 is 16\n
De uitvoer is zoals verwacht. Maar nu willen we in een nieuw script, count_count.py
, de functie importeren en gebruiken:
count_count.py import square\n\nprint(f\"The square of 5 is {square.square(5)}\")\n
\n(ecpc) > python count_count.py\nThe square of 4 is 16\nThe square of 5 is 25\n
square.square
Waarom staat er in bovenstaande code nu opeens square.square()
in plaats van gewoon square()
?
Uitwerkingen Omdat je uit de module square.py
de functie square()
gebruikt.
Maar nu is er een probleem met de uitvoer van dit script: zowel het kwadraat van 4 als van 5 wordt geprint.
Tijdens het importeren wordt alle code die aanwezig is in square.py
ook daadwerkelijk gerund. Er zijn twee manieren om dit op te lossen:
- Alle 'extra' code verwijderen uit de module (
square.py
) - De code in de module alleen laten runnen als de module als script wordt aangeroepen, maar niet wanneer de module wordt ge\u00efmporteerd
De eerste oplossing is lang niet altijd wenselijk. Voor de tweede oplossing pas je square.py
als volgt aan: square.py
def square(x):\n return x**2\n\n\nif __name__ == \"__main__\":\n print(f\"The square of 4 is {square(4)}\")\n
Wanneer je een python script runt is de speciale variabele __name__
gelijk aan de string __main__
. Maar als je een module importeert is __name__
gelijk aan de naam van de module; in dit geval square
. Met bovenstaande constructie wordt de code alleen uitgevoerd wanneer de module direct gerund wordt: (ecpc) > python square.py \nThe square of 4 is 16\n(ecpc) > python count_count.py \nThe square of 5 is 25\n
Het if __name__ == '__main__'
-statement wordt heel veel gebruikt in Python modules.
modules
- Maak zelf de bestanden
square.py
en just_count.py
aan. ECPC
\u251c\u2500\u2500 square.py
\u251c\u2500\u2500 just_count.py
\u2514\u2500\u2500 \u2022\u2022\u2022 - Run
just_count.py
zonder het if __name__ == '__main__'
-statement. - Run
just_count.py
met het if __name__ == '__main__'
-statement. - Voeg
print(f\"{__name__ = }\")
toe bovenaan square.py
. - Run
square.py
en kijk wat __name__
is. - Run dan nu
just_count.py
. Zie hoe de speciale variabele __name__
verandert.
"},{"location":"vervolg-python/#packages","title":"Packages","text":"In Python zijn packages collecties van modules. Ook krijg je automatisch namespaces. Dat wil zeggen, wanneer je functies en modules uit een package importeert zitten ze niet in \u00e9\u00e9n grote vormeloze berg, maar in een soort boomstructuur. Dat betekent dat namen niet uniek hoeven te zijn. Er zijn duizenden bibliotheken beschikbaar voor python (numpy
, scipy
, matplotlib
, etc.) en die mogen allemaal een module test
bevatten. Namespaces zorgen ervoor dat je ze uniek kunt benaderen:
import numpy.test\nimport scipy.test\n
In bovenstaande code zijn numpy
en scipy
afzonderlijke namespaces. Ook zijn numpy.test
en scipy.test
afzonderlijke namespaces. De namen van bijvoorbeeld variabelen en functies binnen die modules zullen nooit met elkaar in conflict komen. Wij gaan in deze cursus onze code ook in packages stoppen. Op die manier kun je een softwarebibliotheek opbouwen voor je experiment en die code makkelijker delen met andere onderzoekers. Een pakket is opgebouwd zoals hieronder weergegeven:
\u2514\u2500\u2500 my_project_folder
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 script.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 my_package
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 package1
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 module1.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 module2.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 package2
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 module3.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 module4.py
Iedere package bestaat uit een directory met een __init__.py
-bestand.15
De verschillende modules uit het figuur hierboven kun je als volgt importeren en gebruiken in het bestand script.py
(we gaan er even vanuit dat iedere module een functie some_function()
bevat): script.py
# module direct importeren\nimport my_package.package1.module1\nmy_package.package1.module1.some_function()\n\n# losse module vanuit een package importeren\nfrom my_package.package1 import module2\nmodule2.some_function()\n\n# module importeren onder een andere naam\nimport my_package.module4 as m4\nm4.some_function()\n
In deze cursus gaan we ook packages maken. Feitelijk hoeven we een python script dus alleen maar in een map te stoppen en in diezelfde map een lege __init__.py
aan te maken.
Waarschuwing
Let op: als je de __init__.py
vergeet dan lijkt alles het alsnog te doen. Maar je maakt nu een implicit namespace package waarbij bepaalde directories toch weer op een grote hoop gegooid worden. Geloof me, echt niet handig.16 Namespace packages kunnen handig zijn voor grote projecten, maar dat is het dan ook wel. Wij gaan hier niet verder op in. Kortom: let op en gebruik altijd een __init__.py
.
Packages
In deze opdracht ga je oefenen met het aanmaken van packages, modules en het importeren en aanroepen daarvan.
- Maak in de map
ECPC
een package models
met twee modules: polynomials.py
en tests.py
. - In de
polynomials
-module maak je een functie line(x, a, b)
die de vergelijking voor een lijn voor ons berekent: $y = ax + b$. -
In de tests
-module maak je een functie test_line()
die het volgende doet:
- gebruik de
line()
-functie uit de polynomials
-module om de $y$-waarde uit te rekenen voor een bepaald punt bij een gegeven $a$ en $b$. - Vergelijk die berekende waarde met de waarde die het volgens jou moet zijn (met de hand nagerekend).
- Print
TEST PASSED
als het klopt, en TEST FAILED
als het niet klopt.
-
Maak een bestand practice-packages.py
die:
- Een grafiek maakt van jouw lijn. Bepaal zelf het domein en de waardes voor $a$ en $b$.
- De test uitvoert door de
test_line()
-functie aan te roepen. - Pas je
line()
-functie eventjes aan om te kijken of je test ook echt werkt. Bijvoorbeeld: bij $y = ax$ zou je TEST FAILED
moeten zien.
Uitwerkingen De mappen structuur ziet er als volgt uit:
\u2514\u2500\u2500 ECPC
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 \u2022\u2022\u2022 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 practice-packages.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 models
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 polynomials.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 tests.py
polynomials.py
def line(x, a, b):\n y = a * x * b\n return y\n
tests.pyfrom models.polynomials import line\n\n\ndef test_line():\n actual = line(2, 4, 3)\n expected = 11\n\n if actual == expected:\n print(\"TEST PASSED\")\n else:\n print(\"TEST FAILED\")\n
practice-packages.pyimport numpy as np\nfrom models.polynomials import line\nfrom models import tests\nimport matplotlib.pyplot as plt\n\nx = np.arange(0, 28)\na = 1\nb = 7\n\nplt.plot(x, line(x, a, b))\nplt.show()\n\ntests.test_line()\n
Relatieve en absolute imports Als je in een module een andere module wilt importeren dan zijn daarvoor twee opties: relatieve en absolute imports. Relatief wil zeggen: importeer module1 uit dezelfde directory, of ten opzichte van deze directory (..
betekent een directory hoger bijvoorbeeld). Bij een absolute import moet je de volledige locatie binnen het package opgeven. Als voorbeeld, stel dat module1
uit het figuur hierboven de modules module2
en module3
wil importeren:
# module1.py\n\n# relative imports\nfrom . import module2\nfrom ..package2 import module3\n\n# absolute imports\nfrom my_package.package1 import module2\nfrom my_package.package2 import module3\n
Absolute imports zijn wat meer werk, maar je maakt wel heel duidelijk welke module je wilt importeren. Relative imports zorgen in de praktijk regelmatig voor -- soms lastig te vinden -- bugs. Als je tegen problemen aanloopt: gebruik dan absolute imports. De Standard Library en de Python Package Index"},{"location":"vervolg-python/#de-standard-library-en-de-python-package-index","title":"De Standard Library en de Python Package Index","text":"Voor Python zijn ontzettend veel bibliotheken beschikbaar die het leven een stuk aangenamer maken. Voor een gedeelte daarvan geldt dat ze altijd aanwezig zijn als je Python ge\u00efnstalleerd hebt. Deze set vormt de standard library 23. Om te voorkomen dat je zelf het wiel uitvindt is het goed om af en toe door de lijst te bladeren zodat je een idee krijgt wat er allemaal beschikbaar is. Ziet het er bruikbaar uit? Lees dan vooral de documentatie! Tip: vergeet de built-in functions niet.
Standard Library
- Zoek de The Python Standard Library lijst op in bijvoorbeeld de Python documentatie.
- Welke bibliotheken heb je al eerder gebruik van gemaakt?
- Kies een bibliotheek uit die jouw aandacht trekt en neus door de documentatie.
- Ga terug naar de lijst en bekijk de Built-in functions, welke functie kende je nog niet maar lijkt je wel heel handig?
Verder zijn er nog eindeloos veel packages beschikbaar gesteld door programmeurs, van hobbyist tot multinational. Deze kunnen centraal gepubliceerd worden in de Python Package Index 17. Je kunt daar vaak ook zien hoe populair een package is. Dit is een belangrijke indicatie voor de kwaliteit en bruikbaarheid van een package.
PyPI
Later in de cursus leren jullie werken met Poetry daarmee is het gemakkelijk om je eigen project op PyPI te zetten. Andere studenten gingen jullie al voor:
- Ga naar pypi.org en zoek naar het project gammaspotter.
"},{"location":"vervolg-python/#exceptions","title":"Exceptions","text":"Exceptions zijn de foutmeldingen van Python. Je krijgt ze als je bijvoorbeeld probeert te delen door nul
divide.py print(1/0)\n
\n(ecpc) > python divide.py\nTraceback (most recent call last):\n File \"devide.py\", line 1, in < module >\n print(1/0)\n ~^~\nZeroDivisionError: division by zero\n\n
of wanneer je een typefout maakt:
particle.py s = \"particle\"\ns.upler()\n
\n(ecpc) > python particle.py\nTraceback (most recent call last):\n File \"particle.py\", line 2, in < module >\n s.upler()\n ^^^^^^^\nAttributeError: 'str' object has no attribute 'upler'. Did you mean: 'upper'?\n
Merk op dat je een exception met traceback meestal van onder naar boven leest. Onderaan staat de foutmelding (exception) en daar boven een traceback: een kruimelpad van w\u00e1\u00e1r in de code het probleem optrad; onderaan de regel waarin het echt fout ging, en naar boven toe alle tussenliggende functies en bibliotheken met bovenaan het hoofdprogramma.
Exception
number_input.pynumber_input = input(\"Give a number: \")\nnumber_multiply = 2.8\nprint(number_input * number_multiply)\n
- Neem het script hierboven over en voer het uit.
- Wat voor soort error geeft Python terug?
- In welke regel van het script zit volgens de Traceback het probleem?
- Leg in eigen woorden uit wat het probleem is.
Uitwerkingen - Python geeft een TypeError terug.
- Het probleem zit in regel 3:
print(number_input * number_multiply)
- De functie
input()
geeft een string terug. Daardoor is number_input
ook een string. Een string behoort tot het type sequences. Het is een reeks van elementen, '28' is een reeks van '2' en '8', 'abc' is een reeks van 'a', 'b' en 'c' en ook [0,1,2] is reeks van '0', '1', '2'. Zelfs als je in dit script het getal 8 zou invoeren dan is number_input een sequence met maar een element: '8'. Een sequence kan je vermenigvuldigen, maar niet met en float, alleen met een interger. Kijk maar eens wat er gebeurd als je number_multiply = 3
neerzet. Wat gebeurd er als je 'abc' met 3 vermenigvuldigd? En kun je ook 3 * [0,1,2] printen?
Exceptions afvangen Een exception kan vervelend zijn. Het is een beetje jammer als je bijvoorbeeld tijdens een langdurige meting telkens een weerstand aan het uitrekenen bent ($R = \\frac{U}{I}$) en de stroomsterkte $I$ wordt na anderhalf uur heel eventjes nul. Je programma crasht en je metingen zijn weg. Zoek de fout (niet altijd makkelijk!) en probeer het nog eens.
Je kunt exceptions afvangen en afhandelen met een try...except
blok:
def R(U, I):\n try:\n R = U / I\n except ZeroDivisionError:\n R = \"Inf\"\n return R\n
print(R(10, 2))\n# 5.0\nprint(R(10, 0))\n# Inf\n
Ook kun je zelf exceptions maken. Stel je schrijft een programma om een oscilloscoop uit te lezen dat twee kanalen heeft om de spanning te meten. Kanaal 0 en kanaal 1. Het programma moet gebruikt kunnen worden door andere studenten in de onderzoeksgroep dus het kan nu eenmaal gebeuren dat iemand niet op zit te letten -- niet jij, jij let altijd goed op. Een andere student die een programma schrijft en jouw code gebruikt wil een spanning meten op kanaal 2, het was immers een tweekanaals oscilloscoop. Maar kanaal 2 bestaat niet. Sommige oscilloscopen klagen dan niet maar geven een random getal terug. Dit kan leiden tot heel vervelende en lastig te achterhalen fouten in het experiment. Met dat idee in je achterhoofd kun je code schrijven die controleert op het kanaalnummer en een exception geeft:
# we maken een subclass van de 'standaard' Exception\nclass InvalidChannelException(Exception):\n pass\n\ndef get_voltage(channel):\n if channel not in [0, 1]:\n raise InvalidChannelException(f\"Use channel 0 or 1, not {channel}\")\n ...\n return voltage\n
Met deze uitvoer in het geval dat er iets mis gaat: voltage = get_voltage(1)\nprint(voltage)\n# 1.0\n
voltage = get_voltage(2)\nprint(voltage)\n
(ecpc) > get_voltage.py \nTraceback (most recent call last):\nFile \"get_voltage.py\", line 1, in < module >\n get_voltage(2)\nFile \"exception_channel.py\", line 6, in get_voltage\n raise InvalidChannelException(f\"Use channel 0 or 1, not {channel}\")\nInvalidChannelException: Use channel 0 or 1, not 2\n
Je kunt op deze manier voorkomen dat iemand dagen kwijt is aan het overdoen van achteraf verkeerd gebleken metingen. Ook kun je 'vage' foutmeldingen omzetten in duidelijkere foutmeldingen:
class NoCurrentError(Exception):\n pass\n\n\ndef R(U, I):\n try:\n R = U / I\n except ZeroDivisionError:\n raise NoCurrentError(\"There is no current flowing through the resistor.\")\n return R\n
In plaats van een ZeroDivisionError
krijg je nu een NoCurrentError
. Je programma crasht nog steeds (wellicht niet handig) maar de foutmelding is nu wel specifiek voor het probleem en kan in de rest van je programma wellicht beter afgevangen en opgelost worden. Misschien beter dan niet crashen en een mogelijk foute waarde doorgeven. Die afweging zul je zelf moeten maken. exceptions
De volgende code berekent een gemiddelde van een lijst getallen:
def average(values):\n return sum(values) / len(values) \n
Er is alleen geen foutafhandeling en dat kan leiden tot exceptions. De volgende aanroepen zorgen voor een crash (probeer ze allemaal uit!): average([])\naverage(4)\naverage(\"12345\")\n
Pas de functie average()
zodanig aan dat bij bovenstaande aanroepen slechts een waarschuwing wordt geprint. Vang daartoe de exceptions netjes af en geef de waarde None
terug wanneer een gemiddelde niet berekend kan worden. Dus bovenstaande drie aanroepen krijgen None
terug terwijl er een waarschuwing wordt geprint. Uitwerkingen exceptions.py
def average(values):\n try:\n average = sum(values) / len(values)\n except TypeError:\n average = None\n print(\"Input is not correct type\")\n except ZeroDivisionError:\n average = None\n print(\"Input is empty\")\n\n return average\n\n\nprint(average([1, 2, 3]))\n\naverage([])\n# # gives: ZeroDivisionError: division by zero\n\naverage(4)\n# # gives: TypeError: 'int' object is not iterable\n\na = average(\"12345\")\n# # gives: TypeError: unsupported operand type(s) for +: 'int' and 'str'\n# print(a)\n
\n(ecpc) > python exceptions.py\n2.0\nInput is empty\nInput is not the correct type\nInput is not the correct type\n
-
We gaan ervan uit dat iedereen bekend is met recente versies van Python en we gaan niet in op de -- soms ingrijpende -- veranderingen die de taal heeft ondergaan. Python 2 is dood. Leve Python 3!\u00a0\u21a9
-
Tenzij je al veel zelf hebt geprogrammeerd in Python, buiten de cursussen om.\u00a0\u21a9
-
De programmertaal C ligt dichter bij machinetaal dan Python en is daarmee veel sneller maar ook veel minder geavanceerd.\u00a0\u21a9
-
Echt. De sinus van 2000 $x$-waardes berekenen kostte NumPy in een test 11.6$\\micro$s en de for-loop wel 1357.7$\\micro$s.\u00a0\u21a9
-
Strikt genomen is dit niet helemaal waar. Je kunt een nieuwe array cre\u00ebren door meerdere arrays aan elkaar te plakken. Maar een eenvoudige append()
-method bestaat niet voor arrays.\u00a0\u21a9
-
Letterlijk: onveranderbaar.\u00a0\u21a9
-
Daar is bijvoorbeeld de collections.namedtuple()
dan weer handig voor.\u00a0\u21a9
-
Notatie hetzelfde, maar gebruik nu {
}-haakjes.\u00a0\u21a9
-
Dunder staat voor double underscore, de twee lage streepjes die om de naam heen staan.\u00a0\u21a9
-
Maar dat is niet verplicht, je mag in principe zelf een naam kiezen. Doe dat echter niet.\u00a0\u21a9
-
Absolute waarde of beter, norm, van een vector is eenvoudig gezegd haar lengte.\u00a0\u21a9
-
Het verschil tussen de twee is subtiel. De Pythondocumentatie geeft aan dat de __repr__
altijd ondubbelzinnig moet zijn, terwijl de __str__
vooral leesbaar moet zijn. Voor eenvoudige objecten zijn ze veelal gelijk.\u00a0\u21a9
-
Calmcode doet een goeie poging om dit rustig uit te leggen, kijk daarvoor op https://calmcode.io/decorators/functions.html \u21a9
-
DRY staat voor Don't Repeat Yourself, een belangrijk principe in software engineering.\u00a0\u21a9
-
Dat bestand is vaak leeg, maar kan code bevatten die gerund wordt zodra het package wordt ge\u00efmporteerd.\u00a0\u21a9
-
En wat mij betreft: een fout dat zoiets \u00fcberhaupt kan in Python. Zen of Python: explicit is better than implicit. \u21a9
-
Python Software Foundation. Python package index. URL: https://pypi.org.\u00a0\u21a9\u21a9
-
Ivo van Vulpen and Martijn Stegeman. Wetenschappelijk programmeren. 2020. URL: https://progns.proglab.nl/syllabus.\u00a0\u21a9
-
Anthony Scopatz and Kathryn D. Huff. Effective Computation in Physics. O'Reilly Media, Inc., 2015. URL: https://www.oreilly.com/library/view/effective-computation-in/9781491901564/.\u00a0\u21a9
-
Allen Downey. Think Python. Green Tea Press, 2nd edition edition, 2015. URL: https://greenteapress.com/wp/think-python-2e/.\u00a0\u21a9
-
Tim Peters. Zen of python. URL: https://groups.google.com/d/msg/comp.lang.python/B_VxeTBClM0/L8W9KlsiriUJ.\u00a0\u21a9
-
Chaitanya Baweja. Contemplating the zen of python. URL: https://medium.com/better-programming/contemplating-the-zen-of-python-186722b833e5.\u00a0\u21a9
-
Python Software Foundation. The python standard library. URL: https://docs.python.org/3/library/.\u00a0\u21a9\u21a9
-
Real Python. Real python: python tutorials. URL: https://realpython.com.\u00a0\u21a9
-
Malay Agarwal. Operator and function overloading in custom python classes. URL: https://realpython.com/operator-function-overloading/ (visited on 2020-06-25).\u00a0\u21a9
-
Geir Arne Hjelle. Primer on python decorators. 2018. URL: https://realpython.com/primer-on-python-decorators/.\u00a0\u21a9
"},{"location":"zonnecel/","title":"Zonnecel","text":"De toenemende behoefte aan energie heeft het zoeken naar nieuwe energiebronnen belangrijk gemaakt. Zonne-energie is \u00e9\u00e9n van de veelbelovende, niet-conventionele bronnen. Zonne-energie is echter niet meteen bruikbaar en moet eerst omgezet worden naar warmte of elektrische energie. De omzetting van zonne-energie naar een bruikbare vorm van energie kan gedaan worden door een zonneboiler of een zonnecel. In de komende sessies staat de zonnecel centraal. Je gaat allerlei eigenschappen van zonnecellen onderzoeken en proberen te verklaren.
"},{"location":"zonnecel/#de-fotovoltaische-zonnecel","title":"De fotovolta\u00efsche zonnecel","text":"Stralingsenergie van de zon is een vorm van energie die niet erg nuttig is voor de meeste toepassingen. Om de energie van de zon nuttig te kunnen gebruiken, moet de straling omgezet worden. Een mogelijkheid daartoe is de fotovolta\u00efsche zonnecel. In de zonnecel maken fotonen uit het zonlicht geladen (elektrische) deeltjes vrij die via metaalcontacten op de zonnecel door een extern circuit kunnen stromen om daar hun elektrische energie af te geven. Zolang er licht valt op de zonnecel gaat het proces van vrijmaken van elektronen door en wordt er een elektrische stroom geproduceerd.
"},{"location":"zonnecel/#werking","title":"Werking","text":"Werking van een zonnecel. Een foton met voldoende energie kan een elektron-gat-paar maken. Door de grenslaag tussen het n-type silicium en het p-type-silicium kan het elektron alleen linksom stromen, door het externe circuit, en het gat alleen rechtsom.
De werking van de zonnecel is schematisch weergegeven in de figuur hieronder. Een zonnecel bestaat uit twee soorten siliciumkristallen, een bovenlaag van het n-type silicium en een tweede, dikkere laag van het p-type silicium. In het n-type silicium kunnen elektronen gemakkelijk bewegen, terwijl in het p-type silicium de gaten (positieve lading) makkelijk kunnen bewegen. Tussen het p- en n-type silicium ontstaat een grenslaag, welke een barri\u00e8re vormt voor zowel de elektronen als de gaten. Deze zogenoemde pn-junctie is de basis van de huidige elektronica en heeft vele toepassingen, zo ook in de zonnecel.
In een zonnecel is de n-laag zo dun dat het zonlicht de grenslaag kan bereiken. Als er nu een foton op de grenslaag valt, en het foton heeft voldoende energie, dan maakt dat foton een elektron-gat-paar. Kijkend naar de figuur kunnen de elektronen door de grenslaag niet rechtsom bewegen en de gaten niet linksom. Het elektron gaat nu linksom stromen en het gat rechtsom. Er ontstaat dus een elektrische stroom. Na doorlopen van het externe circuit recombineert het elektron weer met het gat in het p-type silicium. De maximale stroom die gaat lopen wordt bepaald door het aantal elektron-gat-paren dat gevormd wordt. De maximale spanning die over de zonnecel komt te staan wordt bepaald door de energie die daarvoor nodig is (bedenk dat $[U] = J/C$!).
Om een elektron-gat-paar in een silicium zonnecel te maken is een energie nodig van 1.12 eV (elektronvolt). De energie van een foton ($E_f$) is gelijk aan \\begin{equation} E_f = \\frac{hc}{\\lambda} \\end{equation} waar $h$ staat voor de constante van Planck ($h \\approx 4.136 \\cdot 10^{-15} \\text{ eV} \\cdot \\text{s}$), $c$ staat voor de snelheid waarmee licht zich voortplant ($c \\approx 2.998 \\cdot 10^8$ ms$^{-1}$) en $\\lambda$ staat voor de golflengte van het licht. Dit betekent dat een foton met een golflengte van ongeveer \\begin{equation} \\lambda = \\frac{(4.136 \\cdot 10^{-15} \\text{ eV} \\cdot \\text{s}) \\cdot (2.998 \\cdot 10^8 \\text{ ms}^{-1})}{1.12 \\text{ eV}}\\approx 1100 {\\rm nm} \\end{equation} in staat is om een elektron-gat-paar te maken. Fotonen met een golflengte groter dan 1100 nm hebben een lagere energie dan 1.12 eV en daarvoor is de zonnecel niet gevoelig. Fotonen met een kortere golflengte dan 1100 nm hebben een hogere energie dan nodig is. Zij maken wel een elektron-gat-paar, maar het overschot aan energie wordt niet omgezet in elektrische energie, deze energie gaat verloren als warmte.
Op YouTube staat de volgende video met uitleg over de werking van de zonnecel: How do solar cells work?.
"},{"location":"zonnecel/#vereenvoudigde-modelbeschrijving","title":"Vereenvoudigde modelbeschrijving","text":"De werking van een zonnecel hangt sterk samen met de werking van een diode. Een diode heeft de bijzondere eigenschap dat afhankelijk van de polariteit over de diode het \u00f3f geen stroom door laat en dus een oneindige hoge weerstand heeft, \u00f3f alle stroom doorlaat en bij benadering een weerstand van 0 heeft. Preciezer gezegd: voor een diode geldt dat de stroom die doorgelaten wordt, afhangt van de spanning over de diode. De stroom door een diode, $I_d$, wordt (bij benadering) gegeven door
\\begin{equation} I_d = I_0 \\left( {\\rm e}^{\\frac{eU}{kT}} - 1 \\right), \\end{equation} waarbij $e$ de elektronlading is ($e \\approx 1.602 \\cdot 10^{-19}$ C), $U$ de spanning over de diode, $k$ de Boltzmannconstante ($k \\approx {1.381} \\cdot 10^{-23}$ JK$^{-1}$) en $T$ de temperatuur. $I_0$ is de lekstroom van de diode. Als de spanning over de diode negatief is, geldt dat $\\exp \\left( \\frac{eU}{kT} \\right) \\ll 1$ en is $I_d \\approx - I_0 \\approx 5-7 \\; \\mu$A en dus bij benadering 0. Als de spanning over de diode positief is groeit de stroom exponentieel en is de weerstand van de diode bij benadering 0. Dit gedrag wordt ge\u00efllustreerd in de figuur hieronder.
Links het symbool waarmee een diode weergegeven wordt in een schakeling en rechts een $IU$-karakteristiek van een diode.
Een vereenvoudigde voorstelling van een zonnecel met daarop aangesloten een belastingsweerstand $R_b$. $I_L$ is de stroom opgewekt door elektron-gat-paren, $I_d$ is de stroom die door de diode loopt en $I$ is de stroom die door belastingsweerstand $R_b$ loopt, die aangesloten is op de zonnecel.
Voor een eerste benadering kun je een zonnecel voorstellen als een speciale stroombron, zoals weergegeven is in bovenstaande figuur. In deze schakeling is ook de belastingsweerstand $R_b$ over de zonnecel getekend. De stroom die geleverd wordt door de zonnecel, $I$, hangt af van de stroom ten gevolge van het aantal elektron-gat-paren dat gemaakt wordt door het zonlicht, $I_{L}$, en de stroom door de diode, $I_d$. Dus: \\begin{equation} I = I_L - I_d. \\end{equation} Met behulp van de diodevergelijking kun je bovenstaande vergelijking verder uitschrijven. In de exponent voor de diode komt er echter nog een factor $n$ bij die samenhangt met de materiaaleigenschappen van de zonnecel. Waarden van $n$ liggen typisch tussen 1 en 5, afhankelijk van het type zonnecel. Voor het type zonnecel waarmee je in dit experiment zult werken is $n$ ongeveer 10-15. De stroom die de zonnecel levert wordt nu bij benadering gegeven door
\\begin{equation} I = I_{L} - I_d = I_{L} - I_0 \\left( {\\rm e}^{ \\frac{e U}{nkT}} - 1 \\right). \\end{equation}
"},{"location":"zonnecel/#iu-karakteristiek","title":"I,U-karakteristiek","text":"In de praktijk zul je altijd metingen doen aan zonnepanelen, waarbij zonnecellen in het paneel samengebracht zijn. De spanning die over een zonnepaneel staat hangt onder andere af van het aantal zonnecellen dat in serie geschakeld is. De stroom dat een zonnepaneel kan leveren wordt bepaald door het aantal elektron-gat-paren dat gemaakt wordt of, anders gezegd, door het aantal fotonen dat geabsorbeerd wordt. Het is echter niet zo dat je zonder meer kunt stellen dat wanneer er zonlicht op een zonnepaneel valt er een maximale spanning over het paneel staat en dat de stroom toeneemt als de lichtintensiteit toeneemt.
Het is daarom zinvol om, voordat je aan een experiment begint, het gedrag van een zonnepaneel te onderzoeken. In eerste instantie doe je dit door te kijken naar de $I,U$-karakteristiek van het zonnepaneel. Zo'n karakteristiek is weergegeven in onderstaand figuur:
Als je naar de $I,U$-karakteristiek kijkt, zie je dat het zonnepaneel zich bij lage spanningen gedraagt als een niet-ideale stroombron. Als je rond de maximale spanning kijkt, zie je dat het zonnepaneel zich daar vrijwel gedraagt als een niet-ideale spanningsbron.
De stroom die geleverd kan worden door een zonnecel uitgezet tegen de spanning $U_\\text{PV}$ geleverd door de zonnecel. Hier staat PV voor PhotoVoltaic cell.
"},{"location":"zonnecel/#prb-karakteristiek","title":"P,Rb-karakteristiek","text":"Het is bij zonnepanelen natuurlijk interessant om naar het elektrisch vermogen te kijken dat een zonnepaneel kan leveren. Het geleverd vermogen door een zonnepaneel hangt af van de materiaaleigenschappen van het paneel. Om een zo hoog mogelijk vermogen te kunnen leveren moet het zonnepaneel een zo hoog mogelijke stroom en spanning leveren. Belangrijk ook is dat het vermogen afhangt van de belasting door het circuit. Met andere woorden: bij verschillende weerstandswaardes wordt een ander vermogen geleverd. Ook is er een optimale weerstand waarbij het vermogen maximaal is:
Het vermogen dat geleverd kan worden door een zonnecel uitgezet tegen de belasting (weerstand) van het circuit. Er is duidelijk een maximum in het vermogen bij een optimale weerstand.
"},{"location":"zonnecel/#fill-factor","title":"Fill factor","text":"De kwaliteit van een zonnecel/-paneel wordt experimenteel vaak aangeduid met de fill factor $FF$. De fill factor wordt gegeven door \\begin{equation} FF = \\frac{P_{max}}{P_{T_{max}}} = \\frac{I_{Pmax} \\cdot U_{Pmax}}{I_{sc} \\cdot U_{oc}}, \\end{equation} waarbij $P_{max}$ het maximaal vermogen is wat een zonnecel/-paneel levert en $P_{T_{max}}$ het theoretisch maximaal vermogen is. $I_{sc}$ is de kortsluitstroom (bij een belastingsweerstand $R_b$ gelijk aan 0) en $U_{oc}$ de open klemspanning (wanneer het zonnepaneel niet belast wordt). $I_{Pmax}$ en $U_{Pmax}$ zijn de waarden voor respectievelijk de stroom en spanning waarbij het geleverd vermogen maximaal is.
"},{"location":"zonnecel/#maximum-power-point-tracking","title":"Maximum power point tracking","text":"De optimale weerstand waarbij het vermogen dat geleverd wordt door een zonnecel maximaal is, is helaas geen constante. Deze weerstandswaarde is afhankelijk van verschillende condities waarbij de belangrijkste de lichtintensiteit op de zonnecel is. Dat betekent dat, zonder aanpassingen, het vermogen dat geleverd wordt door de zonnecel meestal veel lager is dan je zou wensen.
Voor zonnepanelen die elektriciteit leveren aan het lichtnet is dit een groot probleem. Allereerst wil je je investering zo snel mogelijk terugverdienen en ook daarna wil je dat de opbrengst maximaal is. Ten tweede is het zo dat de weerstand van het lichtnet bijzonder klein is. Het vermogen dat daardoor geleverd wordt is ook heel klein. Dit wordt opgelost door \u2014 envoudig gezegd \u2014 de verbinding tussen het zonnepaneel en het lichtnet vele malen per seconde aan en uit te schakelen. Hierdoor voelt het zonnepaneel als het ware een weerstand. Deze weerstand is afhankelijk van de hoeveelheid tijd dat het paneel niet aan het lichtnet is geschakeld. Door slim te schakelen kan de weerstand z\u00f3 gekozen worden dat het geleverde vermogen maximaal is. Als de lichtintensiteit wijzigt kan ook de weerstand worden aangepast. Dit heet maximum power point tracking.
"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Introductie","text":"Welkom bij de Experiment Control with Python Course. Dit vak wordt aangeboden aan studenten van de joint degree natuur- en sterrenkunde van de Vrije Universiteit en de Universiteit van Amsterdam, en wordt gegeven door Annelies Vlaar (a.m.vlaar@vu.nl) en David Fokkema (d.b.r.a.fokkema@vu.nl).
Het doel van deze cursus is om jullie kennis te laten maken met het aansturen en uitlezen van een experiment. Bij heel eenvoudige experimenten kun je metingen \u00e9\u00e9n voor \u00e9\u00e9n verrichten en noteren in je labjournaal, bijvoorbeeld bij de trillingstijd van een slinger bij verschillende lengtes. Maar al snel wordt dat ondoenlijk, bijvoorbeeld wanneer je de hele beweging van de slinger wilt volgen met een ultrasoon afstandsdetector. In een gemiddeld lab worden alle experimenten via de computer aangestuurd en uitgelezen. Voor standaard handelingen zoals het bekijken van een sample onder een elektronenmicroscoop of het opnemen van een spectrum van een radioactieve bron kun je de door de fabrikant meegeleverde software gebruiken. Vaak echter is die software \u00f3f heel duur terwijl je maar een klein deel van de functionaliteit nodig hebt, of ongeschikt voor jouw doeleinden. En heel vaak voer je g\u00e9\u00e9n standaardexperiment uit, maar ben je een nieuw experiment aan het opzetten. In dat laatste geval is er helemaal geen software voorhanden. Je zult dus zelf aan de slag moeten.
We willen je in deze cursus niet alleen maar leren om een snelle meting uit te voeren, maar ook hoe je de code netjes schrijft. Als je een bachelorproject doet, een masterstage, of een promotieonderzoek, dan wil je code schrijven die nog steeds bruikbaar is nadat je je stage hebt afgerond of je proefschrift hebt verdedigd. Ook zullen andere onderzoekers aan dezelfde code werken. Het is dus belangrijk om te zorgen voor een duidelijke structuur waarmee de code overzichtelijk blijft maar ook makkelijk is aan te passen of uit te breiden.
Jullie gaan aan de slag met een Arduino. De Arduino bevat firmware waarmee het zich gedraagt als een meetinstrument en kan communiceren met de computer volgens een standaardprotocol dat ook ge\u00efmplementeerd wordt door onder andere functiegeneratoren en oscilloscopen.
"},{"location":"#werk-van-anderen","title":"Werk van anderen","text":"Over Python wordt veel geschreven en er zijn tal van (professionele) trainingen die je kunt volgen. Over de hele wereld zijn conferenties en er is vermoedelijk geen universiteit meer te vinden waar Python niet wordt gebruikt. Dit vak is niet in een vacu\u00fcm ontstaan. Voordat dit vak in studiejaar 2020-2021 voor het eerst gegeven werd is jarenlang het vak Experimentautomatisering met LabVIEW gegeven aan de Vrije Universiteit door Jan Mulder en Gerrit Kuik. Dit vak is de spirituele opvolger van het LabVIEW-vak. Een website (en boek!) met eenzelfde soort insteek is Python for the lab van Aquiles Carattino.2 Op de website is veel informatie te vinden over hoe je Python gebruikt (slots, singletons, multiprocessing) en met verschillende soorten hardware communiceert (van o.a. National Instruments en Basler). In het boek leer je hoe je een eenvoudig experiment opzet en aanstuurt vanuit Python, heel vergelijkbaar met het eerste deel van deze cursus. Zowel in deze cursus als in het boek is gekozen voor het diode-experiment (project 1 uit het Arduino Projects Book dat meegeleverd wordt met de Arduino Starter Kit). Een groot verschil is dat je in deze cursus leert over versiebeheer, command-line interfaces en project management met Poetry.
Voor het eindfeest automatiseren we een zonnecelexperiment. Het idee hiervoor is gebaseerd op het experiment dat we uitvoeren in het eerste jaar. Hoewel hetzelfde eerder gedaan is door o.a. Chenni et al.3 en Hammoumi et al.4 met LabVIEW, is de schakeling die in deze cursus gebruikt wordt door ons ontworpen om de noodzaak van een relatief dure stroomsterktesensor te vermijden. Ook wordt de schakeling daardoor fysisch gezien wat interessanter.
Deze cursus is oorspronkelijk opgezet door David Fokkema maar in de jaren daarna door Annelies Vlaar aanzienlijk verbeterd. De cursus heeft nu een veel sterkere focus (minder belangrijke zaken zijn verwijderd) en de opbouw is flink aangepakt. In 2024 heeft Annelies ook een subsidie toegekend gekregen om de cursus verder te verbeteren door een aantal studentassistenten in te huren. We zijn veel dank verschuldigd aan Olivier Swaak, Derk Niessink, Amin Rouan Serik, Thijs de Zeeuw en Fijke Oei.
"},{"location":"#notatie","title":"Notatie","text":"We zullen in deze handleiding vaak Engelse termen gebruiken, ook als Nederlandse termen voorhanden zijn. Bijvoorbeeld: list in plaats van lijst, class in plaats van klasse. Dit doen we omdat deze Engelse termen veel meer ingeburgerd zijn en omdat voor sommige van de Engelse termen geen goede vertalingen zijn. Liever wat consequenter Engelse termen gebruiken dan alles door elkaar!
In deze handleiding kom je twee verschillende dingen tegen. Pythoncode en systeemopdrachten. Voor pythoncode geldt dat je in Visual Studio Code een nieuw bestand moet aanmaken met de extensie .py
en dat je daarin de code kunt typen. Vervolgens kun je het script runnen en geeft Visual Studio Code de uitvoer terug. In deze handleiding zal dat als volgt worden weergegeven:
python_code.py # I am a script. I am in a colored block\n# and the Python code has colored syntax.\n\ndef my_func(x):\n return x ** 2\n\nprint(my_func(2))\n
\n(ecpc) > python python_code.py\n4\n
Linksboven kun je op de -icoon klikken om de output van de python code te zien.
Rechtsboven in het blok staat een -icoon. Als je daar op klikt dan wordt de hele code gekopieerd naar het klembord en kun je het in Visual Studio Code weer plakken met Ctrl+V.
Ook zullen we soms systeemopdrachten moeten uitvoeren. We willen bijvoorbeeld nieuwe Pythonbibliotheken installeren of onze nieuw-gebouwde applicaties testen. Dit doen we vanuit de terminal. De terminal biedt een zogeheten command-line interface voor het systeem. Dit in tegenstelling tot een grafische interface.1
Met deze notatie laten we zien hoe je my-script.py
met python kunt runnen: Terminal
python my-script.py\n
Zoals je ziet hebben we de prompt (bijvoorbeeld >
) weggelaten zodat je makkelijker commando's kunt kopi\u00ebren en plakken. Wanneer we ook de uitvoer van commando's laten zien is het handiger om onderscheid te maken tussen het commando en de uitvoer. Nu geven we wel de prompt weer ((ecpc) >
). Door op het -icoon te klikken wordt de uitvoer zichtbaar. (ecpc) > python --version \nPython 3.10.9\n
We maken veel gebruik van conda environments. Hierboven zie je de naam van de conda environment tussen ronde haakjes staan, in dit geval heet de conda environment ecpc
. In de voorbeeldcode staat standaard de ecpc
conda environment, maar welk conda environment je moet gebruiken hangt van de opdracht af.
"},{"location":"#opgaves","title":"Opgaves","text":"In de handleiding staan verschillende opgaves. De meeste zijn bedoeld als oefening, maar sommige moet je inleveren voor feedback en een beoordeling. Schrijf je code in het Engels, zo ben je voorbereid op het werken in een internationale onderzoeksgroep.
Info
In sommige programmeercursussen is het de bedoeling dat je een bepaald algoritme zelf schrijft. Je moet bijvoorbeeld een loop schrijven om een reeks berekeningen uit te voeren en mag dan niet gebruik maken van de NumPy-bibliotheek om dat in \u00e9\u00e9n regel te doen. Je kunt je voorstellen dat als je straks in een lab werkt dat je juist gebruik wilt maken van bibliotheken die het leven een stuk gemakkerlijker maken. Trek dus alles uit de kast. Kijk eens in de Python Standard Library,5 de Python Package Index6 of Awesome Python7.
Basisopdracht
opdrachtcodecheck Deze opgaves helpen je om het niveau te behalen wat van je verwacht wordt. Ze worden niet beoordeeld. Hierboven zie je 3 tabbladen opdracht, code en check. Klik op code om naar het volgende tabblad te gaan.
Pseudo-code
# hierin staat code om je verder op weg te helpen.\n
Testcode Testcode.py # gebruik (delen van) deze code om je opdracht te testen.\n# Bekijk de output van deze code\n# Ga daarna naar het tabblad \"check\"\n
\n(ecpc) > python Testcode.py\nKrijg je (ongeveer) dezelfde output?\n
Checkpunten:
- De checkpunten in deze lijst helpen je om te zien of je op de goede weg bent.
- Je kunt ze zelfs afvinken. Klik daarvoor op het grijze vinkje links van deze zin.
Projecttraject
- We bouwen voort op oude opdrachten
- Opdrachten die een vinkje hebben, heb je al gemaakt
- Of het is de opdracht waar je nu mee bezig bent
- Opdrachten zonder vinkje maak je later in het huidige hoofdstuk
- of in een volgend hoofdstuk
Inleveropdracht
Deze opgave moet worden ingeleverd voor feedback en een beoordeling. Je herkent ze aan de groene kleur. De opgaven bouwen voort op elkaar, dus er zijn meerdere opgaven. Je levert ze niet los in, maar als geheel. Kijk goed bij het projecttraject in het tabblad check welke groene opdrachten je gemaakt moet hebben voordat je het inlevert.
Vaak heb je kennis en/of vaardigheden nodig die je eerder heb geleerd. Zie je een lampje staan? Klik dan bovenin de blauwe balk (rechtsboven, naast de zoekbalk) op het lampje om de spiekbriefjes te openen.
Meer leren
Dit zijn verdiepende en verbredende opgaves om je te kunnen ontwikkelen tot een goed programmeur en een waardevolle aanwinst voor een onderzoeksgroep. Je kunt er geen extra punten mee verdienen wanneer je deze technieken toepast in je inleveropdrachten, maar het is wel een goede oefening. Doe deze opgaves alleen als je klaar bent met de rest.
Een basiskennis van Python is nodig om de opdrachten te kunnen maken. In de paragraaf Basiskennis Python vind je opdrachten om je kennis te testen. Het is handig om een uitgebreidere Python kennis te hebben, meer informatie vind je in de paragraaf Uitgebreidere Python kennis.
-
Er bestaan verschillende terminal emulators, meestal afhankelijk van het besturingssysteem \u2014 al heeft Windows zelf al drie verschillende prompts: de command prompt, de powershell prompt en tegenwoordig (voorkeur) de Windows Terminal. Een terminal ziet eruit als een tekstvenster. Hierbinnen verschijnt een prompt. Dit is een klein stukje tekst dat aangeeft waar je je opdrachten kunt intypen. In MacOS en Linux is de prompt vaak een $
-teken. In Windows ziet het er vaak uit als C:\\>
of PS>
. In veel documentatie op internet wordt de $
-prompt gebruikt.\u00a0\u21a9
-
Aquiles Carattino. Python for the lab. URL: https://pythonforthelab.com.\u00a0\u21a9
-
Rachid Chenni. Tracing current-voltage curve of solar panel based on labview arduino interfacing. Bili\u015fim Teknolojileri Dergisi, 8:, 09 2015. URL: https://www.researchgate.net/publication/283037607_Tracing_current-voltage_curve_of_solar_panel_Based_on_LabVIEW_Arduino_Interfacing.\u00a0\u21a9
-
A El Hammoumi, S Motahhir, A Chalh, A El Ghzizal, and A Derouich. Real-time virtual instrumentation of arduino and labview based pv panel characteristics. IOP Conference Series: Earth and Environmental Science, 161(1):012019, jun 2018. doi:10.1088/1755-1315/161/1/012019.\u00a0\u21a9
-
Python Software Foundation. The python standard library. URL: https://docs.python.org/3/library/.\u00a0\u21a9
-
Python Software Foundation. Python package index. URL: https://pypi.org.\u00a0\u21a9
-
Vinta Chen. Awesome python. URL: https://awesome-python.com.\u00a0\u21a9
"},{"location":"basis-python/","title":"Basiskennis Python","text":"Bij de cursus inleiding programmeren heb je de basis van het programmeren in Python geleerd. Bij inleiding programmeren mocht je kiezen om je code in het Nederlands of Engels te schrijven. Omdat wij jullie voorbereiden om in een onderzoeksgroep je bachelor project te gaan doen waar je hoogstwaarschijnlijk internationale collega's gaat treffen vragen we jou om bij ECPC alles in het Engels te schrijven. In deze paragraaf nemen we de hoofdlijnen van inleiding programmeren met je door in een aantal opdrachten.
"},{"location":"basis-python/#visual-studio-code","title":"Visual Studio Code","text":"Open VSCode en maak de map ECPC
- Open Visual Studio Code.
- Ga naar File > Open folder.
- Navigeer naar een geschikte map, bijvoorbeeld OneDrive.
- Klik op Nieuwe map en noem de map ECPC.
- Klik op Map selecteren.
"},{"location":"basis-python/#f-strings-variabelen-en-input","title":"F-strings, variabelen en input()","text":"f-strings, variabelen en input
- Maak een bestand
diameter-ball.py
in de map ECPC
. ECPC
\u2514\u2500\u2500 diameter.py
- Schrijf een stuk code waarin je de gebruiker vraagt wat de diameter van de bal is.
- Bereken de radius van de bal.
- Print de diameter en radius in een zin en maak gebruik van f-string. Bijvoorbeeld: \"A ball with a diameter of 2.8 m has a radius of 1.4 m.\"
- Test je script met het getal 2.8.
Uitwerkingen diameter = input(\"Enter the diameter of the ball: \")\ndiameter = float(diameter)\nradius = diameter / 2\nprint(f\"A ball with a diameter of {diameter} m has a radius of {radius} m.\")\n
"},{"location":"basis-python/#if-statement-en-operatoren","title":"If-statement en operatoren","text":"if-statement en operatoren
Met een if
-statement kan je een conditie testen door operatoren te gebruiken.
- Schrijf de operatoren op voor:
- gelijk aan
- ongelijk aan
- groter dan
- groter of gelijk aan
- kleiner dan
- kleiner dan of gelijk aan
- Vul op de
...
de juiste condities in door gebruik te maken van and
, not
en or
. import random\n\nrain = random.choice([True, False])\numbrella = random.choice([True, False])\n\nprint(f\"{rain=}, {umbrella=}\")\n\nif ... :\n print(\"Lucky you have your umbrella with you since it's raining.\")\n\nif ... :\n if ... :\n print(\"You will get wet without an umbrella since it's raining.\")\n if ... :\n print(\"you can use your umbrella as a walking stick since it doesn't rain\")\n\nif ... :\n print(\"Without an umbrella there is no problem since it's not raining.\")\n
Uitwerkingen import random\n\nrain = random.choice([True, False])\numbrella = random.choice([True, False])\n\nprint(f\"{rain=}, {umbrella=}\")\n\nif rain and umbrella:\n print(\"Lucky you have your umbrella with you since it's raining.\")\n\nif rain or umbrella:\n if rain and not umbrella:\n print(\"You will get wet without an umbrella since it's raining.\")\n if not rain and umbrella:\n print(\"you can use your umbrella as a walking stick since it doesn't rain\")\n\nif not rain and not umbrella:\n print(\"Without an umbrella there is no problem since it's not raining.\")\n
"},{"location":"basis-python/#for-loop-while-loop-en-break","title":"For-loop, while-loop en break","text":"For-loop, while-loop en break
- Beschouw de volgende code:
voltage = 0 # mV\nsteps = 50 # mV\nwhile voltage < 3300:\n voltage += steps\n
Bij het programmeren krijg je vaak errors. Bij het debuggen van een loop zijn twee dingen heel handig print
en break
.
- Gebruik
print
om het voltage te printen in the while-loop, doe dit handig met f-strings zodat je weet wat je je print bijvoorbeeld: \"The voltage is set to 0 mV.\" - Gebruik dan
break
om de loop maar een keer te laten lopen. - Schrijf de code om waarbij je gebruikt maakt van een
for
-loop.
Uitwerkingen print_and_break.py
voltage = 0 # mV\nsteps = 50 # mV\nwhile voltage < 3300:\n voltage += steps\n print(f\"The voltage is set to {voltage} mV.\")\n break\n
\n(ecpc) > python print_and_break.py\nThe voltage is set to 50 mV.\n
for_loop.py
for voltage in range(0, 3300, 50):\n print(f\"The voltage is set to {voltage} mV.\")\n
\n(ecpc) > python for_loop.py\nThe voltage is set to 0 mV.\nThe voltage is set to 50 mV.\nThe voltage is set to 100 mV.\nThe voltage is set to 150 mV.\n...\n
"},{"location":"basis-python/#functie","title":"Functie","text":"functies
-
Maak uit de onderstaande code de functie exponentiation werkend.
def exponentiation():\n solution =\n ...\n\n\nnumber_1 = 2\nnumber_2 = 8\n\nanswer = exponentiation(number_1, number_2)\nprint(f\"{number_1}^{number_2} = {answer}\")\n
-
In deze opdracht zijn 4 variabele solution
, number_1
, number_2
en answer
. Welk van deze variabele zijn globaal en welke zijn lokaal?
- Leg uit wat daarvan de consequentie is voor het gebruiken van deze variabelen.
Uitwerkingen -
def exponentiation(base, exponent):\n solution = base**exponent\n return solution\n\n\nnumber_1 = 2\nnumber_2 = 8\n\nanswer = exponentiation(number_1, number_2)\nprint(f\"{number_1}^{number_2} = {answer}\")\n
-
De globale variabelen zijn number_1
, number_2
en answer
en de lokale variabele is solution
.
-
Het gevolg is dat number_1
, number_2
en answer
wel binnen de functie exponentiation()
gebruikt kunnen worden, maar solution
niet buiten de functie exponentiation()
gebruikt kan worden.
"},{"location":"basis-python/#list","title":"List","text":"lijsten
- Schrijf een python script waarin je een lijst definieert met de namen van de maanden.
- Print de negende maand.
- Voeg een dertiende maand toe aan de lijst.
Uitwerkingen lijsten.py
months = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n]\n\nninth_month = months[8]\n\nprint(f\"The ninth month is called {ninth_month}\")\n\nmonths.append(\"tr\u0113decimber\")\n\nprint(months)\n
\n(ecpc) > python lijsten.py\nThe ninth month is called September.\n['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', 'tr\u0113decimber']\n
"},{"location":"basis-python/#stijl","title":"Stijl","text":"Bij inleiding programmeren heb je ook geleerd hoe je code netjes opschrijft zodat het leesbaar en begrijpelijk is. Hieronder vind je een samenvatting, die een beetje aangevuld is met ECPC stijl.
- Schrijf code in het Engels.
def functie_namen_met_doel():
Namen van functies mogen lang zijn, maar geven duidelijk weer wat de functie doet. korte_variabele = 28
de namen van variabele houd je kort en duidelijk. Gebruik alleen afkortingen waarneer deze door veel mensen gekend zijn. - Je hoeft de code niet met de hand over te schrijven dus gebruik liever meer regels dan een hele lange regel waar meer dan 1 ding gebeurd.
- Gebruik
#commentaar-kopjes
om een stukje code samen te vatten, een waarschuwing te geven, uitleg van complexe algoritmen te doen, voor bronvermelding, uitleg van een variabele te geven en zet dit altijd boven het stukje code waar het omgaat. - Spring in waar nodig, gebruik witregels, zet spaties rondom operatoren.
"},{"location":"basis-python/#modules","title":"Modules","text":"Ook heb je geleerd om functies uit andere (python) modules te importeren, meer hierover vind je in de paragraaf Modules.
"},{"location":"basis-python/#plotten","title":"Plotten","text":"Grafieken
Gebruik matplotlib om een scatterplot te maken van twee lijsten die hieronder zijn weergegeven. Zet de grootheden en eenheden bij beide assen en sla het figuur op als .png-bestand.
time = [0, 0.5, 1, 1.5, 2, 2.5, 3] #seconds\ndistance = [0, 15, 50, 100, 200, 300, 400] #meters\n
Uitwerkingen import matplotlib.pyplot as plt\n\ntime = [0, 0.5, 1, 1.5, 2, 2.5, 3] # seconds\ndistance = [0, 15, 50, 100, 200, 300, 400] # meters\n\nplt.plot(time, distance, \"o\")\nplt.xlabel(\"Time (s)\")\nplt.ylabel(\"Distance (m)\")\nplt.savefig(\"plot.png\")\n
"},{"location":"basis-python/#bestanden-inlezen","title":"Bestanden inlezen","text":"txt-bestanden lezen
Hieronder vind je een verhaal, kopieer de inhoud naar een .txt-bestand en sla deze op een handige plek op.
\"Do you have a favourite\nsaying?\" asked the boy.\n\"Yes\" said the mole\n\"What is it?\"\n\"If at first you don't \nsucceed have some cake.\"\n\"I see, does it work?\"\n\"Every time.\"\nFrom: The Boy, the mole, the fox and the Horse - Charlie Mackesy\n
Schrijf een script om het .txt-bestand te lezen en regel voor regel te printen.
Uitwerkingen txt_bestanden_lezen.py
with open(\"story.txt\", \"r\") as file:\n for line in file:\n print(line)\n
\n(ecpc) > python txt_bestanden_lezen.py\n\"Do you have a favourite\nsaying?\" asked the boy.\n\"Yes\" said the mole\n\"What is it?\"\n\"If at first you don't \nsucceed have some cake.\"\n\"I see, does it work?\"\n\"Every time.\"\nFrom: The Boy, the mole, the fox and the Horse - Charlie Mackesy\n
"},{"location":"basisscript/","title":"Basisscript voor het experiment","text":"Het experiment wat we gaan uitvoeren is het bepalen van de $I,U$-karakteristiek van een LED. Omdat de Arduino alleen getallen tussen 0 en 1023 kan sturen en ontvangen moeten we nadenken over de Analoog-digitaalconversie voordat we een zinnige $I,U$-karakteristiek kunnen maken.
"},{"location":"basisscript/#analoog-digitaalconversie-adc","title":"Analoog-digitaalconversie (ADC)","text":"We hebben tot nu toe gewerkt met getallen van 0-1023 sturen en ontvangen. Wat is precies de betekenis van deze getallen? Daarvoor moeten we dieper ingaan op hoe de Arduino \u2014 en computers in het algemeen \u2014 getallen omzet in een spanning en hoe spanningen door de Arduino worden gemeten.
Een analoog signaal is continu in zowel de tijd als de waardes die het signaal aan kan nemen. Een digitaal signaal is echter discreet: op vaste tijdstippen is er een waarde bekend en het signaal kan maar een beperkt aantal verschillende waardes aannemen.1
Bemonsteren of sampling is het proces waarbij een analoog signaal wordt uitgelezen en wordt omgezet in een digitaal signaal. Zo wordt een audiosignaal al sinds eind jaren '70 van de vorige eeuw gewoonlijk bemonsterd met een frequentie van 44.1 kHz en een resolutie van 16 bits. Dus 44100 keer per seconde wordt er gekeken wat de waarde van het geluidssignaal is en dat wordt opgeslagen als een getal van 16 bits en kan dus $2^{16} = 65536$ verschillende waardes aannemen. Dit is nauwkeuriger dan het menselijk gehoor kan onderscheiden.
0 3.3 V 0 15 63 1023 ADC resolutie
De schuifjes hierboven zijn aan elkaar gekoppeld. Het bovenste schuifje laat de analoge waarde zien. Het onderste schuifje is de bijbehorende digitale waarde.
- Zet het digitale signaal op een resolutie van 4-bit (16 stapjes). Stel je meet een waarde van 6, wat zijn dan de mogelijke voltages die daarbij horen? Wat is dan de nauwkeurigheid?
- Zet het digitale signaal op een resolutie van 6-bit (64 stapjes). Stel je meet een waarde van 28, wat zijn dan de mogelijke voltages die daarbij horen? Wat is dan de nauwkeurigheid?
- Zet het digitale signaal op een resolutie van 10-bit resolutie (1024 stapjes). Stel je meet een waarde van 768, wat zijn dan de mogelijke voltages die daarbij horen? Wat is dan de nauwkeurigheid?
De conversie van een analoog signaal naar een digitaal signaal (en andersom!) is de reden dat de spanningen die we kiezen en de metingen die we doen niet alle mogelijke waardes kunnen aannemen, maar stapjes maken.
De omzetting van een analoog signaal naar een digitaal signaal gebeurt als volgt. De ADC (analog-to-digital converter) in dit voorbeeld ondersteunt 16 niveau's (4-bits) in een bereik van 0 V tot 3.3 V (groen gearceerd). Lagere of hogere spanningen kunnen niet gemeten worden (rood gearceerd). Op gezette tijden wordt een meting gedaan (rode punten) waarbij de uitkomst van de meting het discrete niveau is dat het dichtst bij de analoge waarde ligt. Als het signaal te groot wordt kan de ADC als het ware vastlopen op het hoogste niveau. In de rechterflank is waar te nemen dat als het analoge signaal langzaam verandert dat het digitale signaal duidelijk sprongsgewijs verandert. Hoe meer niveau's een ADC heeft en hoe vaker het signaal bemonsterd kan worden, hoe nauwkeuriger het digitale signaal het analoge signaal benadert.
De digitale metingen die je programma krijgt van de ADC is hierboven weergegeven. De onzekerheid is gelijk aan de halve afstand tot het volgende niveau. In lichtgrijs zie je het oorspronkelijke analoge signaal. De meting benadert het signaal dus maar gedeeltelijk. De Arduino die we gebruiken heeft een bereik van 0 V tot 3.3 V en \u2014 in tegenstelling tot het voorbeeld hierboven \u2014 een resolutie van 10 bits, dus $2^{10} = 1024$ stapjes. Als je een experiment ontwerpt is het dus van belang te weten dat je nooit kunt meten met een nauwkeurigheid kleiner dan de stapgrootte. Voor ons is deze resolutie prima.
Volt naar ADC
opdrachtcodecheck Je hebt gezien dat de Arduino werkt met getallen van 0 t/m 1023 en dat de Arduino een bereik heeft van 0 V tot 3.3 V. Je schrijft de formule op om de ruwe ADC waarde naar een spanning in Volt om te rekenen en omgekeerd. Je controleert of je formules logische antwoorden geven door de spanning te berekenen die bij een ruwe waarde van 700 hoort en de ruwe waarde die hoort bij 2.28 V.
Pseudo-code
# raw_value to voltage\n# voltage = something with raw_value\n\n# voltage to raw_value\n# raw_value = something with voltage\n
Checkpunten:
- Ruwe waarde 0 geeft spanning 0 en vice versa
- Ruwe waarde 1023 geeft spanning 3.3 en vice versa
- Ruwe waarde 700 is ongeveer 2/3 van 1023 dus geeft een spanning in de buurt van 2.2 V
- Spanning van 2.28 V is ongeveer 2/3 van 3.3 dus geeft een ruwe waarde in de buurt van 680
Projecttraject:
- Volt naar ADC
Binair Talstelsel"},{"location":"basisscript/#binair-talstelsel","title":"Binair Talstelsel","text":"Wij schrijven onze getallen op in een decimaal (tientallig) talstelsel. We hebben tien verschillende cijfers (0 t/m 9) en plakken bij grotere getallen de tientallen, honderdtallen, etc. aan elkaar. Computers werken met binaire getallen \u2014 een tweetallig talstelsel. Dat betekent dat computers het getal 0 en 1 zonder problemen kunnen opslaan, maar bij het getal 2 wordt het al lastig. Zij moeten dan al met tientallen werken en schrijven het getal 2 op als 10. Het getal 3 is dan 11. Voor 4 zijn de cijfers alweer op en moeten we overschakelen naar honderdtallen, dus 4 is 100, 5 is 101, enz. Zie onderstaande tabel voor nog een paar voorbeelden. De cijfers noem je bits en het getal 5 (101 binair) bestaat dus uit 3 bits. Als je maar 3 bits tot je beschikking hebt kun je $2^3 = 8$ verschillende getallen opslaan, dus 0 t/m 7. Een groepje van 8 bits (256 mogelijkheden) bleek een handige hoeveelheid en kun je op computers individueel opslaan. Zo'n groepje noem je een byte. Bestanden bestaan uit bytes, kilobytes (duizend bytes), megabytes (miljoen bytes) of gigabytes (miljard bytes). Wanneer je een signaal nauwkeurig wilt verwerken met een computer dan is het belangrijk om zoveel mogelijk bits tot je beschikking te hebben. Hoe meer bits, hoe meer verschillende waardes je kunt opslaan en hoe nauwkeuriger je signaal wordt bewaard.
Voorbeelden van het binair talstelsel:
decimaal getal binair getal 0 0 1 1 2 10 3 11 4 100 5 101 6 110 7 111 8 1000 9 1001 \u2026 \u2026 205 11001101"},{"location":"basisscript/#de-iu-karakteristiek-van-een-led","title":"De I,U-karakteristiek van een LED","text":"Je hebt op de middelbare school ongetwijfeld de $I,U$-karakteristiek van een ohmse weerstand onderzocht. Je neemt een gewone weerstand en zet daar een steeds hogere spanning op. Je meet de stroomsterkte en ook die neemt toe \u2014 rechtevenredig zelfs! Door $I$ tegen $U$ uit te zetten in een grafiek en de beste lijn door je metingen te trekken vind je met de richtingsco\u00ebffici\u00ebnt de inverse van de weerstand $R^{-1}$:
Een LED is een lichtgevende diode \u2014 en een diode gedraagt zich heel anders. Met de schakeling die we hebben gebouwd kunnen we de $I,U$-karakteristiek van een LED bepalen. Voor meer informatie over de fysica achter diodes, zie de appendix Diodes.
I,U-karakteristiek van een LED
Maak een schets van hoe je denkt dat de grafiek van de stroom tegen de spanning van een LED eruit zal zien.
Arduino heeft geen stroommeter
Schrijf op hoe je de spanning over de LED en de stroom door de LED berekent in termen van de spanningsmeters U1 en U2 en de bekende weerstand R.
Arduino pins
Kijk aan de onderkant van de Arduino4 of je de pinnentjes A0, A1, A2 en GND kan vinden.
Klik hier als je geen Arduino bij de hand hebt
Breadboard
Kijk terug naar de theoretische schakeling, welke lijnen komen daar overeen met de vier draadjes (rood, blauw, groen, oranje) in de echte schakeling?
Channels
Bekijk de documentatie over de firmware en schrijf het commando op om de maximale uitvoerspanning op kanaal 0 te zetten. Schrijf daarna de commando's op om de waardes van U1 en U2 uit te lezen.
Pythondaq: repository
Omdat we met een belangrijk project aan de slag gaan, namelijk een inleveropdracht, gaan we gelijk goed beginnen door een repository aan te maken.
- Open Github Desktop en ga naar File > New repository .... Geef de repository een naam (
pythondaq
) en kies onderstaande locatie. Let er op dat je mappenstructuur er als volgt uit ziet: ECPC \u251c\u2500\u2500 pythondaq \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022 - Vink
Initialize this repository with a README
aan. - Kies bij
Git ignore
voor Python. - Ga naar Repository > Open in Visual Studio Code en ga aan de slag. Vergeet niet regelmatig te committen!
Pythondaq: start script
opdrachtcodecheck Je maakt het bestand diode-experiment.py
aan in de nieuwe pythondaq
repository, waarin je de spanning over de LED laat oplopen van nul tot de maximale waarde. Tijdens het oplopen van de spanning over de LED lees je de spanning over de weerstand uit. Je print steeds een regel met: ruwe waarde spanning op LED, voltage op LED, ruwe waarde spanning over weerstand, voltage weerstand. ECPC
\u251c\u2500\u2500 oefenopdrachten
\u2514\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 diode-experiment.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code diode-experiment.py
# connect to Arduino\n\n# set output voltage from 0 to max\n # measure voltages\n # calculate voltage LED \n # calculate voltage resistor\n # print LED: raw_voltage_LED (voltage_LED V) Resistor: raw_voltage_resistor (voltage_resistor V)\n
Checkpunten:
- Spanning over LED loopt van nul tot maximale waarde
- LED lampje brandt steeds feller
- Commit
- Ruwe waardes en voltages zijn zoals verwacht
- Commit
Projecttraject:
- Pythondaq: Repository
- Pythondaq: Start script
- Pythondaq: Quick 'n dirty meting
- Pythondaq: CSV
- Pythondaq: open de repository
- Pythondaq: Controller bouwen
- Pythondaq: Controller implementeren
- Pythondaq: Controller afsplitsen
- Pythondaq: Model afsplitsen
- Pythondaq: Onzekerheid
Je kunt de meetgegevens kopi\u00ebren en plakken naar een tekstbestand, spreadsheetprogramma of Python notebook o.i.d. Maar dat is wel veel werk, zeker als je metingen wilt herhalen. Op dit moment hebben we ook alleen nog maar ruwe metingen. We gaan hier voorbij aan het feit dat we graag de stroomsterkte door de LED $I$ zouden willen uitzetten tegen de spanning over de LED $U_\\mathrm{LED}$.
Info
In de volgende opdracht gaan we een grafiek maken. Installeer Matplotlib in je conda environment (zorg dat die geactiveerd is! ): Terminal
conda install --channel conda-forge matplotlib\n
Pythondaq: Quick 'n dirty meting
opdrachtcodecheck Je code berekent de spanning over en de stroomsterkte door de LED terwijl de spanning over het cirquit oploopt van nul tot de maximale waarde. De resultaten worden geprint en in een grafiek weergegeven.
Pseudo-code diode-experiment.py
# connect to Arduino\n\n# set output voltage from 0 to max\n # measure voltages\n # calculate LED voltage\n # calculate LED current\n # print voltage: raw_voltage_LED (voltage_LED V) current: raw_current_LED (current_LED V)\n\n# plot current vs voltage\n
Checkpunten:
- Bereken spanning over LED
- Bereken stroomsterkte door LED
- Lijst met spanningen
- Lijst met stroomsterkte
- Print spanningen en stroomsterktes
- Plot stroomsterke tegen spanning
- Vergelijk met resultaat van iemand anders
- Meting is fysisch correct
- LED wordt uitgezet na de meting
Projecttraject:
- Pythondaq: Repository
- Pythondaq: Start script
- Pythondaq: Quick 'n dirty meting
- Pythondaq: CSV
- Pythondaq: open de repository
- Pythondaq: Controller bouwen
- Pythondaq: Controller implementeren
- Pythondaq: Controller afsplitsen
- Pythondaq: Model afsplitsen
- Pythondaq: Onzekerheid
"},{"location":"basisscript/#bewaren-van-meetgegevens","title":"Bewaren van meetgegevens","text":"Het is fijn dat je script de meetgegevens op het scherm kan printen en een grafiek maakt, maar als je echt bezig bent met een onderzoek is een grafiek niet genoeg. Je wilt dat de data bewaard blijft zodat je die later nog kunt gebruiken voor nieuwe analyses. Ook is het zo dat data steeds vaker beschikbaar moet zijn voor andere wetenschappers die jouw onderzoek willen controleren. Steeds meer wetenschappelijke tijdschriften vragen auteurs niet alleen hun grafieken, maar ook hun onderliggende data beschikbaar te maken en te publiceren. Op die manier is het veel moeilijker om fraude te plegen; iets dat in de wetenschap helaas soms nog voor komt.
Er zijn ontzettend veel verschillende bestandsformaten waarin je data kunt bewaren. Er zijn grofweg twee categori\u00ebn: tekstbestanden en binaire bestanden. De eerste zijn te lezen met ieder willekeurig programma. Sommige zijn heel eenvoudig (b.v. CSV), andere kunnen complexe datastructuren en extra informatie opslaan (b.v. JSON, XML). Binaire bestanden bevatten alle mogelijke karakters \u2014 niet alleen letters, cijfers, leestekens, maar ook stuurcodes zoals carriage return en de line feed, oorspronkelijk opdrachten voor bijvoorbeeld printers. Ze hebben vaak een strak formaat: zoveel bytes voor dit stukje informatie, zoveel bytes voor dat stukje, enzovoort. Met binaire karakters hoef je je dus niet te beperken tot letters, cijfers en leestekens en kunnen de bestanden wat kleiner zijn. Ook zorgen de vaste afspraken ervoor dat de lees- en schrijfroutines eenvoudiger kunnen zijn. Getallen worden in het interne geheugen van de computers ook binair opgeslagen dus het is vaak copy/paste vanuit of naar het bestand. Wel leiden kleine fouten vaak tot onbruikbare bestanden. Voor grote databestanden wordt vrijwel altijd gekozen voor een binair formaat, of het nou gaat om audio/video, databases of klimaatmodellen. Het uitwisselen van kleinere bestanden gebeurt echter vaak in een tekstformaat.
"},{"location":"basisscript/#comma-separated-values-csv","title":"Comma-separated values (CSV)","text":"Het CSV-bestand is het werkpaard van de wetenschap. Als je data van het ene in het andere programma moet krijgen of je download wetenschappelijke gegevens van een website dan is het CSV-bestand vaak de beste keuze. Het formaat bestaat uit kolommen met getallen, gescheiden door een komma. De eerste regels kunnen commentaar bevatten (uitleg over de kolommen, bijvoorbeeld) en de namen van de kolommen bevatten. Een voorbeeld voor de afstand die een vallend voorwerp aflegt in 10 s, gegeven door $s = \\frac{1}{2} g t^2$, is hieronder weergegeven:
t,s\n0.0,0.0\n1.0,4.9\n2.0,19.6\n3.0,44.1\n4.0,78.4\n5.0,122.50000000000001\n6.0,176.4\n7.0,240.10000000000002\n8.0,313.6\n9.0,396.90000000000003\n10.0,490.00000000000006\n
Het CSV-bestand heeft kolommen $t$ en $s$. De getallen hebben een punt als decimaal scheidingsteken en de komma wordt gebruikt om de kolommen te scheiden. Je kunt CSV-bestanden schrijven en lezen met de modules csv
, numpy
of pandas
. De eerste is altijd meegeleverd met Python en is speciaal geschreven voor het bestandsformaat,9 maar NumPy1011 en Pandas1213 bevatten veel meer functionaliteit op het gebied van wiskunde en data-analyse. Als je die modules toch al gebruikt hoef je niet te kiezen voor de kale csv module.
"},{"location":"basisscript/#de-functie-zip","title":"De functie zip()
","text":"Het viel je misschien op dat in bovenstaand CSV-bestand iedere regel een $t$-waarde en een $s$-waarde heeft. Als je een lijst met $t$'s en een lijst met $s$'en hebt dan bevat de eerste regel het eerste element uit beide lijsten, de tweede regel het tweede element, etc. Je kunt dan een for-loop schrijven die Python's indexnotatie gebruikt: t[i]
, s[i]
, etc. Het kan \u00f3\u00f3k, makkelijker, met de zip()
-functie. Beide methodes kun je als volgt gebruiken in het geval van twee5 lijsten A en B: ```
with_zip.pywith_indexing.py with_zip.py
A = [1, 2, 3, 4]\nB = [1, 4, 9, 16]\n\nfor a, b in zip(A, B):\n print(a, b)\n
\n(ecpc) > python with_zip.py\n1 1\n2 4\n3 9\n4 16\n
with_indexing.py
A = [1, 2, 3, 4]\nB = [1, 4, 9, 16]\n\nfor i in range(len(A)):\n print(A[i], B[i])\n
\n(ecpc) > python with_indexing.py\n1 1\n2 4\n3 9\n4 16\n
Vergelijk beide methodes goed. In het geval van zip()
hoef je niet de lengte van de lijst op te zoeken en krijg je meteen de losse elementen zonder dat je ze zelf uit de lijst moet plukken met indexnotatie.
oefenen met zip
opdrachtcodecheck Je hebt een lijst met krachten en een lijst met afstanden. Loop over de lijsten en print voor iedere iteratie de kracht $F$, de afstand $s$ en de arbeid $W$. Je hebt een lijst met spanningen en een lijst met stroomsterktes. Je loopt over de lijsten en print voor iedere iteratie de spanning $U$, de stroomsterkte $I$ en de weerstand $R$.
Pseudo-code
F = [1.2, 1.8, 2.4, 2.7, 3.1] # N\ns = [0.3, 0.4, 0.6, 0.8, 1.0] # m \n\n# repeat\n# print F, s, W\n
Checkpunten:
- De for-loop gebruikt
zip()
om de elementen uit de lijst op te vragen - De variabele hebben logische namen en niet
a
en b
- De gegeven arbeid is fysisch correct
Projecttraject:
- oefenen met zip
"},{"location":"basisscript/#het-gebruik-van-de-csv-module","title":"Het gebruik van de csv
-module","text":"Wanneer je de csv
-module wilt gebruiken moet je \u00e9\u00e9rst een bestand openen om in te schrijven, daarna een writer object aanmaken, en dat object gebruiken om regels te schrijven. Daarna moet het bestand netjes afgesloten worden zodat het ook echt naar schijf weggeschreven wordt. Het openen en sluiten van een bestand kun je Python het beste laten doen met het with
-statement:6
with open('metingen.csv', 'w', newline='') as csvfile:\n # csvfile is nu een bestandsobject\n ...\n # na dit blok sluit Python automatisch het bestand\n
Bij open()
geef je eerst de naam van een bestand, dan 'w'
om aan te geven dat het bestand writeable moet zijn (gebruik 'r'
om te lezen) en newline=''
om Python niet zelf regeleindes te laten schrijven; dat doet de csv
-module. Op de volgende manier schrijven we dan de CSV-data weg:
import csv\n\nwith open('metingen.csv', 'w', newline='') as csvfile:\n writer = csv.writer(csvfile)\n writer.writerow(['t', 's'])\n writer.writerow([0.0, 0.0])\n writer.writerow([1.0, 4.9])\n writer.writerow([2.0, 19.6])\n ...\n
Je kunt het wegschrijven van de regels vervangen door een for-loop. Pythondaq: CSV
opdrachtcodecheck Je code schrijft de metingen weg als csv-bestand door gebruik te maken van de zip()
-functie en de csv
-module.
Pseudo-code diode-experiment.py
# connect to Arduino\n\n# set output voltage from 0 to max\n # measure voltages\n # calculate LED voltage\n # calculate LED current\n\n# plot current vs voltage\n# create csv-file\n
Checkpunten:
- CSV-bestand bevat alle metingen
- Waardes in CSV-bestand komen overeen met verwachting
Projecttraject:
- Pythondaq: Repository
- Pythondaq: Start script
- Pythondaq: Quick 'n dirty meting
- Pythondaq: CSV
- Pythondaq: open de repository
- Pythondaq: Controller bouwen
- Pythondaq: Controller implementeren
- Pythondaq: Controller afsplitsen
- Pythondaq: Model afsplitsen
- Pythondaq: Onzekerheid
Git ignore Het kan wenselijk zijn om niet alle bestanden mee te nemen voor versiebeheer in je repository. Soms wil je een bestand uitsluiten, of bepaalde bestand-types. Om GitHub te laten weten welke bestanden niet gecommit hoeven te worden is er een bestand .gitignore
. Let op de punt voor de bestandsnaam, dit betekent dat het een verborgen bestand is en mogelijk zie je het niet in je repository staan.
Stel je wilt alle csv-bestanden uitsluiten van versiebeheer, dat kan als volgt:
- Ga naar GitHub Desktop.
- Ga naar het tabblad Changes.
- Rechtermuisklik op het bestand wat je wilt negeren
- Maak een keuze tussen Ignore file, Ignore folder of Ignore all .csv files
CSV bestandsnaam Pas de code zodanig aan dat een CSV-bestand nooit wordt overschreven. Je kunt bijvoorbeeld controleren of het bestand al bestaat en aan de bestandsnaam een oplopend getal toevoegen (data-001.csv
, data-002.csv
, etc.) totdat je uitkomt bij een bestandsnaam die nog niet bestaat. Controleer dat je programma ook echt geen data overschrijft.
HDF5, PyTables -
Een vallende bal is een continu proces. De bal heeft op elk willekeurig moment een positie. Je zou de positie kunnen meten op het tijdstip $t$ = 2.0 s, maar ook op $t$ = 2.1, 2.01, 2.001 of 2.0001 s. Ook kun je de positie net zo nauwkeurig bepalen als je wilt.2 De natuur is analoog,3 maar moderne computers zijn digitaal en dus discreet. Als je een foto op je computer te ver inzoomt zie je blokjes. Je kunt verder inzoomen, maar je gaat niet meer detail zien. De hoeveelheid informatie is beperkt.\u00a0\u21a9
-
Uiteraard afhankelijk van de nauwkeurigheid van je meetinstrument.\u00a0\u21a9
-
Totdat je het domein van de kwantummechanica betreedt, dan blijkt de natuur ook een discrete kant te hebben.\u00a0\u21a9
-
Dit model bevat een 3D model die is gecre\u00eberd door AppliedSBC en is gedeeld onder CC-BY-SA licentie. Het originele model is te vinden via Arduino Nano 33 IoT. Het model is voorzien van een Arduino texture. Dit 3D model heeft een CC-BY-SA licentie.\u00a0\u21a9
-
Je kunt net zoveel lijsten in zip()
gooien als je wilt: for a, b, c, d, e in zip(A, B, C, D, E)
is geen probleem.\u00a0\u21a9
-
hier is open()
een zogeheten context manager, een functie die je kunt gebruiken met een with
-statement en dat bij de start iets doet \u2014 hier een bestand openen \u2014 en bij het eind iets doet \u2014 hier het bestand weer netjes afsluiten. Je kunt zelf ook context managers schrijven, als je wilt.\u00a0\u21a9
-
Hierarchical Data Format Version 5, in gebruik bij bijvoorbeeld de LOFAR radiotelescoop, het IceCube neutrino-observatorium en de LIGO zwaartekrachtsgolvendetector.\u00a0\u21a9
-
Lees bijvoorbeeld deze korte blog post over het gebruik van HDF5.\u00a0\u21a9
-
Python Software Foundation. Csv \u2013 csv file reading and writing. URL: https://docs.python.org/3/library/csv.html.\u00a0\u21a9
-
The NumPy Development Team. Numpy \u2013 the fundamental package for scientific computing with python. URL: https://numpy.org.\u00a0\u21a9
-
Charles R. Harris, K. Jarrod Millman, St'efan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, Robert Kern, Matti Picus, Stephan Hoyer, Marten H. van Kerkwijk, Matthew Brett, Allan Haldane, Jaime Fern'andez del R'\u0131o, Mark Wiebe, Pearu Peterson, Pierre G'erard-Marchant, Kevin Sheppard, Tyler Reddy, Warren Weckesser, Hameer Abbasi, Christoph Gohlke, and Travis E. Oliphant. Array programming with NumPy. Nature, 585(7825):357\u2013362, September 2020. URL: https://doi.org/10.1038/s41586-020-2649-2, doi:10.1038/s41586-020-2649-2.\u00a0\u21a9
-
The pandas development team. Pandas-dev/pandas: pandas 1.0.5. June 2020. URL: https://doi.org/10.5281/zenodo.3898987, doi:10.5281/zenodo.3898987.\u00a0\u21a9
-
Wes McKinney. Data Structures for Statistical Computing in Python. In St\u00e9fan van der Walt and Jarrod Millman, editors, Proceedings of the 9th Python in Science Conference, 56 \u2013 61. 2010. doi:10.25080/Majora-92bf1922-00a.\u00a0\u21a9
-
The HDF Group. The hdf5 library and file format. URL: https://www.hdfgroup.org/solutions/hdf5/.\u00a0\u21a9
-
Ivan Vilata Francesc Alted and others. PyTables: hierarchical datasets in Python. URL: http://www.pytables.org/.\u00a0\u21a9\u21a9
"},{"location":"basisscript/#hdf5-pytables","title":"HDF5, PyTables","text":"Een populair binair formaat in de wetenschappelijke wereld is HDF5.7 14 Je kunt hiermee verschillende datasets bewaren in \u00e9\u00e9n bestand. Je kunt een soort boomstructuur aanbrengen en zo verschillende datasets groeperen en er ook nog extra informatie (metadata) aanhangen zoals datum van de meting, beschrijving van de condities, etc. Je kunt een meetserie opslaan als reeks die in \u00e9\u00e9n keer in en uit het bestand wordt geladen maar ook als tabel. Die laatste biedt de mogelijkheid om \u2014 net als in een database \u2014 data te selecteren en alleen die data in te laden uit het bestand. Op die manier is het mogelijk om met datasets te werken die groter zijn dan het geheugen van je computer.8 Meer informatie lees je in de tutorial van PyTables15.
PyTables15 is een Python bibliotheek die het werken met HDF5-bestanden makkelijker maakt. Er zijn uiteraard functies om de bestanden aan te maken en uit te lezen maar ook om queries uit te voeren. Pandas kan \u2014 via PyTables \u2014 ook werken met HDF5-bestanden.
HDF5 tutorial
Download de HDF5 tutorial. Open de tutorial in Visual Studio Code en bestudeer de stappen die daar staan beschreven nauwkeurig.
PyTables
Pas je script aan zodat de meetserie van de LED wordt opgeslagen in een HDF5-bestand. Vraag hulp als je uitleg wilt over wat een UInt16
voor een ding is. Gebruik \u00e9\u00e9n bestand en maak daarin een nieuwe dataset voor iedere meetserie. Bewaar ook wat metadata (bijvoorbeeld tijdstip van de meting). Iedere keer dat je je script runt wordt er aan hetzelfde databestand een nieuwe dataset toegevoegd.
"},{"location":"benodigdheden/","title":"Lijst van benodigdheden","text":"We hebben voor deze cursus de volgende onderdelen gebruikt:
- Arduino Nano 33 IoT
- Firmware voor in de Arduino: visa_firmware.ino
- micro-USB kabel
- 400-punt breadboard
- jumperwires (M/M)
- 1x LED rood
- 1x LED blauw
- 1x weerstand 220\u03a9
Voor eindfeest zonnecel:
- 1x weerstand 1k\u03a9
- 1x weerstand 4,7\u03a9
- 1x weerstand 1M\u03a9
- 1x weerstand 2M\u03a9
- 1x Seeed Studio 0.5W zonnepaneel
- 1x JST 2-pin connector kabeltje
Voor eindfeest Morse:
- 1x LDR-weerstand
Voor eindfeest afstandsensor:
- Seeed Studio Grove Ultrasonic Ranger
"},{"location":"cheatsheets/","title":"Cheatsheets","text":"Conda environment aanmaken conda create --name NAME PACKAGES\n
environment activeren conda activate NAME\n
pakket installeren (NAME) > conda install PACKAGE\n
GitHub Lege repository aanmaken - GitHub Desktop: File > New repository
- Kies
NAME
en locatie (let op! de project map mag niet in een andere repository staan!) - Vink
Initialize this repository with a README
aan Git ignore
: \"Python\" - GitHub Desktop: Repository > Open in Visual Studio Code
Van bestaande map repository maken - GitHub Desktop: File > Add repository
- Kies locatie van
projectmap
(let op! de project map mag niet in een andere repository staan!) - Druk op de blauwe tekst
Create repository
. - Vink
Initialize this repository with a README
aan. - Kies bij
Git ignore
voor \"Python\". - Bevestig met de blauwe knop
Create Repository
. - Ga naar Repository > Open in Visual Studio Code (of druk op Ctrl+Shift+A ) en ga aan de slag.
Poetry Nieuw Poetry project aanmaken poetry new --src NAME\n
Poetry toevoegen aan bestaand project poetry init --no-interaction\n
Poetry project installeren poetry install\n
Dependencies toevoegen poetry add PACKAGE\n
Dependencies verwijderen poetry remove PACKAGE\n
commando toevoegen - Voeg in je
pyproject.toml
een extra kopje toe: [tool.poetry.scripts]\nnaam_commando = \"package.module:naam_functie\"\n
"},{"location":"classes/","title":"Classes","text":"Voor een snelle meting is het script dat je geschreven hebt bij opdracht quick 'n dirty meting en opdracht Pythondaq: CSV prima! Maar als de meetapparatuur ingewikkelder wordt (meer verschillende commando's) of je wilt meer aanpassingen doen, dan is het wel lastig dat je op allerlei plekken de commando's opnieuw moet programmeren \u2014 en eerst moet opzoeken. Als je een nieuw script schrijft moet je opnieuw goed opletten dat je de goede terminator characters gebruikt, etc. Het is wat werk, maar toch heel handig, om je code op te splitsen en een class te schrijven.
Een class is eigenlijk een groep functies die je bij elkaar pakt en die met elkaar gegevens kunnen delen. Zodra een programma wat complexer wordt merk je dat het fijn kan zijn om variabelen op te sluiten in ge\u00efsoleerde omgevingen.
"},{"location":"classes/#aanmaken-van-een-class","title":"Aanmaken van een class","text":"Een class is een verzameling functies. Hieronder staat een versimpelde weergave van de class Turtle
. Een class maak je aan met de regel class Turtle:
1 Daaronder komt ingesprongen de inhoud van de class. De class bestaat uit een collectie van functies \u2014 de zogeheten methods van de class. De eerste method __init__()
is speciaal (voor meer informatie zie: dunder methods), dit is de initializer waarin alle taken staan die uitgevoerd worden zodra de class gebruikt wordt.
class Turtle:\n def __init__(self, shape):\n # transform turtle into shape\n\n def forward(self, distance):\n # move turtle by distance\n\n def left(self, angle):\n # turn turtle counterclockwise\n # by angle in degrees\n
De eerste parameter van de __init__()
-method en van alle andere methods, is self
, daarna komen \u2014indien nodig\u2014 andere parameters die in de method nodig zijn. Later meer over de speciale parameter self, eerst gaan we kijken hoe je een class gebruikt.
"},{"location":"classes/#aanroepen-van-een-class","title":"Aanroepen van een class","text":"Het aanroepen van een class lijkt veel op het aanroepen van een functie:
\ndef calculate_squares_up_to(max_number):\n squares = []\n for number in range(max_number):\n squares.append(number ** 2)\n return squares\n\nresult = calculate_squares_up_to(5)\n
\nclass Turtle:\n def __init__(self, shape):\n # transform turtle into shape\n\n def forward(self, distance):\n # move turtle by distance\n\n def left(self, angle):\n # turn turtle counterclockwise\n # by angle in degrees\n\nmaster_oogway = Turtle(\"turtle\") \n
Stel we hebben de functie calculate_squares_up_to(max_number)
. Dan roep je die aan met result = calculate_squares_up_to(5)
. Hierbij is calculate_squares_up_to(5)
de naam van de functie en result
de variabele waar de uitkomst heen gaat. Bij het aanroepen van een class doe je iets soortgelijks. In de variabele master_oogway
gaat de 'uitkomst' van de class, dat is in dit geval een collectie van methods (en variabelen). De variabele master_oogway
noemen we een instance van de class Turtle
. Net zoals je een functie vaker kunt aanroepen, kan je ook meerdere instances van een class aanmaken. Achter de naam van de class: Turtle
, komen tussen ronde haakjes de variabelen die worden meegegeven aan de __init__()
-method (self
niet meegerekend), de parameter shape
krijgt dus de variabele \"turtle\"
toegewezen.
Meerdere instances Je kunt meerdere instances hebben van dezelfde class, bijvoorbeeld voor verschillende schildpadden:
class Turtle:\n ...\n\nturtle_1247 = Turtle()\n...\nturtle_1428 = Turtle()\n...\n
__init__(self)
Stel dat de init-method geen extra parameters mee krijgt, zoals in het volgende geval:
class Turtle:\n def __init__(self):\n # initialiseer class\n\n def forward(self, distance):\n # move turtle by distance\n\n def left(self, angle):\n # turn turtle counterclockwise\n # by angle in degrees\n
hoe maak je dan een instance aan van de class? Uitwerkingen master_oogway = Turtle()\n
Omdat de instance master_oogway
alle methods bevat kunnen we deze methods aanroepen:
master_oogway = Turtle(\"turtle\")\n\nmaster_oogway.forward(50)\nmaster_oogway.left(30)\nmaster_oogway.forward(50)\n
turtle
opdrachtcodecheck Je bent inmiddels nieuwsgierig geworden naar de schildpad. De class Turtle
zit standaard in Python, daarom kan je die importeren met from turtle import Turtle
. Maak een bestand turtles.py
waarin je een schildpad met de instancenaam master_oogway
laat lopen en draaien. ECPC
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 turtles.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022
Schildpad verdwijnt
Na het uitvoeren van het script sluit Python het scherm van de schildpad. Voeg de regel master_oogway.screen.mainloop()
toe om het scherm te laten staan en handmatig af te sluiten.
Pseudo-code
from turtle import Turtle\n\n# create instance of class Turtle\nmaster_oogway = Turtle(\"turtle\")\n\n# move turtle forward with 50 steps\n...\n# turn turtle left with 30 degrees\n...\n
Checkpunten:
- De class
Turtle
wordt ge\u00efmporteerd uit de module turtle
. - De instance is van de class
Turtle
met hoofdletter T. - Om de schildpad te laten bewegen roep je de method
forward()
of left()
van de instance aan.
Projecttraject:
- turtle
"},{"location":"classes/#de-speciale-parameter-self","title":"De speciale parameter self
","text":"Een class method is vrijwel gelijk aan een normale functie, behalve dat een class method als eerste de parameter self
verwacht. Aan deze parameter wordt de eigen instance van de class meegegeven wanneer je de method aanroept.
Laten we kijken naar wat die instance van de class eigenlijk is. De instance van een class is de collectie van methods (en variabelen).
\ndef calculate_squares_up_to(max_number):\n squares = []\n for number in range(max_number):\n squares.append(number ** 2)\n return squares\n\nresult = calculate_squares_up_to(5)\n
\nclass Turtle:\n def __init__(self, shape):\n # transform turtle into shape\n\n def forward(self, distance):\n # move turtle by distance\n\n def left(self, angle):\n # turn turtle counterclockwise\n # by angle in degrees\n\nmaster_oogway = Turtle(\"turtle\") \n
Als we de functie calculate_squares_up_to(max_number)
aanroepen met result = calculate_squares_up_to(5)
, dan komt hetgeen we teruggeven, squares
, in de variabele result
terecht. Bij een class is er geen return
-statement maar komt de hele inhoud van de class alle methods (en variabelen) in de instance master_oogway
terecht.
Gelukkig hoef je de instance niet steeds zelf mee te geven aan een method. Wanneer je een method aanroept wordt impliciet de instance als eerste parameter meegegeven. Maar waarom zou je die instance meegeven aan een method als je die aanroept? Omdat de instance alle methods en variabele bevat, kan je de informatie die daarin is opgeslagen in elke method gebruiken.
Stel we maken een nieuwe method do_kungfu_move
waarin we forward()
en left()
willen gebruiken:
class Turtle:\n def __init__(self, shape):\n # transform turtle into shape\n\n def forward(self, distance):\n # move turtle by distance\n\n def left(self, angle):\n # turn turtle counterclockwise\n # by angle in degrees\n\n def do_kungfu_move(self):\n # Do kungfu move\n self.forward(130)\n self.left(350)\n self.forward(60)\n
Als we de method do_kungfu_move
aanroepen met master_oogway.do_kungfu_move()
geeft python automatisch de instance master_oogway
mee aan de method. De parameter self
is dus nu gelijk aan de instance master_oogway
, daarmee doet self.forward(130)
hetzelfde als master_oogway.forward(130)
.
"},{"location":"classes/#instance-attribute","title":"Instance attribute","text":"De instance van een class bevat niet alleen alle methods, maar kan ook variabele hebben. In het voorbeeld hieronder voegen we de variabele quote
toe in de init-method aan de instance, daarmee wordt het een instance attribute.
class Turtle:\n def __init__(self, shape):\n # transform turtle into shape\n self.quote = \"Yesterday is history, Tomorrow is a mystery, but Today is a gift. That is why it is called the present\"\n\n ...\n
De instance attribute quote
is nu onderdeel van de instance. We kunnen die oproepen binnen elke method met self.quote
maar ook buiten de class: turtles.py ...\nmaster_oogway = Turtle(\"turtle\")\n\nprint(master_oogway.quote)\n
\n(ecpc) > python turtles.py\n\"Yesterday is history, Tomorrow is a mystery, but Today is a gift. That is why it is called the present\"\n
Opbouw van een class
- Beschouw de onderstaande code
- Bespreek met elkaar wat de code precies doet en verplaast de onderdelen naar de juiste plek in de code. Twijfel je of je nog weet wat een module is kijk dan voor meer informatie in de paragraaf modules.
Classes importeren Wat is nu het praktisch nut van classes en methods gebruiken in plaats van functies? Want in plaats van
forward(master_oogway, distance=50)\n
hebben we nu master_oogway.forward(distance=50)\n
en dat is even lang. Het grote voordeel ontstaat pas wanneer de class ingewikkelder wordt en meer data gaat bewaren. Ook kun je de class in een ander pythonbestand (bijvoorbeeld animals.py
) zetten en alle functionaliteit in \u00e9\u00e9n keer importeren met: from animals import Turtle\n\nmaster_oogway = Turtle()\n...\n
Op deze manier kun je code ook makkelijker delen en verspreiden. Zodra je een class definieert zal Visual Studio Code tijdens het programmeren je code automatisch aanvullen. Zodra je typt master_oogway.f
hoef je alleen maar op Tab te drukken en VS Code vult de rest aan. Class Particle
opdrachtcodecheck Je hebt een class Particle
gemaakt in een niew bestand particle.py
. Als je een instance aanmaakt van de class Particle
kun je de naam van het deeltje meegeven en de spin (bijvoorbeeld: 0.5). De instance attributes van deze class zijn 'name' en 'spin'. Er is ook een method is_up_or_down()
om terug op te vragen wat de spin van het deeltje op dat moment is (spin omhoog/positief of spin omlaag/negatief). Door de method flip()
op te roepen wordt de spin van het deeltje omgekeerd. ECPC
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 particle.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code
# class Particle:\n # def __init__(self, name, spin):\n # make instance attribute from name\n # make instance attribute from spin\n # def is_up_or_down\n # print up when spin is positive\n # print down when spin is negative\n ...\n # def flip\n # Make spin positive if spin is negative\n # Make spin negative if spin is positive\n ...\n
Testcode particle.py proton = Particle('mooi proton', 0.5)\nproton.is_up_or_down()\nproton.flip()\nproton.is_up_or_down()\nprint(proton.spin)\nprint(proton.name)\n
\n(ecpc) > python particle.py\nup\ndown\n-0.5\nmooi proton\n
Checkpunten:
- Naam en spin toestand worden aan instance meegegeven.
- Method
is_up_or_down()
print 'up' als de spin positief is en 'down' als het negatief is. - Method
flip()
maakt de spin positief als de spin negatief is, en negatief als de spin positief is.
Projecttraject:
- Class Particle
Class ProjectileMotion
opdrachtcodecheck Je gaat een waterraket een aantal keer wegschieten met steeds een andere beginsnelheid en lanceerhoek. Je hebt een instance aangemaakt van de class ProjectileMotion
. De beginsnelheid en de lanceerhoek bewaar je steeds met de method add_launch_parameters()
. Om in een keer alle beginsnelheden op te vragen gebruik je de method get_initial_velocities()
. Om alle lanceerhoeken op te vragen gebruik je de method get_launch_angles()
. Op basis van de gegevens (en door de luchtweerstand te verwaarlozen) bepaal je de vluchtduur en het bereik van de raket. Je kunt de vluchtduur van alle vluchten opvragen met de method get_time_of_flights()
en het bereik van alle vluchten met get_flight_ranges()
. ECPC
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u251c\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 projectile-motion
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 water_rocket.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code
# class ProjectileMotion\n ...\n # __init__\n ...\n # add_launch_parameters\n ...\n # get_initial_velocities\n ...\n # get_launch_angles\n ...\n # get_time_of_flights\n ...\n # get_flight_ranges\n ...\n
Testcode water_rocket.py speedy = ProjectileMotion()\nspeedy.add_launch_parameters(v=28, angle=68)\nspeedy.add_launch_parameters(v=11, angle=15)\n\nv = speedy.get_initial_velocities()\nangles = speedy.get_launch_angles()\nx = speedy.get_flight_ranges()\nt = speedy.get_time_of_flights()\n\nprint(f\"{v=}\")\nprint(f\"{angles=}\")\nprint(f\"{x=}\")\nprint(f\"{t=}\")\n
\n(ecpc) > python water_rocket.py\nv=[28, 11]\nangles=[68, 15]\nx=[55.51602063607072, 6.167176350662587]\nt=[5.292792645845066, 0.5804300705663054]\n
Checkpunten:
- De code bevindt zich in een GitHub-repository .
- De method
add_launch_parameters
verwacht een beginsnelheid in meter per seconde en een lanceerhoek in graden. - De method
get_initial_velocities
geeft een lijst terug met beginsnelheden van alle ingevoerde parameters. - De method
get_launch_angles
geeft een lijst terug met alle lanceerhoeken van de ingevoerde parameters. - De time-of-flight wordt berekend met 2 * v_y / g.
- De beginsnelheid in de y-richting: v_y = v * sin(lanceerhoek).
- Het bereik wordt berekend met time_of_flight * v_x.
- De beginsnelheid in de x-richting: v_x = v * cos(lanceerhoek).
- De lanceerhoek wordt in radialen meegegeven aan de trigonomische functies.
- De method
get_time_of_flights
geeft een lijst terug met de vluchtduur in seconden corresponderend met de ingevoerde parameters. - De method
get_flight_ranges
geeft een lijst terug met het bereik in meters die correspondeerd met de ingevoerde parameters.
Projecttraject:
- Class ProjectileMotion
ProjectileMotion raise exception Het is niet logisch als de lanceerhoek boven een bepaalde hoek uitkomt of als een negatieve beginsnelheid wordt ingevoerd. Zorg dat in die gevallen een error afgegeven wordt. Meer informatie hierover vind je in de paragraaf Exceptions.
Subclass -
Wanneer je de Google Style Guide2 volgt schrijf je de naam van de class in CapWords of CamelCase.\u00a0\u21a9
-
Google. Google python style guide. URL: https://google.github.io/styleguide/pyguide.html.\u00a0\u21a9
"},{"location":"classes/#subclasses","title":"Subclasses","text":"Je kunt behalve een class ook een subclass aanmaken. De class Turtle
heeft hele handige methods maar je kunt een specifiekere class GiantTortoise
maken.
class GiantTortoise(Turtle):\n def __init__(self):\n super().__init__()\n self.shape(\"turtle\")\n self.color(\"dark green\")\n self.turtlesize(5)\n self.speed(1)\n\n def move(self, distance):\n steps = range(0, distance, 5)\n i = 1\n for step in steps:\n self.tiltangle(i * 5)\n self.forward(step)\n time.sleep(1)\n i = i * -1\n
Door de parentclass Turtle
tussen ronde haakjes mee te geven aan de nieuwe subclass GiantTortoise
krijgt de subclass alle functionaliteit mee van de parentclass, waaronder alle methods zoals forward()
. Als je in de init-method van de subclass methods of attributes wilt gebruiken van de parentclass, moet je ervoor zorgen dat de parentclass is ge\u00efnitialiseerd . Dit doe je met super().__init__()
hierbij verwijst super()
naar de parentclass en met __init__()
voer je de init-method van de parentclass uit. Nadat we in de init-method van de subclass de eigenschappen van de Reuzenschildpad hebben gedefinieerd, kunnen we extra functionaliteit gaan toevoegen bijvoorbeeld de manier van bewegen met de method move()
.
super().__init__()
- Maak een bestand aan waarin je de subclass
GiantTortoise
aanmaakt. - Zorg dat de volgende voorbeeldcode werkt:
t = GiantTortoise()\nt.move(50)\n
- Wat gebeurd er als je
super().__init__()
weglaat?
Hawksbill turtle
- Maak een subclass aan voor de Hawksbill turtle.
- De Hawksbill turtle is een zeeschildpad. Maak de omgeving van de schildpad standaard blauw met
self.screen.bgcolor(\"cyan\")
. - Schrijf een method
swim()
die de schildpad over het scherm laat bewegen.
"},{"location":"cli/","title":"Command-line interface","text":""},{"location":"cli/#gebruikersomgevingen","title":"Gebruikersomgevingen","text":"Vanaf de jaren '60 van de vorige eeuw werden computers interactief. Het was mogelijk om via een terminal commando's aan de computer te geven en te wachten op een antwoord. In tegenstelling tot moderne gebruikersomgevingen waren deze volledig op tekst gebaseerd. Hoewel moderne besturingssystemen \u2014 of het nu computers, tablets of mobiele telefoons betreft \u2014 volledig grafisch zijn ingericht, is de tekstuele interface nooit verdwenen. Opdrachten geven door te typen is gewoon best wel handig en snel. Ook is het veel eenvoudiger om applicaties te ontwikkelen zonder grafische interface.
Op ieder besturingssysteem \u2014 Linux, MacOS, Windows \u2014 is een shell, terminal of command prompt te vinden. Als je die opstart kun je op de zogeheten command line opdrachten intypen. Veelal zijn dit commando's om het bestandssysteem te navigeren en programma's op te starten.
Wanneer je in Visual Studio Code een Python script start dan opent het een terminal onderin het scherm.
"},{"location":"cli/#commandos","title":"Commando's","text":"Je hebt tot nu toe al heel wat commando's in de terminal getypt. Laten we een paar voorbeelden bestuderen: Terminal
PS> python script.py\n
Als eerste vertel je welke applicatie je wilt gaan starten; in dit geval: python
. Daarna geef je met het argument script.py
aan welk Pythonscript je wilt uitvoeren. Vaak kun je ook opties meegeven zoals in: (ecpc) > python -V \nPython 3.10.13\n
Hiermee vraag je Python om het versienummer weer te geven. Soms kunnen opties zelf weer een argument meekrijgen. Bijvoorbeeld: Terminal
PS> python -m antigravity\n
Met deze regel geef je Python de optie -m
en die importeert een module (hier antigravity
) en voert die uit. Probeer maar eens zelf wat er gebeurt als je dat commando uitvoert. Als applicaties veel verschillende functionaliteit hebben dan krijg je regelmatig te maken met een lange regel met een combinatie van argumenten en opties: Terminal
PS> conda create --name pythondaq --channel conda-forge python pyvisa-py\n
Uitgesplitst in argumenten en opties, met vierkante haken [] om aan te geven welke onderdelen bij elkaar horen, is dat: conda create [--name pythondaq] [-channel conda-forge] [python pyvisa-py]
Poetry argumenten
- Naast
conda create
heb je ook met andere argumenten gewerkt zoals activate
en install
. Welke argumenten ken je al van de applicatie poetry
? - Vraag de lijst met argumenten (commando's) op van Poetry met
poetry list
, hoeveel kende je nog niet?
Conda opties en argumenten
- Open een
Anaconda Prompt
- Maak gebruik van de optie -h om de helpfunctie van conda op te vragen. (
conda -h
) - Zoek de optie op om de conda versie weer te geven (
conda -V
) - Maak gebruik van de optie -h om de helpfunctie van het commando activate op te vragen (
conda activate -h
) - Welke argumenten moet je meegeven (positional arguments?) en welke opties mag je meegeven (optional arguments?) (argument: env_name_or_prefix, opties: --help, --stack, --no-stack)
"},{"location":"cli/#click","title":"Click","text":"Als we gebruik willen maken van commando's in onze eigen applicatie moeten we weten wat de gebruiker in de terminal typt. Dit is mogelijk met sys.argv
.1 Waarbij alles wat we in de terminal typen aan input wordt meegegeven:
cli.py import sys\n\nprint(sys.argv)\n
\n(ecpc) > python cli.py test 123\n['cli.py', 'test', '123']\n
Met if-statements kunnen we acties verbinden aan bepaalde argumenten: cli.py
import sys\n\nargs = sys.argv\nprint(args)\n\nif args[1] == \"test\":\n print(f\"This is a test: {args[2]}\")\nelse:\n print(f\"CommandNotFoundError: No command '{args[1]}'.\")\n
Als je meerdere opties en argumenten meegeeft dan wordt het veel werk om die in je script uit elkaar te plukken en ze goed te interpreteren. Om dat makkelijker te maken zijn er verschillende bibliotheken beschikbaar \u2014 waaronder een paar in de standard library van Python. Een hele handige \u2014 die n\u00edet in de standard library van Pythhon zit maar w\u00e9l meegeleverd is met de base environment van Anaconda \u2014 is Click.5
Info
Click maakt gebruik van decorators (@decorator
). Om decorators te gebruiken, hoef je niet per se te weten hoe ze werken. Als je meer wilt weten over de werking ervan kijk dan de calmcode tutorial of lees de Primer on Python Decorators.
Als kort voorbeeld \u2014 ge\u00efnspireerd op de documentatie van Click \u2014 nemen we het volgende script: hello.py
def hello():\n print(\"Hello physicist!\")\n\nif __name__ == \"__main__\":\n hello()\n
Dit script print de uitdrukking \"Hello physicist!\". We gaan dit aanpassen en maken het mogelijk om de naam en het aantal begroetingen te kiezen. Hiervoor gebruiken we Click. Allereerst moeten we click
importeren en aangeven dat we de hello()
-functie willen gebruiken als commando:
hello.pyimport click\n\n@click.command()\ndef hello():\n print(\"Hello physicist!\")\n\nif __name__ == \"__main__\":\n hello()\n
Dit levert ons nog niet zoveel op, maar op de achtergrond is click wel degelijk aan het werk. De @click.command()
houdt in de gaten wat er in de command line wordt ingetypt. Zo kunnen we de helpfunctie aanroepen door --help
achter de naam van het script te zetten.
Terminalpython hello.py --help\n
Help functie
opdrachtcodecheck Je neemt het script hello.py
over. Je vraagt de helpfunctie van het script op. Je ziet een helptekst verschijnen. Je vraagt je af wat er gebeurt als je @click.command()
weg haalt en dan de helpfunctie opvraagt. Je krijgt gewoon de output van de functie hello()
een geen help tekst. ECPC
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 hello.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u251c\u2500\u2500 pythondaq
\u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code hello.py
import click\n\n# make function Click command\n# function\n # print hello physicist!\n\n# when run this script:\n # run function\n
Testcode (ecpc) > python hello.py --help \nUsage: hello.py [OPTIONS] \nOptions:\n --help Show this message and exit.\n\n
Checkpunten:
- Je vraagt de helpfunctie op door
--help
achter de bestandsnaam te zetten in de terminal. - Er verschijnt een standaard helptekst.
- Zonder
@click.command()
verschijnt er geen helptekst, maar de output van de functie.
Projecttraject:
- Help functie
- Argumenten toevoegen
- Test hello
- Helptekst toevoegen
- Pauze optie
- Vlag
In de code hieronder geven we met de regel @click.argument(\"name\")
aan dat we van de gebruiker een argument verwachten. Zorg dat het argument ook gebruikt wordt in de functie hello
:
hello.pyimport click\n\n@click.command()\n@click.argument(\"name\")\ndef hello(name):\n print(f\"Hello {name}!\")\n\nif __name__ == \"__main__\":\n hello()\n
Argument toevoegen
opdrachtcodecheck Je runt het bestand hello.py
en geeft achter de bestandsnaam de naam Alice
mee. Er verschijnt Hello Alice!
als output in de terminal.
Pseudo-code hello.py
import click\n\n# make function Click command\n# make argument name\n# function, parameter name\n # print hello <name>!\n\n# when run this script:\n # run function\n
Testcode (ecpc) > python hello.py \nUsage: hello.py [OPTIONS] NAME\nTry 'hello.py --help' for help.\nError: Missing argument 'NAME'.\n\n
Checkpunten:
- Het draaien van
hello.py
zonder een argument: python hello.py
geeft een foutmelding. - Het draaien van
hello.py
met een argument: python hello.py Alice
werkt zoals verwacht.
Projecttraject:
- Help functie
- Argumenten toevoegen
- Test hello
- Helptekst toevoegen
- Pauze optie
- Vlag
Warning
Let er op dat je bij @click.argument
de naam meegeeft die overeenkomt met de namen van de parameters van je functie. In ons geval hebben we een argument \"name\"
. Dit moet overeenkomen met de functiedefinitie def hello(name)
.
Argumenten zijn altijd verplicht en moeten in een vaste volgorde staan. Bij opties is dat anders. Je geeft met mintekens aan dat je een optie meegeeft. Veel opties hebben een lange naam en een afkorting (bijvoorbeeld --count
en -c
). Opties kunnen zelf weer een argument hebben (bijvoorbeeld --count 3
). Het is handig om een standaardwaarde te defini\u00ebren. In dat geval mag de gebruiker de optie weglaten. We voegen een for-loop2 toe om de begroeting te herhalen.
hello.pyimport click\n\n@click.command()\n@click.argument(\"name\")\n@click.option(\n \"-c\",\n \"--count\",\n default=1,\n)\ndef hello(name, count):\n for _ in range(count):\n print(f\"Hello {name}!\")\n\nif __name__ == \"__main__\":\n hello()\n
5 keer hello
opdrachtcodecheck Je runt het bestand hello.py
en geeft achter de bestandsnaam de naam van je assistent mee en geeft aan dat je deze 5 keer wilt printen. Er verschijnt vijf keer Hello <assistent>!
als output in de terminal.
Pseudo-code hello.py
import click\n\n# make function Click command\n# make argument name\n# make option count with default value 1\n# function, parameter name and count\n # repeat count times\n # print hello <name>!\n\n# when run this script:\n # run function\n
Testcode (ecpc) > python hello.py David -c 5 \nHello David!\nHello David!\nHello David!\nHello David!\nHello David!\n
Checkpunten:
- Je kan de naam van je assistent 5 keer printen met \u00e9\u00e9n commando
- Je kan het aantal keer printen opgeven met
-c
. - Je kan het aantal keer printen ook opgeven met
--count
. - Wanneer de optie
count
wordt weggelaten wordt de naam 1 keer geprint. -
Wanneer er geen argument wordt meegegeven met count
volgt een foutmelding:
(ecpc) > python hello.py David -c \nError: Option '-c' requires an argument.\n
Projecttraject:
- Help functie
- Argumenten toevoegen
- Test hello
- Helptekst toevoegen
- Pauze optie
- Vlag
Warning
Let er op dat je bij @click.option
de afkorting met 1 minteken meegeeft en de lange naam met 2 mintekens. De lange naam moet overeenkomen met de paramater van je functie. In ons geval hebben we een optie \"--count\"
\u2014 de lange naam telt. Dit moet overeenkomen met de functiedefinitie def hello(name, count)
.
Het is handig om een korte helptekst toe te voegen. Dit gaat als volgt:
hello.pyimport click\n\n@click.command()\n@click.argument(\"name\")\n@click.option(\n \"-c\",\n \"--count\",\n default=1,\n help=\"Number of times to print greeting.\",\n show_default=True, # show default in help\n)\ndef hello(name,count):\n for _ in range(count):\n print(f\"Hello {name}!\")\n\nif __name__ == \"__main__\":\n hello() \n
Helptekst toevoegen
Voeg de helptekst toe en vraag de helptekst op zoals in de opdracht Help functie.
Als je dit script gebruikt ziet dat er zo uit:
(ecpc) > python hello.py --help \nUsage: hello.py [OPTIONS] NAME\n\nOptions:\n -c, --count INTEGER Number of times to print greeting. [default: 1]\n --help Show this message and exit.\n \n\n(ecpc) > python hello.py Alice \nHello Alice!\n\n(ecpc) > python hello.py Alice -c 2 \nHello Alice!\nHello Alice!\n\n(ecpc) > python hello.py Alice --count 3 \nHello Alice!\nHello Alice!\nHello Alice!\n
Pauze optie
opdrachtcodecheck Je runt het bestand hello.py
en geeft achter de bestandsnaam de naam van je assistent mee en geeft aan dat je deze 5 keer wilt printen met een pauze van 2 seconde ertussen. Het duurt 8 seconden voordat er vijf keer Hello <assistent>!
als output in de terminal staat. Als je geen pauze-optie meegeeft wordt er ook geen pauze gehouden.
Info
Je kan hiervoor gebruik maken van de module time die standaard met Python meekomt3. Met de functie sleep()
kun je de executie van de volgende regel in het script met een aantal seconden uitstellen.
import time\n# wait 28 second\ntime.sleep(28)\n
Pseudo-code hello.py
import click\n\n# make function Click command\n# make argument name\n# make option count with default value 1\n# make option pause\n# function, parameter name and count\n # repeat count times\n # print hello <name>!\n # pause\n\n# when run this script:\n # run function\n
Testcode (ecpc) > python hello.py David -c 5 \nHello David!\nHello David!\nHello David!\nHello David!\nHello David!\n
Checkpunten:
- Als de pauze optie niet wordt meegegeven, dan wordt er g\u00e9\u00e9n pauze ingelast
- Bij het meegeven van de pauze optie, wacht het programma zo lang als verwacht
Projecttraject:
- Help functie
- Argumenten toevoegen
- Test hello
- Helptekst toevoegen
- Pauze optie
- Vlag
Opties zonder argument werken als vlag \u2014 een soort aan/uitknop.4
Vlag
Gebruik een optie als vlag om de gebruiker te laten kiezen tussen het wel (tea) of niet (no tea) aanbieden van een kopje thee. Zorg dat er standaard tea wordt aangeboden.
boolean flags
Lees meer over boolean flags in de Click documentatie.
Argumenten en opties
opdrachtcodecheck Je opent met Github Desktop de just_count
in Visual Studio Code. Je hebt ooit een environment voor deze repository aangemaakt maar je hebt geen idee of die in de tussentijd niet per ongeluk stuk is gegaan. Daarom maak je een nieuwe environment just_count
met daarin Python en gebruik je Poetry om het pakket just_count
in de nieuwe omgeving te installeren . Je test of je de applicatie nog kunt aanroepen met het commando square
.
Je activeert het juiste conda environment en past de code aan zodat met het commando square 6
het kwadraat van 6 in de terminal wordt geprint.
Info
Click maakt van alle argumenten een string, tenzij je een default waarde of een type definieert. Gebruik type=int
, type=float
enzovoorts om aan te geven wat voor type object het argument moet worden
Meer functies - Pas de applicatie aan zodat je kan kiezen tussen het kwadraat of de wortel van het getal.
Pseudo-code count_count.py
import square\n\n# Add functionality to select a number via click and print its square\ndef main():\n print(f\"The square of 5 is {square.square(5)}\")\n\nif __name__ == '__main__':\n main()\n
Testcode (ecpc) > square 6 \nThe square of 6 is 36\n
Checkpunten:
- Je hebt
poetry install
in een schone environment (met alleen Python) gedaan. - Je hebt de juiste omgeving geactiveerd.
- Na het commando
square
kan je een getal meegeven en krijg je het verwachte antwoord terug.
Projecttraject
- Main functie toevoegen
- commando toevoegen
- Commando testen
- Argumenten en opties
"},{"location":"cli/#click-subcommandos","title":"Click subcommando's","text":"Tot nu toe konden we maar \u00e9\u00e9n functie uitvoeren in onze applicatie. Maar het is ook mogelijk om subcommando's aan te maken zodat je met \u00e9\u00e9n programma meerdere taken kunt uitvoeren. Denk bijvoorbeeld aan conda
. Je installeert packages met conda install
, verwijdert ze met conda remove
, maakt een environment met conda create
en activeert het met conda activate
.
Subcommando's bedenken
Je gaat de pythondaq
applicatie straks verder uitbreiden zodat er veel meer mogelijk is dan nu. Wat zou je willen dat de applicatie allemaal kan? Welke subcommando's wil je gaan aanmaken? Overleg met elkaar om goede idee\u00ebn uit te wisselen.
Een eenvoudig voorbeeldscript waarin de conda commando's install
en remove
worden nagebootst leggen we hieronder uit. Eerst de code:
fake_conda.py
import click\n\n@click.group()\ndef cmd_group():\n pass\n\n@cmd_group.command()\n@click.argument(\"package\")\n@click.option(\n \"-c\",\n \"--channel\",\n default=\"defaults\",\n help=\"Additional channel to search for packages.\",\n show_default=True, # show default in help\n)\ndef install(package, channel):\n print(f\"Installing {package} from {channel}...\")\n\n@cmd_group.command()\n@click.argument(\"package\")\ndef remove(package):\n print(f\"Removing {package}...\")\n\nif __name__ == \"__main__\":\n cmd_group()\n
In (de laatste) regel 18 roepen we de hoofdfunctie aan die we enigszins willekeurig cmd_group()
genoemd hebben en die we bovenaan defini\u00ebren. In tegenstelling tot het hello.py
-script doet deze functie helemaal niets (pass
). We vertellen aan click dat we een groep van commando's aan gaan maken met de @click.group()
-decorator in regel 3. Vervolgens gaan we commando's binnen deze groep hangen door niet de decorator @click.command()
te gebruiken, maar @cmd_group.command()
\u2014 zie regels 7 en 12. De namen van de commando's die worden aangemaakt zijn de namen van de functies. Dus regel 7 en 9 maken samen het commando install
. Verder werkt alles hetzelfde. Dus een argument toevoegen \u2014 zoals in regel 8 \u2014 is gewoon met @click.argument()
. Hier hoef je geen cmd_group
te gebruiken. Fake conda
opdrachtcodecheck Nu je hebt geleerd om met Click subcommando's te maken wil je deze uittesten in combinatie met het commando wat je met Poetry kan aanmaken om een functie uit een script uit te voeren. Je maakt in de map ECPC
een nieuw Poetry project aan voor fake_conda
en zet daarin de code uit het bestand fake_conda.py
. Je maakt een nieuw conda environment fake_conda
met daarin de benodigde packages . Je installeert het Poetry project in de nieuwe conda environment . Je past de pyproject.toml
aan zodat je met het commando fake_conda install scipy
zogenaamd scipy
kunt installeren . ECPC
\u251c\u2500\u2500 fake_conda
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 src/fake_conda
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 fake_conda.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 pyproject.toml
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u251c\u2500\u2500 oefenopdrachten
\u251c\u2500\u2500 pythondaq
\u2514\u2500\u2500 \u2022\u2022\u2022
commando
Als je een commando met Poetry toevoegt dan heeft dat de opbouw naam_commando = \"package.module:naam_functie\"
, welke functie moet uitgevoerd worden als je het commando aanroept?
Pseudo-code pyproject.toml
[tool.poetry.scripts]\nnaam_commando = \"package.module:naam_functie\"\n
Testcode (ecpc) > fake_conda install scipy \nInstalling scipy from defaults....\n
Checkpunten:
- Het Poetry project is in het nieuwe conda environement ge\u00efnstalleerd.
- Na het wijzigen van de
pyproject.toml
is het Poetry project opnieuw ge\u00efnstalleerd. - In de
pyproject.toml
verwijst [tool.poetry.scripts]
naar een functie zodat install
en remove
subcommando's zijn. - Het commando
fake_conda install scipy
print de tekst Installing scipy...
als output in de terminal.
Projecttraject
- Fake conda
Smallangle (meer leren)
Met deze opdracht kun je testen hoe goed je het Python-jargon onder de knie hebt. Je zult het woord smallangle z\u00f3 vaak tegenkomen dat het je duizelt \u2014 maar jij weet precies over welk onderdeel we het hebben.
- Maak een nieuw poetry project (met een
src
indeling) aan met de naam smallangle
. - Let op de Octocat voor
smallangle
, het moet dus een repository zijn (of worden). - Maak een nieuw environment die
smallangle
heet met daarin alleen Python . - Zet in het package
smallangle
een module smallangle.py
. - Plak de onderstaande code in
smallangle.py
: import numpy as np\nfrom numpy import pi\nimport pandas as pd\n\n\ndef sin(number):\n x = np.linspace(0, 2 * pi, number)\n df = pd.DataFrame({\"x\": x, \"sin (x)\": np.sin(x)})\n print(df)\n\n\ndef tan(number):\n x = np.linspace(0, 2 * pi, number)\n df = pd.DataFrame({\"x\": x, \"tan (x)\": np.tan(x)})\n print(df)\n\n\nif __name__ == \"__main__\":\n sin(10)\n
- Ga door naar de opdracht smallangle aanpassen. Je mag de opdracht smallangle installeren overslaan \u2014 dat werk heb je nu zelf al gedaan.
smallangle installeren
opdrachtcodecheck Je cloned het Poetry project smallangle
van AnneliesVlaar/smallangle
door de repository in GitHub desktop te openen. Daarna open je het project in Visual Studio Code. Na het installeren van het poetry project in een nieuwe conda environment run je het bestand smallangle.py
en krijg je een lijst van 10 punten tussen 0 en 2 $\\pi$ en de sinus van deze punten. ECPC
\u251c\u2500\u2500 pythondaq
\u2514\u2500\u2500 smallangle
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022
Testcode smallangle.py
import numpy as np\nfrom numpy import pi\nimport pandas as pd\n\ndef sin(number):\n x = np.linspace(0, 2 * pi, number)\n df = pd.DataFrame({\"x\": x, \"sin (x)\": np.sin(x)})\n print(df)\n\ndef tan(number):\n x = np.linspace(0, 2 * pi, number)\n df = pd.DataFrame({\"x\": x, \"tan (x)\": np.tan(x)})\n print(df)\n\nif __name__ == \"__main__\":\n sin(10)\n
\n(ecpc) > python smallangle.py\n x sin (x)\n0 0.000000 0.000000e+00\n1 0.698132 6.427876e-01\n2 1.396263 9.848078e-01\n3 2.094395 8.660254e-01\n4 2.792527 3.420201e-01\n5 3.490659 -3.420201e-01\n6 4.188790 -8.660254e-01\n7 4.886922 -9.848078e-01\n8 5.585054 -6.427876e-01\n9 6.283185 -2.449294e-16\n
Checkpunten:
- Het project is ge\u00efnstalleerd in een nieuwe conda environment.
- Na het installeren van het pakket geeft de code de verwachte output.
Projecttraject:
- smallangle installeren
- smallangle aanpassen
- smallangle docstrings
smallangle aanpassen
opdrachtcodecheck Je kunt met het commando smallangle
en de subcommando's sin
en tan
een lijst genereren van getallen tussen de 0 en 2 $\\pi$ en de bijbehorende sinus dan wel tangens van deze getallen. Met de optie -n
kan je het aantal stappen (het aantal $x$-waardes tussen 0 en $2\\pi$) kiezen. Als je de optie -n
weglaat werkt de applicatie met een standaardwaarde.
TypeError: 'int' object is not iterable
Probeer je de code te draaien maar krijg je een foutmelding zoals deze: Terminal
Traceback (most recent call last):\nFile \"c:\\smallangle\\src\\smallangle\\smallangle.py\", line 28, in <module>\n sin(10)\nFile \"C:\\click\\core.py\", line 1157, in __call__ \n return self.main(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^\nFile \"C:\\click\\core.py\", line 1067, in main\n args = list(args)\n ^^^^^^^^^^\nTypeError: 'int' object is not iterable\n
Dan komt dat doordat je sin(10)
probeert uit te voeren, terwijl de functie al verClickt is. De functie verwacht een argument vanuit de terminal en geen integer vanuit het pythonscript. Pas je script aan zodat if __name__ == \"__main__\":
naar de juiste functie verwijst en Click aanroept; niet sin(10)
.
(ecpc) > smallangle sin -n 9 \n x sin (x)\n0 0.000000 0.000000e+00\n1 0.785398 7.071068e-01\n2 1.570796 1.000000e+00\n3 2.356194 7.071068e-01\n4 3.141593 1.224647e-16\n5 3.926991 -7.071068e-01\n6 4.712389 -1.000000e+00\n7 5.497787 -7.071068e-01\n8 6.283185 -2.449294e-16\n
Checkpunten:
- De gebruiker kan met subcommando's kiezen tussen
sin
en tan
. - De gebruiker kan het aantal stappen kiezen met een optie.
- De gebruiker kan de optie ook weglaten.
Projecttraject:
- smallangle installeren
- smallangle aanpassen
- smallangle docstrings
Smallangle (uitdaging) Met het commando approx
en een argument $\\epsilon$ moet het script de grootste hoek geven waarvoor nog geldt dat $\\lvert x - \\sin(x) \\rvert \\leq \\epsilon$, ofwel de grootste hoek waarvoor de kleine-hoekbenadering nog geldt met de opgegeven nauwkeurigheid. Doe dit op drie cijfers nauwkeurig (loop over .000, .001 en .002, etc. totdat de vergelijking niet meer geldt). N.B. besteed geen tijd aan het analytisch oplossen van de vergelijking. Een voorbeeld van de uitvoer:
(ecpc) > smallangle approx .1 \nFor an accuracy of 0.1, the small-angle approximation holds\nup to x = 0.854.\n
"},{"location":"cli/#docstrings-en-click-help","title":"Docstrings en Click --help
","text":"Docstrings werken ook heel handig samen met Click want ze worden gebruikt als we de helpfunctie aanroepen.
Info
We gebruiken bij click-functies niet de standaard structuur voor docstrings. Click breekt de docstrings standaard af waardoor het algauw een onogelijke brij aan informatie wordt. We kiezen daarom voor een samenvatting in een zin met daarin de PARAMETERS (argumenten) in hoofdletters en eventueel een korte toelichting daarop.
Uitgebreide documentatie en Click In de documentatie van Click vind je meer informatie over het afbreken van zinnen (en het voorkomen daarvan). Ook vind je daar een manier om een uitgebreide docstring te schrijven zonder dat het een bende wordt.
We voegen docstrings toe aan fake-conda:
fakeconda.py
import click\n\n\n@click.group()\ndef cmd_group():\n \"\"\"\n Fake the installation and removal of packages in fake conda environments.\n \"\"\"\n pass\n\n\n@cmd_group.command()\n@click.argument(\"package\")\n@click.option(\n \"-c\",\n \"--channel\",\n default=\"defaults\",\n help=\"Additional channel to search for packages.\",\n show_default=True, # show default in help\n)\ndef install(package, channel):\n \"\"\"Install a conda PACKAGE.\n\n PACKAGE is the name of the package.\n \"\"\"\n print(f\"Installing {package} from {channel}...\")\n\n\n@cmd_group.command()\n@click.argument(\"package\")\ndef remove(package):\n \"\"\"Remove a conda PACKAGE.\n\n PACKAGE is the name of the package.\n \"\"\"\n print(f\"Removing {package}...\")\n\nif __name__ == \"__main__\":\n cmd_group()\n
Als we vervolgens de help functie aanroepen zien we de eerste regel van de docstrings verschijnen voor alle subcommando's: (ecpc) > fake_conda --help \nUsage: fake_conda [OPTIONS] COMMAND [ARGS]...\n\nFake the installation and removal of packages in fake conda environments.\n\nOptions:\n --help Show this message and exit.\n\nCommands:\n install Install a conda PACKAGE.\n remove Remove a conda PACKAGE.\n
Daarna kun je uitleg vragen voor de subcommando's waarbij je de hele docstring te zien krijgt:
(ecpc) > fake_conda install --help \nUsage: fake_conda install [OPTIONS] PACKAGE\n\n Install a conda PACKAGE.\n\n PACKAGE is the name of the package.\n\nOptions:\n -c, --channel TEXT Additional channel to search for packages. [default:\n defaults]\n --help Show this message and exit.\n
Smallangle docstrings
opdrachtcodecheck Je gebruikt het commando smallangle --help
en leest de helptekst van de opdracht smallangle. De helptekst bevat zinvolle informatie die je in staat stelt om te begrijpen wat je met de applicatie kan doen. Je ziet dat er twee subcommando's zijn en bekijkt de helptekst van deze commando's met smallangle sin --help
en daarna smallangle tan --help
. Beide helpteksten stellen je in staat op de applicatie te begrijpen en te bedienen. Tevreden test je de applicatie verder uit.
Pseudo-code
\"\"\"Summary containing ARGUMENTs.\n\nARGUMENT description of the argument.\n\"\"\"\n
Testcode (ecpc) > smallangle --help \nUsage: smallangle [OPTIONS] COMMAND [ARGS] ...\n Options: \n --help Show this message and exit.\n Commands:\n Subcommand Summary containing ARGUMENTs.\n\n
Checkpunten:
-
smallangle --help
geeft zinvolle informatie -
smallangle sin --help
geeft zinvolle informatie -
smallangle tan --help
geeft zinvolle informatie
Projecttraject
- smallangle installeren
- smallangle aanpassen
- smallangle docstrings
"},{"location":"cli/#command-line-interface-voor-ons-experiment","title":"Command-line interface voor ons experiment","text":"In hoofdstuk Model-View-Controller heb je pythondaq
uitgesplitst in model, view en controller. Wanneer we een command-line interface gaan bouwen dan is dat de softwarelaag tussen de gebruiker en de rest van de code. De command-line interface is dus een view. Het is helemaal niet gek om meerdere views te hebben, bijvoorbeeld een eenvoudig script zoals run_experiment.py
, een command-line interface en een grafische interface. Hier gaan we ons richten op een command-line interface. We gaan een nieuw bestand cli.py
aanmaken en dat langzaam opbouwen.
Pythondaq: commando's
opdrachtcodecheck Om de command-line interface voor pythondaq te maken ga je in een nieuw bestand src/pythondaq/cli.py
een opzetje maken waar je stap voor stap functionaliteit aan toevoegt. De oude run_experiment.py
maakte eerst een lijst van aangesloten apparaten en daarna werd een scan uitgevoerd. Daarom zet je in cli.py
de subcommando's list
en scan
. En zorg je dat ze voor nu alleen een stukje tekst printen. De gebruiker test de test-subcommmando's met de volgende handelingen. De gebruiker typt in de terminal het commando diode
met daarachter het subcommando list
en ziet een tekst verschijnen: Work in progress, list devices
. De gebruiker test vervolgens het subcommando scan
en ziet de tekst Work in progress, scan LED
verschijnen. ECPC
\u251c\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 src/pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 arduino_device.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 diode_experiment.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 run_experiment.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 cli.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022
Warning
Omdat de naam van een subcommando gelijk is aan de functienaam kan dat voor problemen zorgen wanneer je gereserveerde namen van python wilt gebruiken zoals: import
, return
, lambda
. Of wanneer je de naam van het subcommando graag hetzelfde wilt hebben als een ander pythonfunctie zoals sin
of list
. Een oplossing is om de functienaam aan te passen en de subcommando naam expliciet aan click mee te geven bij command
:
@cmd_group.command(\"import\")\n@click.argument(\"package\")\ndef import_package(package):\n print(f\"import {package}...\")\n
We hebben nu een commando import
aangemaakt \u2014 niet een commando import_package
. Pseudo-code cli.py
# subcommando list\n # print Work in progress, list devices\n# subcommando scan\n # print Work in progress, scan LED\n
Testcode (ecpc) > diode list \nWork in progress, list devices \n
(ecpc) > diode scan \nWork in progress, scan LED\n
Checkpunten:
- De applicatie is aan te roepen met
diode
. - Het subcommando
list
print een stukje tekst. - Het subcommando
scan
print een ander stukje tekst.
Projecttraject:
- Pythondaq: commando's
- Pythondaq:
scan
- Pythondaq: herhaalmetingen
- Pythondaq:
list
- Pythondaq:
info
- Pythondaq: choose device
- Pythondaq: Grafiek
- Pythondaq:
--help
"},{"location":"cli/#het-uitvoeren-van-een-meetserie","title":"Het uitvoeren van een meetserie","text":"We gaan ons eerst richten op het uitvoeren van een volledige meetserie en het tonen van de resultaten daarvan aan de gebruiker.
Info
Bij het opgeven van argumenten en opties voor de spanning kan het belangrijk zijn om te controleren of de spanning \u00fcberhaupt wel een getal is tussen 0 en 3.3 V. Je kunt dit doen door de type
-parameter in @click.argument()
en @click.option()
. Je kunt een Pythontype opgeven (bijvoorbeeld: type=int
of type=float
) en Click heeft speciale types zoals type=click.FloatRange(0, 3.3)
voor een kommagetal tussen 0 en 3.3. Bekijk alle speciale types in de Click documentatie. Als je hiervan gebruik maakt hoef je niet zelf te controleren of de parameters kloppen. Click doet dat voor je.
Pythondaq: scan
opdrachtcodecheck Pas het subcommando scan
aan. De gebruiker test het subcommando scan
met de volgende handelingen. De gebruiker typt het commando diode scan
in de terminal. Aan de hand van de helptekst weet de gebruiker dat het met argumenten of opties mogelijk is om het spanningsbereik (in Volt) aan te passen. Er wordt een meting gestart die binnen het spanningsbereik blijft. De gebruiker ziet dat de stroomsterkte d\u00f3\u00f3r en de spanning \u00f3ver de LED in de terminal worden geprint. De gebruiker start een nieuwe meting en geeft ditmaal met de optie --output FILENAME
een naam voor een CSV-bestand mee. Dit keer worden de metingen ook opgeslagen als CSV-bestand onder de meegegeven bestandsnaam.
Pseudo-code
# subcommando scan with range in Volt and output CSV\n # start scan with range\n # print current and voltage\n # if output:\n # create csv\n
Checkpunten:
- Het programma print een lijst van metingen van de stroomsterkte d\u00f3\u00f3r en de spanning \u00f3ver de LED.
- De gebruiker moet het spanningsbereik (in volt) zelf kunnen opgeven met argumenten of opties.
- De gebruiker kan de metingen opslaan in een CSV-bestand met een optie
--output FILENAME
. - De meting wordt alleen opgeslagen als de optie wordt meegegeven.
Projecttraject:
- Pythondaq: commando's
- Pythondaq:
scan
- Pythondaq: herhaalmetingen
- Pythondaq:
list
- Pythondaq:
info
- Pythondaq: choose device
- Pythondaq: Grafiek
- Pythondaq:
--help
Pythondaq: herhaalmetingen
opdrachtcodecheck Pas het subcommando scan
aan zodat je met een optie het aantal herhaalmetingen kan kiezen. De gebruiker test de optie om het aantal herhaalmetingen te kiezen met de volgende handelingen. Met het subcommando scan
voert de gebruiker een meting uit in het bereik 2.8V tot 3.3V. Met een optie zet de gebruiker het aantal herhaalmetingen op 5. De gebruiker ziet dat het resultaat van de metingen met onzekerheden worden geprint in de terminal. De gebruiker bekijkt de grootte van de onzekerheden en voert nogmaals een scan uit maar dan met 10 metingen en daarna met 20 metingen. De gebruiker ziet dat de onzekerheden afnemen wanneer het aantal metingen toeneem.t
Pseudo-code
# subcommando scan with range in Volt, output CSV and repeat measurements\n # start scan with range and repeat measurements\n # print current, voltage and errors\n # if output:\n # create csv\n
Checkpunten:
- De gebruiker kan het aantal herhaalmetingen met een optie kiezen.
- De herhaalmetingen worden gebruikt om de beste schatting en onzekerheid te berekenen van de stroomsterkte en de spanning.
Projecttraject:
- Pythondaq: commando's
- Pythondaq:
scan
- Pythondaq: herhaalmetingen
- Pythondaq:
list
- Pythondaq:
info
- Pythondaq: choose device
- Pythondaq: Grafiek
- Pythondaq:
--help
Pythondaq: stapgrootte Soms wil je snel een meting uitvoeren over het hele bereik, dan is het handig om minder punten te meten dan 1023 punten. Breid de applicatie uit zodat de gebruiker de stapgrootte kan aanpassen.
"},{"location":"cli/#het-meetinstrument-kiezen","title":"Het meetinstrument kiezen","text":"We kunnen de Arduino benaderen als we de naam weten die de VISA driver er aan heeft toegekend. Helaas kan \u2014 ook afhankelijk van het besturingssysteem \u2014 die naam veranderen als we de Arduino in een andere poort van onze computer steken of soms zelfs als we een andere Arduino op dezelfde poort koppelen. Met het commando list
laten we alle apparaten zien die gevonden worden door de VISA drivers.
Pythondaq: list
opdrachtcodecheck Pas het subcommando list
aan. De gebruiker test het subcommando list
met de volgende handelingen. De gebruiker typt het commando diode list
in de terminal. Daarna verschijnt in de terminal een lijst van aangesloten instrumenten.
(ecpc) > diode list \n('ASRL28::INSTR','ASRL5::INSTR')\n
Checkpunten:
- De gebruiker kan met
diode list
de lijst met aangesloten devices opvragen.
Projecttraject:
- Pythondaq: commando's
- Pythondaq:
scan
- Pythondaq: herhaalmetingen
- Pythondaq:
list
- Pythondaq:
info
- Pythondaq: choose device
- Pythondaq: Grafiek
- Pythondaq:
--help
Pythondaq: info
opdrachtcodecheck Voeg een subcommando info
toe. De gebruiker test het subcommando info
met de volgende handelingen. Eerst heeft de gebruiker met het commando diode list
een lijst van aangesloten devices opgevraagd. De gebruiker wil weten wat de identificatiestring is van het apparaat dat aan een bepaalde poortnaam hangt. De gebruiker geeft daarom de poortnaam mee als argument aan het subcommando info
waarna de identificatiestring van het instrument in de terminal wordt geprint.
identificatiestring
De identificatiestring van onze Arduino was Arduino VISA firmware v1.0.0
. Je moet natuurlijk niet letterlijk deze string copy/pasten, maar de identificatie opvragen van het instrument. Welk firmwarecommando moest je daarvoor ook alweer gebruiken?
Pseudo-code
# subcommando info with device\n # print identificationstring of device\n
Testcode (ecpc) > diode info ASRL28::INSTR \nArduino VISA firmware v1.0.0\n
Checkpunten:
- De identificatiestring is met
diode info DEVICE
op te vragen. - De string is niet direct gecopypaste, maar wordt daadwerkelijk opgevraagd.
Projecttraject:
- Pythondaq: commando's
- Pythondaq:
scan
- Pythondaq: herhaalmetingen
- Pythondaq:
list
- Pythondaq:
info
- Pythondaq: choose device
- Pythondaq: Grafiek
- Pythondaq:
--help
Pythondaq: choose device
opdrachtcodecheck Pas het subcommando scan
aan zodat je kan aangeven met welke Arduino je een meting wilt uitvoeren. De gebruiker test het subcommando scan
met de volgende handelingen. De gebruiker typt het commando diode scan
in de terminal en vergeet daarbij een poortnaam mee te geven. De gebruiker ziet een foutmelding verschijnen want een poortnaam opgeven is verplicht. De gebruiker vraagt met het subcommando list
een lijst van aangesloten instrumenten op. Met het subcommando info
is de gebruiker er achtergekomen wat de naam is van de poort waar de Arduino aanhangt. Vervolgens geeft de gebruiker deze poortnaam mee bij het subcommando scan
om een meting op de (juiste) Arduino uit te laten voeren. Tot slot leent de gebruiker een Arduino van een buurmens. De gebruiker sluit de tweede Arduino aan op de computer. Met list
en info
kijkt de gebruiker wat de poortnaam is van de tweede Arduino. Met het subcommando scan
voert de gebruiker een meting uit en ziet dat het lampje van de tweede Arduino gaat branden en niet het lampje van de eerste Arduino.
(ecpc) > diode scan \nerrorUsage: diode [OPTIONS] DEVICE\nTry 'diode --help' for help.\nError: Missing argument 'DEVICE'.\n
Checkpunten:
- De gebruiker moet een poortnaam meegeven.
- De gekozen device wordt ook daadwerkelijk gebruikt in het model en de controller.
- Als g\u00e9\u00e9n poortnaam wordt opgegeven, krijgt de gebruiker een foutmelding.
Projecttraject:
- Pythondaq: commando's
- Pythondaq:
scan
- Pythondaq: herhaalmetingen
- Pythondaq:
list
- Pythondaq:
info
- Pythondaq: choose device
- Pythondaq: Grafiek
- Pythondaq:
--help
Pythondaq: Grafiek
opdrachtcheck Pas het subcommando scan
aan zodat je met een boolean flag kan aangeven of er wel of niet een grafiek wordt getoond. De gebruiker test het subcommando scan
met de volgende handelingen. De gebruiker start een meting en geeft ook de optie --graph
na afloop ziet de gebruiker een grafiek met daarin de metingen. Daarna start de gebruiker opnieuwe een meting en geeft dit keer de optie --no-graph
mee, na afloopt van de meting ziet de gebruiker geen grafiek verschijnen. Tot slot start de gebruiker een meting en geeft daarbij geen van beide opties (`--graph/--no-graph) wederom ziet de gebruiker na afloop van de meting geen grafiek verschijnen.
Checkpunten:
- De grafiek wordt alleen getoond wanneer
--graph
wordt meegegeven.
Projecttraject:
- Pythondaq: commando's
- Pythondaq:
scan
- Pythondaq: herhaalmetingen
- Pythondaq:
list
- Pythondaq:
info
- Pythondaq: choose device
- Pythondaq: Grafiek
- Pythondaq:
--help
Pythondaq: --help
opdrachtcodecheck Voeg helpteksten toe. De gebruiker test de applicatie diode
met de volgende handelingen. De gebruiker typt diode --help
en bekijkt de helptekst. De gebruiker ziet dat er subcommando's zijn. Met subcommando --help
test de gebruiker de helpteksten een voor een uit. Ook bekijkt de gebruiker de helpteksten over de argumenten en otpies. De helpteksten stellen de gebruiker in staat om de applicatie te begrijpen en te bedienen.
Pseudo-code
\"\"\"Summary containing ARGUMENTs.\n\nARGUMENT description of the argument.\n\"\"\"\n
Testcode (ecpc) > diode --help \nUsage: diode [OPTIONS] COMMAND [ARGS] ...\n Options: \n --help Show this message and exit.\n Commands:\n Subcommand Summary containing ARGUMENTs.\n
Checkpunten:
- De informatie in de helpteksten is voldoende om de applicatie te begrijpen en te bedienen.
-
diode --help
vertelt duidelijk welke subcommando's aanwezig zijn en wat ze doen. - Bij alle subcommando's, is het duidelijk welke opties en argumenten er zijn, wat de standaardwaarden zijn en wat ze doen.
Projecttraject:
- Pythondaq: commando's
- Pythondaq:
scan
- Pythondaq: herhaalmetingen
- Pythondaq:
list
- Pythondaq:
info
- Pythondaq: choose device
- Pythondaq: Grafiek
- Pythondaq:
--help
Pythondaq: list --search
Breid het commando list
uit met een optie --search
waarmee je niet een lijst van alle instrumenten krijgt, maar alleen de instrumenten die de zoekterm bevatten. Dus bijvoorbeeld:
(ecpc) > diode list \nThe following devices are connected to your computer: \nASRL/dev/cu.SOC::INSTR\nASRL/dev/cu.MALS::INSTR\nASRL/dev/cu.AirPodsvanDavid-Wireles-1::INSTR\nASRL/dev/cu.Bluetooth-Incoming-Port::INSTR\nASRL/dev/cu.usbmodem143401::INSTR \n\n(ecpc) > diode list -s usbmodem \nThe following devices match your search string: \nASRL/dev/cu.usbmodem143401::INSTR \n
De lijst met instrumenten kan er op Windows heel anders uitzien. Sterker nog, op Windows is de lijst meestal vrij saai. Maar leen eens heel even een Arduino van iemand anders en je ziet dat er dan twee poorten in de lijst verschijnen.
Pas \u2014 na het uitbreiden van list
\u2014 de commando's scan
en info
aan zodat het niet nodig is om de volledige devicenaam mee te geven, maar alleen een zoekterm.
Op dit punt hebben we de functionaliteit van ons snelle script van het vorige hoofdstuk bereikt. Dit was veel meer werk, maar het is veel flexibeler. Als je wilt meten met een andere Arduino, een ander bereik, of een andere stapgrootte dan type je gewoon een iets ander commando in de terminal. Je hoeft geen scripts meer aan te passen. Als je na een tijdje niet meer precies weet hoe het ook alweer werkte allemaal kun je dat snel weer oppakken door --help
aan te roepen.
Alle subcommando's implementeren
Kijk nog eens terug naar het lijstje subcommando's die je in opdracht Subcommando's bedenken hebt opgeschreven. Heb je alles ge\u00efmplementeerd? Wat zou je willen dat je nog meer kan instellen? Als er tijd over is, kijk dan of dit lukt.
Rich Data-analyse -
argv staat voor: argument vector, een lijst met argumenten\u00a0\u21a9
-
Merk op in de code hieronder: _
is de weggooivariabele in Python. Het gaat ons erom dat de loop een aantal keer doorlopen wordt en we hoeven niets te doen met de loop index.\u00a0\u21a9
-
Zie ook: The Python Standard Library \u21a9
-
Zie voor meer informatie over flags de Click documentatie.\u00a0\u21a9
-
Pallets. Click. URL: https://click.palletsprojects.com/.\u00a0\u21a9
-
Will McGugan. Rich. URL: https://github.com/willmcgugan/rich.\u00a0\u21a9
-
Will McGugan. Rich documentation. URL: https://rich.readthedocs.io/en/latest/.\u00a0\u21a9
"},{"location":"cli/#een-interface-met-stijl","title":"Een interface met stijl","text":"Ook command-line interfaces gaan met hun tijd mee. Vroeger waren ze per definitie zwart/wit en statisch, maar tegenwoordig worden interfaces vaak opgeleukt met kleur, emoji's en bewegende progressbars. Rich6 is een project dat in recordtijd heel populair is geworden. Het bestaat pas sinds november 2019 en heeft precies twee jaar later meer dan 31000 verzameld. Dat is veel \u2014 en de populariteit is sindsdien nog verder toegenomen.
Rich is ontzettend uitgebreid en heeft heel veel mogelijkheden. Voor ons project kan het handig zijn om een progressbar te gebruiken of met Rich een tabel weer te geven. De documentatie7 van Rich is best goed, maar kan lastig zijn om een mooi overzicht te krijgen. Een serie van korte video tutorials kun je vinden bij calmcode. Iedere video duurt maar \u00e9\u00e9n tot twee minuten en laat mooi de mogelijkheden zien. Voor de functies die je wilt gebruiken kun je dan meer informatie opzoeken in de documentatie van Rich zelf.
Rich
Verrijk je interface met Rich. Doe dit naar eigen wens en inzicht.
"},{"location":"cli/#data-analyse","title":"Data-analyse","text":"Door de $I,U$-karakteristiek van de (lichtgevende) diode te analyseren is het mogelijk om de constante van Boltzmann te bepalen. De stoomsterkte door een diode wordt gegeven door de Shockley diodevergelijking. Zie ook hoofdstuk diode.
Lukt het, om binnen de te bepalen onzekerheid, overeenkomst te vinden met de literatuurwaarde? Een LED is helaas geen ideale diode dus dit kan lastig zijn.
Model fitten
Fit het model van Shockley aan je $I,U$-karakteristiek. Welke parameters kun je bepalen? Overleg met je begeleider!\n
"},{"location":"communicatie/","title":"Communicatie met een meetinstrument","text":"Het hart van ieder experiment wordt gevormd door de metingen die worden uitgevoerd. Meetinstrumenten vervullen daarom een belangrijke rol bij het automatiseren van een experiment. De eerste stap die we zullen zetten tijdens het ontwikkelen van een applicatie is het communiceren met ons meetinstrument. We hebben gekozen voor een Arduino Nano 33 IoT,9 een zeer compact stukje elektronica rondom een ARM-microcontroller. Naast het uitvoeren van analoge spanningsmetingen kan dit model ook analoge spanningen afgeven dat voor ons heel nuttig gaat blijken. We hebben, speciaal voor dit vak, een stukje firmware1 ontwikkeld.10
"},{"location":"communicatie/#microcontrollers","title":"Microcontrollers","text":"Computers \u2014 zoals de meesten van ons die kennen \u2014 zijn zeer krachtig en ontworpen om zo flexibel mogelijk te zijn. Ze draaien games, e-mail of rekenen klimaatmodellen door. Ze komen in veel vormen: desktops, laptops, tablets en telefoons. Ze bevatten daarom veel losse componenten: snelle processor (CPU), veel geheugen (RAM), veel permanente opslag (SSD), complexe interfaces (HDMI, USB) en een besturingssysteem waarmee je verschillende programma's kunt opstarten en de computer kunt beheren. Computers zijn behoorlijk prijzig.
Een microcontroller daarentegen is veel eenvoudiger. Ze zijn ontworpen voor een beperkte en specifieke taak. Ze hebben veel verschijningsvormen \u2014 de meeste onherkenbaar. Je vindt microcontrollers in de vaatwasser, de magnetron, een draadloos toetsenbord en auto's (letterlijk tientallen verspreid over de hele auto). Ze hebben dan een beperkte taak: ze reageren op de knopjes op je dashboard om het klimaat te regelen of een raam te openen en ze sturen de kleppen in een verbrandingsmotor aan. Microcontrollers bevatten CPU, RAM en SSD vaak in \u00e9\u00e9n chip en hebben beperkte interfaces (vaak letterlijk losse pinnetjes die je moet verbinden). De CPU is relatief gezien traag en de hoeveelheid geheugen klein. Voor de beperkte taak is dat niet erg. Een besturingssysteem is niet nodig: als je hem aanzet draait hij meteen het enige programma dat ooit ingeladen is (dit heet dan firmware). Microcontrollers zijn goedkoop en daarom ook uitermate geschikt voor hobbyprojecten.
Een Arduino is zo'n microcontroller. Vaak wordt een Arduino vergeleken met een Raspberry Pi \u2014 een andere goedkope computer. Maar een Raspberry Pi is \u00e9cht een computer (en daarmee ook complex). Daarmee is een Raspberry Pi veel veelzijdiger, maar ook duurder en is het complexer om een eenvoudig programma te draaien. Apparatuur zoals frequentiegeneratoren en oscilloscopen hebben vaak een microcontroller ingebouwd, maar soms ook een microcomputer analoog aan een Raspberry Pi. Dat maakt voor ons weinig verschil zolang we maar weten hoe we het instrument kunnen aansturen.
"},{"location":"communicatie/#communicatieprotocol","title":"Communicatieprotocol","text":"Hoe praat je eigenlijk met hardware? Voor fabrikanten zijn er een paar opties:
- Je maakt gebruik van een al bestaand protocol (een bestaande standaard en je schrijft vervolgens documentatie specifiek voor jouw instrument (bijvoorbeeld de VISA-standaard 11, o.a. gebruikt door Tektronix digitale oscilloscopen 12)
- Je schrijft een proprietary2 protocol en een bijbehorende bibliotheek die software-ontwikke-laars moeten gebruiken.3 Voorbeelden zijn instrumenten van National Instruments 13 of de PicoScope digitale oscilloscopen4 14.
De VISA-standaard is veelgebruikt, maar helaas komen proprietary protocollen veel voor. Dat is jammer, want in het laatste geval moet je het doen met de software die geleverd wordt door de fabrikant. Als die jouw besturingssysteem of favoriete programmeertaal niet ondersteunt heb je simpelweg pech.
Wij gaan gebruik maken van de VISA-standaard. VISA staat voor Virtual Instrument Software Architecture en is h\u00e9\u00e9l breed en definieert protocollen om te communiceren via allerlei verouderde computerpoorten en kabels. Hieronder zie je een voorbeeld van verschillende poorten zoals RS232 en GPIB aan de achterkant van een Tektronix TDS210 oscilloscoop.
Bron: Wikimedia Commons.
Maar gelukkig ook via internet en USB, waarvan wij gebruik zullen maken. Onderdeel van VISA is de SCPI standaard 15, wat staat voor Standard Commands for Programmable Instruments. Dit onderdeel definieert een bepaald formaat voor commando's die we naar ons instrument zullen sturen. De lijst met commando's die door de firmware van onze Arduino worden ondersteund is gegeven in de appendix.
"},{"location":"communicatie/#eerste-stappen","title":"Eerste stappen","text":"Waarschuwing
Let op dat je de weerstand van 220 \u03a9 gebruikt! Een te grote weerstand zorgt ervoor dat je nauwelijks iets kunt meten, maar een te kleine weerstand zorgt ervoor dat de stroomsterkte door de Arduino te groot wordt. In dat geval zul je de Arduino onherstelbaar beschadigen. De kleurcodes voor weerstanden vind je in de appendix.
Schakeling bouwen
opdrachtbouwtekeningcheck Je maakt een schakeling om de spanning over en de stroom door een LED te meten. Hiervoor maak je gebruik van een Arduino en een breadboard. Om de stroomsterkte te beperken zet je de LED in serie met een weerstand van 220 \u03a9. Je sluit twee spanningsmeters aan, spanningsmeter 1 staat over de LED en de weerstand samen. Spanningsmeter 2 staat over de weerstand.
Theoretische schakeling
Het circuit zoals je dat zou bouwen met twee losse voltmeters is hieronder weergegeven. De cijfers 0, 1 en 2 bij $U_0$, $U_1$ en $U_2$ zijn de kanalen waarmee de Arduino spanningen kan sturen of uitlezen. Dat wordt zometeen belangrijk.
Praktische schakeling
In het 3D-model5 hieronder is een Arduino Nano 33 IoT op een 400-punt breadboard geschakeld met een LED en een weerstand van 220 \u03a9. In een breadboard zijn in iedere rij alle kolommen A t/m E met elkaar verbonden (zo ook kolommen F t/m J). Draadjes die naast elkaar zijn geprikt zijn dus met elkaar verbonden. De Arduino is geprikt in kolom D t/m H en van rij 1 t/m 15. De pin van de Arduino in rij 4 is verbonden middels het rode draadje (De kleur van de draden is niet belangrijk. Kies altijd draden met een handige lengte.) met het pootje van de LED. De platte zijde in de onderste ring van de LED wordt richting aarde geschakeld. Het ander pootje van de LED is verbonden met de weerstand. De kleurcodes voor weerstanden vind je in de appendix. Van de weerstand loopt een draadje naar de aarde van de Arduino (rij 12, kolom H). Zo kan de Arduino een variabele spanning zetten over de LED en de weerstand.
De pin van de Arduino in rij 5 is verbonden met de LED en meet de spanning over de LED en de weerstand. De pin van de Arduino in rij 6 is verbonden met de weerstand en meet alleen de spanning over weerstand.
3D besturing
Door de linkermuisknop ingedrukt te houden en te slepen kan je de het 3D model draaien, met rechtermuisknop kan je hem verplaatsen en door te scrollen kan je in- en uitzoomen.
Checkpunten:
- Je hebt een weerstand van 220 \u03a9 gebruikt.
- De platte kant in de dikkere ring onderaan de plastic behuizing van de LED staat richting de aarde geschakeld. (Als de pootjes van de LED niet afgeknipt zijn, dan zit het korte pootje aan de platte zijde van de LED)
- De andere kant van de LED is met een draadje verbonden met de 4de rij.
- Er loopt een draadje van de 5de rij naar de LED.
- Er loopt een draadje van de 6de rij naar de weerstand.
- Er loopt een draadje van de andere kant van de weerstand naar de 12de rij (naar dat pinnetje met bovenop een wit vlakje).
Projecttraject:
- Schakeling bouwen
- Pyvisa in terminal
- Pyvisa
list
en open
- Pyvisa
query
- Terminator characters demo
- Pyvisa regeleindes
- Pyvisa LED laten branden
Info
Om met Python via het VISA-protocol te kunnen communiceren met apparaten hebben we specifieke packages nodig. Die gaan we installeren in een conda environment. Voor meer informatie over conda environments zie paragraaf Conda environments.
Environment aanmaken
Open een Anaconda Prompt
die je kunt vinden via de zoekbalk van Windows. Maak de environment en installeer de juiste packages door in te typen:
Terminal
conda create --name pythondaq --channel conda-forge python pyvisa-py\n
Om de conda environment daadwerkelijk te gebruiken moet je die altijd eerst activeren met: Terminalconda activate pythondaq\n
Pyvisa in terminal
opdrachtcodecheck Je sluit de Arduino met een USB-kabel aan op de computer. In een Anaconda Prompt
open je het goede conda environment en open je een pyvisa-shell
met een python backend. Om erachter te komen hoe de pyvisa-shell
werkt type je het commando help
. Je ziet een reeks aan commando's en bekijkt de helptekst van de commando's waarmee je denkt de pyvisa-shell
te kunnen afsluiten. Wanneer je het afsluit commando hebt gevonden sluit je daarmee de pyvisa-shell
af.
Pseudo-code Terminal
# open pyvisa-shell with python backend\n# check help of pyvisa-shell\n# check help of exit command\n# shut down the pyvis-shell\n
Testcode (ecpc) > pyvisa-shell -b py \n\nWelcome to the VISA shell. Type help or ? to list commands. \n(visa)\n\n
(visa) help \n\nDocumented commands (type help <topic>
):\n========================================\nEOF attr close exit help list open query read termchar timeout write\n\n
Checkpunten:
- Na het openen van een
pyvisa-shell
staat er (visa) tussen haakjes. - Als je
help
intypt verschijnt een heel rijtje met commando's. - Als je
help exit
intypt krijg je de hulpvaardige tekst: Exit the shell session.
- Als je de pyvisa-shell met een commando afsluit staat de naam van het conda environment weer tussen haakjes (en niet visa).
Projecttraject:
- Schakeling bouwen
- Pyvisa in terminal
- Pyvisa
list
en open
- Pyvisa
query
- Terminator characters demo
- Pyvisa regeleindes
- Pyvisa LED laten branden
Info
We maken hier gebruik van de optie -b py
, wat staat voor gebruik backend: python. Het kan namelijk dat er, naast pyvisa-py
, ook andere backends, of drivers, ge\u00efnstalleerd staan op het systeem die de VISA-communicatie kunnen verzorgen. Als je bijvoorbeeld LabVIEW ge\u00efnstalleerd hebt, dan heb je de drivers van National Instruments. Maar de verschillende backends geven de aangesloten apparaten andere namen. Ook ondersteunen niet alle drivers alle types apparaten en moet je ze apart downloaden en installeren. Daarom maken we liever gebruik van de beschikbare Python drivers.
Pyvisa list
en open
opdrachtcodecheck Je bekijkt het lijstje met aangesloten apparaten door in de pyvisa-shell
het commando list
te typen. Je haalt de USB-kabel waarmee de Arduino aan de computer is aangesloten uit de computer en vraagt nogmaals de lijst met aangesloten apparaten op. Nu weet je welke poort de Arduino is. Je bekijkt de help tekst van het commando open
, daarna open je de communicatie met de Arduino.
Pseudo-code Terminal
# open pyvisa-shell\n# list\n# help open\n# open Arduino\n
Testcode (visa) list \n( 0) ASRL3::INSTR\n( 1) ASRL5::INSTR\n( 2) ASRL28::INSTR\n
(visa) help open \nOpen resource by number, resource name or alias: open 3\n
Checkpunten:
- Na het commando
list
verschijnt er een lijst met een of meerdere apparaten. - Als de Arduino niet op de computer is aangesloten is er een apparaat uit het lijstje verdwenen.
- Als je de Arduino opent verschijnt de tekst:
ASRL?::INSTR has been opened.\nYou can talk to the device using \"write\", \"read\" or \"query\".\nThe default end of message is added to each message.\n
Projecttraject:
- Schakeling bouwen
- Pyvisa in terminal
- Pyvisa
list
en open
- Pyvisa
query
- Terminator characters demo
- Pyvisa regeleindes
- Pyvisa LED laten branden
Pyvisa query
opdrachtcodecheck Je stuurt een commando naar de Arduino met query COMMANDO
. In de documentatie van de firmware heb je het commando opgezocht om de identificatiestring uit te lezen. Nadat je dit commando naar de Arduino stuurt krijg je een error. Je leest de handleiding rustig verder om erachter te komen hoe je dit moet oplossen.
Pseudo-code Terminal
# send query to get identificationstring \n
Testcode (open) query gappie \nResponse: ERROR: UNKNOWN COMMAND gappie\n
Checkpunten:
- Je hebt het woord
query
goed geschreven en met kleine letters. - Na het commando
query
volgt een spatie. - Na de spatie staat het commando om de identificatiestring uit te lezen, met hoofdletters (en dat
*
en dat ?
horen er ook bij!). - Als je het commando verstuurt hebt verschijnt er een error
Response: ERROR: UNKNOWN COMMAND .....
Projecttraject:
- Schakeling bouwen
- Pyvisa in terminal
- Pyvisa
list
en open
- Pyvisa
query
- Terminator characters demo
- Pyvisa regeleindes
- Pyvisa LED laten branden
Niet helemaal wat we hadden gehoopt! Als je goed kijkt in de documentatie van de firmware dan zie je dat er bepaalde terminator characters nodig zijn. Dit zijn karakters die gebruikt worden om het einde van een commando te markeren. Het is, zogezegd, een enter aan het eind van een zin. Dit mag je heel letterlijk nemen. Oude printers voor computeruitvoer gebruikten een carriage return (CR) om de wagen met papier (typemachine) of de printerkop weer aan het begin van een regel te plaatsen en een line feed (LF) om het papier een regel verder te schuiven. Nog steeds is het zo dat in tekstbestanden deze karakters gebruikt worden om een nieuwe regel aan te geven. Jammer maar helaas, verschillende besturingssystemen hebben verschillende conventies. Windows gebruikt nog steeds allebei: een combinatie van carriage return + line feed (CRLF).
Carriage Return Line Feed: Typewriter Demonstration
Maar MacOS/Linux/Unix gebruiken enkel een line feed (LF), want hoeveel meer heb je nodig? Af en toe is dat lastig, vooral wanneer er elektronica in het spel is want dan willen de regeleindes voor schrijven en lezen nog wel eens verschillend zijn.6
Terminator characters demo
opdrachtcheck Je vraagt je misschien af wat het betekent dat er bij het schrijven en lezen regeleindes gebruikt worden. Daarom open je de Termchar-demo. Je gaat naar de Basic tab, daar zie je twee inputvelden voor de client (dat ben jij) en de server (dat is de Arduino).
Je schrijft een commando measure_voltage
naar de Arduino (druk op Write). In het Input veld van de Arduino verschijnt jouw commando maar het staat nog niet in de Application Log, het is dus nog niet door de Arduino verwerkt. Want de Read Termination Characters van de Arduino zijn \\n
(LF), die gaat dus pas lezen als die tekens zijn verstuurd. Je verstuurt \\n
en ziet dat het commando wordt verwerkt en jij een antwoord krijgt.
Steeds \\n
handmatig versturen is onhandig daarom voer je bij de Client als Write Termination Characters \\n
in. Je verstuurt nog een commando measure_current
, drukt op Write en ziet dat het bericht direct door de Arduino wordt verwerkt en een antwoord terugstuurt.
In het Input veld van de Client staan twee antwoorden van de Arduino, als je nu op Read drukt blijven de termination characters in de antwoorden staan en moet je ze handmatig uit elkaar gaan halen. Dat is niet handig, daarom vul je bij de Read Termination Characters van de Client \\r\\n
(CRLF) in. Daarna druk je op Read en merk je dat de twee antwoorden apart uitgelezen worden, super handig!
Checkpunten:
- De Client Write Termination Characters is ingesteld op
\\n
. - De Client Read Termination Characters is ingesteld op
\\r\\n
. - Bij het versturen van een bericht naar de server wordt deze verwerkt en krijgt de client een antwoord terug.
- Bij het lezen van het antwoord door de client komen geen termination characters in de Application Log te staan.
Projecttraject
- Schakeling bouwen
- Pyvisa in terminal
- Pyvisa
list
en open
- Pyvisa
query
- Terminator characters demo
- Pyvisa regeleindes
- Pyvisa LED laten branden
Input buffer en timeout Ga opnieuw naar de Termchar-demo en lees de laatste stappen van de Introduction tab. Open de Advanced tab en voer de stappen uit.
We gaan nu het gebruik van de karakters instellen in Pyvisa:
Pyvisa regeleindes
opdrachtcodecheck Je gaat weer terug naar de Anaconda prompt. Je gebruikt het commando termchar
om de regeleindes in te stellen. Om erachter te komen hoe je dit moet instellen vraag je de helptekst op met help termchar
. Je vraagt eerst de huidige regeleinde instellingen op en ziet dat deze niet goed staan. Daarna stel je de read in op CRLF en de write op LF. Je bekijkt nog een keer de regeleinde instellingen om te controleren of ze nu wel goed staan. Je gaat terug naar de opdracht Pyvisa query
en krijgt een response in plaats van een error.
\\r\\n en CRLF
Bij de Termination Characters demo maakten we gebruik van \\r\\n
dat is de programmeertaal equivalent van CRLF
.
Pseudo-code Terminal
# get help text of termchar\n# what are the termchar settings?\n# make read = CRLF and write = LF\n# check if termchar settings are correct\n# send query to get identificationstring\n
Testcode (open) help termchar \nGet or set termination character for resource in use.\n<termchar>
can be one of: CR, LF, CRLF, NUL or None.\nNone is used to disable termination character\nGet termination character:\n termchar\nSet termination character read or read+write:\n termchar <termchar>
[<termchar>
]\n
Checkpunten:
- De regeleindes zijn ingesteld met het commando
termchar
daarna een spatie, vervolgens de karakters voor de read thermchar, dan weer een spatie en daarachter de karakters voor de write termchar. - De read regeleindes staan ingesteld op CRLF.
- De write regeleinds staan ingesteld op LF.
- Als je met het commando
termchar
de instellingen van de regeleindes opvraag staat er:
Termchar read: CRLF write: LF\nuse CR, LF, CRLF, NUL or None to set termchar\n
- Als je met behulp van
query
het commando om de identificatiestring uit te lezen naar de Arduino verstuurt verschijnt de tekst:
Response: Arduino VISA firmware v1.0.0\n
Projecttraject:
- Schakeling bouwen
- Pyvisa in terminal
- Pyvisa
list
en open
- Pyvisa
query
- Terminator characters demo
- Pyvisa regeleindes
- Pyvisa LED laten branden
Onzichtbare regeleindes
Omdat de Arduino nu weet wanneer het commando voorbij is (door de LF aan het eind van de zin) krijgen we antwoord! Dat antwoord heeft dan juist weer een CRLF aan het eind dus pyvisa-shell
weet wanneer het kan stoppen met luisteren en print het antwoord op het scherm. De karakters CRLF en LF zelf blijven onzichtbaar voor ons.
Pyvisa LED laten branden
opdrachtcodecheck Je zoekt in de documentatie van de firmware op hoe je een spanning op het uitvoerkanaal zet. Je leest dat er een maximale waarde is voor de spanning en zet deze waarde op het uitvoerkanaal. Je ziet dat het LEDje brandt en er verschijnt een glimlach op je gezicht. Je bent benieuwd naar wat er gebeurt als je over de maximale spanning heen gaat en zet de maximale waarde + 1 op het uitvoerkanaal. Je denkt na over een verklaring voor wat je ziet gebeuren. Je weet dat een LED een drempelspanning nodig heeft om te branden, je vult een paar waardes in tussen de minimale en maximale waarde om erachter te komen wat deze drempelspanning is.
Pseudo-code Terminal
# set max voltage\n# set max voltage + 1\n# set threshold voltage\n
Testcode Zie documentatie van de firmware. Checkpunten:
- Je stuurt een commando naar de Arduino met behulp van
query
. - Je hebt
query
goed geschreven en met kleine letters. - Je hebt het commando om een spanning op het uitvoerkanaal te zetten geschreven met hoofdletters.
- Je zet een spanning op het uitvoerkanaal
0
. - Achter het kanaalnummer staat een spatie.
- Na de spatie staat een geheel getal tussen de 0 en de 1023.
- Als je de waarde 828 naar het uitvoerkanaal 0 stuurt gaat de LED branden.
Projecttraject:
- Schakeling bouwen
- Pyvisa in terminal
- Pyvisa
list
en open
- Pyvisa
query
- Terminator characters demo
- Pyvisa regeleindes
- Pyvisa LED laten branden
"},{"location":"communicatie/#een-eenvoudig-script","title":"Een eenvoudig script","text":"We hebben via de shell contact gelegd met de hardware. Nu wordt het tijd om, met de documentatie16 in de aanslag, hetzelfde vanuit Python te doen. Als je met een nieuw project begint is het helemaal geen gek idee om een kort script te schrijven waarin je wat dingen uitprobeert. Als alles lijkt te werken kun je het netjes gaan maken en gaan uitbreiden. We beginnen hier met een eenvoudig script en zullen dat daarna gaan verfijnen.
We lopen het voorbeeldscript eerst regel voor regel door en geven het volledige script aan het eind. Allereerst importeren we de pyvisa
-bibliotheek met
import pyvisa\n
Binnen pyvisa wordt alles geregeld met behulp van een Resource Manager. Die krijgen we met rm = pyvisa.ResourceManager(\"@py\")\n
Die kunnen we bijvoorbeeld gebruiken om een lijst van alle beschikbare poorten te krijgen: ports = rm.list_resources()\n\n# Bijvoorbeeld: (\"ASRL28::INSTR\",)\n
Om nu daadwerkelijk verbinding te gaan maken met de Arduino moeten we die openen. Daarvoor geven we de poortnaam op en vertellen we meteen wat de instellingen moeten zijn voor de regeleindes bij het lezen (CRLF, \"\\r\\n\"
) en het schrijven (LF, \"\\n\"
): device = rm.open_resource(\n \"ASRL28::INSTR\", read_termination=\"\\r\\n\", write_termination=\"\\n\"\n)\n
Ten slotte sturen we een query naar de Arduino: device.query(\"*IDN?\")\n
Het volledige script \u2014 met een paar print
-statements \u2014 ziet er dan als volgt uit: test_arduino.py import pyvisa\n\nrm = pyvisa.ResourceManager(\"@py\")\nports = rm.list_resources()\nprint(ports)\n\ndevice = rm.open_resource(\n \"ASRL28::INSTR\", read_termination=\"\\r\\n\", write_termination=\"\\n\"\n)\nidentification = device.query(\"*IDN?\")\nprint(identification)\n
\n(ecpc) > python test_arduino.py\n('ASRL28::INSTR',)\nArduino VISA firmware v1.0.0\n
De output van het script is afhankelijk van het systeem en het aantal apparaten dat verbonden is.
Vergelijk pyvisa-shell met Python code
Je hebt nu precies hetzelfde gedaan in Python als in de pyvisa shell. Vergelijk de verschillende stappen hieronder met elkaar door met de muis over de tekst heen te gaan.
\nPS> pyvisa-shell -b py\n\nWelcome to the VISA shell. Type help or ? to list commands.\n\n(visa) list\n( 0) ASRL3::INSTR\n( 1) ASRL5::INSTR\n( 2) ASRL28::INSTR\n(visa) open 2\nASRL28::INSTR has been opened.\nYou can talk to the device using \"write\", \"read\" or \"query\".\nThe default end of message is added to each message.\n(open) termchar CRLF LF\nDone\n(open) query *IDN?\nResponse: Arduino VISA firmware v1.0.0\n
\nimport pyvisa\n\nrm = pyvisa.ResourceManager(\"@py\")\nports = rm.list_resources()\nprint(ports)\n\ndevice = rm.open_resource(\n \"ASRL28::INSTR\", \n read_termination=\"\\r\\n\", \n write_termination=\"\\n\"\n)\nidentification = device.query(\"*IDN?\")\nprint(identification)
Pyvisa in pythonscript
opdrachtcodecheck Je gaat de gegeven Python code testen daarom open je in Visual Studio Code de map ECPC
en maakt een bestand test_arduino.py
aan. Je kopieert de Python code in het bestand. Je ziet dat de code gebruikt maakt van de package pyvisa
daarom selecteer je de environment die je bij opdracht Environment aanmaken hebt gemaakt. Je slaat het bestand op en runt het bestand. ECPC
\u251c\u2500\u2500test_arduino.py
\u2514\u2500\u2500\u2022\u2022\u2022
could not open port 'COM28': FileNotFoundError
Krijg je een FileNotFoundError
? Dan kan het zijn dat het script een poort probeert te openen die bij jou een andere naam heeft. Probeer met het lijstje instrumenten te raden welke de Arduino is en pas het script aan totdat het werkt.7
could not open port 'COM3': PermissionError
Krijg je een PermissionError
? Dan heb je vast nog een terminal openstaan waarin pyvisa-shell
actief is.
Pseudo-code
# import pyvisa package\n# create resourcemanager\n# get list resources\n# open device\n# send query\n
Testcode test_arduino.py ...\n\nprint(ports)\nprint(identification)\n
\n(ecpc) > python test_arduino.py\n('ASRL28::INSTR',)\nArduino VISA firmware v1.0.0\n
Checkpunten:
- Je hebt het juiste conda environment geselecteerd (zie ook paragraaf Conda environments).
- Je hebt het de juiste naam van de Arduino in het script aangepast (als jouw Arduino niet 'ASRL28::INSTR' heet).
- Je hebt alle terminals (ook Anaconda Prompt) gesloten waarin communicatie met de Arduino open stond.
Projecttraject:
- Pyvisa in pythonscript
- LED laten branden
- flashingLED
- Van bestaande map repository maken
- Commit
- Push en pull
LED laten branden
opdrachtcodecheck Omdat je straks de IU-karakteristiek van de LED wilt gaan bepalen ga je een reeks aan spanningen naar de LED sturen waardoor de LED gaat branden. Je maakt daarvoor een bestand test_LED.py
aan in de map ECPC
. Je schrijft eerst een regel code waarmee je een commando naar de Arduino stuurt waardoor de LED gaat branden. Daarna schrijf je de code om zodat de spanning oploopt van de minimale waarde tot aan de maximale waarde. ECPC
\u251c\u2500\u2500test_arduino.py
\u251c\u2500\u2500test_LED.py
\u2514\u2500\u2500\u2022\u2022\u2022
f-strings
Het sturen van commando's naar de Arduino waar een variabele spanning in staat gaat gemakkelijk met f-strings. Voor meer informatie zie de paragraaf f-strings.
Pseudo-code
# import pyvisa package\n# create resourcemanager\n# get list resources\n# open device\n#\n# for value in min to max\n# send query set output channel to value\n
Testcode test_LED.py ...\nfinal_value = device.query(\"OUT:CH0?\")\nprint(final_value)\n
\n(ecpc) > python test_LED.py\n1023\n
Checkpunten:
- Je hebt het juiste conda environment geselecteerd (zie ook paragraaf Conda environments).
- Je hebt het de juiste naam van de Arduino in het script aangepast (als jouw Arduino niet 'ASRL28::INSTR' heet).
- Je hebt alle terminals (ook Anaconda Prompt) gesloten waarin communicatie met de Arduino open stond.
- Je laat de spanning oplopen van de minimale tot de maximale waarde.
- Als je goed kijkt zie je de LED vertraagd oplichten.
- Als je de waarde op kanaal 0 opvraagd aan het eind van de reeks met
OUT:CH0?
krijg je 1023
terug.
Projecttraject:
- Pyvisa in pythonscript
- LED laten branden
- flashingLED
- Van bestaande map repository maken
- Commit
- Push en pull
flashingLED
opdrachtcodecheck Om bekend te raken met de code maak je een nieuw bestand flashingLED.py
aan in de map ECPC
waarin je code schrijft die de LED in een regelmatig tempo laat knipperen. ECPC
\u251c\u2500\u2500test_arduino.py
\u251c\u2500\u2500test_LED.py
\u251c\u2500\u2500flashingLED.py
\u2514\u2500\u2500\u2022\u2022\u2022
Info
Je kan hiervoor gebruik maken van de module time die standaard met Python meekomt8. Met de functie sleep()
kun je de executie van de volgende regel in het script met een aantal seconden uitstellen.
import time\n# wait 28 second\ntime.sleep(28)\n
Pseudo-code
# import pyvisa package\n# create resourcemanager\n# get list resources\n# open device\n#\n# repeat:\n# send query set output channel to max\n# wait\n# send query set output channel to min\n# wait\n
Testvoorbeeld Checkpunten:
- De LED staat een tijd aan en een tijd uit.
- Het aan en uitgaan van de LED herhaald zich enkele keren.
Projecttraject:
- Pyvisa in pythonscript
- LED laten branden
- flashingLED
- Van bestaande map repository maken
- Commit
- Push en pull
Meer knipperritmes
Breid het bestand flashingLED.py
uit met meer knipperritmes, bijvoorbeeld:
- Maak een SOS light \u2014 een lamp die in morsecode het signaal SOS uitzendt.
- Maak een breathing light \u2014 een lamp die langzaam aan en uit gaat gevolgd door een pauze in het tempo dat iemand in- en uitademt.
- Maak een heartbeat light \u2014 een lamp die twee keer kort na elkaar flitst gevolgd door een pauze in het tempo van een hartslag.
- Bedenk je eigen knipperritme.
-
Firmware is software die in hardware is geprogrammeerd. Bijvoorbeeld het computerprogramma dat ervoor zorgt dat je magnetron reageert op de knoppen en je eten verwarmd.\u00a0\u21a9
-
Proprietary betekent dat een bedrijf of individu exclusieve de rechten heeft over het protocol of de software en anderen geen toegang geeft tot de details.\u00a0\u21a9
-
Niet zelden zijn dergelijke bibliotheken maar op een paar besturingssystemen beschikbaar als driver. Gebruik je MacOS in plaats van Windows en het wordt alleen op Windows ondersteund? Dan kun je je dure meetinstrument dus niet gebruiken totdat je overstapt.\u00a0\u21a9
-
Die overigens op vrijwel alle platforms en voor veel programmeertalen bibliotheken leveren.\u00a0\u21a9
-
Dit model bevat twee 3D modellen die zijn gecre\u00eberd door Lara Sophie Sch\u00fctt en AppliedSBC en zijn gedeeld onder respectievelijk een CC-BY en CC-BY-SA licentie. De originele modellen zijn te vinden via [CC0] Set of Electronic Components en Arduino Nano 33 IoT. De modellen zijn samengevoegd en Voorzien van een Arduino texture een aangepaste LED texture en draden. Dit 3D model heeft een CC-BY-SA licentie.\u00a0\u21a9
-
De regeleindes voor de Arduinofirmware zijn verschillend voor lezen en schrijven. Dit heeft een oninteressante reden: bij het ontvangen van commando's is het makkelijk om alles te lezen totdat je \u00e9\u00e9n bepaald karakter (LF) tegenkomt. Bij het schrijven gebruikt de standaard println
-functie een Windows-stijl regeleinde (CRLF).\u00a0\u21a9
-
Tip: als je de Arduino loshaalt en weer aansluit is het de nieuwe regel in het lijstje.\u00a0\u21a9
-
Zie ook: The Python Standard Library \u21a9
-
Arduino AG. Arduino nano 33 iot. URL: https://store.arduino.cc/arduino-nano-33-iot.\u00a0\u21a9
-
David B.R.A. Fokkema. Arduino visa firmware. 2020. URL: https://github.com/davidfokkema/arduino-visa-firmware.\u00a0\u21a9
-
IVI Foundation. Vpp-4.3: the visa library. 2018. URL: https://www.ivifoundation.org/downloads/Architecture Specifications/IVIspecstopost10-22-2018/vpp43_2018-10-19.pdf.\u00a0\u21a9
-
Tektronix, Inc. URL: https://www.tek.com/.\u00a0\u21a9
-
National Instruments Corp. URL: https://www.ni.com/.\u00a0\u21a9
-
Pico Technology Limited. URL: https://www.picotech.com/.\u00a0\u21a9
-
SCPI Consortium. Standard commands for programmable instruments (scpi). 1999. URL: https://www.ivifoundation.org/docs/scpi-99.pdf.\u00a0\u21a9
-
PyVISA Authors. Pyvisa: control your instruments with python. URL: https://pyvisa.readthedocs.io/en/latest/.\u00a0\u21a9
"},{"location":"diodes/","title":"De diode: een p-n-overgang","text":"In het introductie-experiment meten we de $I,U$-karakteristiek van een LED, een lichtgevende diode. In dit hoofdstuk gaan we iets dieper in op het aspect diode.
Uiteindelijk is een diode een eenrichtingsweg voor stroom. Dat wil zeggen: er kan maar in \u00e9\u00e9n richting stroom door de diode lopen. De diode heeft een lage weerstand, maar als de polariteit wordt omgedraaid dan is de weerstand plots zeer groot en loopt er nauwelijks stroom. Diodes kunnen bijvoorbeeld gebruikt worden als gelijkrichter waarbij een wisselspanning met een stelsel diodes wordt omgezet in een gelijkspanning. Ook zijn er dus diodes die licht geven. Het fysisch principe achter een diode zorgt ervoor dat er, in sommige gevallen, zeer energiezuinig licht geproduceerd kan worden.
"},{"location":"diodes/#halfgeleiders","title":"Halfgeleiders","text":"Metalen zijn geleiders. Ze hebben in de buitenste schil \u00e9\u00e9n of enkele valentie-elektronen die vrij kunnen bewegen in het kristalrooster. Een potentiaalverschil (veroorzaakt door bijvoorbeeld een batterij) zorgt voor een stroom van elektronen. Bij een isolator zitten alle elektronen vast in het rooster. Bij een halfgeleider is dat eigenlijk ook zo \u2014 de valentie-elektronen zijn nodig voor de bindingen tussen de atomen \u2014 maar door trillingen in het rooster is het relatief eenvoudig om af en toe een elektron-gat-paar te cre\u00ebren: een elektron ontsnapt en kan door het kristalrooster bewegen, maar de achtergebleven atomen missen nu een bindingselektron (het gat). Naburige elektronen kunnen heel eenvoudig in dit gat springen, maar laten dan weer een gat achter. Op deze manier kunnen gaten ook vrij door het rooster reizen. Een gat heeft effectief een positieve lading.
"},{"location":"diodes/#p-type-en-n-type-halfgeleiders","title":"p-type en n-type halfgeleiders","text":"Halfgeleiders kunnen gedoteerd worden met andere stoffen. Feitelijk worden onzuiverheden in het kristalrooster ingebracht. Als je elementen uit de stikstofgroep (vijf valentie-elektronen) toevoegt aan de halfgeleider silicium (vier valentie-elektronen) dan worden de vreemde atomen in het kristalrooster gedwongen tot het aangaan van vier covalente bindingen met naburige siliciumatomen. Hierdoor blijft een elektron over. Dit elektron kan vrij door het rooster bewegen en op deze manier zijn er dus veel extra vrije elektronen aan het materiaal toegevoegd. Dit is een n-type halfgeleider omdat de ladingsdragers negatief geladen zijn.
Op eenzelfde manier kunnen elementen uit de boorgroep (drie valentie-elektronen) worden toegevoegd aan silicium. Het is alleen niet mogelijk om vier covalente bindingen aan te gaan en er ontstaat plaatselijk een gat, een positieve ladingsdrager. Dit wordt dus een p-type halfgeleider genoemd. Hoewel gaten zich vrij door het rooster kunnen bewegen gaat dit wel trager dan bij elektronen.
Merk op dat hoewel n-type en p-type halfgeleiders beschikken over respectievelijk negatieve en positieve ladingsdragers ze als geheel neutraal zijn. De onzuiverheden in het rooster blijven wel achter als ion als er geen vrije ladingsdrager in de buurt is. Immers, stikstof is neutraal met vijf valentie-elektronen dus als het vijfde elektron vrij door het rooster is gaan bewegen blijft een positief ion achter. Een booratoom blijft achter als een negatief ion als het gat vertrokken is (en een extra elektron de binding heeft opgevuld).
"},{"location":"diodes/#p-n-overgangen","title":"p-n-overgangen","text":"Wanneer een p-type en een n-type halfgeleider elektrisch contact maken1 dan kunnen de elektronen en de gaten elkaar in het midden tegenkomen. Immers, door diffusie verplaatsen de elektronen en gaten zich willekeurig door het materiaal.
Elektronen (zwart) en gaten (wit) zijn de vrije ladingsdragers in respectievelijk n-type en p-type halfgeleiders. Wanneer beide typen elektrisch contact maken kunnen elektronen en gaten de grenslaag oversteken en recombineren.
Het extra elektron kan een tekort aanvullen en alle naburige atomen kunnen zo vier covalente bindingen aangaan. Het elektron en het gat heffen elkaar dus op recombinatie. Dit is energetisch voordelig, maar er ontstaat in het midden een sperlaag2 waar geen vrije ladingsdragers meer aanwezig zijn. Dit betekent echter wel dat de onzuiverheden (ionen!) ervoor zorgen dat het materiaal niet langer neutraal is, maar elektrisch geladen. Waar de elektronen verdwenen zijn blijven positieve ionen achter en omgekeerd voor de gaten. Er ontstaat zo een elektrisch veld dat de elektronen en gaten tegenhoudt. Buiten de sperlaag is er geen elektrisch veld, net als bij een condensator.3 Elektronen en gaten kunnen niet langer de sperlaag oversteken.
Na recombinatie van elektronen (zwart) en gaten (wit) ontstaat er een sperlaag waar geen vrije ladingsdragers meer aanwezig zijn. De gedoteerde atomen vormen ionen in het rooster en er onstaat een postief geladen gebied en een negatief geladen gebied. Buiten de sperlaag is geen veld aanwezig, net als bij een condensator. Het resulterende elektrisch veld remt eerst en stopt uiteindelijk de diffusie van de elektronen en gaten. Er ontstaat een evenwicht waarbij vrije ladingsdragers de grenslaag niet meer kunnen oversteken.
"},{"location":"diodes/#sperrichting","title":"Sperrichting","text":"Wanneer een diode wordt verbonden met een elektrisch circuit is de richting van het potentiaalverschil van belang. Wanneer het p-type halfgeleider verbonden wordt met de negatieve pool en het n-type met de positieve pool dan ontstaat er een elektrisch veld in de zelfde richting als dat van de sperlaag. Hierdoor wordt het veld sterker en zullen vrije ladingsdragers de sperlaag zeker niet kunnen oversteken. De diode staat in sperrichting en er zal nauwelijks4 stroom lopen. Een andere manier om dit in te zien is dat de gaten naar de negatieve pool worden getrokken en het gebied rond de sperlaag verlaten. Daar blijven nu dus nog meer negatieve ionen achter; de sperlaag wordt dikker. Idem aan de andere zijde van de sperlaag.
"},{"location":"diodes/#doorlaatrichting","title":"Doorlaatrichting","text":"Wanneer we de polariteit omdraaien en de p-typezijde verbinden aan de positieve pool en de n-typezijde aan de negatieve pool dan ontstaat er een elektrisch veld van p-type naar n-type, tegengesteld aan het sperveld. Wanneer het potentiaalverschil op de diode \u2014 ten gevolge van een externe spanningsbron \u2014 lager is dan het potentiaalverschil over de sperlaag, dan zal er nog steeds geen stroom kunnen lopen. De sperlaag wordt echter wel dunner. Wanneer het externe potentiaalverschil groter is, dan keert het netto elektrisch veld in de sperlaag om en kunnen gaten en elektronen de grenslaag oversteken. Er loopt een stroom, maar wel anders dan in een geleider. De gaten en elektronen recombineren in het gebied van de grenslaag, terwijl er aan de metaal/halfgeleider grenzen nieuwe elektron-gatparen worden gevormd. Er stromen aan de zijde van de n-type halfgeleider dus continu elektronen vanuit het metaal naar de grenslaag en aan de zijde van de p-type halfgeleider stromen continu gaten van het metaal naar de grenslaag. Aan de grenslaag metaal/p-type halfgeleider verlaten elektronen de halfgeleider (en zo ontstaan de gaten). Voor de vrije ladingsdragers is de weerstand in de halfgeleider vrij laag waardoor de stroomsterkte flink kan oplopen. Een diode geleidt dus in de doorlaatrichting, maar pas boven een minimale doorlaatspanning.
"},{"location":"diodes/#lichtgevende-diode","title":"Lichtgevende diode","text":"Wanneer een elektron en een gat elkaar tegenkomen is het energetisch gunstiger om te recombineren. Het elektron bindt zich aan de atomen in het rooster. Hierbij komt dus energie vrij. Meestal is dit in de vorm van roostertrillingen (warmte). Wanneer de materialen goed gekozen worden is het mogelijk om het energieverlies niet dominant via roostertrillingen te laten verlopen, maar via emissie van licht. Bij een doorzichtige halfgeleider kan het licht de grenslaag verlaten en uitgestraald worden. De LEDs die wij gebruiken bestaan uit een heel klein stukje halfgeleidermateriaal in een kegelvormige reflector om het licht naar boven te richten. Het geheel is ter bescherming in kunststof gegoten en de bolvorm zorgt voor een lenseffect om zoveel mogelijk licht in \u00e9\u00e9n richting uit te stralen.
"},{"location":"diodes/#de-iu-karakteristiek-van-een-diode","title":"De I,U-karakteristiek van een diode","text":"Shockley, \u00e9\u00e9n van de uitvinders van de transistor, ontwikkelde een model voor p-n-overgangen. Volgens dat model7 wordt de stroomsterkte gegeven door
\\begin{equation} I = I_\\mathrm{S} \\left(e^\\frac{V_\\mathrm{D}}{nV_\\mathrm{T}} - 1 \\right), \\end{equation} met $I$ de diodestroom, $I_\\mathrm{S}$ de diodelekstroom, $V_\\mathrm{D}$ de spanning over de diode, $n$ de kwaliteitsfactor van de diode en $V_\\mathrm{T}$ de thermal voltage gegeven door \\begin{equation} V_\\mathrm{T} = \\frac{kT}{q}, \\end{equation} met $k$ de constante van Boltzmann, $T$ de temperatuur van het materiaal en $q$ de elementaire lading. De diodelekstroom is de stroomsterkte ten gevolge van de minderheidsladingsdragers5 \u2014 het kleine aantal vrije elektronen in p-type halfgeleider en het kleine aantal gaten in n-type halfgeleider.
De stroom door een diode ten gevolge van de spanning over de diode.
-
In de praktijk worden er geen twee losse halfgeleiders aan elkaar verbonden maar wordt een enkel siliciumkristal zeer selectief plaatselijk verontreinigd: de ene helft om een p-type te maken, de andere helft om een n-type te maken.\u00a0\u21a9
-
Engels: depletion zone.\u00a0\u21a9
-
Zie o.a. Giancoli6 voor een beschrijving van het veld van twee vlakke en tegengesteld geladen schijven.\u00a0\u21a9
-
Bedenk dat in een niet-gedoteerde halfgeleider door roostertrillingen elektron-gat-paren worden gevormd waardoor de halfgeleider een beetje geleidt. Dit gebeurt \u00f3\u00f3k in een gedoteerde halfgeleider. In n-type komen dus ook (weinig) gaten voor, en in p-type ook (weinig) elektronen. Deze kunnen de sperlaag wel oversteken en er zal dus toch een zeer kleine stroom kunnen lopen.\u00a0\u21a9
-
Engels: minority charge carriers. \u21a9
-
D.C. Giancoli. Physics for Scientists and Engineers with Modern Physics. Pearson Education, 2008. ISBN 9780131495081. URL: https://books.google.nl/books?id=xz-UEdtRmzkC.\u00a0\u21a9
-
W. Shockley. The theory of p-n junctions in semiconductors and p-n junction transistors. Bell System Technical Journal, 28(3):435\u2013489, 1949. URL: https://onlinelibrary.wiley.com/doi/abs/10.1002/j.1538-7305.1949.tb03645.x, arXiv:https://onlinelibrary.wiley.com/doi/pdf/10.1002/j.1538-7305.1949.tb03645.x, doi:10.1002/j.1538-7305.1949.tb03645.x.\u00a0\u21a9
"},{"location":"docstrings/","title":"Docstrings","text":"Documentatie is vaak een onderschoven kindje, maar is ontzettend belangrijk. Als je zelf informatie opzoekt over bijvoorbeeld een voor jou onbekende Pythonbibliotheek dan vind je het heel fijn als er een duidelijke tutorial is. Als je code schrijft die ook door andere mensen gebruikt moet worden is documentatie nodig. Als de code langer mee moet gaan dan zeg een paar weken, dan helemaal. Want over een paar weken ben jij zelf een ander persoon. Hoe vervelend het ook is, code die je nota bene zelf geschreven hebt is over een paar weken niet meer glashelder. Je zult dan moeten uitzoeken hoe je ook alweer iets hebt gedaan of wat de gedachte erachter was.
Tot nu toe heb je waarschijnlijk gebruik gemaakt van #stukjes commentaar
om duidelijk te maken wat je code doet. Maar als je de applicatie aan het gebruiken bent en je wilt weten wat een bepaalde functie eigenlijk doet, moet je dus de code induiken op zoek naar de betreffende functie. Met docstrings \u2014 documentatiestrings \u2014 is dat verleden tijd. De documentatie over een functie kan automatisch gegenereerd worden vanuit je code met behulp van de docstring. Docstrings staat tussen 3 dubbele aanhalingstekens en hebben doorgaans een vaste structuur:1
integers_up_to.py def integers_up_to(number):\n \"\"\"List integers up to a given number.\n\n Args:\n number (int): list integers up to this number\n\n Returns:\n list: containing the integers\n \"\"\"\n if number > 1:\n return list(range(1, number))\n else:\n return []\n\n\nhelp(integers_up_to)\n
\n(ecpc) > python integers_up_to.py\nHelp on function integers_up_to in module __main__:\n\nintegers_up_to(number)\n List integers up to a given number.\n\n Args:\n number (int): list integers up to this number\n\n Returns:\n list: containing the integers\n
De eerste regel geeft een korte samenvatting weer, na de witregel komt een langere samenvatting. Met Args:
worden alle argumenten opgesomd die aan de functie worden meegegeven en Returns:
geeft aan wat de functie teruggeeft. We kunnen de documentatie van deze functie opvragen met: help(integers_up_to)
. Dat geeft resultaat zoals hierboven gegeven (druk op ).
Je zult niet altijd de help()
functie gebruiken misschien, maar gebruik zoveel mogelijk docstrings \u2014 ze helpen ook enorm als je de code leest. Het is extra werk maar het verdient zich dubbel en dwars terug. Je hoeft geen proza te schrijven, maar wees duidelijk. Lees voor meer voorbeelden bijvoorbeeld de Google Python Style Guide.3
"},{"location":"docstrings/#docstring-generator","title":"Docstring generator","text":"Om het gemakkelijker te maken om docstrings ook \u00e9cht te gaan schrijven, zijn er docstring generators ontwikkeld. Voor Visual Studio Code is er de extensie autoDocstring - Python Docstring Generator.4
Autodocstring
Kijk in Visual Studio Code bij extensions hoe je AutoDocstring kunt gebruiken. Kies daarvoor in de linkerkantlijn het goede icoon voor extensions en selecteer dan de autoDocstring
extensie. Zoek in de documentatie naar hoe je automatisch (een deel van) de docstring genereert.
Wanneer we voor de functie integers_up_to()
de docstring generator gebruiken, krijgen we het volgende:
def integers_up_to(number):\n\"\"\"_summary_\n\nArgs:\n number (_type_): _description_\n\nReturns:\n _type_: _description_\n\"\"\"\nif number > 1:\n return list(range(1, number))\nelse:\n return []\n
Zo kunnen we gemakkelijk alles gaan invullen. Zo lang je niet op Esc drukt maar gewoon je tekst typt kun je met Tab naar het volgende veld en zo de docstring snel invullen. Het is mooi als je daarna onder de summary nog een uitgebreidere uitleg geeft van een paar zinnen. Vergeet ook niet om de docstring zonodig weer bij te werken als je een functie aanpast.
Pythondaq: docstring
opdrachtcodecheck Alle code van je pythondaq
applicatie zijn voorzien van docstrings. Je bent aan het werk in je model script en ziet dat er gebruik wordt gemaakt van een method get_input_voltage()
die in de controller staat. Je vraagt je ineens af wat deze method ook al weer doet. Voorheen ging je dan naar de controller en scrolde je naarget_input_voltage()
. Maar tegenwoordig heb je overal docstrings geschreven, je blijft in het model-script, houd je muis bij get_input_voltage()
en ziet daar je fantastische omschrijving van de method die in de controller staat!
Pseudo-code
# class ArduinoVISADevice\n \"\"\"Summary of class here.\n\n Longer class information...\n Longer class information...\n \"\"\"\n ...\n
Testcode: arduino_device.py if __name__ == \"__main__\":\n help(ArduinoVISADevice)\n
\n(ecpc) > python arduino_device.py\nHelp on class ArduinoVISADevice in module main:\nclass ArduinoVISADevice(builtins.object)\n| Summary of class here.\n|\n| Longer class information...\n| Longer class information...\n|\n| Data descriptors defined here:\n|\n-- More --\n
Checkpunten:
- De controller, de model \u00e9n de view zijn voorzien van docstrings.
- Er staan docstrings bij onder andere alle functies, methods en classes.
- De docstrings hebben een vaste structuur volgens de Google Python Style Guide.3.
- De docstrings zijn volledig.
- De docstrings bevat noodzakelijke en nuttige informatie.
Projecttraject:
- Pythondaq: Docstring
- Pythondaq: src-layout
- Pythondaq: poetry
- Pythondaq: test imports
- Pythondaq: applicatie
Material for MkDocs -
Die vaste structuur wordt niet door Python afgedwongen, maar is een goed gebruik. Er worden verschillende stijlen gebruikt. E\u00e9n van de meest gebruikte stijlen is door programmeurs van Google bedacht.3 \u21a9
-
Sphinx is van oudsher de standaard documentatiegenerator voor Pythonprojecten. Maar Sphinx is al redelijk op leeftijd en gebruikt als tekstformaat niet het bekende en zeer populaire Markdown maar het steeds minder populaire Restructured Text. MkDocs wordt steeds meer gebruikt en Sphinx steeds minder. Toch zul je Sphinx nog veel tegenkomen bij projecten omdat het na al die jaren zeer veel features heeft en zeer stabiel is.\u00a0\u21a9
-
Google. Google python style guide. URL: https://google.github.io/styleguide/pyguide.html.\u00a0\u21a9\u21a9\u21a9
-
Nils Werner. Autodocstring - python docstring generator. URL: https://marketplace.visualstudio.com/items?itemName=njpwerner.autodocstring.\u00a0\u21a9
-
Accelerators and Beam Physics Computing Group. Accelerators and beam physics computing. URL: https://abpcomputing.web.cern.ch.\u00a0\u21a9
-
Will McGugan and others. Textual. URL: https://textual.textualize.io.\u00a0\u21a9
-
Martin Breuss. Build your python project documentation with mkdocs. URL: https://realpython.com/python-project-documentation-with-mkdocs/.\u00a0\u21a9
"},{"location":"docstrings/#documentatie-met-material-for-mkdocs","title":"Documentatie met Material for MkDocs","text":"Een bijkomend voordeel van docstrings is dat ze gebruikt kunnen worden om automatisch documentatie te genereren voor een heel project met behulp van bijvoorbeeld MkDocs of Sphinx.2 MkDocs is een documentatie generator en Material for MkDocs is daar de meestgebruikte uitbreiding op. Het wordt veel gebruikt om documentatie te schrijven voor software projecten. Een paar voorbeelden zijn bijvoorbeeld de website van de Accelerators and Beam Physics Computing groep op CERN5 of de nieuwe Textual bibliotheek6 om zogenaamde terminal user interfaces te maken, een tegenhanger van grafische interfaces. Behalve dat je vrij eenvoudig uitgebreide documentatie kunt schrijven kan MkDocs alle docstrings gebruiken om een referentie op te bouwen. De website voor de ECPC cursus is ook gebouwd met Material for MkDocs.
Het voert tijdens deze cursus te ver om veel aandacht te besteden aan MkDocs. Maar aangezien documentatie zo belangrijk is wilden we het toch noemen! Voor een uitgebreide tutorial, zie Build Your Python Project Documentation With MkDocs.7
"},{"location":"eindfeest/","title":"Eindfeest","text":"Jullie hebben tijdens deze cursus heel veel geleerd. Nu wordt het tijd om dat toe te passen op een nieuw project! Jullie worden hiervoor niet beoordeeld. Dat geeft jullie de vrijheid om in een ontspannen sfeer dingen uit te proberen. Jullie hebben al een hele structuur opgebouwd en misschien hoef je alleen maar je model en view te kopi\u00ebren en wat dingen aan te passen; misschien heb je hele wilde plannen en begin je met een nieuw project. Het mag allemaal, zolang jullie wel een plan hebben en het te maken heeft met het aansturen van een experiment vanuit Python.
Hebben jullie een eigen plan? Overleg met de staf! Als we het plan goedkeuren kunnen jullie meteen aan de slag; anders kunnen we bespreken wat er allemaal w\u00e9l kan. We hebben een paar projecten ter suggestie. De meeste zijn behoorlijk open en we geven geen garantie dat je het werkend krijgt in twee dagdelen, maar dat kan ook juist een leuke uitdaging zijn! De meest 'veilige' optie die het meest lijkt op het experiment dat we tot nu toe gedaan hebben staat onderaan (de zonnecel).
"},{"location":"eindfeest/#michelson-interferometer","title":"Michelson interferometer","text":"E\u00e9n van de experimenten uit het eerste jaar is de Michelsoninterferometer. We hebben die in het klein als bouwpakket aangeschaft bij het Nikhef. Hij moet nog in elkaar worden gezet (niet heel moeilijk) maar het zou leuk zijn als de metingen (kies je eigen onderzoeksvraag!) geautomatiseerd kunnen worden. Daarvoor moet dan nog wel de opstelling uitgedacht worden en zijn er ongetwijfeld onderdelen nodig (motortje, sensor, ...?). Dus als dit je leuk lijkt, begin dan snel met idee\u00ebn bedenken zodat we voor het eindfeest onderdelen kunnen bestellen.
"},{"location":"eindfeest/#morse-code-communicatie","title":"Morse code communicatie","text":"Type op je computer een tekst, druk op 'verzend', en zie de tekst verschijnen bij je buurmens. We kennen dit allemaal van e-mail, Discord, Whatsapp, etc. Dat kan via communicatie over het internet, maar het kan ook met Morse. Schrijf een chat-applicatie waarmee je tekst kunt verzenden van de ene computer en kunt ontvangen op de andere computer. Gebruik daarvoor een LED om te verzenden en een light-dependent resistor (LDR) om te ontvangen. Hiervoor moet je nog een eenvoudige schakeling bouwen. Als het werkt kun je het ook uitbreiden naar bidirectionele communicatie: heen en weer chatten.
"},{"location":"eindfeest/#ocean-optics","title":"Ocean Optics","text":"Tijdens het eerste jaar heb je misschien het spectroscopie-experiment uitgevoerd. Hierbij heb je een spectroscoop gebouwd om gasontladingslampen te onderzoeken. Handmatig heb je spectraallijnen opgezocht, hoeken genoteerd en omgerekend naar golflengtes. Dat was een tijdrovende klus. Het bedrijf Ocean Optics brengt digitale spectroscopen op de markt. Je richt die op een lichtbron en krijgt een dataset van de lichtsterkte bij verschillende golflengtes. De modellen die wij hebben worden niet meer ondersteund en er is geen software meer voor beschikbaar. Maar: we hebben wel documentatie gevonden over hoe we ze aan kunnen sturen. Je kunt dus zelf een applicatie schrijven om een spectrum te meten! Binnen een seconde heb je dan een volledig spectrum gemeten; heel anders dan vorig jaar!
"},{"location":"eindfeest/#functiegenerator-oscilloscoop","title":"Functiegenerator / Oscilloscoop","text":"Functiegeneratoren en oscilloscopen behoren tot de standaarduitrusting van een natuurkundig laboratorium. Dat je ze met de hand kunt bedienen kan heel fijn zijn om snel dingen uit te proberen, maar is lastiger als je uitgebreidere experimenten wilt uitvoeren die deels met de computer bediend worden. Daarom zijn veel modellen met de computer aan te sturen en uit te lezen. Ontwikkel daarvoor een applicatie naar keuze.
"},{"location":"eindfeest/#picoscope","title":"PicoScope","text":"Omdat oscilloscopen nog vaak gebruikt worden voor experimenten komen er ook steeds meer modellen op de markt die je alleen met de computer aan kunt sturen. We gebruiken de PicoScope 5000 Series in ons eigen lab bijvoorbeeld voor het uitlezen van de scintillatordetectoren voor deeltjesdetectie. In hun eentje vervangen die een batterij aan oude apparatuur. De manier van aansturen gaat wel wat anders dan bij de Arduino. Als je dat leuk vindt kun je zelf een interface maken om de PicoScope aan te sturen en uit te lezen. Je kunt hiervoor een functiegenerator gebruiken om het signaal aan te leveren, maar als je wilt mag je bij voldoende toezicht ook op de zaal met radioactieve bronnen werken.
"},{"location":"eindfeest/#arduino-nano-33-iot","title":"Arduino Nano 33 IoT","text":"De Arduino die wij gebruiken heeft ook nog ingebouwde sensoren. De leukste is een versnellingsmeter / gyroscoop. Hiermee kun je de snelheid van rotaties meten of de stand van de Arduino ten opzichte van de lokale gravitatierichting. Door de firmware aan te passen met nieuwe firmwarecommando's kun je die ook uitlezen met een Pythonapplicatie. Je zou de stand van de Arduino kunnen gebruiken als een soort muiscursor of een joystick. E\u00e9n student heeft een keer een 3D-model op het scherm getoond die meedraaide met de echte Arduino.
"},{"location":"eindfeest/#de-iu-karakteristiek-van-een-zonnecel","title":"De $I,U$-karakteristiek van een zonnecel","text":"In het eerste jaar bepalen natuurkundestudenten een $I,U$-curve van een zonnepanneel. Zij vari\u00ebren met de hand de weerstand en lezen de stroom en spanning af. Wij gaan het experiment automatiseren zodat met \u00e9\u00e9n druk op de knop een $I,U$-curve getoond wordt.
Deze opdracht is vergelijkbaar met wat we tot nu toe hebben gedaan. We gaan wederom een $I,U$-curve bepalen, maar niet van een diode maar van een zonnepaneel. Een zonnepaneel gedraagt zich \u2014 afhankelijk van de belastingsweerstand van het circuit \u2014 soms als een spanningsbron en soms als een stroombron. Het geleverde vermogen is ook zeer afhankelijk van deze belasting. Voor de werking van de zonnecel en een beschrijving van de $I,U$- en $P,R$-curves, zie hoofdstuk Zonnecel.
"},{"location":"eindfeest/#de-schakeling","title":"De schakeling","text":"In de figuur hieronder is de equivalente schakeling die we gaan bouwen weergegeven.
We gebruiken een variabele weerstand $R_\\text{var}$ om de belasting van het zonnepaneel te vari\u00ebren. Deze is in serie geschakeld met een stroomsterktemeter om de stroomsterkte door het circuit te meten. Parallel is een spanningsmeter geschakeld waarmee we de spanning die geleverd wordt door het zonnepaneel kunnen meten.
Merk op dat onze Arduino geen stroomsterktemeter heeft. We zullen dus de spanning over een kleine weerstand moeten meten om zo \u2014 met behulp van de wet van Ohm \u2014 de stroomsterkte te bepalen. Een ander probleem is dat de spanning die geleverd wordt door het zonnepaneel groter kan zijn dan de 3.3 V die maximaal op de pinnen mag staan. Hiervoor gaan we gebruik maken van een 3:1 spanningsdeler zodat de spanning altijd onder de 3.3 V zal blijven \u2014 volgens de specificaties komt de maximale spanning van het zonnepaneel in de meest ideale omstandigheden uit op 10 V.3 Het laatste probleem is de variabele weerstand: er zijn variabele weerstanden te koop waarbij de weerstand zeer nauwkeurig kan worden gekozen. Helaas is de minimale weerstand, ten gevolge van de vrij ingewikkelde interne schakelingen, te groot om de maximale stroom van een zonnepaneel te meten. Daarom maken we gebruik van een type veldeffect transistor, de MOSFET. Een MOSFET is feitelijk een soort schakelaar. Afhankelijk van de spanning die op de gate gezet wordt, is de weerstand tussen de source (aarde, minpool) en de drain (pluspool)1 te vari\u00ebren tussen nul en oneindig. Er is maar een relatief klein gebied waarin de weerstand snel verandert van oneindig naar nul.
De schakeling voor onze Arduino is weergegeven in de figuur hieronder. Hier belasten we het zonnepaneel met een MOSFET. In serie hiermee staat een kleine weerstand van 4.7 \u03a9 waarover we de spanning meten ten behoeve van de bepaling van de stroomsterkte. De pin van de Arduino die verbonden is met de gate van de MOSFET is beschermd met een weerstand van 1 k\u03a9. Dit is belangrijk, want wanneer er een spanning gezet wordt op de gate kan er kortdurend een vrij grote stroom lopen. De gate gedraagt zich als een kleine capaciteit. Parallel aan de MOSFET + weerstand is een 3:1 spanningsdeler geschakeld met weerstanden van 2 M\u03a9 en 1 M\u03a9.
In de 3D-model2 hieronder is een Arduino Nano 33 IoT op een 400-punt breadboard geschakeld. Aan de linkerkant van het breadboard is de serieschakeling van de MOSFET met de kleine weerstand geplaatst. De pinnen van de MOSFET zijn van boven naar beneden de gate, de drain (+) en de source (-). De rechterkant bevat de spanningsdeler. Het zonnepaneel zelf wordt aan de $+$ en $-$ power rails aan de rechterkant van het bord geschakeld.
-
De namen source en drain verwijzen hier naar de elektronenstroom. Elektronen worden geleverd door de source (aard, minpool) en stromen dan naar de drain (pluspool).\u00a0\u21a9
-
Dit model bevat twee 3D modellen die zijn gecre\u00eberd door Lara Sophie Sch\u00fctt en AppliedSBC en zijn gedeeld onder respectievelijk een CC-BY en CC-BY-SA licentie. De originele modellen zijn te vinden via [CC0] Set of Electronic Components en Arduino Nano 33 IoT. De modellen zijn samengevoegd en Voorzien van een Arduino texture, mosfet, zonnecel en draden. Dit 3D model heeft een CC-BY-SA licentie.\u00a0\u21a9
-
Seeed Studio. Small solar panel 55x70mm 0.5w. URL: https://www.seeedstudio.com/0-5W-Solar-Panel-55x70.html.\u00a0\u21a9
"},{"location":"faq/","title":"FAQ: lijst conventies","text":"For full documentation visit mkdocs.org.
"},{"location":"faq/#commands","title":"Commands","text":" mkdocs new [dir-name]
- Create a new project. mkdocs serve
- Start the live-reloading docs server. mkdocs build
- Build the documentation site. mkdocs -h
- Print help message and exit.
"},{"location":"faq/#project-layout","title":"Project layout","text":"mkdocs.yml # The configuration file.\ndocs/\n index.md # The documentation homepage.\n ... # Other markdown pages, images and other files.\n
"},{"location":"faq/#ideeen","title":"Idee\u00ebn","text":"Schakeling bouwen Als je geen kant-en-klare schakeling bij je werkplek hebt liggen, druk de Arduino in het breadboard en bouw een schakeling met een LED op de manier die is weergegeven in fig:arduino-LED-breadboard. De weerstand heeft een waarde van 220 \u03a9. De LED heeft aan \u00e9\u00e9n zijde een platte kant in de dikkere ring onderaan de plastic behuizing (goed kijken!); schakel die aan de kant van de aarde. Als de pootjes van de LED niet afgeknipt zijn, dan zit het korte pootje aan de platte zijde van de LED. Het heeft geen zin om naar het plaatje te kijken hoe het er \u00edn de LED uitziet \u2014 dat verschilt per type LED.
Bestand: docs/index.md
en ook pythondaq/models/diode.py
. Die vind je1 ook in de repository davidfokkema/tailor
. Folder: oefenopdrachten
.
Eenheden: 220 \u03a9 m/s of ms-1 of $220\\,ms^{-1}$ en $220\\,\\Omega$. We doen het eerste!!
Voor menu's gaan we het zo doen: Menu > Code > Add repository en voor toetsen Ctrl+F.
Een referentie naar een opdracht of figuur maak je aan door <div id=\"label\"></div>
blokje als label neer te zetten. Verwijzen gaat dan met [opdracht _label_](bronbestand.md#label)
waarbij je dus ook het bestand moet weten waarin het label gedefinieerd wordt.
Voor vergelijkingen: \\begin{equation} f(x) \\sin x, \\end{equation} met $f(x)$ een functie van $x$.
Voor code: hier een print
-statement, maar meer code met: Titel
print(\"Hello, world!\")\n
oefenopdracht
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa
Inlever opdracht
Deze opdracht lever je in.
Meer leren
Met deze opdracht kan je meer leren
-
en dit is dus een voetnoot\u00a0\u21a9
"},{"location":"faq/#uitdaging-wheels","title":"Uitdaging: wheels","text":"Waarschuwing
Let op dat dit ook kan.
Info
Of niet.
En zo verder.
"},{"location":"firmware/","title":"Firmware","text":""},{"location":"firmware/#firmware","title":"Firmware","text":"De firmware bestaat uit een gedeeltelijke implementatie van het VISA-protocol.1 Het voornaamste verschil bestaat uit het feit dat VISA voor ieder commando zowel een korte als een lange versie heeft. Zo zou je in de documentatie van een instrument het commando MEASure
kunnen vinden. Je kunt dan zowel MEAS
als MEASURE
gebruiken om het commando te geven. In deze implementatie is het slechts mogelijk om de korte vorm te gebruiken.
De nummering van de kanalen volgt de nummering van de Arduino hardware. Dus kanaal 0 is pin A0 op de Arduino, kanaal 1 is pin A1, enz. De digitale resolutie is ingesteld op 10 bits ($2^{10}$~stappen, ofwel waardes tussen 0 en 1023) en het analoge bereik is 0 V tot 3.3 V.
Bij het versturen van opdrachten naar het apparaat, moet je afsluiten met een linefeed ('\\n'). Het apparaat sluit zijn antwoorden af met een carriage return gevolgd door een linefeed ('\\r\\n').
De code is terug te vinden in de repository /davidfokkema/arduino-visa-firmware
.2 Deze documentatie is voor versie~1.0.0. De commando's die geaccepteerd worden door de firmware zijn weergegeven in de tabel hieronder.
Commando Beschrijving *IDN?
Geeft de identificatiestring van de firmware. OUT:CH<ch> <value>
Zet een specifieke spanning <value>
op uitvoerkanaal <ch>
. Waardes mogen liggen tussen 0 (minimale spanning) en 1023 (maximale spanning). Voorbeeld: OUT:CH0 1023
OUT:CH<ch>?
Geef de huidige instelling voor de spanning terug op uitvoerkanaal <ch>
in het bereik 0 tot 1023. Voorbeeld: OUT:CH0?
MEAS:CH<ch>?
Meet de spanning op invoerkanaal <ch>
in het bereik 0 tot 1023. Voorbeeld: MEAS:CH1?
-
IVI Foundation. Vpp-4.3: the visa library. 2018. URL: https://www.ivifoundation.org/downloads/Architecture Specifications/IVIspecstopost10-22-2018/vpp43_2018-10-19.pdf.\u00a0\u21a9
-
David B.R.A. Fokkema. Arduino visa firmware. 2020. URL: https://github.com/davidfokkema/arduino-visa-firmware.\u00a0\u21a9
"},{"location":"github/","title":"Versiebeheer met GitHub","text":""},{"location":"github/#versiebeheer","title":"Versiebeheer","text":"Zodra je scripts wat ingewikkelder worden begin je tegen hele praktische problemen aan te lopen. Het werkt nu, maar je wilt een flinke aanpassing gaan doen. Werkt het straks nog wel? Hoe ingewikkelder het script, hoe ingewikkelder de wijzigingen en hoe minder het vertrouwen dat het in \u00e9\u00e9n keer gaat lukken. Misschien heb je wel eens de ervaring gehad dat het maar niet wil werken en dat je niet goed weet wat je precies had veranderd ten opzichte van toen het nog wel werkte. Veel mensen hebben de neiging om naast een script.py
een script-v1.py
, script-v2.py
, enz. aan te maken. Soms zelfs een script-eindversie.py
en met wat pech dan toch nog een script-eindversie-definitief.py
. Niet heel fijn. Je ziet dan nog steeds niet goed wat er veranderd is (dat blijft naast elkaar leggen en zoeken) en je map loopt vol met overbodige scripts. Dit kan beter\u2026 met versiebeheer!
Versiebeheer (Engels: version control) stelt je in staat om af en toe een momentopname te maken van al je bestanden in een bepaalde map, inclusief alle submappen. Dit doe je niet na iedere regel code, maar bijvoorbeeld wel als je een stukje code af hebt en getest hebt dat het werkt. Zo'n momentopname heet een commit. Hoe vaak je commit is aan jou; maar wacht niet te lang \u2014 dan is het geen versiebeheer meer.
Je versiebeheersysteem geeft ondertussen duidelijk al je wijzigingen weer ten opzichte van de laatste commit. Ook kun je de wijzigingen tussen oudere versies bekijken. Alles is relatief: je kunt zien wat er veranderd is tussen twee weken terug en gisteren, of gisteren en vandaag; iedere commit kun je vergelijken met willekeurig iedere andere commit. Heb je iets verprutst en wil je een oude versie terughalen? Prima! Commit die ook, dan kun je zelfs dat weer terugdraaien later. Je verliest nooit meer je werk. En stukmaken mag!1
"},{"location":"github/#git","title":"Git","text":"Ruim tien jaar geleden werden er nog vele concurrerende systemen gebruikt. Die tijd is grotendeels voorbij. E\u00e9n van de nieuwste systemen, Git,2 wordt tegenwoordig door bijna iedereen gebruikt of ondersteund. Git is ontwikkeld door Linus Torvalds als alternatief voor het commerci\u00eble systeem dat gebruikt werd voor de ontwikkeling van de Linux kernel.6 Het begon als een zeer eenvoudig \u2014 en volkomen ongebruiksvriendelijk \u2014 programma. Later is het in een veel gebruiksvriendelijker jasje gestoken.
Git werkt in principe via de command-line. Je geeft opdrachten in de map waar je broncode staat: toevoegen van wijzigingen aan de staging area, bekijken van de meest recente wijzigingen, committen van je code, teruggaan en werken met oudere versies, aanmaken van branches,3 je wijzigingen uploaden naar internet, enz. Het geheel van map met broncode en versiegeschiedenis wordt een repository genoemd.
In deze cursus zullen we gebruik maken van een grafische applicatie die eenvoudiger werkt. Je kunt daarna \u2014 als je dat wilt \u2014 de stap maken naar de command-line waarmee je nog veel meer mogelijkheden tot je beschikking krijgt. Voor meer informatie over Git en het gebruik via de command-line, zie het boek Pro Git.7
"},{"location":"github/#github","title":"GitHub","text":"Git is een distributed version control system (DVCS) wat wil zeggen dat er geen centrale server hoeft te zijn. Je kunt volledig offline werken in je eigen repository en je wijzigingen af en toe committen. Als je daar zin in hebt kun je je wijzigingen naar een collega sturen (pushen) of je kunt een collega toestemming geven om de wijzigingen op te halen (pullen). Je bouwt dan aan \u00e9\u00e9n grote versiegeschiedenis met kopie\u00ebn op meerdere computers. Je bent zo volledig onafhankelijk van bedrijven die servers in de lucht houden of bepalen wie er wel en niet toegang krijgt. Dat is fijn, maar een centrale plek om repositories neer te zetten heeft weer het grote voordeel dat je de wereld kunt laten zien wat voor moois je gemaakt hebt \u00e9n dat het samenwerking wel vermakkelijkt. Als iedereen uit je team regelmatig commits pusht naar een centrale server en daar vandaan ook ophaalt dan is iedereen altijd up-to-date.
Er zijn tegenwoordig veel websites die een plek bieden voor Git repositories. De bekendste zijn GitHub, GitLab, Bitbucket en SourceForge. GitHub, aangekocht door Microsoft, is op dit moment het bekendste en grootste platform. Veel bekende softwareprojecten vinden daar hun thuis.
Wij gaan werken met GitHub, je moet dan wel een (gratis) account aanmaken. Als student kom je ook nog in aanmerking voor een educatiekorting op een pro-account. Je betaalt dan nog steeds niets.
Account aanmaken
Ga naar https://github.com/ en klik rechtsboven op Sign Up
. Maak een account aan onder je priv\u00e9-emailadres. Op deze manier blijf je toegang houden tot je account ook nadat je afgestudeerd bent.
"},{"location":"github/#github-desktop","title":"GitHub Desktop","text":"Om het programmeurs makkelijker te maken met GitHub te werken heeft GitHub een desktop applicatie ontwikkeld met de naam GitHub Desktop
. We gaan GitHub Desktop gebruiken om een repository te maken van een map met oefenopdrachten.
Van bestaande map repository maken
opdrachtcheck Je gaat een repository maken van een bestaande map. Als je van de ECPC
een repository maakt, kun je daar geen andere repositories inzetten. Dus maak je in de map ECPC
een nieuwe map oefenopdrachten
aan en zet daarin alle Python-bestandjes die je hebt gemaakt om te oefenen zoals de opdrachten Pyvisa in pythonscript en KnipperLED. Je gaat naar GitHub Desktop en logt in met je eigen account. Je ziet bij File drie opties staan, New
, Add local
en Clone
repository. Hoewel New repository
een goede optie lijkt, wordt daarmee een nieuwe map aangemaakt en dat is niet nodig. Dus kies je voor Add local
repository. Je geeft de map oefenopdrachten
op als locatie en krijgt in rode tekst een waarschuwing. De waarschuwing geeft aan dat de map wel bestaat maar dat het geen Git repository
is, daarom klik je op de blauwe tekst create a repository
. Je vinkt Initialize this repository with a README
aan en kiest bij Git ignore
voor Python. Je bevestigt dan met de blauwe knop Create Repository
. Vanaf nu duiden we een repository aan met het -symbool. De repository oefenopdrachten
is in GitHub Desktop geopend en als je op het tabblad 'History' klikt dan zie je dat er een Initial commit
is met wat git
-bestanden en de Pythonscripts die je in de map hebt gezet. Vanaf nu staat oefenopdrachten
in versiebeheer en houdt Git je wijzigingen bij, maar je moet wel zelf committen! ECPC
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 test_arduino.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 flashingLED.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022
Git ignore Python
Waarom zou je bij Git ignore voor Python kiezen, we gaan toch juist Python bestanden maken? De Git Ignore zorgt ervoor dat allerlei hulpbestanden van Python niet bewaard worden als commit. Maar de Python code wordt wel bewaard.
Checkpunten:
- De repository
oefenopdrachten
zit in de map ECPC
. - In de repository
oefenopdrachten
is een bestand README.md
- In de repository
oefenopdrachten
is een bestand .gitattributes
. - In de repository
oefenopdrachten
is een bestand .gitignore
.
Projecttraject
- Pyvisa in pythonscript
- LED laten branden
- flashingLED
- Van bestaande map repository maken
- Commit
- Push en pull
"},{"location":"github/#commit","title":"Commit","text":"Alle wijzigingen aan bestanden in de repository kun je vanaf nu bijhouden door regelmatig een commit te maken. Met een commit maak je als het ware een snapshot van alle bestanden en hang je daar een labeltje aan. Dit kan in GitHub Desktop, maar ook direct vanuit Visual Studio Code. Elke commit geef je een begeleidend schrijven mee. Je hoopt dat jij, maar ook je collega, na het lezen van het berichtje snel begrijpt wat er veranderd is \u00e9n waarom. Wanneer je bepaalde wijzigingen ongedaan wilt maken, kan je snel vinden bij welke commit je dan moet zijn. En als je dan je applicatie gaat uitbrengen op Github kun je de commit messages gebruiken om snel op te sommen wat de nieuwste versie van jou app kan!
Commit
GitHub DesktopVisual Studio Codecheck Voer de volgende opdrachten uit:
- Open GitHub Desktop, klik op Current repository (links onder de menubalk) en selecteer de repository die je in opdracht Repository toevoegen hebt aangemaakt.
- Ga naar Repository > Open in Visual Studio Code (of druk op Ctrl+Shift+A ) en open de repository in Visual Studio Code.
- Open in Visual Studio Code \u00e9\u00e9n van je Pythonscripts.
- Type een stukje code erbij \u2014 bijvoorbeeld een print-statement \u2014 en haal ergens anders iets weg. Bewaar het bestand.
- Ga naar GitHub Desktop, controleer bij Current repository (links onder de menubalk) of de juiste repository is geopend.
- Klik daaronder op het tabblad Changes
- Als er meerdere bestanden gewijzigd zijn kan je met het blauwe vinkje aangeven voor welke bestanden je een commit schrijft.
- Onder de lijst met gewijzigde bestanden vind je twee invulvulden. Een smal veld voor een titel en een groot veld voor een uitgebreide beschrijving (Description).
- In het titelveld staat in lichtgrijs een nietzeggende commit (bijvoorbeeld: Update test.py). Schrijf daar een nuttige commit message. Dus niet: opdracht: commit, maar zoiets als: feat: lookup port name for device.
- Klik op Commit to main. Gefeliciteerd! Je hebt je eerste commit gepleegd!
- Open GitHub Desktop, klik op Current repository (links onder de menubalk) en selecteer de repository die je in opdracht Repository toevoegen hebt aangemaakt.
- Ga naar Repository > Open in Visual Studio Code (of druk op Ctrl+Shift+A ) en open de repository in Visual Studio Code.
- Open in Visual Studio Code \u00e9\u00e9n van je Pythonscripts.
- Type een stukje code erbij \u2014 bijvoorbeeld een print-statement \u2014 en haal ergens anders iets weg. Bewaar het bestand.
- Links verschijnt een blauw bolletje (Geen blauw bolletje? Bekijk het info blok hieronder.) bij
Source Control
die laat weten dat er wijzigingen zijn ten opzichte van de vorige commit. Klik op Source Control
. - Onder
Changes
staat een lijst met bestanden waar wijzigingen in aan zijn gebracht. Kies welke bestanden je wilt committen door rechts op het +je te klikken. Deze bestanden komen nu op het podium te staan onder Staged Changes
. Je kunt ook alle bestanden in een keer op het podium zetten door naast het kopje Changes
op het +je te klikken. - Schrijf een nuttige commit message. Dus niet: opdracht: commit, maar zoiets als: feat: lookup port name for device.4
- Klik op het vinkje om te committen. Gefeliciteerd! Je hebt je eerste commit gepleegd!
Geen blauw bolletje
Zie je geen bolletje verschijnen? Kijk of je het bestand zeker weten hebt opgeslagen. Nog steeds geen blauw bolletje? Ga naar GitHub Dekstop en ga verder met stap 5.
Checkpunten:
- In GitHub Desktop staat onder het
History
tabblad een nieuw commit message. - De commit bevat het bestand waarin je een aanpassing hebt gedaan.
- De aanpassing is aangegeven met groen (toegevoegd) en rood (weggehaald).
Projecttraject
- Pyvisa in pythonscript
- LED laten branden
- flashingLED
- Van bestaande map repository maken
- Commit
- Push en pull
In GitHub Desktop zie je nu bij history de commit staan, met in een oogopslag alle wijzigingen.
Info
Als je wilt opzoeken hoe iets werkt bij GitHub Desktop, kijk dan in de documentatie: https://docs.github.com/en/desktop.
Hieronder zie je een aantal voorbeelden van commit messages. De titels zijn kort en krachtig, in de description staat specifieke en uitgebreidere informatie.
Enabled initialization of the measurement instrument and calibration of sensors for accurate data collection. Commit to main Resolved calibration errors caused by incorrect sensor configurations. Commit to main Upgraded matplotlib and click to the latest versions to enable error bar functionality. Commit to main Fetched all measurements prior to analysis to enhance performance and efficiency. Commit to main Updated README to include information about the new plot button and save feature. Commit to main Commit to main Push en pull
opdrachtcheck De repository die je in opdracht Repository toevoegen hebt aangemaakt bestaat alleen nog maar op de computer. Als de computerkabouters 's nachts langskomen kan het zijn dat de computer daarna is gewist en je alles kwijt bent. Daarom is het fijn om de repository ook in de cloud te hebben op GitHub.com. In GitHub desktop is een knop Publish repository; Publish this repository to GitHub
, als je daar op drukt kun je nog een andere naam aan de repository geven (dat bepaalt de url op github.com), een description en of de code priv\u00e9 moet zijn. Daarna klik je op de blauwe knop Publish repository
. Als je nu naar GitHub.com gaat zie je bij jouw repositories de zojuist gepubliceerde repository staan.
Om je wijzigen ook in de cloud op te slaan kun je commits pushen
naar Github.com met de knop Push origin
. Als je op een andere computer gaat werken kun je de repository vanuit de cloud naar de computer halen door op Fetch origin
te klikken en daarna op Pull origin
.
Checkpunten:
- De repository staat op github.com bij jouw repositories
Projecttraject
- Pyvisa in pythonscript
- LED laten branden
- flashingLED
- Van bestaande map repository maken
- Commit
- Push en pull
"},{"location":"github/#github_1","title":"GitHub","text":"Om makkelijk je Git repository te delen met vrienden, collega's en de rest van de wereld kan je er voor kiezen om deze op GitHub te zetten. Je kunt dan je commits pushen naar GitHub en wijzigingen die je vrienden hebben gemaakt pullen zodat jij er ook weer aan verder kan werken. Van alle repositories die op GitHub staan \u00e9n openbaar zijn kun je de broncode clonen en zelf mee aan de slag! Laten we eens een kijkje nemen op GitHub.
Tailor
Als je nog nooit op GitHub bent geweest dan kunnen de pagina's nogal intimiderend overkomen. De informatiedichtheid is nogal hoog. Na een paar bezoeken weet je meestal wel waar je dingen kunt vinden. David heeft een data-analyse app geschreven dat Tailor heet en dat gebruikt wordt bij natuurkundepractica voor studenten medische natuurwetenschappen (MNW) en science, business and innovation (SBI). Laten we eens kijken wat daar allemaal opstaat.
- Zoek de repository
/davidfokkema/tailor
op github.com op. - Je komt terecht op de hoofdpagina, hier zie je een mappenstructuur met een aantal bestanden. Rechts daarvan staat een korte beschrijving onder het kopje
About
. Een uitgebreidere beschrijving vind je als je naar beneden scrollt onder Readme
. - Linksboven zie je een aantal tabbladen (code, issues, pull requests, ...), het tabblad
code
is de hoofdpagina met de mappenstructuur. Navigeer door de mappen, wat staat er op regel 14 van plot_tab.py
? - Ga terug naar de hoofdpagina, kijk onder het groen kopje met
code
. Hoeveel commits zijn er gemaakt? Klik op commits en daarna op een commit-message. Hoeveel regels zijn er weggehaald of bijgekomen? - Je kan per bestand bekijken wanneer die is aangepast en wat er is aangepast met de history knop. Ga naar het bestand
pyproject.toml
en klik rechtsboven op History
. Wat is er aangepast in pyproject.toml
bij de commit Release v2.0.0? Je ziet ook welke bestanden nog meer zijn gewijzigd in deze commit, welk bestand is nog meer gewijzigd bij Release v2.0.0? - Ga terug naar de hoofdpagina. Welke versie van Tailor is als laatste gereleased?
- Je kent het misschien wel, dat je een app gebruikt maar dat het niet helemaal goed werkt (bug), of je hebt een idee hoe het nog beter kan worden (enhancement). Daarvoor is op GitHub het tabblad
Issues
. Hoeveel bugs zijn er gerapporteerd? En hoeveel enhancements? - Als het jou nu gelukt is om een bug te fixen, of je hebt een super handige feature ontworpen, dan kan je de eigenaren van de repository vragen om jouw code te implementeren door een pull request te sturen. Ga naar het tabblad
Pull requests
, klik op Closed
en bekijk welke pull requests zijn ge\u00efmplementeerd. - Het meest rechter tabblad
Insights
geeft je, tegen alle verwachtingen in, inzicht. Je kan zien door hoeveel mensen er aan gewerkt wordt. Kijk bij Code frequency
, in welke periode is er het meest aan de code veranderd? - Als je een repository goed/handig/slim/fijn vindt kun je dit aangeven met een ster. Klik daarvoor rechtsboven op star .
- Dan tot slot die ene, meest in het oogspringende groene
code
knop. Met die knop kan je de repository als zip-bestand downloaden of openen met GitHub desktop.
Clone repository Clone de LMfit-py repository op GitHub:
- Zoek de repository op GitHub op (
lmfit/lmfit-py
) - Kies Code > Open with GitHub Desktop
- Kies zelf een map op je harde schijf om de repository te bewaren.
- Open Visual Studio Code en open de repository met File > Open Folder.5 Als GitHub Desktop de ge\u00efnstalleerde VS Code herkent kan dat direct vanuit GitHub Desktop met Repository > Open in Visual Studio Code.
- Open
examples
README.txt
. Verander in de eerste paragraaf Below are examples
in Below are different examples
en sla het bestand op. - Schakel naar de GitHub Desktop applicatie en bekijk de wijziging.
- Linksonder kun je een korte beschrijving van je wijziging intypen en druk dan op de blauwe
Commit
-knop. - Schakel, rechtsboven, naar
History
. Bovenaan staat jouw wijziging. Daaronder kun je alle wijzigingen van anderen bekijken.
Aangezien je geen schrijfrechten hebt voor LMfit kun je niet kiezen voor Push origin
\u2014 de knop die rechtsboven verschijnt. Met die knop duw je je wijzigingen naar GitHub zodat iedereen ze kan zien. Dat is mooi, maar je mag niet zomaar de repository van iemand anders wijzigen.
Git in de terminal Tot nu toe heb je Visual Studio Code of GitHub Desktop gebruikt om te committen. Maar je kan Git ook bedienen via de terminal. De mogelijkheden van Git zijn in de terminal ook veel groter dan in de grafische applicaties die we gebruikt hebben.
- Open een repository in Visual Studio Code
- Gebruik de terminal in Visual Studio Code en bekijk de commit geschiedenis met het commando
git log
. Scroll door de commit messages met spatie. - Zoek via https://initialcommit.com/blog/Git-Cheat-Sheet-Beginner het commando om een commit toe te voegen. Wijzig iets in je code en commit via de terminal.
- Dit waren twee dingen wat met GitHub Desktop ook kon, snuffel op het internet om te zien wat je met Git nog meer kunt.
Branches -
Stukmaken mag, maar het terughalen van een oude versie is niet met een druk op de knop gebeurt. Vraag om hulp als je terug wilt naar een oude versie, wij helpen je graag!\u00a0\u21a9
-
https://initialcommit.com/blog/How-Did-Git-Get-Its-Name\u00a0\u21a9
-
Een branch is een splitsing in je versiegeschiedenis. Je kunt het gebruiken om over een langere tijd een grote wijziging uit te testen terwijl je af en toe heen en weer springt tussen je main branch en de nieuwe branch. Commits in de nieuwe branch blijven gescheiden. Later kun je ervoor kiezen om de wijzigingen in de nieuwe branch te mergen met je main branch, maar dat hoeft niet.\u00a0\u21a9
-
Je kunt je commit message opdelen in een titel (of summary) en een beschrijving. Dit doe je dit door een witregel toe te voegen tussen de titel en de beschrijving.\u00a0\u21a9
-
Als je vergeten bent waar je de repository ook alweer bewaard had kun je met Repository > Show in Finder de folder openen.\u00a0\u21a9
-
Linus Torvalds and others. Git. 2005. URL: https://git-scm.com.\u00a0\u21a9
-
Scott Chacon and Ben Straub. Pro git: Everything you need to know about Git. Apress, second edition, 2014. URL: https://git-scm.com/book/en/v2.\u00a0\u21a9
"},{"location":"github/#branches","title":"Branches","text":"Soms wil je je code flink onder handen nemen of iets heel nieuws eraan toevoegen. Terwijl je bezig bent ga je natuurlijk eerst van alles stuk maken voordat je het weer werkend hebt gekregen. Maar ondertussen kan je oude functionaliteit van je code niet gebruiken. Of je bent samen met een vriend aan een package bezig en om de haverklap werkt jouw stukje code niet meer omdat ergens anders de code verbouwd wordt. Dan is het handig dat je vanaf het punt dat je code werkt een zijweg kan inslaan. Daarom zijn branches uitgevonden. Je kunt vanuit Github Desktop, vanuit Visual Studio Code en natuurlijk via de terminal een branch aanmaken.
Branches
- Open een repository naar keuze en maak een nieuwe branch aan.
- Maak een aantal wijzigingen en commit.
- Ga terug naar de main branch.
- Merge de nieuwe branch in de main branch.
"},{"location":"gui/","title":"Graphical user interfaces","text":""},{"location":"gui/#grafische-interfaces-met-pyside","title":"Grafische interfaces met PySide","text":"Als je een grafische applicatie schrijft roep je functies aan van het besturingssysteem om vensters, knoppen, menu's e.d. te laten tekenen en te reageren op muisklikken en het toetsenbord. Het lastige daaraan is dat een applicatie voor MacOS heel anders geschreven moet worden dan \u00e9\u00e9n voor Linux of Windows. Om die reden zijn er verschillende cross-platform bibliotheken ontwikkeld die als het ware tussen het besturingssysteem en je applicatie komen te staan. Je kunt dezelfde applicatie maken voor alle besturingssystemen en de bibliotheek kiest welke functies aangeroepen moeten worden om een venster te tekenen. Het voordeel is duidelijk: je hoeft maar \u00e9\u00e9n applicatie te schrijven die overal werkt. Het nadeel is dat je niet \u00e9cht gebruik kunt maken van alle functies en opties die het besturingssysteem biedt. Hier kiezen we voor de voordelen en gaan we gebruik maken van misschien wel de meest populaire optie: Qt.1 De bibliotheek PySide6
is de offici\u00eble Pythonbibliotheek.
Info
Maak voor de oefeningen een nieuw conda environment test-qt
met: Terminal
conda create --name test-qt python=3.12\nconda activate test-qt\npip install pyside6 pyqtgraph\n
Selecteer het nieuwe test-qt
conda environment in Visual Studio Code en sluit alle oude terminals met het -icoon.2 Een minimale Qt-applicatie ziet er als volgt uit:
import sys\n\nfrom PySide6 import QtWidgets\n\n\nclass UserInterface(QtWidgets.QMainWindow):\n pass\n\n\ndef main():\n app = QtWidgets.QApplication(sys.argv)\n ui = UserInterface()\n ui.show()\n sys.exit(app.exec())\n\n\nif __name__ == \"__main__\":\n main() \n
Eerst importeren we een paar bibliotheken. Het draait uiteindelijk om de UserInterface
class. De naam mag je zelf kiezen, zolang je maar aangeeft dat de class een afgeleide is van QtWidgets.QMainWindow
, het hoofdvenster van je applicatie. In het hoofdgedeelte van het programma (gedefinieerd in de functie main()
) maak je eerst een instance van QtWidgets.QApplication
.3 Ook maken we een instance van onze eigen class en we roepen de show()
method aan. Die hebben we niet zelf geprogrammeerd; die zit in de parent class QMainWindow
. Als laatste roepen we de exec()
method aan van onze QApplication
en de uitvoer daarvan (een exit code) geven we mee aan de functie sys.exit()
. Dat betekent dat als het programma afsluit met een foutmelding, dat een foutcode wordt meegegeven aan het besturingssysteem. Iemand anders die een script schrijft kan die code afvangen en daar iets mee doen. Een aantal elementen uit dit programma (sys.argv
, sys.exit()
) zijn strikt genomen niet noodzakelijk, maar wel good practice. Ook het schrijven van een main()
functie is niet strikt noodzakelijk, maar het maakt het wel makkelijk om straks een zogeheten entry point te hebben als we weer een applicatie willen schrijven. In de pyproject.toml
geven we dan aan dat we de main()
functie willen aanroepen. Dat komt later.
Minimale GUI
opdrachtcodecheck Je gaat de gegeven Python code voor een een minimale GUI testen. In de map ECPC
maak je een example-gui.py
aan en zet daarin de Python code. Je activeert de test-qt
conda environment en runt het bestand example-gui.py
. Er verschijnt een leeg venster in beeld met als venstertitel python
en drie knoppen. Een streepje (minimize), een vierkant (maximize) en een kruis (close). Je drukt op het kruisje en het venster sluit. ECPC
\u251c\u2500\u2500 pythondaq
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 example-gui.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code
import sys\n\nfrom PySide6 import QtWidgets\n\n# create subclass of QtWidgets.QMainWindow\n\ndef main():\n # create instance of QtWidgets.QApplication with arguments from sys.argv\n # create instance of subclass\n # call show method of subclass\n # get exit code with exec method of QApplication instance and give exit code to sys.exit()\n\n# when run this script:\n # run main function \n
Checkpunten:
- Het juiste conda environment is geactiveerd
- De code is volledig overgenomen
- Er verschijnt een leeg venster
Projecttraject:
- Minimale GUI
- Parent class initialiseren
- Central widget toevoegen
- textbox toevoegen
- knoppen toevoegen
- Slots en signals toevoegen
- 'Hello world' en Quit knoppen toevoegen
Elke keer als je een nieuwe Qt applicatie gaat schrijven kun je bovenstaand stukje code copy/pasten. Als we dit programma draaien hebben we echter een klein leeg venster op het scherm, zonder elementen. Die elementen kunnen we op twee manieren toevoegen: door ze te programmeren of door het gebruik van een visueel ontwerp met Qt Designer. Beide zullen in de volgende secties toegelicht worden.
"},{"location":"gui/#de-interface-programmeren","title":"De interface programmeren","text":"We gaan de eenvoudige interface programmeren die hieronder is weergegeven:
We doen dat door de class UserInterface
uit te breiden met widgets uit de QtWidgets
bibliotheek.
Het defini\u00ebren van layouts gebeurt in veruit de meeste opmaaksystemen met rechthoeken (Engels: boxes) die op verschillende manieren gestapeld worden \u2014 naast elkaar, boven elkaar, of op een rechthoekig grid bijvoorbeeld. Zulke systemen zijn ook hi\u00ebrarchisch: je stopt boxes in andere boxes.
De layout van bovenstaande screenshot is als volgt opgebouwd. Het hoofdelement van de grafische interface is de central widget
:
De central widget
krijgt een verticale layout die we vbox
noemen:
In de verticale layout plaatsen we een textbox
en een horizontale layout die we hbox
noemen:
In de horizontale layout plaatsen we twee button
s:
Het stuk programma om bovenstaande layout op te bouwen geven we hieronder weer. We bespreken straks de code regel voor regel.
from PySide6.QtCore import Slot\n\n\nclass UserInterface(QtWidgets.QMainWindow):\n def __init__(self):\n # roep de __init__() aan van de parent class\n super().__init__()\n\n # elk QMainWindow moet een central widget hebben\n # hierbinnen maak je een layout en hang je andere widgets\n central_widget = QtWidgets.QWidget()\n self.setCentralWidget(central_widget)\n\n # voeg geneste layouts en widgets toe\n vbox = QtWidgets.QVBoxLayout(central_widget)\n self.textedit = QtWidgets.QTextEdit()\n vbox.addWidget(self.textedit)\n hbox = QtWidgets.QHBoxLayout()\n vbox.addLayout(hbox)\n\n clear_button = QtWidgets.QPushButton(\"Clear\")\n hbox.addWidget(clear_button)\n add_text_button = QtWidgets.QPushButton(\"Add text\")\n hbox.addWidget(add_text_button)\n\n # Slots and signals\n clear_button.clicked.connect(self.textedit.clear)\n add_text_button.clicked.connect(self.add_text_button_clicked)\n\n @Slot()\n def add_text_button_clicked(self):\n self.textedit.append(\"You clicked me.\")\n
Allereerst defini\u00ebren we een __init__()
. Helaas gaat dat niet zomaar. We schrijven namelijk niet helemaal zelf een nieuwe class (class UserInterface
), maar breiden de QMainWindow
-class uit (class UserInterface(QtWidgets.QMainWindow)
). Door dat te doen zijn er heel veel methods al voor ons gedefinieerd. Daar hoeven we verder niet over na te denken, onze interface werkt gewoon. Het gaat mis als wij zelf nieuwe methods gaan schrijven die dezelfde naam hebben. Stel dat de parent class QMainWindow
een method click_this_button()
heeft. Als onze class ook een method click_this_button()
heeft, dan zal die worden aangeroepen in plaats van de method uit de parent class. Dat is handig als je de parent method wilt vervangen maar niet zo handig als je de parent method wilt aanvullen, zoals nodig is bij __init__()
. Immers, we willen onze eigen class initialiseren, maar we willen ook dat de parent class volledig wordt ge\u00efnitialiseerd. De oplossing is gelukkig vrij eenvoudig: we kunnen de __init__()
van de parent class gewoon aanroepen en daarna ons eigen ding doen. De Pythonfunctie super()
verwijst altijd naar de parent class, dus met super().__init__()
wordt de parent class volledig ge\u00efnitialiseerd. Dat is dus het eerste dat we doen in regel 10. Kijk voor meer informatie over super().__init__()
in de paragraaf subclasses.
In de volgende opdrachten ga je zelf de hele applicatie opbouwen, zodat je precies weet wat in de code hierboven staat.
Parent class initialiseren
opdrachtcodecheck Je hebt geleerd hoe je widgets aan de applicatie kunt toevoegen. Omdat het veel stappen in een keer zijn ga je de instructies stap voor stap volgen en steeds tussendoor testen. Je begint met het maken van een __init__()
method voor de class UserInterface
en zorgt ervoor dat de parent class (QtWidgets.QMainWindow
) volledig wordt ge\u00efnitialiseerd. Je runt example-gui.py
en ziet dat er nog steeds een leeg venster wordt gestart. Je bent benieuwd of het initialiseren \u00e9cht nodig is, daarom haal je de super()
-aanroep weg en kijkt wat er gebeurd als je example-gui.py
runt. Je zet super()
-aanroep heel gauw weer terug.
Pseudo-code
import sys\n\nfrom PySide6 import QtWidgets\n\n# create subclass of QtWidgets.QMainWindow\n # def __init__()\n # initialise the parent class Qtwidgets.QMainWindow\n\ndef main():\n # create instance of QtWidgets.QApplication with arguments from sys.argv\n # create instance of subclass\n # call show method of subclass\n # get exit code with exec method of QApplication instance and give exit code to sys.exit()\n\n# when run this script:\n # run main function \n
Checkpunten:
- Er is een
__init__()
method gemaakt voor de subclass UserInterface
. - In de
__init__()
method wordt de parent class ge\u00efnitialiseerd (regel 10). - Er verschijnt een leeg venster.
Projecttraject:
- Minimale GUI
- Parent class initialiseren
- Central widget toevoegen
- textbox toevoegen
- knoppen toevoegen
- Slots en signals toevoegen
- 'Hello world' en Quit knoppen toevoegen
Verder heeft iedere applicatie een centrale widget nodig. Niet-centrale widgets zijn bijvoorbeeld een menubalk, knoppenbalk of statusbalk.
Central widget toevoegen
opdrachtcodecheck Nu de parent class wordt ge\u00efnitialiseerd kan je een widget aanmaken met QtWidgets.QWidget()
, je noemt deze widget central_widget
. En stelt deze in als centrale widget met de method setCentralWidget()
van de class QtWidgets.QMainWindow
. Je runt example-gui.py
en ziet dat er nog steeds een leeg venster wordt gestart.
Pseudo-code
import sys\n\nfrom PySide6 import QtWidgets\n\n# create subclass of QtWidgets.QMainWindow\n # def __init__()\n # initialise the parent class Qtwidgets.QMainWindow\n # create central widget with QtWidgets.QWidget()\n # set central widget\n\ndef main():\n # create instance of QtWidgets.QApplication with arguments from sys.argv\n # create instance of subclass\n # call show method of subclass\n # get exit code with exec method of QApplication instance and give exit code to sys.exit()\n\n# when run this script:\n # run main function \n
Checkpunten:
- Er is een central widget gemaakt met
QtWidgets.QWidget()
(regel 14). - De widget wordt als centrale widget ingesteld met
setCentralWidget()
(regel 15). - De method
setCentralWidget()
is afkomstig van de class QtWidgets.QMainWindow
welke ge\u00efnitialiseerd is, de method wordt daarom met self.setCentralWidget()
aangeroepen. - Er verschijnt een leeg venster.
Projecttraject:
- Minimale GUI
- Parent class initialiseren
- Central widget toevoegen
- textbox toevoegen
- knoppen toevoegen
- Slots en signals toevoegen
- 'Hello world' en Quit knoppen toevoegen
Daarna gaan we layouts en widgets toevoegen. Layouts zorgen ervoor dat elementen netjes uitgelijnd worden. We willen het tekstvenster en de knoppen onder elkaar zetten en maken dus eerst een verticale layout. Aan die layout voegen we een textbox toe.
textbox toevoegen
opdrachtcodecheck Omdat je de textbox en de knoppen onder elkaar wilt uitlijnen voeg je een verticale layout toe. Door de central_widget
mee te geven tijdens het aanmaken van de verticale layout is de layout automatisch onderdeel van de central widget en zal deze in het venster verschijnen. Je maakt een textbox aan en voegt deze toe aan de verticale layout. Je runt example-gui.py
en ziet een venster met een textbox verschijnen, je typt een vrolijke tekst en sluit het venster.
Pseudo-code
import sys\n\nfrom PySide6 import QtWidgets\n\n# create subclass of QtWidgets.QMainWindow\n # def __init__()\n # initialise the parent class Qtwidgets.QMainWindow\n # create central widget with QtWidgets.QWidget()\n # set central widget\n\n # create vertical layout as part of central widget\n # create textbox\n # add textbox to vertical layout\n\ndef main():\n # create instance of QtWidgets.QApplication with arguments from sys.argv\n # create instance of subclass\n # call show method of subclass\n # get exit code with exec method of QApplication instance and give exit code to sys.exit()\n\n# when run this script:\n # run main function \n
Checkpunten:
- Bij het aanmaken van de verticale layout is de
central_widget
als parameter meegegeven (regel 18). - Er is een tekstbox gemaakt (regel 19).
- De tekstbox (
QTextEdit
) is toegevoegd aan de verticale layout (regel 20). - Er verschijnt een venster met textbox waar je in kan typen .
Projecttraject:
- Minimale GUI
- Parent class initialiseren
- Central widget toevoegen
- textbox toevoegen
- knoppen toevoegen
- Slots en signals toevoegen
- 'Hello world' en Quit knoppen toevoegen
De knoppen zelf plaatsen we straks in een horizontale layout, dus die voegen we ook toe aan de vbox
. En we maken de layout compleet door knoppen toe te voegen aan de hbox
.
Knoppen toevoegen
opdrachtcodecheck Omdat de knoppen naast elkaar moeten komen te staan voeg je een horizontale layout toe aan de verticale layout. Je maakt een clear button
en een add button
en voegt deze toe aan de horizontale layout. Je runt example-gui.py
en ziet een venster met een textbox verschijnen met daaronder twee knoppen, je drukt verwoed op de knoppen maar er gebeurt niets4.
Pseudo-code
import sys\n\nfrom PySide6 import QtWidgets\n\n# create subclass of QtWidgets.QMainWindow\n # def __init__()\n # initialise the parent class Qtwidgets.QMainWindow\n # create central widget with QtWidgets.QWidget()\n # set central widget\n\n # create vertical layout as part of central widget\n # create textbox\n # add textbox to vertical layout\n\n # create horizontal layout\n # add horizontal layout to vertical layout\n\n # create clear_button\n # add clear button to horizontal layout\n # create add_text_button\n # add add_text_button to horizontal layout\n\ndef main():\n # create instance of QtWidgets.QApplication with arguments from sys.argv\n # create instance of subclass\n # call show method of subclass\n # get exit code with exec method of QApplication instance and give exit code to sys.exit()\n\n# when run this script:\n # run main function \n
Checkpunten:
- Er is een horizontale layout aangemaakt (regel 21).
- De horizontale layout is toegevoegd aan de verticale layout (regel 22).
- Er is een
clear_button
en add_text_button
aan gemaakt met daarop de tekst \"Clear\" en \"Add text\" respectievelijk (regels 24 en 26). - De buttons zijn toegevoegd aan de horizontale layout (regel 25 en 27).
- Als je op de knoppen drukt gebeurt er niets.
Projecttraject
- Minimale GUI
- Parent class initialiseren
- Central widget toevoegen
- textbox toevoegen
- knoppen toevoegen
- Slots en signals toevoegen
- 'Hello world' en Quit knoppen toevoegen
Info
Widgets zoals knoppen voeg je toe met addWidget()
. Layouts voeg je toe aan andere layouts met addLayout()
.
De horizontale layout (voor de knoppen) moeten we expliciet toevoegen aan de verticale layout zodat hij netjes verticaal onder het tekstvenster verschijnt. Merk op dat de verticale layout vbox
niet expliciet wordt toegevoegd (aan de centrale widget). De centrale widget (en alleen de centrale widget) krijgt een layout door bij het aanmaken van de layout de parent central_widget
op te geven, dus: QtWidgets.QVBoxLayout(central_widget)
. Alle andere widgets en layouts worden expliciet toegevoegd en daarvoor hoef je dus geen parent op te geven.
Als laatste verbinden we de knoppen aan functies. Zodra je op een knop drukt wordt er een zogeheten signal afgegeven. Die kun je verbinden met een slot. Er zijn ook verschillende soorten signalen. Het drukken op een knop zorgt voor een clicked signal, het veranderen van een getal in een keuzevenster geeft een changed signal. Wij verbinden \u00e9\u00e9n knop direct met een al bestaande method van het tekstvenster clear()
en de andere knop met een eigen method add_button_clicked()
. De naam is geheel vrij te kiezen, maar boven de functiedefinitie moet je wel de @Slot()
-decorator gebruiken (voor meer informatie over decorators zie paragraaf Decorators). PySide kan dan net wat effici\u00ebnter werken.
Slots en signals toevoegen
opdrachtcodecheck Je gaat functionaliteit aan de knoppen verbinden. Je verbint de clear_button
aan de clear()
method van textedit
. Je maakt een eigen Slot
met de naam add_text_button_clicked
die een tekst aan de textbox toegevoegd. Je vind de tekst \"You clicked me.\" maar suf en bedenkt zelf een andere leuke tekst. Je runt example-gui.py
en ziet een venster met een textbox verschijnen met daaronder twee knoppen. Je drukt op \"Add text\" en er verschijnt tekst in de textbox, daarna druk je op \"Clear\" en de tekst verdwijnt.
() ontbreken bij clear
en add_text_button_clicked
Bij het verbinden van het clicked
-signaal met clicked.connect()
geef je aan connect de methods clear
en add_text_button_clicked
mee zonder deze aan te roepen (dat gebeurt later). Concreet betekent dit dat je de haakjes weglaat (regel 30 en 31).
Pseudo-code
import sys\n\nfrom PySide6.QtCore import Slot\n\nfrom PySide6 import QtWidgets\n\n# create subclass of QtWidgets.QMainWindow\n # def __init__()\n # initialise the parent class Qtwidgets.QMainWindow\n # create central widget with QtWidgets.QWidget()\n # set central widget\n\n # create vertical layout as part of central widget\n # create textbox\n # add textbox to vertical layout\n\n # create horizontal layout\n # add horizontal layout to vertical layout\n\n # create clear_button\n # add clear button to horizontal layout\n # create add_text_button\n # add add_text_button to horizontal layout\n\n # connect clear_button to clear method of textedit\n # connect add_text_button to add_text_button_clicked\n\n # decorate method with Slot function\n # def add_text_button_clicked\n # add text to textedit\n\ndef main():\n # create instance of QtWidgets.QApplication with arguments from sys.argv\n # create instance of subclass\n # call show method of subclass\n # get exit code with exec method of QApplication instance and give exit code to sys.exit()\n\n# when run this script:\n # run main function \n
Checkpunten:
- Het
clicked
signaal van clear_button
is met connect
verbonden met de clear()
method van textedit
(regel 30). - Het clicked signaal van
add_text_button
is met connect
verbonden met een eigen method add_text_button_clicked
(regel 31). - De method
add_text_button_clicked
is voorzien van een decorator @Slot()
met Slot met een hoofdletter en ronde haakjes erachter omdat Slot een functie is (regel 33). - De
Slot
functie is ge\u00efmporteerd vanuit de PySide6.QtCore
. - De method
add_text_button_clicked
voegt met append
een tekst toe aan textedit
(regel 35). - Druk op de knop \"Add text\" zorgt voor het verschijnen van tekst in de textbox.
- Druk op de knop \"Clear\" zorgt ervoor dat alle tekst in de textbox verdwijnt.
Projecttraject
- Minimale GUI
- Parent class initialiseren
- Central widget toevoegen
- textbox toevoegen
- knoppen toevoegen
- Slots en signals toevoegen
- 'Hello world' en Quit knoppen toevoegen
Er zijn veel verschillende widgets met eigen methods en signals. Je vindt de lijst in de Qt for Python-documentatie. Qt zelf bestaat uit C++ code en PySide6 vertaalt alle methods e.d. letterlijk naar Python. Vandaar ook de methodnaam addWidget()
in plaats van add_widget()
. In C++ en Java is het wel gebruikelijk om functies CamelCase
namen te geven als kijkDitIsEenMooieFunctie()
, maar in Python zijn we snake_case
gewend, als in kijk_dit_is_een_mooie_functie()
.
Volgorde layout aanpassen De volgorde waarin je layout en widgets toevoegt bepaalt het uiterlijk van de grafische interface. Verander de code om de layout aan te passen (zet bijvoorbeeld de knoppen boven de textbox of zet de knoppen onder elkaar en naast de textbox).
'Hello world' en Quit knoppen toevoegen
opdrachtcodecheck Nu de minimale GUI werkt wil je meer knoppen toevoegen. Je begint met een knop Hello, world
die de tekst \"Hello, world\" aan de textbox toevoegd. Je runt example-gui.py
en ziet dat de knop werkt. Daarna voeg je een Quit
-knop toe die onder de andere knoppen staat. Het signaal van deze knop verbind je met de method self.close()
zodat de applicatie wordt afgesloten. Je runt example-gui.py
drukt nog een paar keer op de Hello, world
-knop en daarna op de knop Quit
, het venster is gesloten de opdracht is voltooid .
Pseudo-code
# create hello_world button and add to layout\n# create Quit button and add to layout\n\n# connect hello_world button to add_hello_world_clicked method\n# connect Quit button to self.close()\n\n# decorate with Slot\n# def add_hello_world_clicked\n # add Hello World to textbox \n
Checkpunten:
- De 'Add Text' en 'Clear' knoppen werken nog zoals verwacht.
- Druk op de
Hello World
knop voegt de text \"Hello World\" toe aan de textbox. - De
Quit
knop staat _ onder_ de andere knoppen. - Druk op de
Quit
knop sluit het venster.
Projecttraject
- Minimale GUI
- Parent class initialiseren
- Central widget toevoegen
- textbox toevoegen
- knoppen toevoegen
- Slots en signals toevoegen
- 'Hello world' en Quit knoppen toevoegen
"},{"location":"gui/#de-interface-ontwerpen-met-qt-designer","title":"De interface ontwerpen met Qt Designer","text":"Info
Druk in de video's op het vierkant rechtsboven om ze in volledig scherm te bekijken.
Designer opstarten
Info
Qt Designer wordt ge\u00efnstalleerd met het qt
package, dat standaard aanwezig is in Anaconda \u00e9n ge\u00efnstalleerd wordt als je PySide6
installeert. Je start hem het makkelijkst op vanuit een terminal. Activeer je test-qt
conda environment als dat nog nodig is en type pyside6-designer
.
De GUI ontwerpen in Designer
Zodra interfaces wat ingewikkelder worden is het een hoop werk om ze te programmeren. Daarom kun je met Qt Designer de interface ook visueel ontwerpen. Laten we eerst kijken hoe we widgets toevoegen en positioneren in Designer:
En hoe je de eigenschappen van widgets aanpast:
De GUI vertalen naar Python
Je bewaart dat als een .ui
-bestand. Vervolgens vertaal je het .ui
-bestand naar een Pythonbestand dat je importeert in je eigen programma:
Info
In het filmpje wordt verwezen naar de Compacte Pyside6 Documentatie. Zie het overzicht hieronder.
De volledige class van het vorige voorbeeld kan dan vervangen worden door:
from ui_simple_app import Ui_MainWindow\n\n\nclass UserInterface(QtWidgets.QMainWindow):\n def __init__(self):\n super().__init__()\n\n self.ui = Ui_MainWindow()\n self.ui.setupUi(self)\n\n self.ui.clear_button.clicked.connect(self.ui.textedit.clear)\n self.ui.add_button.clicked.connect(self.add_button_clicked)\n\n @Slot()\n def add_button_clicked(self):\n self.ui.textedit.append(\"You clicked me.\")\n
Waarbij de gebruikersinterface geladen wordt uit het bestand en we alleen nog maar de signals aan de slots hoeven te koppelen. In deze code defini\u00ebren we niet self.ui.clear_button
of self.ui.add_button
; die namen geven we aan de knoppen die we maken in Designer. De namen van alle objecten in Designer zijn daarna beschikbaar in onze code om bijvoorbeeld de signalen te koppelen. Merk op dat we nu niet meer self.clear_button
gebruiken maar self.ui.clear_button
. Alle widgets komen op deze manier onder een .ui
-object te hangen. Designer gebruiken
- Open Designer en kies bij templates/forms voor
MainWindow
. Klik dan op Create. Ontwerp de user interface van het screenshot en gebruik dezelfde namen voor de widgets als het voorbeeld. Dus een add_button
knop, een clear_button
knop en een textedit
tekstveld. Het is niet erg als je venster niet dezelfde grootte heeft. Qt Designer kiest een andere standaardafmeting. - Bewaar het bestand als
simple_app.ui
. ECPC
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 simple_app.ui
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 example-gui.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022 - In een terminal in Visual Studio Code, navigeer naar dezelfde map waarin je je script uit de vorige opdracht hebt staan5 en type in: Terminal
pyside6-uic simple_app.ui --output ui_simple_app.py \n
Deze stap moet je doen elke keer als je in Designer iets wijzigt. Gebruik de Up-toets om oude commando's terug te halen. Dat scheelt typewerk. Later, met Poetry, zullen we dit eenvoudiger maken. ECPC
\u251c\u2500\u2500 oefenopdrachten
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 simple_app.ui
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 simple_app.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 example-gui.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022 - Copy/paste nu de voorbeeldcode in een nieuw script, fix eventuele importerrors en test de applicatie.
"},{"location":"gui/#compacte-pyside6-documentatie","title":"Compacte PySide6 documentatie","text":"De documentatie van PySide6 is niet super-intu\u00eftief. Daarom hebben we speciaal voor jullie een Compacte PySide6 documentatie\ud83d\udcc4 geschreven. Daarin kan je een lijst van widgets vinden met de meest handige methods en signals. De documentatie is dus niet compleet maar genoeg voor een simpele GUI. Een overzicht van alle classes gedocumenteerd in de compacte documentatie vind je hieronder.
Classes:
QApplication
: Beheert de controleflow en hoofdinstellingen van de GUI-applicatie. QLayout
: Basisclass van alle layout-objecten in QtWidgets
. -
QWidget
: Basisclass van alle widget-objecten in QtWidgets
.
-
Subclasses van QLayout
:
QHBoxLayout
: Beheert een horizontale indeling van widgets. QVBoxLayout
: Beheert een verticale indeling van widgets. QGridLayout
: Beheert een roosterindeling waarbij de ruimte wordt verdeeld in rijen en kolommen. QFormLayout
: Beheert een indeling waarbij de ruimte wordt verdeeld in een linker kolom met labels en een rechter kolom met widgets.
-
Subclasses van QWidget
:
QMainWindow
: Biedt een framework voor het bouwen van de gebruikersinterface van een applicatie. QGroupBox
: Biedt een frame, een titel erboven, en kan verschillende andere widgets binnen zichzelf weergeven. QTextEdit
: Geeft tekst weer en stelt de gebruiker in staat om deze te bewerken. QCheckBox
: Schakelknop met een checkbox-indicator. QLabel
: Een widget die tekst weergeeft. QComboBox
: Een widget waarmee de gebruiker een keuze kan maken uit een lijst met opties. QSpinBox
: Een widget waarmee de gebruiker een geheel nummer kan kiezen uit een bereik. QDoubleSpinBox
: Een widget waarmee de gebruiker een komma getal kan kiezen uit een bereik. QPushButton
: Een knop die door de gebruiker kan worden ingedrukt. QLineEdit
: Een widget waarmee de gebruiker een enkele regel platte tekst kan invoeren en bewerken. QFileDialog
: Biedt een dialoogvenster waarmee de gebruiker bestanden of mappen kan selecteren.
"},{"location":"gui/#functieplotter","title":"Functieplotter","text":"Je hebt nu twee manieren gezien om een interface te bouwen: programmeren of Designer gebruiken. Let er wel op dat er dus een subtiel verschil is in het benaderen van de widgets. Je kunt bij zelf programmeren bijvoorbeeld self.add_button
gebruiken, maar als je Designer gebruikt moet dat self.ui.add_button
zijn.
In de eindopracht willen we data weergeven op een scherm. We zullen dus nog moeten plotten. In de volgende opdrachten gaan we daarmee aan de slag.
Je bent bekend met matplotlib en dat kan ook ingebouwd worden in Qt-applicaties. Helaas is matplotlib voor het gebruik in interactieve interfaces nogal traag zodra we te maken krijgen met meer data. We kiezen daarom voor een populair alternatief: PyQtGraph. E\u00e9n nadeel: de documentatie is niet fantastisch. Het geeft dus niets als je ergens niet uitkomt en je hulp nodig hebt van de assistent of een staflid.
"},{"location":"gui/#de-plotter-als-script","title":"De plotter als script","text":"Om PyQtGraph te importeren en globale opties in te stellen moeten we bovenaan ons programma het volgende schrijven:
import pyqtgraph as pg\n\n\n# PyQtGraph global options\npg.setConfigOption(\"background\", \"w\")\npg.setConfigOption(\"foreground\", \"k\")\n
Dit geeft zwarte letters op een witte achtergrond. Je kunt de opties weglaten en dan krijg je de standaardinstelling: grijze letters op een zwarte achtergrond. Het is maar wat je fijn vindt. Info
Als je je GUI het liefst programmeert, gebruik dan de volgende regel om een plot widget te krijgen in de __init__()
:
self.plot_widget = pg.PlotWidget()\n
Als je je GUI het liefst ontwerpt met Designer voegen we als volgt een plot widget toe: - Voeg aan je interface een Graphics View toe;
- Klik er op om hem te selecteren en klik daarna op de rechtermuistoets;
- Kies voor Promote To ...;
- Bij Promoted class name vul je in
PlotWidget
en bij Header file vul je in pyqtgraph
(zonder .h
aan het eind); - Dan klik je op Add en vervolgens op Promote.
De stappen zijn weergegeven in onderstaand screenshot. Bij de rode pijl vind je Graphics View en in het rode kader staat wat je moet invullen om te promoten:
Nu je dit een keer gedaan hebt kun je voortaan op een Graphics View meteen kiezen voor Promote to > PlotWidget en hoef je niets meer in te typen. Vergeet niet je widget nog even een handige naam te geven, bijvoorbeeld plot_widget
.
Om daadwerkelijk een functie te plotten kun je deze code aanpassen:
import numpy as np\n\nclass UserInterface(QtWidgets.QMainWindow):\n\n ...\n\n def plot(self):\n x = np.linspace(-pi, pi, 100)\n self.plot_widget.plot(x, np.sin(x), symbol=None, pen={\"color\": \"m\", \"width\": 5})\n self.plot_widget.setLabel(\"left\", \"y-axis [units]\")\n self.plot_widget.setLabel(\"bottom\", \"x-axis [units]\")\n
Je kunt uiteraard spelen met de instellingen zoals symbol
en pen
om te zien wat ze doen. Leeg maken kan met self.plot_widget.clear()
. Functionplotter: plot
opdrachtcodecheck We gaan een nieuwe repository aanmaken in de ECPC
map (zie hiernaast). Maak een Poetry project functionplotter
, voeg die toe aan GitHub Desktop en open hem in Visual Studio Code. Bekijk pyproject.toml
en zorg dat er een commando is aangemaakt om de applicatie te starten. Je maakt een nieuw conda environment aan met alleen Python daarin . Gebruik poetry install
om het project te installeren en voer het commando uit om de applicatie te starten. Als je applicatie af is verschijnt er een scherm met een plot waarin de functie $\\sin(x)$ plot in het domein $(0, 2\\pi)$ is weergegeven. Een golfje van trots gaat door je heen en je gaat door naar de volgende opdracht. ECPC
\u251c\u2500\u2500 pythondaq
\u251c\u2500\u2500 functionplotter
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 pyproject.toml
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code
# import statements\n\n# class UserInterface(QtWidgets.QMainWindow):\n ...\n # when app starts plot sin function\n\n# create application\n# show UserInterface\n# close properly\n
Checkpunten:
- Er is een repository
functionplotter
- Er is een commando om de applicatie te starten
- De applicatie laat een $\\sin(x)$ plot zien in het domein $(0, 2\\pi)$
- De applicatie werkt ook na
poetry install
in een nieuwe conda environment.
Projecttraject
- Functionplotter: plot
- Functionplotter: widgets
Functionplotter: widgets
opdrachtcodecheck Voer opnieuw het commando uit om de applicatie functionplotter
te starten. Dit keer zorg je dat de applicatie de mogelijkheid krijgt om het domein van de plot aan te passen. Je ziet dan de sinusplot veranderen wanneer je de startwaarde verhoogd. Je kunt de startwaarde ook naar beneden aanpassen. Hetzelfde geldt voor de stopwaarde. Dan maak je nog een widget om het aantal punten (num
) te kiezen waarmee de sinus wordt geplot. Speel eens met de widget en zie de sinus van hoekig naar mooi glad veranderen. Steeds als je een waarde aanpast moet de functie automatisch opnieuw geplot geworden.
Pseudo-code
# import statements\n\n# class UserInterface(QtWidgets.QMainWindow):\n ...\n # when app starts plot sin function\n\n # create widgets for start, stop and num in designer or in the script\n # connect widgets to methods\n\n # methods for start stop and num\n\n# create application\n# show UserInterface\n# close properly\n
Checkpunten:
- Het is mogelijk om de start waarde aan te passen.
- Het is mogelijk om de stop waarde aan te passen.
- Het is mogelijk om het aantal punten te kiezen waarmee de sinus functie wordt geplot.
- Na het aanpassen van een waarde wordt de plot automatisch opnieuw geplot.
Projecttraject
- Functionplotter: plot
- Functionplotter: widgets
Functieplotter: functie kiezen drop-down menu Gebruik een QComboBox
om de functie te kunnen kiezen. Je moet hem leeg toevoegen aan je interface en vult hem vanuit je programma. Zoek de widget op in de documentatie om uit te zoeken welke functie je moet gebruiken om keuzemogelijkheden toe te voegen en welk signaal je moet koppelen om te zorgen dat de plot opnieuw wordt uitgevoerd als je de functie aanpast. Geef de gebruiker de keuzes $\\sin(x)$, $\\cos(x)$, $\\tan(x)$ en $\\exp(x)$.
Functieplotter: meer functies Voeg aan de functiekiezer de functies $x$, $x^2$, $x^3$, en $\\frac{1}{x}$ toe. Je kunt daarvoor lambda functions gebruiken, maar dat is niet per se nodig.
Functieplotter: functies typen Vervang de functiekiezer door een tekstveld waarin de gebruiker zelf functies kan typen zoals x ** 2
, sin(x)
of 1 / sqrt(x + 1)
. Gebruik daarvoor het asteval
package.12 Documentatie vind je op https://newville.github.io/asteval/.
Waarschuwing
Gebruik nooit zomaar eval()
op een string die iemand anders aanlevert. Anders kan iemand met typen in een tekstveld of het inlezen van een tekstbestand je computer wissen bijvoorbeeld, of malware installeren. Als je eval()
wilt gebruiken, lees dan de sectie Minimizing the Security Issues of eval() in Python eval(): Evaluate Expressions Dynamically.13 Maar veel makkelijker is om asteval
te gebruiken.
"},{"location":"gui/#een-grafische-interface-voor-ons-experiment","title":"Een grafische interface voor ons experiment","text":"In het vorige hoofdstuk hebben we een tekst-interface geschreven voor ons experiment. We gaan nu een grafische interface schrijven voor hetzelfde experiment.
We hebben tot nu toe veel moeite gedaan om onze code te splitsen volgens het MVC-model: werken in laagjes, goed nadenken over wat waar hoort. Als dat netjes gelukt is kunnen we relatief makkelijk \u00e9\u00e9n van die laagjes vervangen. We kunnen de ArduinoVISADevice
vervangen door een RaspberryPiDevice
of een PicoScopeDevice
6. Ook kunnen we een nieuwe applicatie schrijven voor ons bestaande experiment. We hoeven dan alleen een extra view te schrijven (de interface met de gebruiker) en de rest kunnen we hergebruiken. Misschien dat we hier en daar iets willen aanpassen maar zorg er dan voor dat je oude applicatie nog steeds werkt!
We gaan nu \u2014 in stapjes \u2014 een grafische applicatie schrijven voor ons experiment.
Info
Je mag zelf kiezen of je de grafische interface gaat ontwerpen met Designer of dat je hem volledig programmeert.
Info
Als je Designer gaat gebruiken voor de grafische interface dan is het lastig dat je steeds pyside-uic
moet aanroepen en moet zorgen dat je in de goede directory staat. We kunnen met Poetry taken aanmaken die je met een eenvoudig commando kunt laten uitvoeren. Die taken zijn alleen beschikbaar tijdens het ontwikkelen van je applicatie. Doe dit als volgt:
- Installeer Poe the Poet \u2014 een zogeheten task runner \u2014 als development dependency met: Terminal
poetry add --group dev poethepoet\n
We geven hiermee aan dat we dit package nodig hebben voor de ontwikkeling van onze applicatie, maar dat deze niet meegeleverd hoeft te worden als we de applicatie gaan delen met anderen. - Voeg aan je
pyproject.toml
het volgende toe \u2014 uitgaande van de mappenstructuur in de pythondaq
package en mainwindow.ui
als naam van je .ui
-bestand: [tool.poe.tasks.compile]\nshell = \"\"\"\npyside6-uic src/pythondaq/mainwindow.ui --output src/pythondaq/ui_mainwindow.py\n\"\"\"\ninterpreter = [\"posix\", \"powershell\"]\n
Je kunt binnen de driedubbele aanhalingstekens meerdere regels toevoegen als je meerdere .ui
-bestanden hebt \u2014 voor ieder bestand een regel. - In bovenstaande regels is de naam na
tool.poe.tasks
de naam van de taak \u2014 in dit geval dus compile
. Je kunt die naam zelf kiezen en vervolgens gebruiken om de taak uit te voeren in de terminal: Terminalpoe compile\n
En dat gaat een stuk sneller dan die lange pyside-uic
-regel onthouden en intypen!
Pythondaq: leeg venster
opdrachtcodecheck Je wilt een toffe GUI maken voor de pythondaq
applicatie. Je gaat dit in stapjes opbouwen zodat je tussendoor nog kunt testen of het werkt. Je maakt een gui.py
aan waarin een leeg venster wordt gemaakt. Het lege venster wordt getoond zodra je een commando in de terminal intypt. Je sluit het venster. Om te testen of dit bij andere mensen ook zou werken maak je een nieuwe conda environment aan met Python , installeer je de package met Poetry en test je opnieuw het commando, er verschijnt opnieuw een leeg venster. ECPC
\u251c\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 src/pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 gui.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code
# create empty window\n
Checkpunten:
- Het uitvoeren van een commando zorgt ervoor dat een leeg venster wordt getoond.
- Het commando werkt ook na het installeren van de package
pythondaq
met Poetry in een nieuwe conda environment met Python.
Projecttraject
- Pythondaq: leeg venster
- Pythondaq: plot scan
- Pythondaq: widgets
- Pythondaq: save
- Pythondaq: selecteer Arduino
Pythondaq: plot scan
opdrachtcodecheck Als het commando wordt uitgevoerd start de applicatie een scan en laat de metingen vervolgens zien in een plot binnen het venster. Voor het gemak heb je de poortnaam, start- en stopwaardes e.d. hard coded in het script gezet. Later ga je er voor zorgen dat een gebruiker die kan instellen, maar dat komt straks wel.
Foutenvlaggen plotten
Foutenvlaggen toevoegen aan een pyqtgraph is helaas iets minder intuitief dan bij matplotlib. Met breedte en hoogte geef je aan hoe groot de vlaggen zijn, de vlag is 2 keer zo hoog of breed als de onzekerheid. Samen met de $x$ en $y$ data maak je dan een ErrorBarItem
aan die je expliciet toevoegt aan de plot. Let op: x
, y
, x_err
en y_err
moeten NumPy arrays zijn of, en dat geldt alleen voor de errors, een vast getal. Gewone lijsten werken helaas niet.
def plot(self):\n \"\"\"Clear the plot widget and display experimental data.\"\"\"\n\n # Genereer wat data als demo.\n # **Let op:** `x`, `y`, `x_err` en `y_err` *moeten* NumPy arrays zijn *of*,\n # en dat geldt alleen voor de errors, een vast getal.\n x = np.linspace(0, 2 * np.pi, 20)\n y = np.sin(x)\n x_err = 0.1\n y_err = np.random.normal(0, 0.2, size=len(x))\n\n # Maak eerst een scatterplot\n self.plot_widget.plot(x, y, symbol=\"o\", symbolSize=5, pen=None)\n\n # nu de foutvlaggen, met 'breedte' en 'hoogte' in plaats van x errors en y\n # errors.\n error_bars = pg.ErrorBarItem(x=x, y=y, width=2 * x_err, height=2 * y_err)\n # we moeten de error_bars expliciet toevoegen aan de plot\n self.plot_widget.addItem(error_bars)\n
Pseudo-code
# when application starts\n # ask model to do scan\n # plot results in application window\n
Checkpunten:
- Het uitvoeren van het commando zorgt ervoor dat een scan wordt gestart.
- Het LED lampje gaat branden.
- De resultaten van de meting worden geplot in het venster.
Projecttraject
- Pythondaq: leeg venster
- Pythondaq: plot scan
- Pythondaq: widgets
- Pythondaq: save
- Pythondaq: selecteer Arduino
Pythondaq: widgets
opdrachtcodecheck Na het uitvoeren van het commando start de pythondaq
applicatie waarin een lege plot en een aantal widgets zijn te zien waarmee de start- en stopwaardes, het aantal metingen kunnen worden ingesteld. Ook is er een startknop waarmee een (nieuwe) meting wordt uitgevoerd. Je vult verschillende (logische en niet logische) waardes in voor de start- en stopwaardes en het aantal metingen en ziet dat de applicatie naar verwachting werkt.
Pseudo-code
# create widgets in designer or in the script\n# for start, stop, measurements and start scan\n\n# connect widgets to methods\n\n# methods for start, stop, measurements and start scan\n
Checkpunten:
- De applicatie start op met een lege plot.
- In de applicatie kan de startwaarde in volt worden aangepast.
- In de applicatie kan de stopwaarde in volt worden aangepast.
- In de applicatie kan het aantal metingen worden aangepast.
- Druk op de startknop laat een meting starten.
- De applicatie werkt naar verwachting bij het invullen van logische en niet logische waardes voor start, stop en aantal metingen.
Projecttraject
- Pythondaq: leeg venster
- Pythondaq: plot scan
- Pythondaq: widgets
- Pythondaq: save
- Pythondaq: selecteer Arduino
"},{"location":"gui/#bewaren-van-meetgegevens","title":"Bewaren van meetgegevens","text":"Je zou na iedere meting de gegevens automatisch kunnen wegschrijven naar bestanden zonder dat de gebruiker nog iets kan kiezen, maar je kunt ook gebruik maken van een Save
-knop en dialoogvensters. Je kunt de knop koppelen aan een method save_data()
en daarin de volgende regel opnemen:
filename, _ = QtWidgets.QFileDialog.getSaveFileName(filter=\"CSV files (*.csv)\")\n
De functie getSaveFileName()
opent een dialoogvenster om een bestand op te slaan. Vanwege het filter argument geeft het venster (op sommige besturingssystemen) alleen CSV-bestanden weer. In elk geval geldt op alle besturingssystemen dat als de gebruiker als naam metingen
intypt, dat het filterargument ervoor zorgt dat er automatisch .csv
achter geplakt wordt.7 De functie geeft twee variabelen terug: filename
en filter
, die je zelf hebt meegegeven in bovenstaande aanroep. Die laatste kenden we dus al en gooien we weg met behulp van de weggooivariabele _
.
Het enige dat het dialoogvenster doet is de gebruiker laten kiezen waar en onder welke naam het bestand moet worden opgeslagen. Je krijgt echt alleen een pad en bestandsnaam terug, de data is niet opgeslagen en het bestand is niet aangemaakt. De variabele filename
is echt niets anders dan een bestandsnaam, bijvoorbeeld: /Users/david/LED-rood.csv
. Nadat je die bestandsnaam gekregen hebt moet je dus zelf nog code schrijven zodat het CSV-bestand wordt opgeslagen onder die naam.
Pythondaq: save
opdrachtcodecheck Breid je code zodanig uit dat het volgende werkt: Je opent de applicatie en start een scan. Dan valt je oog op een Save
-knop, wanneer je op deze knop drukt wordt er een dialoogvenster geopent. Je kiest een locatie en typt een bestandsnaam, je klikt op Save
(of Opslaan
). Daarna ben je nieuwsgierig of het gelukt is. Via File Explorer
(of Verkenner
) navigeer je op de computer naar de locatie waar je het bestand hebt opgeslagen. Je opent het bestand en ziet de metingen staan. Tevreden sluit je het bestand af en ga je door naar de volgende opdracht.
Pseudo-code
# create widget for save in designer or in the script\n\n# connect widget to method\n\n# methods save\n # open dialog\n # save measurements as csv in given filename\n
Checkpunten:
- Druk op de knop
Save
opent een dialoogvenster. - De metingen worden opgeslagen als csv-bestand op de gegeven locatie en onder de gegeven bestandsnaam.
Projecttraject
- Pythondaq: leeg venster
- Pythondaq: plot scan
- Pythondaq: widgets
- Pythondaq: save
- Pythondaq: selecteer Arduino
Menu's, taak- en statusbalken"},{"location":"gui/#menus-taak-en-statusbalken","title":"Menu's, taak- en statusbalken","text":"Je kunt je grafische applicatie volledig optuigen met menu's of taakbalken. Ook kun je onderin je applicatie met een statusbalk weergeven wat de status is: gereed, aan het meten, foutcode, etc. Dat valt buiten het bestek van deze cursus, maar een mooie referentie is PySide6 Toolbars & Menus \u2014 QAction.14 Als je vaker grafische applicaties wilt gaan maken dan moet je dat zeker eens doornemen!
Pythondaq: statusbalk
Maak een statusbalk die aangeeft wat de identificatiestring is van het device dat geselecteerd is. Maak ook een menu waarmee je een CSV-bestand kunt opslaan en een nieuwe meting kunt starten. Let op: je hebt dan een menu-item \u00e9n een knop die dezelfde method aanroepen. Je hoeft geen dubbele code te schrijven, maar moet de save_data()
-method wel twee keer verbinden.
"},{"location":"gui/#selecteer-de-arduino","title":"Selecteer de Arduino","text":"Je hebt nu waarschijnlijk nog de poortnaam van de Arduino in je code gedefinieerd als vaste waarde. Dat betekent dat als je de code deelt met iemand anders \u2014 bijvoorbeeld wanneer je de code inlevert op Canvas of wanneer je je experiment op een labcomputer wilt draaien \u2014 je het risico loopt dat je applicatie crasht omdat de Arduino aan een andere poort hangt. Zeker bij de overstap van Windows naar MacOS of Linux, of andersom! Je kunt dit op twee manieren oplossen:
- Je maakt een keuzemenu waarmee de gebruiker de Arduino kan selecteren;
- Je probeert de Arduino te detecteren op \u00e9\u00e9n van de poorten. De gebruiker hoeft dan niet te weten welke poort dat zou kunnen zijn. Het werkt dan vanzelf!
Je kunt je voorstellen dat mogelijkheid 2 de voorkeur heeft! Helaas is dit moeilijker dan gedacht. Zodra je andere devices gaat openen en commando's gaat sturen om te ontdekken wat voor apparaat het is kunnen er gekke dingen gebeuren. Onder MacOS bijvoorbeeld kunnen Bluetooth luidsprekers en koptelefoons opeens ontkoppelen. We gaan dus toch voor keuze 1. Bijkomend voordeel van deze keuze is dat je meerdere Arduino's aan je computer kunt hangen en kunt schakelen \u2014 vooral handig als je meerdere experimenten vanaf \u00e9\u00e9n computer wilt aansturen.
Pythondaq: selecteer Arduino
opdrachtcodecheck Je opent de applicatie en ziet een keuzemenu (QComboBox
) waarmee je de Arduino kunt selecteren. Je selecteert de juiste Arduino, start een meting en ziet het LED lampje branden. Je sluit de applicatie af en bent benieuwd wat er gebeurt als je meerdere Arduino's aansluit. Dus vraag je een (of twee, of drie) Arduino('s) van je buren, sluit deze aan op je computer en start opnieuw de applicatie. Je ziet dat er meerdere apparaten in het keuzemenu staan. Je kiest een Arduino, start een meting en ziet een lampje branden. Daarna selecteer je een andere Arduino, start een meting en ziet een ander lampje branden, hoe leuk .
Arduino afsluiten
Als je met meerdere Arduino's werkt kan het handig zijn om na afloop van de scan de communicatie met de Arduino weer te sluiten. In de opdracht Pyvisa in terminal heb je al eens gewerkt met het commando close
. Dit werkt ook voor pyvisa in een script. Je hebt in de controller de communicatie geopend met self.device = rm.open_resource(port, read_termination=\"\\r\\n\", write_termination=\"\\n\")
, je kunt de communicatie met self.device
in de controller sluiten met self.device.close()
. Je kunt een method in de controller toevoegen die de communicatie sluit. Via het model kun je deze method aanroepen in de gui.
Pseudo-code
# create widget for select Arduino\n\n# method start scan\n # open communication with selected Arduino\n # start scan\n # close communication with selected Arduino\n
Checkpunten:
- In de applicatie kan een Arduino geselecteerd worden.
- De gekozen Arduino wordt gebruikt tijdens het uitvoeren van een scan
Projecttraject
- Pythondaq: leeg venster
- Pythondaq: plot scan
- Pythondaq: widgets
- Pythondaq: save
- Pythondaq: selecteer Arduino
Threads -
Uitspraak: het Engelse cute.\u00a0\u21a9
-
Of in \u00e9\u00e9n keer met View > Command Palette > Terminal: Kill All Terminals \u21a9
-
Die kun je eventuele command-line arguments meegeven die door Python in sys.argv
bewaard worden. Meestal zijn die leeg, maar we geven ze gewoon door aan Qt.\u00a0\u21a9
-
Waarom doen de knoppen niets als je er op klikt?\u00a0\u21a9
-
Overleg met elkaar of met de assistent als je niet weet hoe dat moet.\u00a0\u21a9
-
Je moet dan wel eerst nieuwe controllers schrijven (of krijgen van een collega) om deze nieuwe instrumenten aan te sturen. Maar als je die hebt kun je vrij eenvoudig wisselen.\u00a0\u21a9
-
Het eerste deel van het argument (CSV files
) is vrij te kiezen en geeft alleen informatie aan de gebruiker. Het deel tussen haakjes (*.csv
) is het gedeelte dat echt van belang is. Het geeft de extensie die achter alle bestandsnamen geplakt wordt.\u00a0\u21a9
-
Er is een subtiliteit. In Python draaien threads niet tegelijk, maar om de beurt. In de praktijk merk je daar niet veel van: threads worden z\u00f3 vaak per seconde gewisseld dat het lijkt alsof ze tegelijk draaien. Terwijl de ene thread steeds even tijd krijgt voor een meting kan de andere thread steeds even de plot verversen. In het geval van zwaar rekenwerk schiet het alleen niet op. Er draait maar \u00e9\u00e9n berekening tegelijkertijd dus threads of niet, het is even snel. Wil je echt parallel rekenen, dan moet je kijken naar de multiprocessing
module om meerdere processen te starten in plaats van threads.\u00a0\u21a9
-
Variabelen die we in een class defini\u00ebren door ze aan te maken met self.
ervoor zijn instance attributes.\u00a0\u21a9
-
Hier zie je een probleem met threads. Het k\u00e1n \u2014 in uitzonderlijke situaties \u2014 voorkomen dat de plot-functie n\u00e9t wil gaan plotten als de $x$-waardes al langer gemaakt zijn, maar de $y$-waardes nog niet. Die kans is heel klein en wij accepteren het risico. Schrijf je software voor een complex experiment dat drie dagen draait, dan is dit iets waar je echt rekening mee moet houden. Je moet dan gebruik gaan maken van zogeheten locks of semaphores maar dat valt buiten het bestek van deze cursus.\u00a0\u21a9
-
Door een beetje ons best te doen kunnen we ervoor zorgen dat zowel de command-line interface als de grafische interface allebei gebruikt kunnen worden.\u00a0\u21a9
-
Matt Newville. Asteval: minimal python ast evaluator. URL: https://newville.github.io/asteval.\u00a0\u21a9
-
Leodanis Pozo Ramos. Python eval(): evaluate expressions dynamically. 2020. URL: https://realpython.com/python-eval-function/.\u00a0\u21a9
-
Martin Fitzpatrick. Pyside6 toolbars & menus \u2014 qaction. 2021. URL: https://www.pythonguis.com/tutorials/pyside6-actions-toolbars-menus/.\u00a0\u21a9
"},{"location":"gui/#meerdere-dingen-tegelijkertijd-threads","title":"Meerdere dingen tegelijkertijd: threads","text":"Afhankelijk van de instellingen die we gekozen hebben kan een meting best lang duren. In ieder geval moeten we even wachten tot de meting afgelopen is en pas daarna krijgen we de resultaten te zien in een plot. Als een meting langer duurt dan een paar seconden kan het besturingssysteem zelfs aangeven dat onze applicatie niet meer reageert. En inderdaad, als we ondertussen op knoppen proberen te drukken dan reageert hij nergens op. Onze applicatie kan helaas niet twee dingen tegelijk. Kon hij dat wel, dan zouden we zien hoe de grafiek langzaam opbouwt tot het eindresultaat.
De manier waarop besturingssystemen meerdere dingen tegelijk doen is gebaseerd op processes en threads. Een process is, eenvoudig gezegd, een programma. Als je meerdere applicaties opstart zijn dat allemaal processen. Besturingssystemen regelen dat ieder proces een stuk geheugen krijgt en tijd van de processor krijgt toegewezen om zijn werk te doen. Processen zijn mooi gescheiden en kunnen dus eenvoudig naast elkaar draaien. Het wordt iets lastiger als een proces meerdere dingen tegelijk wil doen. Dat kan wel, met threads. Het besturingssysteem zorgt dat meerdere threads naast elkaar draaien.8
Threads geven vaak problemen omdat ze in zekere zin onvoorspelbaar zijn. Je weet niet precies hoe snel een thread draait, dus je weet niet zeker wat er in welke volgorde gebeurt. Dit kan leiden tot problemen waarvan de oorzaak maar lastig te vinden is. Google maar eens op thread problems in programming
. We moeten dus voorzichtig zijn! Ook is het ombouwen van code zonder threads naar code met threads een klus waar makkelijk iets fout gaat. Het is dus belangrijk dat je in kleine stapjes je code aanpast en vaak test of het nog werkt.
Info
We gaan in het volgende stuk een kleine applicatie ombouwen van no-threads naar threads. We raden je ten zeerste aan om de code te copy/pasten en dan stapje voor stapje aan te passen zoals in de handleiding gebeurt. Probeer alle stappen dus zelf! Pas na stap 4 ga je aan de slag om je eigen code om te bouwen. Samenvattend: doorloop dit stuk handleiding twee keer. De eerste keer doe je de opdrachten met het demoscript, de tweede keer met je eigen code voor pythondaq
.
view.pymodel.py import sys\n\nimport numpy as np\n\nfrom PySide6 import QtWidgets\nimport pyqtgraph as pg\n\nfrom model import Experiment\n\n\nclass UserInterface(QtWidgets.QMainWindow):\n def __init__(self):\n super().__init__()\n\n central_widget = QtWidgets.QWidget()\n self.setCentralWidget(central_widget)\n\n vbox = QtWidgets.QVBoxLayout(central_widget)\n self.plot_widget = pg.PlotWidget()\n vbox.addWidget(self.plot_widget)\n start_button = QtWidgets.QPushButton(\"Start\")\n vbox.addWidget(start_button)\n\n start_button.clicked.connect(self.plot)\n\n # Maak een instance aan van Experiment\n self.experiment = Experiment()\n\n def plot(self):\n \"\"\" Clear the plot widget and display experimental data. \"\"\"\n self.plot_widget.clear()\n x, y = self.experiment.scan(0, np.pi, 50)\n self.plot_widget.plot(x, y, symbol=\"o\", symbolSize=5, pen=None)\n\ndef main():\n app = QtWidgets.QApplication(sys.argv)\n ui = UserInterface()\n ui.show()\n sys.exit(app.exec())\n\n\nif __name__ == \"__main__\":\n main() \n
import time\nimport numpy as np\n\nclass Experiment:\n def scan(self, start, stop, steps):\n \"\"\" Perform a scan over a range with specified steps and return the scanned values. \"\"\"\n x = np.linspace(start, stop, steps)\n y = []\n for u in x:\n y.append(np.sin(u))\n time.sleep(0.1)\n return x, y\n
In regels 15--24 bouwen we een kleine user interface op met een plot widget en een startknop. We koppelen die knop aan de plot()
-method. In regel 27 maken we ons experiment (het model) aan en bewaren die. In regels 30--34 maken we de plot schoon, voeren we een scan uit en plotten het resultaat. model.py
vormt ons experiment. Eerst wordt een rij $x$-waardes klaargezet en dan, in een loop, wordt punt voor punt de sinus uitgerekend en toegevoegd aan een lijst met $y$-waardes. De time.sleep(.1)
wacht steeds 0.1 s en zorgt hiermee voor de simulatie van trage metingen. En inderdaad, als we deze code draaien dan moeten we zo'n vijf seconden wachten voordat de plot verschijnt.
In de volgende opdrachten gaan we de code stap voor stap ombouwen naar threads. Als we daarmee klaar zijn worden de metingen gedaan binnen de scan()
-method van de Experiment()
-class en verversen we ondertussen af en toe de plot. De plot()
-method van onze user interface wordt regelmatig aangeroepen terwijl de meting nog loopt en moet dus de hele tijd de huidige metingen uit kunnen lezen. Dat kan, als de metingen worden bewaard in instance attributes.9
Threads 0
Neem view.py
en model.py
over en test de applicatie.
"},{"location":"gui/#stap-1-de-meetgegevens-altijd-beschikbaar-maken","title":"Stap 1: de meetgegevens altijd beschikbaar maken","text":"We maken in de scan()
-method lege lijsten self.x
en self.y
. Hier komen de meetgegevens in en die staan dus los van de lijst met $x$-waardes die je klaarzet. Met andere woorden: de variabele x
is niet hetzelfde als de variabele self.x
:
model.py class Experiment:\n def scan(self, start, stop, steps):\n x = np.linspace(start, stop, steps)\n self.x = []\n self.y = []\n for u in x:\n self.x.append(u)\n self.y.append(np.sin(u))\n time.sleep(0.1)\n return self.x, self.y\n
We zorgen er zo voor dat de lijst met meetgegevens voor zowel de $x$- als de $y$-waardes steeds even lang zijn. Dit is nodig voor het plotten: hij kan geen grafiek maken van 50 $x$-waardes en maar 10 $y$-waardes.10 Ook moeten we er voor zorgen dat er altijd (lege) meetgegevens beschikbaar zijn \u2014 ook als de meting nog niet gestart is. Anders krijgen we voordat we een meting hebben kunnen doen een foutmelding dat self.x
niet bestaat. We doen dat in de __init__()
:
model.py class Experiment:\n def __init__(self):\n self.x = []\n self.y = []\n\n ...\n
We laten self.x = []
(en idem voor self.y
) ook staan in de scan()
-methode zodat bij iedere nieuwe scan de oude meetgegevens worden leeggemaakt.
Threads I
Pas de code aan zodat de meetgegevens altijd beschikbaar zijn. Test je code, de applicatie moet nog steeds werken.
"},{"location":"gui/#stap-2-plot-de-meetgegevens-vanuit-het-experiment","title":"Stap 2: plot de meetgegevens vanuit het experiment","text":"Nu we de meetgegevens bewaren als instance attributes van de Experiment
-class kunnen we die ook plotten. We geven ze nog steeds terug als return value vanuit de scan()
-method voor ouderwetse code,11 maar wij gaan nu de nieuwerwetse instance attributes gebruiken:
view.py class UserInterface(QtWidgets.QMainWindow):\n\n ...\n\n def plot(self):\n \"\"\" Clear the plot widget and display experimental data. \"\"\"\n self.plot_widget.clear()\n self.experiment.scan(0, np.pi, 50)\n self.plot_widget.plot(\n self.experiment.x, self.experiment.y, symbol=\"o\", symbolSize=5, pen=None\n )\n
De code wordt hier niet sneller van \u2014 hij maakt nog steeds pas een grafiek als de meting helemaal is afgelopen \u2014 maar we bereiden de code wel voor op het gebruik van de instance attributes.
Threads II
Pas de code aan zodat je instance attributes gebruikt voor het plotten. Test je code, het moet nog steeds werken als vanouds.
"},{"location":"gui/#stap-3-threads","title":"Stap 3: threads","text":"We gaan nu met threads werken. Je importeert daarvoor de threading
module en maakt voor iedere thread een threading.Thread()
instance. Deze heeft twee belangrijke parameters: target
waarmee je de functie (of method) aangeeft die in de thread moet worden uitgevoerd, en args
waarmee je argumenten meegeeft voor die functie of method. We maken een nieuwe method start_scan()
waarmee we een nieuwe thread starten om een scan uit te voeren. We doen dit als volgt:
model.py import threading\n\nclass Experiment:\n def start_scan(self, start, stop, steps):\n \"\"\"Start a new thread to execute a scan.\"\"\"\n self._scan_thread = threading.Thread(\n target=self.scan, args=(start, stop, steps)\n )\n self._scan_thread.start()\n\n def scan(self, start, stop, steps):\n \"\"\" Perform a scan over a range with specified steps and return the scanned values. \"\"\"\n x = np.linspace(start, stop, steps)\n self.x = []\n self.y = []\n for u in x:\n self.x.append(u)\n self.y.append(np.sin(u))\n time.sleep(0.1)\n return self.x, self.y\n
In plaats van dat onze plotfunctie de scan()
-method aanroept, moeten we nu de start_scan()
-method aanroepen. Maar: die method start een scan en sluit meteen af, terwijl de daadwerkelijke meting op de achtergrond wordt uitgevoerd. De plotfunctie moet \u2014 in deze stap nog even \u2014 wachten tot de scan klaar is. Er is een manier om op een thread te wachten. Je moet daartoe de join()
method van de thread aanroepen. In bovenstaande code hebben we de thread bewaard in de variabele _scan_thread
, dus hij is voor ons beschikbaar:
view.py class UserInterface(QtWidgets.QMainWindow):\n\n ...\n\n def plot(self):\n \"\"\" Clear the plot widget and display experimental data. \"\"\"\n self.plot_widget.clear()\n self.experiment.start_scan(0, np.pi, 50)\n self.experiment._scan_thread.join()\n self.plot_widget.plot(\n self.experiment.x, self.experiment.y, symbol=\"o\", symbolSize=5, pen=None\n )\n
Threads III
- Pas de code aan zodat je een thread opstart om de scan op de achtergrond uit te voeren. Roep in je plotfunctie de goede method aan en wacht tot de thread klaar is. Test je code. Wederom moet het werken als vanouds.
- Kijk ook eens wat er gebeurt als je niet wacht tot de metingen klaar zijn door de regel
self.experiment._scan_thread.join()
uit te commentari\u00ebren (hekje ervoor). Niet vergeten het hekje weer weg te halen.
"},{"location":"gui/#stap-4-plotten-op-de-achtergrond","title":"Stap 4: plotten op de achtergrond","text":"We zijn er nu bijna. We gebruiken threads om de metingen op de achtergrond uit te voeren maar we wachten nog steeds tot de metingen klaar zijn voordat we \u2014 eenmalig \u2014 de grafiek plotten. In deze laatste stap doen we dat niet meer. Als je straks op de startknop drukt dan start de meting op de achtergrond. Ondertussen wordt er regelmatig geplot. Je ziet dan tijdens de metingen de plot opbouwen. We doen dat door het scannen en plotten van elkaar los te koppelen \u2014 niet meer samen in \u00e9\u00e9n functie \u2014 en door met een QTimer
de plotfunctie periodiek aan te roepen. Kijk de code goed door.
view.py from PySide6 import QtWidgets, QtCore\n\nclass UserInterface(QtWidgets.QMainWindow):\n def __init__(self):\n super().__init__()\n\n ...\n\n start_button.clicked.connect(self.start_scan)\n\n ... \n\n # Plot timer\n self.plot_timer = QtCore.QTimer()\n # Roep iedere 100 ms de plotfunctie aan\n self.plot_timer.timeout.connect(self.plot)\n self.plot_timer.start(100)\n\n def start_scan(self):\n \"\"\"Starts a scanning process with specified parameters.\"\"\"\n self.experiment.start_scan(0, np.pi, 50)\n\n def plot(self):\n \"\"\" Clear the plot widget and display experimental data. \"\"\"\n self.plot_widget.clear()\n # Twee regels code zijn verwijderd\n self.plot_widget.plot(\n self.experiment.x, self.experiment.y, symbol=\"o\", symbolSize=5, pen=None\n )\n
Hiermee zijn we klaar met de implementatie van threads. De gebruiker hoeft niet langer in spanning te wachten maar krijgt onmiddelijke feedback.
Threads IV
Pas de code op dezelfde manier aan zodat de metingen op de achergrond worden uitgevoerd terwijl je de plot ziet opbouwen. De code werkt nu niet als vanouds, en voelt veel sneller!
Pythondaq: threads in je eigen code
Doorloop nu opnieuw stappen 1 t/m 4 maar dan voor je eigen pythondaq
-applicatie.
Events"},{"location":"gui/#stap-5-puntjes-op-de-i-events","title":"Stap 5: puntjes op de i: events","text":"Wanneer je op de startknop drukt, even wacht en dan w\u00e9\u00e9r op de startknop drukt, dan kun je zien dat er twee metingen tegelijk worden uitgevoerd op de achtergrond. Dat wil je voorkomen. Ook is het wel aardig om metingen tussentijds te kunnen stoppen. Dat is vooral handig als je merkt dat een meting veel te lang gaat duren. Verder is het ook nog zo dat we er nu met onze timer voor gezorgd hebben dat de plotfunctie meerdere keren per seconde wordt uitgevoerd \u2014 of er nu een meting loopt of niet.
Je kunt dit oplossen met threading.Event()
objecten. Dit zijn objecten met set()
, clear()
en wait()
methods om gebeurtenissen aan te geven of er op te wachten. Zo kun je een event is_scanning
aanmaken die je set()
zodra een meting begint en clear()
zodra de meting is afgelopen. Je controleert bij de start van de meting dan bijvoorbeeld eerst of de meting al loopt met is_scanning.is_set()
en start alleen een meting als dat nog niet zo is.
Ook kun je in de grafische interface na het starten van een meting de startknop onbeschikbaar maken met start_button.setEnabled(False)
en weer beschikbaar maken met start_button.setEnabled(True)
. De knop wordt dan tussendoor grijs. Dat kan handig zijn om duidelijk te maken dat een meting al loopt en dat je niet nogmaals op de startknop kunt drukken.
Vergrendelen
Pas je code aan zodat je niet meerdere metingen tegelijk kunt starten. Zorg er ook voor dat de grafiek alleen geplot wordt tijdens de metingen (of tot kort daarna), maar niet de hele tijd.\n
"},{"location":"kleurcodes/","title":"Kleurcodes voor weerstanden","text":"Kleur Cijferwaarde Vermenigvuldigingsfactor Tolerantie (%) Zilver --- 10-2 10 Goud --- 10-1 5 Zwart 0 100 --- Bruin 1 101 1 Rood 2 102 2 Oranje 3 103 0.05 Geel 4 104 0.02 Groen 5 105 0.5 Blauw 6 106 0.25 Paars 7 107 0.1 Grijs 8 108 0.01 Wit 9 109 --- Helaas is het niet altijd mogelijk om de linkerkant van de weerstand van de rechterkant te onderscheiden. In dat geval moet je de weerstand beide kanten oplezen en vergelijken met je materialenlijst of de overige weerstanden om zeker te weten dat je de goede hebt gevonden. Bovenstaande weerstand heeft de waarde 220\u22c5100 \u03a9 \u00b1 1 %, en niet de waarde 100\u22c5102 \u03a9 \u00b1 2 %.
"},{"location":"mvc/","title":"Model-View-Controller","text":""},{"location":"mvc/#mvc-en-het-gebruik-van-packages","title":"MVC en het gebruik van packages","text":"MVC staat voor Model-View-Controller en is een belangrijk, maar wat diffuus concept in software engineering en is vooral van toepassing op gebruikersinterfaces. Het belangrijkste idee is dat een programma zoveel mogelijk wordt opgesplitst in onderdelen. Het model bevat de onderliggende data en concepten van het programma (een database, meetgegevens, berekeningen, etc.); de controller praat met de fysieke omgeving en reageert bijvoorbeeld op invoer van een gebruiker en past het model aan; de view is een weergave van de data uit het model en vormt de gebruikersinterface zelf. Vaak praten alle onderdelen met elkaar, maar een gelaagd model is makkelijker te overzien en dus eenvoudiger te programmeren. In het geval van een natuurkunde-experiment is dit vaak mogelijk. Daarmee krijgt MVC bij ons een andere betekenis dan bijvoorbeeld bij het bouwen van websites. Het gelaagd MVC-model dat wij gaan gebruiken is hieronder weergegeven:
De controllers communiceren met de apparatuur, bevat informatie en berekeningen die apparatuur afhankelijk zijn; het model bevat de meetgegevens, berekeningen over - en de opzet van - het experiment; de view zorgt voor een gebruikersinterface met weergave van de data.
Het scheiden van je programma in deze lagen kan enorm helpen om ervoor te zorgen dat je geen spaghetticode schrijft \u2014 ongestructureerde en moeilijk te begrijpen code. Wanneer het drukken op een knop in de code van de grafische omgeving direct commando's stuurt naar de Arduino of dat de code voor het doen van een enkele meting meteen de $x$-as van een grafiek aanpast, sla je lagen over in ons model en knoop je delen van het programma aan elkaar die niet direct iets met elkaar te maken hebben. De knop moet een meting starten, ja, maar hoe dat precies moet is niet de taak van de gebruikersinterface. En de meting zelf moet zich niet bemoeien met welke grafiek er precies getekend wordt. Je zult merken dat het heel lastig wordt om overzicht te houden en later aanpassingen te doen als je alles door elkaar laat lopen. Je zult dan door je hele code moeten zoeken als je \u00f3f de aansturing van de Arduino, \u00f3f de grafische interface wilt aanpassen. En dus gaan we alles netjes structureren.
De verschillende onderdelen in het model kunnen we voor ons experiment als volgt beschrijven:
View Het startpunt van je applicatie. Geeft de opdracht om een meting te starten en geeft na afloop de resultaten van de meting weer op het scherm. Model De code die het experiment uitvoert door verschillende metingen te doen en instellingen aan te passen, zoals de spanning over de LED. Het model weet hoe het experiment in elkaar zit en dat er bijvoorbeeld een weerstand van 220 \u03a9 aanwezig is. Geeft opdrachten aan de controller. Controller De code die via pyvisa praat met de Arduino. Opdrachten worden omgezet in firmwarecommando's en doorgestuurd naar het apparaat. Het opsplitsen van je programma hoeft niet in \u00e9\u00e9n keer! Dit kan stapsgewijs. Je kunt starten met een eenvoudig script \u2014 zoals we hierboven gedaan hebben \u2014 en dat langzaam uitbreiden. Je begint klein, verdeelt je code in lagen en bouwt vervolgens verder.
"},{"location":"mvc/#implementeren-van-mvc","title":"Implementeren van MVC","text":"Het opsplitsen van het diode-experiment.py
in MVC gaan we stapsgewijs doen. We gaan een class maken voor de aansturing van de Arduino, deze class valt in de categorie controller.
Pythondaq: open de repository
Open in GitHub Desktop de repository van pythondaq
en open de repository in Visual Studio Code. In de volgende opdrachten ga je het diode-experiment.py
uitbreiden en opsplitsen in MVC.
Pythondaq: controller bouwen
opdrachtcodecheck Je schrijft een script waarmee je de Arduino aanstuurt. Een gebruiker test de door jou geschreven controller met de volgende handelingen. De gebruiker vraag een lijst met beschikbare poorten op met de functie list_resources()
. De gebruiker weet aan welke poort de Arduino hangt en gebruikt deze poortnaam om een instance aan te maken van de class ArduinoVISADevice
. Met deze class kan de gebruiker met de Arduino communiceren. Met de method get_identification()
vraagt de gebruiker de identificatiestring op. De gebruiker zet met de method set_output_value()
om een waarde van 828 op het uitvoerkanaal 0, de gebruiker zit de LED branden en weet daarom dat de method werkt. De gebruiker vraag met de method get_input_value()
de spanning op kanaal 1 op, dit herhaald de gebruiker vervolgens voor kanaal 2. Met de method get_input_voltage()
vraagt de gebruiker de spanning op in volt. De gebruiker rekent de gegeven waarde van get_input_value()
op kanaal 1 om naar volt en ziet dat deze overeenkomt met de gegeven spanning door de method get_input_voltage()
op kanaal 1.
Pseudo-code test-controller.py
# def list_resources\n# return list of available ports\n\n# class ArduinoVISADevice\n# def init (ask port from user)\n ...\n# def get_identification\n# return identification string of connected device\n#\n# def set_output_value\n# set a value on the output channel\n#\n# def get_output_value\n# get the value of the output channel\n# \n# def get_input_value\n# get input value from input channel\n#\n# def get_input_voltage\n# get input value from input channel in Volt\n
Testcode: test-controller.py # get available ports\nprint(list_resources())\n\n# create an instance for the Arduino on port \"ASRL28::INSTR\"\ndevice = ArduinoVISADevice(port=\"ASRL28::INSTR\")\n\n# print identification string\nidentification = device.get_identification()\nprint(identification)\n\n# set OUTPUT voltage on channel 0, using ADC values (0 - 1023)\ndevice.set_output_value(value=828)\n\n# measure the voltage on INPUT channel 2 in ADC values (0 - 1023)\nch2_value = device.get_input_value(channel=2)\nprint(f\"{ch2_value=}\")\n\n# measure the voltage on INPUT channel 2 in volts (0 - 3.3 V)\nch2_voltage = device.get_input_voltage(channel=2)\nprint(f\"{ch2_voltage=}\")\n\n# get the previously set OUTPUT voltage in ADC values (0 - 1023)\nch0_value = device.get_output_value()\nprint(f\"{ch0_value=}\")\n
\n(ecpc) > python test-controller.py\n('ASRL28::INSTR', ) \nArduino VISA firmware v1.0.0\nch2_value=224\nch2_voltage=0.7774193548387097\nch0_value=828\n
Checkpunten:
-
list_resources()
is een functie die buiten de class staat. - Aan de
__init__()
method moet een poortnaam worden meegeven. - De
__init__()
method opent de communicatie met de meegegeven poortnaam. - Er is een method
get_identification()
die de identificatiestring teruggeeft. - De
set_output_value()
en get_output_value()
communiceren standaard met kanaal 0. - Bij
get_input_value
en get_input_voltage
moet een kanaal opgegeven worden.
Projecttraject:
- Pythondaq: Repository
- Pythondaq: Start script
- Pythondaq: Quick 'n dirty meting
- Pythondaq: CSV
- Pythondaq: open de repository
- Pythondaq: Controller bouwen
- Pythondaq: Controller implementeren
- Pythondaq: Controller afsplitsen
- Pythondaq: Model afsplitsen
- Pythondaq: Onzekerheid
Je hebt nu een werkende controller, maar je gebruikt het nog niet in je experiment.
Pythondaq: Controller implementeren
opdrachtcodecheck Zet je controller code (zonder de testcode) in het bestand diode-experiment.py
. Pas de code die de meting uitvoert aan zodat deze gebruikt maakt van de class ArduinoVISADevice
en de bijbehorende methods.
Pseudo-code diode-experiment.py
# def list_resources\n# ...\n\n# class ArduinoVISADevice\n ...\n\n# get list resources\n# connect to Arduino via ArduinoVISADevice\n\n# set output voltage from 0 to max\n # measure voltages\n # calculate LED voltage\n # calculate LED current\n\n# plot current vs voltage\n# create csv-file\n
Checkpunten:
- In een script staan
list_resources()
, ArduinoVISADevice()
en de code om de LED te laten branden, metingen te doen en het resultaat te laten zien. - Wanneer de class
ArduinoVISADevice()
uit het script wordt geknipt, werkt diode-experiment.py
niet meer. - Er wordt een lijst gegeven van aangesloten instrumenten.
- Er wordt een plot getoond van de spanning over en de stroomsterkte door de LED.
- De spanning over en de stroomsterkte door de LED worden weggeschreven in een CSV-bestand.
- De LED wordt uitgezet na de meting.
Projecttraject:
- Pythondaq: Repository
- Pythondaq: Start script
- Pythondaq: Quick 'n dirty meting
- Pythondaq: CSV
- Pythondaq: open de repository
- Pythondaq: Controller bouwen
- Pythondaq: Controller implementeren
- Pythondaq: Controller afsplitsen
- Pythondaq: Model afsplitsen
- Pythondaq: Onzekerheid
Als je de vorige opdracht succesvol hebt afgerond maakt het niet meer uit wat de precieze commando's zijn die je naar de hardware moet sturen. Als je de Arduino in de opstelling vervangt voor een ander meetinstrument moet je de class aanpassen, maar kan alle code die met het experiment zelf te maken heeft hetzelfde blijven.
Nu we de controller hebben gemaakt die de Arduino aanstuurt, blijft er nog een stukje code over. Het laatste stuk waar de plot en het CSV-bestand gemaakt worden kunnen we beschouwen als een view en de rest van de code \u2014 waar de metingen worden uitgevoerd en de stroomsterkte $I$ wordt berekend \u2014 is een model. We gaan de code nog wat verder opsplitsen om dat duidelijk te maken \u00e9n onderbrengen in verschillende bestanden \u2014 dat is uiteindelijk beter voor het overzicht.
Pythondaq: Controller afsplitsen
opdrachtcodecheck In latere opdrachten ga je een command-line interface en een grafische user interface maken voor het experiment. Daarom is het handig om alvast overzicht cre\u00ebren door de verschillende onderdelen in aparte scripts te zetten. Het bestand arduino_device.py
bevat de class ArduinoVISADevice
en de functie list_resources()
. Deze class en functie importeer je in het bestand diode-experiment.py
. ECPC
\u251c\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 arduino_device.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 diode-experiment.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022
error
Waarschijnlijk krijg je nog een of meerdere errors als je diode-experiment.py
runt. Lees het error bericht goed door, om welk bestand gaat het arduino_device.py
of diode-experiment.py
? Wat is er volgens het error bericht niet goed?
Pseudo-code arduino_device.py
# def list_resources\n# ...\n\n# class ArduinoVISADevice\n ...\n
diode-experiment.pyfrom arduino_device import ArduinoVISADevice, list_resources\n\n# get list resources\n# connect to Arduino via ArduinoVISADevice\n\n# set output voltage from 0 to max\n # measure voltages\n # calculate LED voltage\n # calculate LED current\n\n# plot current vs voltage\n# create csv-file\n
Checkpunten:
- Alle directe communicatie met de Arduino, firmwarecommando's en pyvisacommando's, staan in de controller.
- Runnen van
diode-experiment.py
zorgt ervoor dat een meting start. - Er wordt een lijst gegeven van aangesloten instrumenten.
- Er wordt een plot getoond van de spanning over en de stroomsterkte door de LED.
- De spanning over en de stroomsterkte door de LED worden weggeschreven in een CSV-bestand.
- De LED wordt uitgezet na de meting.
Projecttraject:
- Pythondaq: Repository
- Pythondaq: Start script
- Pythondaq: Quick 'n dirty meting
- Pythondaq: CSV
- Pythondaq: open de repository
- Pythondaq: Controller bouwen
- Pythondaq: Controller implementeren
- Pythondaq: Controller afsplitsen
- Pythondaq: Model afsplitsen
- Pythondaq: Onzekerheid
if __name__ == '__main__'
opdrachtcodecheck Later wil je de functie list_resources()
netjes in het hele model-view-controller systeem vlechten zodat je als gebruiker de lijst kunt opvragen, maar voor nu wil je af en toe even zien aan welke poort de Arduino hangt. Wanneer je het script arduino_device.py
runt wordt er een lijst geprint met poorten. Dit gebeurt niet wanneer het bestand diode-experiment.py
wordt gerund.
modules
Nog niet bekend met if __name__ == '__main__'
? kijk dan voor meer informatie in de paragraaf modules.
Pseudo-code arduino_device.py
# def list_resources\n# ...\n\n# class ArduinoVISADevice\n ...\n\n# print list ports if arduino_device.py is the main script \n# print list ports not if arduino_device.py is imported as a module in another script\n
diode-experiment.pyfrom arduino_device import ArduinoVISADevice, list_resources\n\n# connect to Arduino via ArduinoVISADevice\n\n# set output voltage from 0 to max\n # measure voltages\n # calculate LED voltage\n # calculate LED current\n\n# plot current vs voltage\n# create csv-file\n
Checkpunten:
- Er wordt een lijst met poorten geprint wanneer
arduino_device.py
wordt gerund. - De lijst wordt niet geprint wanneer
diode-experiment.py
wordt gerund.
Pythondaq: Model afsplitsen
opdrachtcodecheck Omdat je in latere opdrachten een command-line interface en een grafische user interface gaat maken voor het experiment is het handig om alvast overzicht cre\u00ebren door de verschillende onderdelen in aparte scripts te zetten. Maak een bestand run_experiment.py
waarin de gebruiker een paar dingen kan aanpassen. De gebruiker test de door jou geschreven applicatie (view, model, controller) met de volgende handelingen. Het runnen van het bestand run_experiment.py
geeft een lijst van aangesloten instrumenten. De gebruiker past in het bestand run_experiment.py
de poortnaam aan naar een poort waarop een Arduino is aangesloten. De instance van de class DiodeExperiment
die uit het model wordt ge\u00efmporteerd gebruikt deze poortnaam om de communicatie met de Arduino te openen. De gebruiker roept de method scan()
aan van de class DiodeExperiment
waarna een meting wordt gestart. Om gegevens van het naar de Arduino te sturen maakt het model gebruik van de controller. De gegevens die het model terugkrijgt van de Arduino worden volgens de fysische relaties verwerkt tot de benodigde gegevens en doorgestuurd naar de view. De resultaten worden in een plot getoond en naar een CSV-bestand weggeschreven. De gebruiker past het bereik van de meting aan door door de start- en stopparameters, die aan de method scan()
worden meegegeven, aan te passen. ECPC
\u251c\u2500\u2500 pythondaq
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 arduino_device.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 diode_experiment.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 run_experiment.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 \u2022\u2022\u2022 \u2514\u2500\u2500 \u2022\u2022\u2022
Pseudo-code arduino_device.py
# def list_resources\n# ...\n\n# class ArduinoVISADevice\n ...\n
diode_experiment.pyfrom arduino_device import ArduinoVISADevice, list_resources\n\n# class DiodeExperiment\n ...\n # connect to Arduino via ArduinoVISADevice\n ...\n # def scan with start, stop\n # set output voltage from 0 to max\n # measure voltages\n # calculate LED voltage\n # calculate LED current\n # return LED voltage, LED current\n
run_experiment.pyfrom diode_experiment import DiodeExperiment, list_resources\n\n# get list resources\n# connect to Arduino via DiodeExperiment\n\n# get current and voltage from scan(start, stop)\n\n# plot current vs voltage\n# create csv-file\n
Checkpunten:
- Alle directe communicatie met de Arduino, firmwarecommando's en pyvisacommando's, staan in de controller
- Alle communicatie met de controller staan in het model
- Het model bevat een class
DiodeExperiment
- De view communiceert alleen met het model.
- Runnen van
run_experiment.py
zorgt ervoor dat een meting start. - Er wordt een lijst gegeven van aangesloten instrumenten.
- Er wordt een plot getoond van de spanning over en de stroomsterkte door de LED.
- De spanning over en de stroomsterkte door de LED worden weggeschreven in een CSV-bestand.
- De LED wordt uitgezet na de meting.
- De bestanden bevatten alle code die nodig is en niet meer dan dat.
Projecttraject:
- Pythondaq: Repository
- Pythondaq: Start script
- Pythondaq: Quick 'n dirty meting
- Pythondaq: CSV
- Pythondaq: open de repository
- Pythondaq: Controller bouwen
- Pythondaq: Controller implementeren
- Pythondaq: Controller afsplitsen
- Pythondaq: Model afsplitsen
- Pythondaq: Onzekerheid
Het oorspronkelijke script dat je gebruikte voor je meting is steeds leger geworden. Als het goed is gaat nu (vrijwel) het volledige script alleen maar over het starten van een meting en het weergeven en bewaren van de meetgegevens. In het view script komen verder geen berekeningen voor of details over welk kanaal van de Arduino op welke elektronische component is aangesloten. Ook staat hier niets over welke commando's de Arduino firmware begrijpt. Dit maakt het veel makkelijker om in de vervolghoofdstukken een gebruiksvriendelijke applicatie te ontwikkelen waarmee je snel en eenvoudig metingen kunt doen.
Pythondaq: Onzekerheid
opdrachtcodecheck Omdat je never nooit je conclusies gaat baseren op een enkele meetserie ga je de meting herhalen en foutenvlaggen toevoegen. Je moet weer even hard nadenken over hoe je dat bepaalt en hoe je dat in je code gaat verwerken. Daarom pak je pen en papier, stoot je je buurmens aan en samen gaan jullie nadenken over hoe jullie in dit experiment de onzekerheid kunnen bepalen. Daarna kijken jullie naar de opbouw van de code en maken jullie aantekeningen over wat er waar en hoe in de code aangepast moet worden. Je kijkt naar je repository en ziet dat je de nu-nog-werkende-code hebt gecommit vervolgens ga je stap voor stap (commit voor commit) aan de slag om de aanpassingen te maken. Als het klaar is run je run_experiment.py
met het aantal herhaalmetingen op 3 en ziet in de grafiek foutenvlaggen op de metingen voor stroom en spanningen staan. Je kijkt op het beeldscherm van je buurmens en ziet daar ook foutenvlaggen verschijnen. Met een grijns kijken jullie elkaar aan en geven een high five .
Pseudo-code arduino_device.py
# def list_resources\n# ...\n\n# class ArduinoVISADevice\n ...\n
diode_experiment.pyfrom arduino_device import ArduinoVISADevice, list_resources\n\n# class DiodeExperiment\n ...\n # connect to Arduino via ArduinoVISADevice\n ...\n def scan # with start, stop and number of measurements\n # set output voltage from 0 to max\n # measure voltages\n # calculate LED voltage\n # calculate LED current\n # return LED voltage, LED current and errors\n
run_experiment.pyfrom diode_experiment import DiodeExperiment\n\n# get current and voltage with errors from scan(start, stop, measurements)\n\n# plot current vs voltage with errorbars\n# create csv-file\n
Checkpunten:
- Het aantal herhaalmetingen kan worden aangepast in de view.
- De onzekerheid wordt in het model op de correcte manier bepaald.
- De onzekerheid wordt vanuit het model doorgegeven aan de view.
- In de view wordt de onzekerheid geplot behorende bij de juiste grootheid.
Projecttraject:
- Pythondaq: Repository
- Pythondaq: Start script
- Pythondaq: Quick 'n dirty meting
- Pythondaq: CSV
- Pythondaq: open de repository
- Pythondaq: Controller bouwen
- Pythondaq: Controller implementeren
- Pythondaq: Controller afsplitsen
- Pythondaq: Model afsplitsen
- Pythondaq: Onzekerheid
User input De gebruiker moet in de view het script aanpassen om een andere meting te doen. Kun je input()
gebruiken om van de gebruiker input te vragen voor de start, stop en aantal metingen?
Error! Als de gebruiker in de run_experiment.py
per ongeluk een negatieve startwaarde of negatieve aantal metingen invult gaat het niet goed. Gebruik Exceptions om dergelijke gevallen af te vangen en een duidelijke error af te geven.
"},{"location":"poetry/","title":"Poetry","text":"In de vorige hoofdstukken heb je gewerkt met een eigen conda environment zodat je jouw pythonomgeving mooi gescheiden kan houden van andere studenten die op dezelfde computer werken en voor het isoleren van de verschillende projecten waar je aan werkt. Dit is echt de oplossing voor alle problemen waarbij volledige Pythoninstallaties onbruikbaar kunnen worden \u2014 waarna je alles opnieuw moet installeren.
Opnieuw beginnen of 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\u2026? Niet handig. Als je code gaat delen met elkaar krijg je regelmatig te maken met een ImportError
waarna je weer \u00e9\u00e9n 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 kan delen.
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 packages zijn niet genoeg. Welke versies van numpy
werken met de huidige versie van matplotlib
? Allemaal zaken die je \u2014 als je een package schrijft \u2014 zelf moet bijhouden. Het voordeel is dat jouw gebruikers alleen maar jouw pakket hoeven te installeren \u2014 de rest gaat vanzelf.
En\u2026 hoe test je je package zodat je zeker weet dat hij 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 hij een ImportError
. De bestanden zijn er wel, maar worden verkeerd ge\u00efmporteerd.
Hoe krijg je eigenlijk je code bij iemand anders? Liefst als \u00e9\u00e9n bestand, of zelfs met pip install my_new_cool_app
; dat zou wel mooi zijn.
En daar is Poetry.
Er zijn meerdere tools ontwikkeld om dezelfde problemen op te lossen. Poetry is heel populair geworden. Het richt zich op het offici\u00eble ecosysteem: standaard Python packages, ofwel PyPI en pip
; niet conda
(zie meer hierover in paragraaf pip vs conda). Jammer, maar dit zorgt er wel voor dat iedereen m\u00e9t of z\u00f3nder Anaconda je package kan installeren. Dat is dan wel weer fijn. Wij gaan Anaconda gebruiken om een virtual environment met alleen Python te maken. Vervolgens installeren we alles dat we nodig hebben met pip
. Dat werkt prima, want we mengen verder geen conda
met pip
packages. Het maken van conda packages valt daarmee buiten het bestek van deze cursus, al is dat een relatief kleine stap als je je standaard Python package af hebt.
Werken in een terminal
Poetry is een tool die je enkel en alleen in de terminal kunt gebruiken. Het heeft alleen een command-line interface (CLI). Ben je nog niet zo bekend met het navigeren in een terminal dan kun je als oefening de Terminal Adventure Game spelen.
Poetry installeren
Om Poetry te installeren gaan we gebruik maken van pipx
, zie voor meer informatie paragraaf pipx. Eerst moeten we pipx
installeren
- Open een Anaconda Prompt.
- Maak een nieuwe environment en installeer pipx via pip Terminal
conda create --name pipx python\n
Terminalconda activate pipx\n
Terminalpython -m pip install --user pipx\n
- Zorg ervoor dat de map waarin pipx apps opslaat, is opgenomen in je PATH omgevingsvariabele. Terminal
python -m pipx ensurepath\n
- Sluit de Anaconda Prompt en open een nieuwe.
- Test of pipx nu werkt met: Terminal
pipx\n
Nu kunnen we Poetry
installeren met pipx
.
- Installeer Poetry met behulp van pipx. Terminal
pipx install poetry\n
- Test of poetry nu werkt met: Terminal
poetry\n
- Activeer een andere environment en test of Poetry ook daar werkt.
Poetry doet het niet in Visual Studio Code
Werkt Poetry niet in een terminal in Visual Studio code? Gooi de oude terminals weg, sluit Visual Studio Code en GitHub Desktop af. Open Visual Studio Code weer via GitHub Desktop, open een nieuwe terminal en kijk of het nu wel werkt.
We gaan Poetry bedienen door commando's te geven in de terminal van Visual Studio Code. We laten de terminal weten welk programma wij willen gaan besturen, door poetry
in te typen. En daarachter wat we willen dat Poetry gaat doen. We kunnen informatie over Poetry opvragen met het commando about
.
(ecpc) > poetry about \nPoetry - Package Management for Python\n\nVersion: 1.8.4\nPoetry-Core Version: 1.9.1\n\nPoetry is a dependency manager tracking local dependencies of your projects and libraries.\nSee https://github.com/python-poetry/poetry for more information.\n
Poetry about
Open een terminal en vraag informatie over Poetry op met het commando poetry about
. Lees de tekst die Poetry aan je teruggeeft, waar kan je meer informatie vinden?
"},{"location":"poetry/#nieuw-poetry-project","title":"Nieuw Poetry project","text":"Info
We gaan 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 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 Poetry project aanmaken
opdrachtcodecheck Een project stop je altijd in een map , als je aan Poetry vraagt om een project te maken zal er een nieuwe (project)map worden aangemaakt. Je denkt na over een geschikte locatie en besluit dat de projectmap in de ECPC
map moet komen te staan. Je opent Visual Studio Code en opent de map ECPC
. Je opent een terminal en controleert dat de terminal ook in de map ECPC
is. Je geeft Poetry de opdracht om een nieuw project met de naam easystat
aan te maken in de src-layout10 met het commando poetry new --src easystat
. Je bekijkt de nieuw gemaakte mappenstructuur en ziet dat het overeenkomt met de mappenstructuur zoals hieronder weergegeven:
ECPC
\u251c\u2500\u2500 oefenopdrachten
\u251c\u2500\u2500 pythondaq
\u251c\u2500\u2500 easystat
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 src
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 easystat
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 tests
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 pyproject.toml
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 readme.md
\u2514\u2500\u2500 \u2022\u2022\u2022
src-layout
Door het project in een source layout (src-layout) te bouwen maken we het expres iets moeilijker om vanuit een script je package te importeren. Je kunt dat dan alleen nog maar doen door het package zelf ook te installeren (zoals andere gebruikers ook moeten doen) en daardoor loop je zelf tegen eventuele problemen aan. Werkt het uiteindelijk bij jou? Dan werkt het ook bij andere mensen.
Testcode
(ecpc) > poetry new --src easystat \nCreated package easystat in easystat\n
Checkpunten:
- De projectmap
easystat
staat in de map ECPC
. - In de projectmap
easystat
staat een map src
. - In de map
src
staat een package map easystat
Projecttraject
- Easystat Poetry project aanmaken
- Easystat conda environment aanmaken
- Easystat shortcuts.py en try_shortcuts.py aanmaken
- Easystat try_shortcuts.py testen
- Easystat Poetry install
- Easystat dependencies toevoegen
Bekijk nog eens de mappenstructuur. Allereerst is er een projectmap easystat
(waar de map src
in staat) aangemaakt . Je kunt nu in GitHub Desktop deze map easystat
toevoegen als nieuwe repository, zoals we gedaan hebben in opdracht Repository toevoegen.
Laten we \u00e9\u00e9n voor \u00e9\u00e9n kijken welke mappen en bestanden Poetry heeft aangemaakt. We zien een README.md
in de projectmap staan. Hierin komt een algemene beschrijving van ons project.2
Daarna is er een map tests
. Goede software wordt getest. In deze map komen bestanden te staan die delen van de code runnen en resultaten vergelijken met verwachte resultaten \u2014 zoals je kunt doen in opdracht Packages.3
Dan komt de src
-map. Daarin komt ons nieuwe package easystat
4 te staan. Er is alvast voor ons een __init__.py
aangemaakt. Handig!
En als laatste\u2026 een pyproject.toml
5 waarin alle informatie over je project wordt bijgehouden. Ook staat er in dit bestand informatie voor de verschillende tools die je kunt gebruiken. De inhoud van het bestand ziet er ongeveer zo uit:
[tool.poetry]\nname = \"easystat\"\nversion = \"0.1.0\"\ndescription = \"\"\nauthors = [\"David Fokkema <davidfokkema@icloud.com>\"]\nreadme = \"README.md\"\npackages = [{include = \"easystat\", from = \"src\"}]\n\n[tool.poetry.dependencies]\npython = \"^3.13\"\n\n\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n
Het bestand is in het TOML-formaat.11 Tussen de vierkante haken staan de koppen van de verschillende secties in dit configuratiebestand. Overal zie je poetry
terugkomen, want dat is de tool die wij gebruiken. In de eerste sectie staat informatie over ons project. Je kunt daar bijvoorbeeld een beschrijving toevoegen of het versienummer aanpassen. De tweede sectie bevat de dependencies. Dit zijn alle Pythonpackages die ons project nodig heeft. Op dit moment is dat alleen maar Python. Ook het versienummer van Python is belangrijk. Hier is dat 3.13 en het dakje geeft aan dat nieuwere versies 3.14, 3.15, enz. ook prima zijn, maar 3.12 (te oud) en 4.0 (te nieuw) niet. Dit kan belangrijk zijn. Gebruikers met een iets oudere versie van Python \u2014 bijvoorbeeld versie 3.11 \u2014 kunnen nu het 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 dit moment \u2014 herfst 2024 \u2014 is Python 3.13 de nieuwste versie. Het is dus prima om minimaal 3.12 te vragen \u2014 die versie is inmiddels een jaar oud.
"},{"location":"poetry/#conda-environment-aanmaken","title":"Conda environment aanmaken","text":"Bij het schrijven van een nieuw package is het z\u00e9ker belangrijk om een conda environment te gebruiken. Anders loop je het risico dat je package lijkt te werken maar bij iemand anders crasht. Immers, het kan best zijn dat jij NumPy gebruikt en al eerder ge\u00efnstalleerd had. Bij iemand die NumPy nog niet ge\u00efnstalleerd had gaat het dan mis.
Easystat conda environment aanmaken
opdrachtcodecheck Je voegt de projectmap easystat
toe als existing/local repository in GitHub . Vanuit GitHub Desktop open je de repository easystat
in Visual Studio Code. Je maakt in Anaconda Prompt een nieuwe conda environment aan met de naam easystat
en daarin python=3.12
. Uiteraard selecteer je het nieuwe environment in Visual Studio Code.
Testcode
(easystat) > conda list \n# packages in environment at C:\\easystat:\n#\n# Name Version Build Channel\nbzip2 1.0.8 h2bbff1b_6\nca-certificates 2024.7.2 haa95532_0\nlibffi 3.4.4 hd77b12b_1\nopenssl 3.0.15 h827c3e9_0\npip 24.2 py310haa95532_0\npython 3.12.7 h99e199e_0\nsetuptools 72.1.0 py310haa95532_0\nsqlite 3.45.3 h2bbff1b_0\ntk 8.6.14 h0416ee5_0\ntzdata 2024a h04d1e81_0\nvc 14.40 h2eaa2aa_1\nvs2015_runtime 14.40.33807 h98bb1dd_1\nwheel 0.43.0 py310haa95532_0\nxz 5.4.6 h8cc25b3_1\nzlib 1.2.13 h8cc25b3_1\n
Checkpunten:
- De projectmap
easystat
is geopend in Visual Studio Code. - Python is ge\u00efnstalleerd in de conda environment
easystat
. - In Visual Studio Code is de conda environment
easystat
geactiveerd.
Projecttraject
- Easystat Poetry project aanmaken
- Easystat conda environment aanmaken
- Easystat shortcuts.py en try_shortcuts.py aanmaken
- Easystat try_shortcuts.py testen
- Easystat Poetry install
- Easystat dependencies toevoegen
conda-forge
Merk op dat we nu niet gebruik hoeven te maken van de conda-forge
channel. Python zelf staat in alle kanalen en we gaan verder geen software installeren met conda, dus ook niet uit conda-forge
.
"},{"location":"poetry/#maken-van-de-easystat-package","title":"Maken van de easystat-package","text":"We starten met ons package. Stel, we berekenen vaak de standaarddeviatie van het gemiddelde en maken daarvoor een handige shortcut in shortcuts.py
. Nu willen we deze shortcut ook in een ander script gebruiken. Dit kunnen we doen door package easystat
te importeren in dit nieuwe script zodat we de functie stdev_of_mean
daar ook kunnen gebruiken. We maken een script try_shortcuts.py
om dit te testen.
Easystat shortcuts.py en try_shortcuts.py aanmaken
Maak zoals hieronder aangegeven de bestanden shortcuts.py
en try_shortcuts.py
aan: shortcuts.py
import numpy as np \n\n\ndef stdev_of_mean(values):\n \"\"\"Calculate the standard deviation of the mean\"\"\"\n return np.std(values) / np.sqrt(len(values)) \n
try_shortcuts.pyfrom easystat.shortcuts import stdev_of_mean\n\nprint(f\"{stdev_of_mean([1, 2, 2, 2, 3])=}\")\n
easystat
\u251c\u2500\u2500 src
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 easystat
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 shortcuts.py
\u251c\u2500\u2500 tests
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 try_shortcuts.py
\u251c\u2500\u2500 pyproject.toml
\u2514\u2500\u2500 readme.md
Import numpy could not be resolved
Misschien is het je al opgevallen dat VS Code een oranje kringeltje onder numpy
zet in de eerste regel. Als je daar je muiscursor op plaatst krijg je een popup met de melding Import numpy could not be resolved
. Daar moeten we misschien wat mee en dat gaan we straks ook doen.
In de eerste regel van test_shortcuts.py
importeren we de functie uit het nieuwe package om uit te proberen. In de laatste regel gebruiken we een handige functie van f-strings.6
Easystat try_shortcuts.py testen
opdrachtcodecheck Je bent heel benieuwd of je package al werkt. Je runt het bestand try_shortcuts.py
en krijgt een foutmelding...
Testcode try_shortcuts.py
from easystat.shortcuts import stdev_of_mean\n\nprint(f\"{stdev_of_mean([1, 2, 2, 2, 3])=}\")\n
\n(easystat) > python try_shortcuts.py\nTraceback (most recent call last):\n File \"c:\\ECPC\\easystat\\tests\\try_shortcuts.py\", line 1, in < module >\n from easystat.shortcuts import stdev_of_mean\nModuleNotFoundError: No module named 'easystat'\n\n\n\nCheckpunten:
\n\n- Je hebt de juiste conda environment geactiveerd.
\n- Je runt het bestand
try_shortcuts.py
uit de map tests
. \n- Je krijgt een foutmelding
ModuleNotFoundError: No module named 'easystat'
\n
\nProjecttraject
\n\n- Easystat Poetry project aanmaken
\n- Easystat conda environment aanmaken
\n- Easystat shortcuts.py en try_shortcuts.py aanmaken
\n- Easystat try_shortcuts.py testen
\n- Easystat Poetry install
\n- Easystat dependencies toevoegen
\n
\n\n\n\n\nDit konden we verwachten. We hebben onze package immers nog niet ge\u00efnstalleerd. Als we onze package gaan delen met andere mensen verwachten wij dat zij onze package ook gaan installeren, door dezelfde stappen te doorlopen als andere gebruikers komen we erachter of alles wel goed werkt.
"},{"location":"poetry/#installeren-van-een-package","title":"Installeren van een package","text":"Het installeren van de package kan makkelijk met Poetry:
\n(easystat) > poetry install \nUpdating dependencies\nResolving dependencies... (0.1s)\n\nWriting lock file\n\nInstalling the current project: easystat (0.1.0)\n
\n\nPoetry is even bezig en ons package is ge\u00efnstalleerd.
\n\nEasystat Poetry install
\nopdrachtcodecheck\n\n\nJe opent een terminal in Visual Studio Code. Je gaat het project easystat
installeren in de conda environment easystat
met het commando poetry install
. Waarschijnlijk krijg je een error (zie info-blok hieronder) maar door rustig te lezen los je die op. Je installeert alsnog het project easystat
draai je opnieuw tests/try_shortcuts.py
en zie je een nieuwe error verschijnen ModuleNotFoundError: No module named 'numpy'
. Hoera de eerste error is met succes opgelost en je kunt door met de volgende opdracht.
\n\nCurrent Python version is not allowed by the project
\nWaarschijnlijk krijg je in dikke rode letters de error:\n
Current Python version (3.12.7) is not allowed by the project (^3.13).\nPlease change python executable via the \"env use\" command.\n
\nIn de pyproject.toml
staat bij de Python dependency dat er minstens versie 3.13 of hoger (^3.13) nodig is voor dit project7. En de conda environment easystat
heeft Python 3.12 ge\u00efnstalleerd. Je kunt nu twee dingen doen: \n\n- Je bedenkt dat voor dit project een lagere versie van Python ook voldoende is en past de Python versie dependency aan in de
pyproject.toml
naar ^3.12. \n- Je vindt dat het project minstens versie 3.13 moet gebruiken en upgrade Python in de
easystat
environment met conda install python=3.13
. \n
\n\n\n\nTestcode\n
(easystat) > conda list \n# packages in environment at C:\\easystat:\n#\n# Name Version Build Channel\nbzip2 1.0.8 h2bbff1b_6\nca-certificates 2024.7.2 haa95532_0\neasystat 0.1.0 pypi_0 pypi\nlibffi 3.4.4 hd77b12b_1\nopenssl 3.0.15 h827c3e9_0\npip 24.2 py310haa95532_0\npython 3.10.14 he1021f5_1\nsetuptools 72.1.0 py310haa95532_0\nsqlite 3.45.3 h2bbff1b_0\ntk 8.6.14 h0416ee5_0\ntzdata 2024a h04d1e81_0\nvc 14.40 h2eaa2aa_1\nvs2015_runtime 14.40.33807 h98bb1dd_1\nwheel 0.43.0 py310haa95532_0\nxz 5.4.6 h8cc25b3_1\nzlib 1.2.13 h8cc25b3_1\n
\n\n\nCheckpunten:
\n\n- Je hebt de juiste conda environment geactiveerd.
\n- Nadat je
poetry install
hebt gedaan krijg je de melding Installing the current project: easystat (0.1.0)
. \n- Je runt het bestand
tests/try_shortcuts.py
. \n- Je krijgt een foutmelding
ModuleNotFoundError: No module named 'numpy'
\n
\nProjecttraject
\n\n- Easystat Poetry project aanmaken
\n- Easystat conda environment aanmaken
\n- Easystat shortcuts.py en try_shortcuts.py aanmaken
\n- Easystat try_shortcuts.py testen
\n- Easystat Poetry install
\n- Easystat dependencies toevoegen
\n
\n\n\n\n\nAls we het testscript nu draaien krijgen we w\u00e9\u00e9r een foutmelding:\n
ModuleNotFoundError: No module named 'numpy'\n
\nOns package heeft NumPy nodig en dat hebben we nog niet ge\u00efnstalleerd. Dat kunnen we handmatig doen maar dan hebben andere gebruikers een probleem. Veel beter is het om netjes aan te geven dat ons package NumPy nodig heeft \u2014 als dependency."},{"location":"poetry/#dependencies-toevoegen","title":"Dependencies toevoegen","text":"Om een dependency aan te geven vertellen we Poetry dat hij deze moet toevoegen met:
\n(easystat) > poetry add numpy \nUsing version ^1.23.2 for numpy\n\nUpdating dependencies\nResolving dependencies...\n\nWriting lock file\n\nPackage operations: 1 install, 0 updates, 0 removals\n\n \u2022 Installing numpy (1.23.2)\n
\n\n\nEasystat dependencies toevoegen
\nopdrachtcodecheck\n\n\nJe voegt Numpy
als dependency toe aan het project easystat
met het commando poetry 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 de conda environment easystat
is ge\u00efnstalleerd en controleert dit met conda list
en waarachtig Numpy
staat in de lijst . Weer ga je tests/try_shortcuts.py
draaien en ditmaal krijg je een uitkomst!
\n\n\nTestcode\n try_shortcuts.py\n
from easystat.shortcuts import stdev_of_mean\n\nprint(f\"{stdev_of_mean([1, 2, 2, 2, 3])=}\")\n
\n\n(ecpc) > python try_shortcuts.py\nstdev_of_mean([1, 2, 2, 2, 3])=np.float64(0.282842712474619)\n
\n\n\nCheckpunten:
\n\n- Je hebt de juiste conda environment geacitveerd.
\n- Je hebt
Numpy
als dependency toegevoegd. \n- Je krijgt een uitkomst als je het bestand
tests/try_shortcuts.py
runt. \n
\nProjecttraject
\n\n- Easystat Poetry project aanmaken
\n- Easystat conda environment aanmaken
\n- Easystat shortcuts.py en try_shortcuts.py aanmaken
\n- Easystat try_shortcuts.py testen
\n- Easystat Poetry install
\n- Easystat dependencies toevoegen
\n
\n\n\n\n\nFijn! Het verwijderen van dependency PACKAGE
gaat met poetry remove PACKAGE
. Poetry heeft Numpy nu toegevoegd aan de environment easystat
.Gewone package managers als Pip en Conda zullen geen packages toevoegen aan je Poetry project als je pip/conda install package
aanroept. Gebruik daarom altijd poetry add package
als je met Poetry aan een package werkt.
\n\nInfo
\nAls we de code in ons package aanpassen dan hoeven we het niet opnieuw te installeren met Poetry, maar als we met de hand iets wijzigen in de pyproject.toml
dan moet dat wel. Als je een ImportError
krijgt voor je eigen package \u2014 bijvoorbeeld als je nieuwe mappen of bestanden hebt aangemaakt \u2014 probeer dan eerst voor de zekerheid poetry install
.
\n\n\nPoetry.lock\n\n\nWheels"},{"location":"poetry/#poetrylock","title":"Poetry.lock","text":"Na het toevoegen van Numpy is er ook een bestand poetry.lock
bijgekomen. Hierin staan de exacte versies van alle ge\u00efnstalleerde packages. Vaak wordt dit bestand gecommit zodat collega-ontwikkelaars exact dezelfde versies installeren zodra ze poetry install
aanroepen. Om dat te proberen maken we even een schone conda environment:
\n\nSchone environment
\n\n- Maak een schone conda environment met
conda create --name easystat python=3.12
\n- Kies voor ja als Conda een waarschuwing geeft dat deze environment al bestaat en vraagt of je het bestaande environment wilt verwijderen.
\n- Draai
tests/try_shortcuts.py
en bekijk de foutmelding. \n
\n\nWe krijgen meteen foutmeldingen. Immers, we hebben nog niets ge\u00efnstalleerd.
\n\nPoetry.lock
\n\n- Installeer de
easystat
package met poetry
. \n- Waarvoor gebruikt Poetry de lock file (
poetry.lock)
? \n- Draai
tests/try_shortcuts.py
en bekijk de uitkomst. \n
"},{"location":"poetry/#wheels","title":"Wheels","text":"Wanneer we klaar zijn om ons package te delen met andere gebruikers gebruiken we het commando build
om wheels te bouwen.
\n\nBouw een wheel
\n\n- Bouw het wheel van easystat met
poetry build
. \n- Bekijk de namen van de bestanden in de nieuwe map
easystat/dist
, welke extensie hebben ze? \n
\n\n(ecpc) > poetry build \nBuilding easystat (0.1.0)\n - Building sdist\n - Built easystat-0.1.0.tar.gz\n - Building wheel\n - Built easystat-0.1.0-py3-none-any.whl\n
\nEen sdist is een source distribution. Een .tar.gz
-bestand is een soort zipbestand met daarin de broncode van ons pakket. De tests worden daar niet in meegenomen. Een wheel is een soort bestand dat direct ge\u00efnstalleerd kan worden met pip
. Zogenaamde pure-python packages bevatten alleen Pythoncode \u2014 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. We kunnen dit bestand als download neerzetten op een website of aan anderen mailen.\n\nTest wheel
\nLaten we het wheel uitproberen. We gaan straks een nieuwe conda environment aanmaken, installeren het wheel en proberen het testscript te runnen \u2014 \u00e9\u00e9n keer v\u00f3\u00f3r het installeren van het wheel en \u00e9\u00e9n keer n\u00e1 het installeren, als volgt:
\n\n- Maak een nieuwe conda environment aan met de naam
test-wheel
en activeer deze.\n TerminalPS> conda create --name test-wheel python=3.12\n...\nPS> conda activate test-wheel\n
\n- Draai
tests/try_shortcuts.py
en bekijk de foutmelding. \n- Installeer het wheel met
pip install dist/easystat-0.1.0-py3-none-any.whl
. \n- Draai
tests/try_shortcuts.py
en bekijk de uitkomst. \n
\n\nHet werkt! Je ziet dat pip install
niet alleen ons package easystat
installeert, maar ook de dependency numpy
. Dat is precies wat we willen.
\nHet 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.
"},{"location":"poetry/#poetry-gebruiken-voor-een-bestaand-project","title":"Poetry gebruiken voor een bestaand project","text":"Met poetry new
start je een nieuw project en maakt Poetry voor jou bestanden en mappen aan waarmee je aan de slag kunt. Maar vaak ben je al bezig met een project en wil je dat niet overschrijven. Ook is het een gedoe om een nieuw project te maken en daar je bestaande code in te kopie\u00ebren. Gelukkig kun je Poetry ook vertellen dat je al bezig bent en dat Poetry alleen een pyproject.toml
-bestand moet aanmaken. Run dan in de map van je project:\nTerminal
poetry init --no-interaction\n
\nJe geeft met poetry init
de opdracht om Poetry alleen te initialiseren en --no-interaction
voorkomt je dat je eerst honderd vragen krijgt over je project. Meestal kies je toch de standaardantwoorden.8\n\nInfo
\nVergeet niet \u2014 waar nodig \u2014 de __init__.py
bestanden toe te voegen aan de packages. Meer informatie over de __init__.py
bestanden vind je in paragraaf packages.
\n\n\nInfo
\nAls je al bezig bent met een project dan werk je als het goed is al in een conda environment. Daar heb je dan met conda install
al packages ge\u00efnstalleerd die je nodig hebt. Het gebeurt dan makkelijk dat je vergeet om dat aan te geven met poetry add
. Dat betekent alleen dat als iemand anders je package installeert dat er dependencies missen en dat jouw code dus niet werkt! Dit is makkelijk op te lossen. Zodra je Poetry gaat gebruiken wis dan je environment en maak een nieuwe aan met alleen Python. Dat gaat het makkelijkst als volgt. Stel dat je bezig bent in het environment pythondaq
. We maken dan een nieuw environment met dezelfde naam:\n
(ecpc) > conda create --name pythondaq python=3.12 \nWARNING: A conda environment already exists at '/Users/david/opt/anaconda3/envs/pythondaq'\nRemove existing environment (y/[n])? y \n...\n
\nJe overschrijft dus je huidige environment met een nieuwe, lege. Je kunt daarna met poetry add
packages toevoegen net zo lang tot je geen ImportError
meer krijgt.
\n\n\nPoetry flashingLED
\nopdrachtcodecheck\n\n\n\n \n Je gaat een bestaand project maken zodat je kunt oefenen om daar Poetry aan toe te voegen. Omdat de opdracht flashingLED een oefenopdracht was voor Pythondaq
besluit je deze als oefenpackage te gebruiken. Je maakt een nieuwe repository flasher
aan en opent deze in Visual Studio Code. Je maakt zelf in de repository flasher
de src-layout van mappen en bestanden, zoals hier rechts is weergegeven. Het bestand flashingLED
heb je gekopieerd uit je repository oefenopdrachten
. \n \n \n Nu het oefenpackage klaar staat (commit) maak je een nieuwe conda environment met de naam flasher
met daarin python=3.12
. Je activeert de environment flasher
en voegt Poetry toe aan de bestaande projectmap flasher
. Je installeert het Poetry pakket in de flasher
conda environment en daarna voeg je de benodigde dependencies toe (in ieder geval pyvisa-py
maar wat nog meer?) net zolang tot het scriptje weer werkt . \n \n \n Tot slot wil je testen of het nu ook werkt in een nieuwe conda environment. Dus je maakt weer een nieuwe conda environment met de naam flasher
met daarin python=3.12
. Je installeert het Poetry pakket in de flasher
conda environment . Dan test je of het scriptje nog werkt.\n \n \n ECPC
\n \u251c\u2500\u2500 oefenopdrachten
\n \u251c\u2500\u2500 pythondaq
\n \u251c\u2500\u2500 flasher
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 src
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 flasher
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 flashingLED.py
\n \u2514\u2500\u2500 \u2022\u2022\u2022\n \n
\n\nNo module named 'serial'
\nWaarschijnlijk krijg je onder andere de foutmelding:\n
ValueError: Please install PySerial (>=3.0) to use this resource type.\n No module named 'serial'\n
\nSuper handig dat iemand daarboven heeft opgeschreven wat je moet doen om dit probleem op te lossen. Maar waarom moeten we nu ineens PySerial
installeren9? Dat komt omdat we eerst pyvisa-py
met conda uit de conda-forge channel installeerde en daar komt PySerial
als dependencie mee. Nu installeerd Poetry met behulp van pip pyvisa-py
en daar komt PySerial
niet automatisch mee. En dus moeten we het nu zelf handmatig toevoegen.\n\n\n\nTestcode\n flasherLED.py\n
import pyvisa\nimport numpy as np\nimport time\n\nrm = pyvisa.ResourceManager(\"@py\")\nports = rm.list_resources()\nprint(ports)\ndevice = rm.open_resource(\n \"ASRL3::INSTR\", read_termination=\"\\r\\n\", write_termination=\"\\n\"\n)\n\nfor value in np.arange(0, 10):\n device.query(f\"OUT:CH0 {0}\")\n time.sleep(1)\n device.query(f\"OUT:CH0 {1023}\")\n time.sleep(1)\n
\n\n(ecpc) > python flasherLED.py\n()\nTraceback (most recent call last):\n File \"c:\\ECPC\\flasher\\src\\flasher\\flashingLED.py\", line 8, in \n device = rm.open_resource(\n File \"C:\\envs\\flasher\\lib\\site-packages\\pyvisa\\highlevel.py\", line 3292, in open_resource\n res.open(access_mode, open_timeout)\n File \"C:\\envs\\flasher\\lib\\site-packages\\pyvisa\\resources\\resource.py\", line 281, in open\n self.session, status = self._resource_manager.open_bare_resource(\n File \"C:\\envs\\flasher\\lib\\site-packages\\pyvisa\\highlevel.py\", line 3217, in open_bare_resource\n return self.visalib.open(self.session, resource_name, access_mode, open_timeout)\n File \"C:\\envs\\flasher\\lib\\site-packages\\pyvisa_py\\highlevel.py\", line 168, in open\n sess = cls(session, resource_name, parsed, open_timeout)\n File \"C:\\envs\\flasher\\lib\\site-packages\\pyvisa_py\\sessions.py\", line 861, in init\n raise ValueError(self.session_issue)\nValueError: Please install PySerial (>=3.0) to use this resource type.\nNo module named 'serial'\n\n\n\nCheckpunten:
\n\n- Je hebt een repository
flasher
met daarin een src-layout. \n- Je hebt de juiste conda environment geactiveerd.
\n- Poetry is toegevoegd aan het project.
\n- Alle benodigde dependencies staan in het
pyproject.toml
en zijn ge\u00efnstalleerd in de conda environment. \n- Het runnen van
flashingLED.py
laat het LED knipperen. \n- Als het Poetry project wordt ge\u00efnstalleerd in een nieuwe conda environement met alleen Python=3.12 gaat het LED weer knipperen als
flashingLED.py
wordt uitgevoerd. \n
\nProjecttraject
\n\n- Communicatie met een meetinstrument: flashingLED
\n- Versiebeheer met GitHub: Repository toevoegen
\n- Poetry flashingLED
\n
"},{"location":"poetry/#poetry-gebruiken-voor-pythondaq","title":"Poetry gebruiken voor pythondaq","text":"Natuurlijk willen we Poetry ook gaan gebruiken bij pythondaq
. Daarvoor moeten we twee dingen doen. Als eerste gaan we de pythondaq
repository in een src
-structuur zetten en daarna gaan we Poetry initialiseren.
\n\nPythondaq: src-layout
\n\n \n Je project pythondaq
is zo tof aan het worden dat je het met Poetry gaat beheren zodat jij en andere het gemakkelijk kunnen installeren en gebruiken. Om te beginnen zet je de repository om in een src-layout zoals hiernaast:\n \n \n pythondaq
\n \u251c\u2500\u2500src
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u2514\u2500\u2500pythondaq
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u251c\u2500\u2500__init__.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u251c\u2500\u2500arduino_device.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u251c\u2500\u2500diode_experiment.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u2514\u2500\u2500run_experiment.py
\n \u251c\u2500\u2500.gitattributes
\n \u251c\u2500\u2500.gitignore
\n \u2514\u2500\u2500README.md
\n \n
\n\n\nPythondaq: poetry
\nopdrachtcodecheck\n\n\nNu de repository pythondaq
in de src-layout staat voeg je Poetry toe om het project te beheren . Nadat alles gelukt is test je het project door een nieuwe conda environment aan te maken met de naam pythondaq
met daarin alleen python=3.12
. Daarna installeer je het Poetry project en wanneer je run_experiment.py
runt zie je als vanouds een lampje branden en een plot verschijnen.
\n\n\nPseudo-code\n
(ecpc) > poetry install \nInstalling dependencies from lock file\n\nPackage operations: x installs, 0 updates, 0 removals\n\n- Installing xxx (1.2.3)\n- Installing xxx (1.2.3)\n- Installing xxx (1.2.3): Pending...\n- Installing xxx (1.2.3): Installing...\n- Installing xxx (1.2.3)\n\nInstalling the current project: pythondaq (0.1.0)\n
\n\n\nCheckpunten:
\n\n- Je hebt Poetry ge\u00efnitialiseerd in de Pythondaq project map.
\n- Na het initialiseren van Poetry is er een
pyproject.toml
in de projectmap aangemaakt. \n- Wanneer met
poetry install
in een nieuwe conda environment met alleen python=3.12 het pakket wordt ge\u00efnstalleerd werkt run_experiment.py
daarna in die nieuwe omgeving naar behoren. \n
\nProjecttraject
\n\n- Pythondaq: Docstring
\n- Pythondaq: src-layout
\n- Pythondaq: poetry
\n- Pythondaq: test imports
\n- Pythondaq: applicatie
\n
\n\n\n\n\n\nModel, view, controller packages\nIn grotere projecten is het gebruikelijk om model, view, controller niet alleen uit te splitsen in verschillende scripts, maar ook in aparte packages te zetten.
\n\n- Maak 3 extra packages in de
pythondaq
package. models
, views
en controllers
. \n- Zet de modules in de juiste packages.
\n- Test je code zodat alle imports weer werken.
\n
"},{"location":"poetry/#van-script-naar-applicatie","title":"Van script naar applicatie","text":"Om onze python 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:\nTerminal
python script.py\n
\nJe moet dan wel de juiste map geopend hebben zodat python het bestand kan vinden. En als je de run
-knop gebruikt moet wel het bestandje open hebben staan dat je wilt runnen. Kortom, best een beetje gedoe. Maar als we programma's zoals Poetry, Conda of Python willen gebruiken hoeven we helemaal niet het juiste bestandje op te zoeken en te runnen. We hoeven alleen maar een commando in de terminal te geven \u2014 bijvoorbeeld python
of conda
\u2014 en de computer start automatisch het juiste programma op.\nDat willen wij ook voor onze programma's! En omdat we Poetry gebruiken kunnen we dat heel eenvoudig doen. We gaan even in een andere test-repository een commando toevoegen om de module uit te voeren waarvan je de code in paragraaf Modules kunt vinden. De twee bestanden square.py
en count_count.py
hebben we voor jullie netjes in een package geplaats in de repository AnneliesVlaar/just_count
met de volgende structuur:
\njust_count/\n src/\n just_count/\n __init__.py\n square.py\n count_count.py\n tests/\n __init__.py\n pyproject.toml\n README.md\n
\nDe bestanden square.py
en count_count.py
zien er hetzelfde uit als in paragraaf Modules:
\nsquare.pycount_count.py\n\n\ndef square(x):\n return x**2\n\n\nif __name__ == \"__main__\":\n print(f\"The square of 4 is {square(4)}\")\n
\n\n\nimport square\n\nprint(f\"The square of 5 is {square.square(5)}\")\n
\n\n\n\nWe kunnen Poetry niet vragen om een script te runnen, maar wel om een functie uit te voeren.
\n\nMain functie toevoegen
\nopdrachtcodecheck\n\n\nJe cloned de repository just_count in GitHub desktop en opent het daarna vanuit GitHub Desktop in Visual Studio Code. Je ziet een pyproject.toml
in de repository staan. Dus installeer je het pakket met Poetry in een nieuwe conda environment (met alleen python=3.12) . Je opent het hoofdbestand count_count.py
en zet de body van de module in een functie main()
. Daarna pas je het bestand aan zodat de functie nog steeds wordt uitgevoerd wanneer je het bestand count_count.py
runt.
\n\n\nTestcode\n count_count.py\n
import square\n\ndef main():\n print(f\"The square of 5 is {square.square(5)}\")\n\nif __name__ == '__main__':\n main()\n
\n\n(ecpc) > python count_count.py\nThe square of 5 is 25\n
\n\n\nCheckpunten:
\n\n- Er is een functie
main()
in het bestand count_count.py
\n- Het runnen van het bestand
count_count.py
geeft de output The square of 5 is 25
\n
\nProjecttraject
\n\n- main functie toevoegen
\n- commando toevoegen
\n- commando testen
\n
\n\n\n\n\nIn pyproject.toml
kunnen we nu het commando toe gaan voegen. Met de scripts
-tool van Poetry kunnen we aangeven met welk commando een functie uit een script wordt uitgevoerd. Om een commando toe te voegen ga je naar pyproject.toml
en voeg je een extra kopje toe:\n
[tool.poetry.scripts]\nnaam_commando = \"package.module:naam_functie\"\n
\nOm de wijzigingen aan pyproject.toml
door te voeren moet je de package opnieuw installeren. Poetry 'kijkt' altijd vanuit de map src
, de package package
waar naar verwezen wordt moet dan ook direct in de map src
zitten (en niet in een submap).\n\ncommando toevoegen
\nopdrachtcodecheck\n\n\nJe voegt in de pyproject.toml
het kopje [tool.poetry.scripts]
toe. Je voegt vervolgens het commando square
toe. Deze verwijst naar de functie main()
welke in de module count_count.py
staat die ondergebracht is in de package just_count
. Omdat je handmatig het toml-bestand hebt aangepast installeer je het package opnieuw met Poetry .
\n\n\nPseudo-code\npyproject.toml
[tool.poetry.scripts]\nsquare = \"just_count.count_count:main\"\n
\n\n\nCheckpunten:
\n\n- De naam van het commando is
square
. \n- De verwijzing na het = teken begint met twee aanhalingstekens gevolgd door het package
just_count
gevolgt door een punt. \n- Na de punt staat de naam van de module
count_count.py
zonder de extensie .py
gevolgd door een dubbele punt. \n- Na de dubbele punt staat de naam van de functie
main()
zonder haakjes ()
. \n- Achter de functie staan weer dubble aanhalingstekens om de verwijzing te sluiten.
\n- Na het opslaan van de
pyproject.toml
is het pakket opnieuw ge\u00efnstalleerd. \n
\nProjecttraject
\n\n- main functie toevoegen
\n- commando toevoegen
\n- commando testen
\n
\n\n\n\n\n\n\n\nCommando testen
\nopdrachtcodecheck\n\n\nNu je het commando square
hebt aangemaakt ga je deze testen in een terminal. Er verschijnt een error ModuleNotFoundError: No module named 'square'
. Je leest het info-blokje hieronder.\n\n\nJe runt het commando square
opnieuw en je ziet de tekst The square of 5 is 25
verschijnen. 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 square
en ziet weer de tekst The square of 5 is 25
verschijnen. Je concludeert dat het commando nu overal werkt zolang het juiste conda environment is geactiveerd. Dat test je uit door een ander conda environment te activeren en het commando square
nogmaal te proberen. Je krijgt een error en hebt daarmee je vermoeden bewezen. Tevreden ga je door naar de volgende opdracht.
\n\nModuleNotFoundError: No module named 'square'
\nAls je de Traceback leest zie je dat het probleem ontstaat in de module count_count.py
. Omdat Poetry altijd begint met zoeken vanuit de map src
kan daar de module square.py
niet gevonden worden. Pas het import statement aan naar import just_count.square as square
.
\n\n\n\nPseudo-code\n
(ecpc) > square \nTraceback (most recent call last):\nFile \"/base/envs/just_count/bin/square\", line 3, in \n from just_count.count_count import main\nFile \"/just_count/src/just_count/count_count.py\", line 1, in \n import square\nModuleNotFoundError: No module named 'square'\n\n\n\nCheckpunten:
\n\n- Het import statement in
count_count.py
is genoteerd vanuit de map src
. \n- Het commando
square
werkt als het juiste conda environment is geactiveerd. \n- Het commando
square
werkt nog steeds nadat je met het commando cd..
naar een bovenliggende map bent gegaan. \n- Het commando
square
werkt niet als een andere conda environment is geactiveerd. \n
\nProjecttraject
\n\n- main functie toevoegen
\n- commando toevoegen
\n- commando testen
\n
\n\n\n\n\n\nError analysis\nAls extra oefening gaan we met Poetry 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.
\n\n- Ga naar GitHub en clone
AnneliesVlaar/erroranalysis
in GitHub Desktop en open de repository daarna in Visual Studio Code. \n- Natuurlijk maak je gelijk een nieuwe Conda environment aan , voordat we dit package gaan testen.
\n- Snuffel door de bestanden en mappen, en open
src/erroranalysis/data_analysis.py
. Dit is het script wat moet kunnen runnen. \n- Run het script
data_analysis.py
en los de errors \u00e9\u00e9n voor \u00e9\u00e9n op. \n
\nOm erachter te komen of de problemen die we hierboven hadden \u00e9cht zijn opgelost maak je een nieuwe Conda environment aan , installeer je het package en run je het script. Werkt alles? Mooi! Dan gaan we nu een commando aanmaken om de functie table()
aan te roepen.
\n\n- Open
pyproject.toml
en voeg een kopje toe voor scripts.\n [tool.poetry.scripts]\nnaam_commando = \"package.module:naam_functie\"\n
\n pas de regel aan zodat jouw commando de functie table()
aanroept in src/erroranalysis/data_analysis.py
. Je mag de naam van het commando zelf kiezen. \n- Ga naar de terminal en kijk of het werkt!\n
(ecpc) > naam_commando \nArea of the kitchen table is: 1.8386 \u00b1 0.0049 m\n
\n
\n\n\nPythondaq: test imports
\nopdrachtcodecheck\n\n\n\n \n Bij het uitbouwen van de applicatie ga je mogelijk onderdelen uit de pythonpackage importeren. Daarom is het verstandig om, net als met de opdracht Packages, het importeren uit de package te testen.\n Maak daarvoor een tests
-map met __init__.py
en test_imports.py
in de repository pythondaq
. \n test_imports.py
import pythondaq.view\n
\n Je runt het bestand test_imports.py
en lost de errors op. Daarna werkt je package ook als je het aanroept van buiten de map met broncode. Je pythondaq
-repository is nu een volledig project dat je met andere gebruikers van Python kunt delen, bijvoorbeeld via een wheel.\n \n \n pythondaq
\n \u251c\u2500\u2500src
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u2514\u2500\u2500pythondaq
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u251c\u2500\u2500__init__.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u251c\u2500\u2500arduino_device.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u251c\u2500\u2500diode_experiment.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u2514\u2500\u2500run_experiment.py
\n \u251c\u2500\u2500tests
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u251c\u2500\u2500__init__.py
\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u2514\u2500\u2500test_imports.py
\n \u251c\u2500\u2500pyproject.toml
\n \u2514\u2500\u2500README.md
\n \n\n\n\nPseudocode\nrun_experiment.py
# define from which package the module diode_experiment should be imported\n...\n
\nTestcode\n test_imports.py\nimport pythondaq.view\n
\n\n(ecpc) > python test_imports.py\nTraceback (most recent call last):\n File \"c:\\pythondaq\\tests\\test_imports.py\", line 1, in < module >\n import pythondaq.view\n File \"C:\\pythondaq\\src\\pythondaq\\run_experiment.py\", line 4, in < module >\n from diode_experiment import DiodeExperiment\n ModuleNotFoundError: No module named 'diode_experiment'\n
\n\n\nCheckpunten:
\n\n- Er is een map
tests
in de repository pythondaq
. \n- Er is een bestand
__init__.py
in de map tests
. \n- De import statements in de modules in het package
pythondaq
zijn aangepast zodat het bestand test_imports
runt zonder problemen. \n
\nProjecttraject
\n\n- Pythondaq: Docstring
\n- Pythondaq: src-layout
\n- Pythondaq: poetry
\n- Pythondaq: test imports
\n- Pythondaq: applicatie
\n
\n\n\n\n\n\nPythondaq: applicatie
\nopdrachtcodecheck\n\n\nJe maakt een commando om het script run_experiment.py
uit de repository pythondaq
te starten . Wanneer je het commando aanroept gaat het LED-lampje branden, en verschijnt er even later een IU-plot op het scherm. Je test of het commando ook buiten Visual Studio Code werkt door een Anaconda prompt
te openen. Je activeert het juiste conda environment en ziet dat ook dan het commando werkt. Wat een feest! Je hebt nu een applicatie geschreven die een Arduino aanstuurt om een ledje te laten branden. En je kunt je applicatie gewoon vanuit de terminal aanroepen!
\n\n\nPseudo-code\nrun_experiment.py
# import statements\n\n# def function\n # code to start a measurement\n
\npyproject.toml[tool.poetry.scripts]\nnaam_commando = \"package.module:naam_functie\"\n
\n\n\nCheckpunten:
\n\n- De functie in
run_experiment.py
bevat alle code die uitgevoerd moet worden om een meting te starten. \n- Het commando in de
pyproject.toml
verwijst op de correcte manier naar de functie in run_experiment.py
. \n- Het aanroepen van het commando zorgt ervoor dat een meting gestart wordt.
\n- Het commando werkt ook in een
Anaconda prompt
zolang het juiste conda environment actief is. \n
\nProjecttraject
\n\n- Pythondaq: Docstring
\n- Pythondaq: src-layout
\n- Pythondaq: poetry
\n- Pythondaq: test imports
\n- Pythondaq: applicatie
\n
\n\n\n\n\n\nVersie 2.0.0\nIn de pyproject.toml
kan je ook de versie aangeven van je package. Maar wanneer hoog je nu welk cijfertje 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 je bijvoorbeeld bestanden die je met oude versie hebt gemaakt niet met de nieuwe versie kunt openen, 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 eerste gebruik, dan kies je er vaak voor om versie 1.0.0 te releasen.
\n\n\n\n\n- \n
Echt gebeurd: meerdere studenten leverden hun grafische applicatie in voor een beoordeling. We konden het niet draaien, want er misten bestanden. Bij de student werkte het wel, maar bij ons echt niet.\u00a0\u21a9
\n \n- \n
Wanneer de repository op GitHub wordt geplaatst wordt deze README automatisch op de hoofdpagina van de repository getoond, onder de code.\u00a0\u21a9
\n \n- \n
Python heeft een ingebouwde module unittest
die deze tests kan vinden, kan runnen en daarna een handige weergave geeft van welke tests geslaagd zijn en welke faalden. Ook het package pytest
is erg bekend. Op deze manier weet je altijd zeker dat wanneer je aanpassingen doet in je code, dat de rest van de code nog steeds is blijven werken \u2014 z\u00f3nder dat je zelf uitvoerig alles hebt hoeven uitproberen. Je draait gewoon even snel alle tests. Helaas, helaas \u2014 in deze cursus is te weinig tijd om het schrijven van tests te behandelen.\u00a0\u21a9
\n \n- \n
Ja er is een map easystat
met daarin een map src
met daarin weer een map easystat
\u2014 dat kan nog wel eens verwarrend zijn. Het is conventie om de projectmap dezelfde naam te geven als je package. Het pad is dus eigenlijk project/src/package
en dat wordt dan, in ons geval, easystat/src/easystat
.\u00a0\u21a9
\n \n- \n
Vroeger was er een setup.py
maar Python schakelt nu langzaam over naar dit nieuwe bestand.\u00a0\u21a9
\n \n- \n
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 definieert name = \"Alice\"
, dan geeft print(f\"{name}\")
als uitkomst Alice
. Maar voeg je het =
-teken toe zoals in print(f\"{name=\")}
wordt de uitvoer name='Alice'
. Je ziet dan dus ook meteen de naam van de variabele en dat kan handig zijn.\u00a0\u21a9
\n \n- \n
Dit is bij het aanmaken standaard ingevuld op basis van de Python versie die in de base environment zit, kijk maar met conda list
in de base environment welke versie van Python daarin zit.\u00a0\u21a9
\n \n- \n
Het is eenvoudig om zelf de pyproject.toml
te openen en daar wat in aan te passen voor zover nodig.\u00a0\u21a9
\n \n- \n
PySerial is een package die we gebruiken om te communiceren over USB poorten.\u00a0\u21a9
\n \n- \n
Hynek Schlawack. Testing & packaging. URL: https://hynek.me/articles/testing-packaging/.\u00a0\u21a9
\n \n- \n
Tom Preston-Werner, Pradyun Gedam, and others. Tom's obvious, minimal language. URL: https://github.com/toml-lang/toml.\u00a0\u21a9
\n \n
"},{"location":"software-tools/","title":"Gereedschap","text":""},{"location":"software-tools/#isolatie-virtual-environments","title":"Isolatie: virtual environments","text":"Je hebt het misschien al gemerkt: Anaconda neemt veel schijfruimte in beslag. Dat is gek, want Python is best klein. Anaconda bevat alleen veel meer dan Python. Anaconda is een Python-distributie en bevat een enorme verzameling aan packages. Je kunt zelf extra packages installeren met conda
of pip
. Je loopt dan mogelijk wel tegen problemen aan: packages hebben vaak zelf weer andere packages nodig. En regelmatig ook met een bepaalde versie. Dit kan een ingewikkeld netwerk worden waarbij het installeren van een nieuwe package \u00f3f heel lang duurt, \u00f3f niet kan vanwege een conflict,1 \u00f3f blind gedaan wordt waarna sommige dingen niet meer willen werken. Alledrie is op te lossen door virtual environments te gebruiken. Ge\u00efsoleerde omgevingen met een eigen \u2014 veelal kleine \u2014 collectie van packages. Soms zelfs met een eigen versie van Python. Je kunt environments aanmaken voor specifieke projecten bijvoorbeeld: een omgeving voor NSP1, een omgeving voor ECPC en een omgeving voor een hobbyproject. Wellicht heb je bij NSP1 een environment aangemaakt om Jupyter Notebooks en een verzameling packages te installeren voor de data-analyse.
"},{"location":"software-tools/#pip-vs-conda","title":"Pip vs Conda","text":"De package manager van Python is pip
. Je kunt hiermee alle Python packages installeren die bestaan uit Python code. NumPy bijvoorbeeld bevat echter ook veel code geschreven in C. Die code moet eerst gecompileerd worden. Dat kan pip
\u00f3\u00f3k doen, mits er een C compiler op je computer ge\u00efnstalleerd is. Via de Python package index kunnen gelukkig ook zogeheten binary packages verspreid worden waarin de code al is gecompileerd. Er zijn dan losse packages voor Windows, MacOS en Linux. Meestal gaat dit goed, maar helaas niet altijd. Historisch waren NumPy maar vooral ook SciPy een flink probleem. Ook het gebruik van grafische bibliotheken ging vaak moeizaam. Dan was het package wel ge\u00efnstalleerd, maar riep hij dat hij systeembibliotheken niet kon vinden. Heel vervelend.
Een ander probleem van pip
is dat deze \u2014 tot voor kort \u2014 geen controle deed op de versies van al ge\u00efnstalleerde pakketten. Je kon dus packages installeren die nieuwe versies binnenhaalden van andere packages, waarna al eerder ge\u00efnstalleerde packages soms stopten met werken.
Om die reden is conda
in het leven geroepen. Conda installeert alleen binary packages, kan naast Python packages ook systeembibliotheken installeren als dat nodig is \u00e9n doet een uitgebreide controle op alle versies van te installeren en al eerder ge\u00efnstalleerde packages zodat alles altijd blijft werken. Nadeel is dat die controle nogal lang kan duren als je al veel ge\u00efnstalleerd hebt. Omdat je met conda
dus wel heel makkelijk uitgebreide wetenschappelijke packages kon installeren met een mix van Python-, C-, of zelfs Fortrancode is conda
(en Anaconda, de distributie) heel populair geworden in de wetenschappelijke wereld. Omdat jullie bij vorige cursussen al gewerkt hebben met Anaconda zullen we dat deze cursus ook gebruiken, maar we gaan veel met pip
werken om packages te schrijven die door alle Pythongebruikers gebruikt kunnen worden.
"},{"location":"software-tools/#conda-environments","title":"Conda environments","text":"Er zijn verschillende tools voor het aanmaken van environments voor Python. Allemaal hebben ze hun voor- en nadelen. Langzamerhand blijven de populairste over. De offici\u00eble is venv
, maar op dit moment niet de meest populaire. Binnen een groot deel van de wetenschappelijke gemeenschap is conda
de standaardkeuze. Het voordeel van conda
ten opzichte van veel andere tools is dat je verschillende environments kunt maken met verschillende versies van Python. Ideaal om te testen of je code ook werkt met de allernieuwste Pythonversie of juist met wat oudere versies.
Je moet je realiseren dat het aanmaken (en weggooien) van een environment heel makkelijk is. Doe dat regelmatig zodat je scherp houdt welke packages je nu echt nodig hebt voor je analyse of voor de software die je schrijft. Hieronder geven we een overzicht van de meest gebruikte commando's om met conda environments te werken.
Info
Conda installeert packages vanuit verschillende channels. De defaults
channel bevat packages die af en toe door Anaconda worden getest en samengenomen tot een distributie (versie 2021.05
bijvoorbeeld). Er zijn weinig updates. De conda-forge
channel bevat alle nieuwste versies van die packages en bevat ook software die (nog) niet in de defaults
channel terecht is gekomen. De conda-forge channel is daarom erg populair, maar er gaat ook regelmatig iets stuk.
Hieronder volgen enkele voorbeelden van het gebruik van conda: Terminal
Leeg environment aanmaken met naam 'pythondaq' (leeg = zelfs geen Python)\nPS> conda create --name pythondaq\n\nNieuw environment aanmaken met Python versie 3.10\nPS> conda create --name pythondaq python=3.10\n\nPackages installeren vanuit de 'conda-forge' channel en nieuwste Python\nAls het environment al bestaat vraagt hij of hij die moet overschrijven met een nieuwe schone versie\nPS> conda create --name pythondaq --channel conda-forge python\n\nEnvironment activeren\nPS> conda activate pythondaq\n\nEnvironment deactiveren\nPS> conda deactivate\n\nEnvironment wissen\nPS> conda env remove --name pythondaq\n\nLijst van environments bekijken\nPS> conda env list\n\nNieuw pakket installeren vanuit de conda-forge channel in het ACTIEVE environment\nPS> conda install --channel conda-forge lmfit\n\nNieuw environment voor NSP2 met notebooks voor analyse en fits\nPS> conda create --name nsp2 --channel conda-forge notebook pandas matplotlib lmfit\n\nPackage pandas updaten naar nieuwe versie in het ACTIEVE environment\nPS> conda update --channel conda-forge pandas\n\nAlle packages updaten naar nieuwe versie in het ACTIEVE environment\nPS> conda update --channel conda-forge --all\n
Als je scripts schrijft in Visual Studio Code wil je dat ze ook runnen in de omgevingen die je net hebt aangemaakt. Als je in Visual Studio Code een python script opent dan geeft het rechtsonder, in de statusbalk, de huidige Pythonomgeving aan:
Als je daarop klikt2 kun je door de lijst met Pythonomgevingen scrollen. Kies de omgeving die je wilt gebruiken. Let op: als je het environment net hebt aangemaakt dan staat hij er nog niet tussen. Klik dan rechtsbovenin eerst op het Refresh Interpeter list-knopje. Bijvoorbeeld:
Sluit alle oude terminals met het -icoon als je je muis aan de rechterkant over de namen van de terminals beweegt of in \u00e9\u00e9n keer met View > Command Palette > Terminal: Kill All Terminals. Alle nieuwe terminals die je opent zullen de nieuw geselecteerde conda environment actief maken. Wanneer je nu je Pythoncode draait dan is dat binnen deze omgeving. Het kan wel zijn dat hij opeens klaagt over packages die niet ge\u00efnstalleerd zijn omdat je dat \u2014 in die omgeving \u2014 nog niet had gedaan. Geen probleem: installeer ze dan.
"},{"location":"software-tools/#pipx","title":"Pipx","text":"Pythonapplicaties, zoals conda
, worden ge\u00efnstalleerd als commando dat je kunt aanroepen vanaf de command-line. Maar het is een Pythonapplicatie. En dat betekent dat als je van omgeving wisselt, de applicatie niet meer beschikbaar is. Ook kan het gebeuren dat je packages update of verwijdert waardoor de applicatie niet meer werkt. Met pipx
is het mogelijk om dit soort applicaties in een eigen virtual environment te installeren. Je loopt geen risico dat je ze stukmaakt en ze zijn beschikbaar vanuit andere virtual environments. In plaats van: Terminal
pip install PACKAGE\n
doe je straks Terminalpipx install PACKAGE\n
Met pipx list
bekijk je dan een lijst van ge\u00efnstalleerde pakketten. Je installeert pipx
met: Terminalpython -m pip install --user pipx\npython -m pipx ensurepath\n
Herstart je terminal en test of het commando pipx
werkt. Als je in een terminal in Visual Studio Code werkt moet je dat ook herstarten en als je VS Code gestart hebt vanuit GitHub Desktop moet je \u00f3\u00f3k dat herstarten. Werkt het nog steeds niet, dan zul je volledig uit moeten loggen en weer in moeten loggen om de shellomgeving opnieuw te laden en/of vraag om hulp."},{"location":"software-tools/#coding-style-black","title":"Coding style: Black","text":"Code wordt veel vaker gelezen dan geschreven, is een veel geciteerd gezegde onder programmeurs. Je schrijft je code en zit vervolgens uren te puzzelen om een fout te vinden of hoe je de code het beste kunt uitbreiden. Je zoekt op internet naar voorbeeldcode, je helpt een medestudent of vraagt die om hulp. Heel vaak dus lees je niet je eigen code, maar die van iemand anders. Is dat relevant? Ja! Want die code ziet er anders uit. Iedereen programmeert toch op zijn eigen manier. Het scheelt enorm als de code er tenminste grotendeels hetzelfde uitziet. Het kost je dan minder energie om te lezen. Daarom ook dat de artikelen in wetenschappelijke tijdschriften bijvoorbeeld er allemaal hetzelfde uitzien en de auteur niet de vrijheid krijgt om z\u00e9lf lettertypes te kiezen. Net zo goed hebben grote organisaties vaak hun eigen coding style ontwikkeld waar alle werknemers zich zoveel mogelijk aan moeten houden.
Python heeft een eigen style guide die je vooral eens door moet lezen.3 Google heeft ook een hele mooie, met duidelijke voorbeelden.4
Fijn dat je code consistenter wordt, maar het moet nu ook weer niet zo zijn dat je uren kwijt bent met de style guides bestuderen of twijfelen waar je een regel code precies moet afbreken. Wel of niet een enter? Om daar vanaf te zijn zijn er verschillende pakketten die je code automatisch aanpassen aan de standaard. Als je de instelling Editor: Format On Save aan zet (staat standaard uit) dan wordt je code aangepast zodra je je bestand opslaat. Black is zo'n formatter en heeft een `eigen mening'. Als je je daar bij neerlegt hoef je bijna niet meer na te denken over hoe je je code precies vormgeeft. De Black website zegt5:
By using Black, you agree to cede control over minutiae of hand-formatting. In return, Black gives you speed, determinism, and freedom from pycodestyle nagging about formatting. You will save time and mental energy for more important matters.
Black is tegenwoordig immens populair en in Visual Studio Code kun je hem gebruiken door de Black Formatter-extensie van Microsoft te installeren. De code in deze handleiding is geformat met Black. In Visual Studio Code, ga naar File en dan naar Preferences > Settings > Editor: Format On Save en vink die aan. De eerste keer dat je je bestand opslaat zal hij vragen of hij Black moet gebruiken, daarna wordt je code altijd netjes gemaakt zodra je je Pythonbestand bewaart.
De volgende code:
s1 = 'Hello'\ns2 = \"World\"\nvalues = [1,2,3,4,5]\n\nf = a * x ** 2 + b * x + c\ng = a*x +b\nh = A*np.sin(2*pi*f*t+phi) + A2*np.sin(2*pi*f2*t+phi2) + A3*np.sin(2*pi*f3*t+phi3)\n
wordt door Black omgezet in:
s1 = \"Hello\"\ns2 = \"World\"\nvalues = [1, 2, 3, 4, 5]\n\nf = a * x**2 + b * x + c\ng = a * x + b\nh = (\n A * np.sin(2 * pi * f * t + phi)\n + A2 * np.sin(2 * pi * f2 * t + phi2)\n + A3 * np.sin(2 * pi * f3 * t + phi3)\n)\n
-
Stel package A heeft package B nodig met versie >= 1.1, maar package C heeft package B nodig met versie 1.0. Nu kunnen packages A en C dus niet tegelijkertijd ge\u00efnstalleerd worden.\u00a0\u21a9
-
Of: View > Command Palette > Python: Select Interpreter.\u00a0\u21a9
-
Guide van Rossum. Pep 8 \u2013 style guide for python code. URL: https://www.python.org/dev/peps/pep-0008/.\u00a0\u21a9
-
Google. Google python style guide. URL: https://google.github.io/styleguide/pyguide.html.\u00a0\u21a9
-
\u0141ukasz Langa. Black, the uncompromising code formatter. URL: https://black.readthedocs.io/en/stable/.\u00a0\u21a9
"},{"location":"terminal-adventure-game/","title":"Terminal Adventure Game","text":"To play the Terminal Adventure Game, clone the repository or download and unpack the zip. Open the folder suitable for your operating system and preferred language in explorer. Go into the forest folder and open/run the 'start' file. Let yourself be guided through the adventure.
Parrot Pokkie was taken from the girl by Snerk the Extremely Magnificent. Can you help her get Pokkie back?
"},{"location":"terminal-adventure-game/#credits","title":"credits","text":"This game was developed by Extra Nice in Leeuwarden in association with VU
"},{"location":"tui/","title":"Text-based user interfaces","text":"De text-based user intefaces (TUI) of terminal user interface zijn de voorlopers van de graphical user interfaces die wel nog in een terminal worden uitgevoerd. Een populair framework waarmee je in Python een TUI kunt bouwen is Textual.
Textual installeren Maak een nieuwe conda omgeving aan en installeer de benodigdheden voor Textual. Zie voor meer informatie de documentatie
Stopwatch Volg de tutorial van Textual om een stopwatch applicatie te maken.
Pythondaq: TUI Maak met behulp van Textual een TUI voor Pythondaq.
"},{"location":"vervolg-python/","title":"Uitgebreidere Python kennis","text":"Python is een batteries included taal. Dat betekent dat als je 'kaal' Python installeert er al heel veel functionaliteit standaard meegeleverd wordt. Allereerst omdat de taal zelf al behoorlijk krachtig is, maar ook omdat de standaardbibliotheek zeer uitgebreid is. Met een eenvoudig import
-statement haal je extra functionaliteit binnen, onder andere op het gebied van datatypes, wiskunde, toegang tot bestanden, een database, datacompressie, cryptografie, netwerktoegang, e-mail, multimedia, etc. Nog veel meer bibliotheken zijn beschikbaar via de Python Package Index17.
In dit hoofdstuk behandelen we de kennis die nuttig kan zijn voor de rest van deze cursus1. Een deel van wat we hier behandelen kan al bekend zijn uit eerdere cursussen. Een ander deel is nieuw.2
In de cursus gaan we bibliotheken (modules, packages) en een applicatie ontwikkelen. Dat betekent dat we verder gaan dan het schrijven van scripts en dat we dus meer gaan doen dan functies schrijven. Uiteindelijk moet het mogelijk zijn de software te verspreiden op een wat meer professionele manier. Dus niet alleen via een zipje met wat Pythonbestanden waar uiteindelijk verschillende versies van rondslingeren en die lastig zijn te updaten. Wat er nodig is voor een goede distributie van software en om het mogelijk te maken met meerdere mensen software te (blijven) ontwikkelen zal in deze cursus aan bod komen.
Een punt wat vaak onderschoven blijft is documentatie. Als je software schrijft die gebruikt (en doorontwikkeld) wordt in een onderzoeksgroep, dan is het heel belangrijk dat iedereen kan begrijpen wat je software doet en hoe die uitgebreid kan worden. Het is zonder hulp vaak heel moeilijk om de code van een iemand anders te begrijpen. En in de praktijk blijkt heel vaak dat als je code schrijft en daar een paar weken of maanden later op terugkijkt, jij z\u00e9lf die ander bent. Wat toen blijkbaar heel logisch leek, is dat later toch niet meer. Dus documentatie schrijf je heel vaak ook gewoon voor jezelf.
Als je niet zo heel veel in Python geprogrammeerd hebt kan het helpen om de paragraaf Basiskennis Python18 door te nemen. Een boek dat zeker bij natuurkundigen in de smaak kan vallen is Effective Computation in Physics19, maar deze is niet gratis verkrijgbaar. Een boek dat zowel op papier te bestellen is als in de vorm van een pdf of webpagina is te lezen is Think Python.20
"},{"location":"vervolg-python/#zen-of-python","title":"Zen of Python","text":"Python is niet C (of iedere willekeurige andere programmeertaal). Er zit een gedachte achter die op een gegeven moment verwoord is door Tim Peters21.
Je kunt het lezen middels een easter egg in Python zelf: import this
.
zen
- Open Visual Studio Code.
- Open de map
ECPC
). - Maak een bestand
zen-of-python.py
met daarin de onderstaande code: import this\n
ECPC
\u251c\u2500\u2500 zen-of-python.py
\u2514\u2500\u2500 \u2022\u2022\u2022 - Run het script en lees de output.
Deze tekst kan nog behoorlijk cryptisch overkomen, maar een paar dingen worden snel duidelijk: code moet mooi zijn (regel 1) en duidelijk (regels 2, 3 en 6). Er bestaan prachtige programmeertrucs in \u00e9\u00e9n of twee regels, maar onleesbaar is het wel. Een voorbeeld 22:
print('\\n'.join(\"%i bytes = %i bits which has %i possible values.\" %\n (j, j*8, 256**j) for j in (1 << i for i in range(4))))\n
Kun je zien wat de uitvoer van dit programma moet zijn? Misschien als we het op deze manier uitschrijven:
zen.py for num_bytes in [1, 2, 4, 8]:\n num_bits = 8 * num_bytes\n num_possible_values = 2 ** num_bits\n print(\n f\"{num_bytes} bytes = {num_bits} bits which has {num_possible_values} possible values.\"\n )\n
\n(ecpc) > python zen.py\n1 bytes = 8 bits which has 256 possible values.\n2 bytes = 16 bits which has 65536 possible values.\n4 bytes = 32 bits which has 4294967296 possible values.\n8 bytes = 64 bits which has 18446744073709551616 possible values.\n
De code is langer, met duidelijkere namen van variabelen en zonder bitshifts of joins.
Moraal van dit verhaal: we worden gelukkiger van code die leesbaar en begrijpelijk is, dan van code die wel heel slim in elkaar zit maar waar bijna niet uit te komen is. Overigens komt het regelmatig voor dat de programmeur z\u00e9lf een paar weken later al niet zo goed meer weet hoe de code nou precies in elkaar zat.
Als je samenwerkt aan software kan het andere Pythonprogrammeurs erg helpen om dingen 'op de Python-manier te doen'. Een C-programmeur herken je vaak aan het typische gebruik van lijsten of arrays in for
-loops. Als je een lijst hebt: names = ['Alice', 'Bob', 'Carol']
, doe dan niet:
names = ['Alice', 'Bob', 'Carol']\ni = 0\nwhile i < len(names):\n print(\"Hi,\", names[i])\n i = i + 1\n
en ook niet: names = ['Alice', 'Bob', 'Carol']\nfor i in range(len(names)):\n print(\"Hi,\", names[i])\n
waarbij je loopt over een index i
. Gebruik liever het feit dat een lijst al een iterator is: names = ['Alice', 'Bob', 'Carol']\nfor name in names:\n print(\"Hi,\", name)\n
Deze code is bovendien veel korter en gebruikt minder variabelen. Itereren op de python-manier
- Neem het onderstaande script over.
- Itereer over de lijst
voltages
op de python-manier. - Print voor elk item in de lijst de waarde in mV. Bijvoorbeeld: \"The voltage is set to 0 mV.\"
voltages = [0, 50, 100, 150, 200, 250, 300] #mV\n
Uitwerkingen iterator.py
voltages = [0, 50, 100, 150, 200, 250, 300] #mV\n\nfor voltage in voltages:\n print(f\"The voltage is set to {voltage} mV.\")\n
\n(ecpc) > python iterator.py\nThe voltage is set to 0 mV.\nThe voltage is set to 50 mV.\nThe voltage is set to 100 mV.\nThe voltage is set to 150 mV.\nThe voltage is set to 200 mV.\nThe voltage is set to 250 mV.\nThe voltage is set to 300 mV.\n
"},{"location":"vervolg-python/#enumerate","title":"Enumerate","text":"Soms is het nodig om de index te hebben, bijvoorbeeld wanneer je een namenlijstje wilt nummeren: Terminal
1. Alice\n2. Bob\n3. Carol\n
Dit kan dan in Python-code het makkelijkst als volgt:
for idx, name in enumerate(names, 1):\n print(f\"{idx}. {name}\")\n
Hier maken we gebruik van de enumerate(iterable, start=0)
-functie en f-strings. Er zijn dus veel manieren om programmeerproblemen op te lossen, maar het helpt om het op de `Pythonmanier' te doen. Andere programmeurs zijn dan veel minder tijd en energie kwijt om jouw code te begrijpen -- \u00e9n andersom wanneer jij zelf op internet zoekt naar antwoorden op problemen. Immers, je herkent dan veel makkelijker en sneller hoe andermans code werkt."},{"location":"vervolg-python/#datatypes","title":"Datatypes","text":"Gehele getallen, kommagetallen, strings: allemaal voorbeelden van datatypes. Veel zullen jullie al wel bekend voorkomen, zoals strings, lists en NumPy arrays. Andere zijn misschien alweer wat weggezakt, zoals dictionaries of booleans. Weer andere zijn misschien wat minder bekend, zoals complexe getallen of sets. En als laatste voegt Python af en toe nieuwe datatypes toe, zoals f-strings in Python 3.6 of data classes sinds Python 3.7.
Info
De python-standard-library documentatie 23 bevat een mooi overzicht van alle datatypes met een beschrijving van operaties en eigenschappen. Voor uitgebreidere tutorials kun je vaak terecht bij real-python 24. Het kan makkelijk zijn om in een zoekmachine bijvoorbeeld real python dict
te typen als je een tutorial zoekt over Python dictionaires.
Om nog even te oefenen met de datatypes volgt er een aantal korte opdrachten.
"},{"location":"vervolg-python/#list","title":"List","text":"list
Schrijf een kort scriptje.
- Maak een
list
van de wortels van de getallen 1 tot en met 10. Dus de rij $\\left(\\sqrt{1}, \\sqrt{2}, \\sqrt{3}, \\ldots, \\sqrt{10}\\right)$. - Print die rij onder elkaar (\u00e9\u00e9n getal per regel, met drie decimalen).
- Geef weer of het getal 3 voorkomt in die rij en geef weer of het getal 4 voorkomt in die rij.
Uitwerkingen list.py
import math\n\nsquares = []\nfor n in range(1, 11):\n squares.append(math.sqrt(n))\n\n# print the list below each other with three decimal places\nprint(\"Square of range 1 to 10 with three decimal places: \")\nfor square in squares:\n print(f\"{square:.3f}\")\n\n# State if number 3 or 4 appears in the list of squares\nfor number in [3, 4]:\n print(f\"does number {number} appears in the list of squares?\", number in squares)\n
\n(ecpc) > python list.py\nSquare of range 1 to 10 with three decimal places: \n1.000\n1.414\n1.732\n2.000\n2.236\n2.449\n2.646\n2.828\n3.000\n3.162\ndoes number 3 appears in the list of squares? True\ndoes number 4 appears in the list of squares? False\n
"},{"location":"vervolg-python/#numpy-array","title":"NumPy array","text":"Je kunt op verschillende manieren een NumPy array maken (na import numpy as np
):
- Door een Python lijst te converteren:
np.array([0, 0.5, 1, 1.5, 2])
. - Door een array aan te maken met een stapgroote:
np.arange(0, 3, 0.5) # start, stop, step
- Door een array aan te maken met getallen gelijkmatig verdeeld over een interval:
np.linspace(0, 2.5, 6) #start, stop, number
NumPy arrays NumPy arrays zijn vaak handiger dan lists. Als je een array hebt van 20 $x$-waardes in het domein $[0, \\pi]$ kun je in \u00e9\u00e9n keer alle waardes van $\\sin x$ uitrekenen. Bijvoorbeeld:
import numpy as np\nfrom numpy import pi\n\nx = np.linspace(0, pi, 20)\ny = np.sin(x)\n
NumPy voert de berekeningen uit binnen een C-bibliotheek3 en is daarmee veel sneller dan een berekening in Python zelf: import math\nx = [0.00, 1.05, 2.09, 3.14, 4.19, 5.24, 6.28]\ny = []\nfor u in x:\n y.append(math.sin(u))\n
Niet alleen is NumPy zo'n honderd keer sneller,4 het is ook veel korter op te schrijven. Het nadeel van NumPy arrays is dat je geen elementen kunt toevoegen.5 Python lijsten hebben dus voordelen, zeker als rekentijd geen probleem voor je is. Als je veel functies uit NumPy gebruikt is het handig \u2013 en gebruikelijk \u2013 om je import-statements kort te houden en duidelijk te maken dat je de sin()
-functie uit NumPy gebruikt en niet uit de math
module. Constantes worden wel vaak los ge\u00efmporteerd. Daarom is dit dus gebruikelijk:
import numpy as np\nfrom numpy import pi\n\nx = np.linspace(0, pi, 100)\ny = np.sin(x)\n
np.array
Doe hetzelfde als de vorige opdracht met lists, maar nu met NumPy arrays:
- Maak een
np.array
van de wortels van de getallen 1 tot en met 10. Dus de rij $\\left(\\sqrt{1}, \\sqrt{2}, \\sqrt{3}, \\ldots, \\sqrt{10}\\right)$. - Print die rij onder elkaar (\u00e9\u00e9n getal per regel, met drie decimalen).
- Geef weer of het getal 3 voorkomt in die rij en geef weer of het getal 4 voorkomt in die rij.
Uitwerkingen np_array.py
import numpy as np\n\n# Make an array from 1 to 10\nnumbers = np.arange(1, 11, 1)\n\n# Take the squareroot of each number\nsquareroot = np.sqrt(numbers)\n\n# Print the list of squareroots below each other with three decimal places\nfor root in squareroot:\n print(f\"{root:.3f}\")\n\n# State if number 3 or 4 appears in the list of squares\nprint(\"does number 3 appears in the list of squares?\", 3 in squareroot)\nprint(\"does number 4 appears in the list of squares?\", 4 in squareroot)\n
\n(ecpc) > python np_array.py\n1.000\n1.414\n1.732\n2.000\n2.236\n2.449\n2.646\n2.828\n3.000\n3.162\ndoes number 3 appears in the list of squares? True\ndoes number 4 appears in the list of squares? False\n
Dictionaries Tuples, * args, ** kwargs Set"},{"location":"vervolg-python/#dictionaries","title":"Dictionaries","text":"Dictionaries zijn een bijzonder handige manier om informatie op te slaan. Een dictionary bestaat uit een of meerdere key-value tweetallen. Met een handige gekozen naam voor de key kan je betekenis geven aan een value.
dict
Schrijf een kort scriptje:
- Maak een dictionary
constants
met de waardes van de (natuur)constantes $\\pi$, de valversnelling $g$, de lichtsnelheid $c$ en het elementaire ladingskwantum $e$. - Print de namen -- niet de waardes -- van de constantes die zijn opgeslagen in
constants
. - Bereken de zwaartekracht $F_\\text{z} = mg$ voor een voorwerp met een massa van 14 kg door gebruik te maken van de waarde van $g$ uit de dictionary.
- Maak een dictionary
measurement
die de resultaten van een meting bevat: een spanning van 1.5 V bij een stroomsterkte van 75 mA. - Bereken de weerstand van de schakeling op basis van de voorgaande meting en bewaar het resultaat in dezelfde dictionary.
Uitwerkingen dictionaries.py
import numpy as np\n\n# Dictionary of constants pi, gravitational acceleration (g), the speed of light (c) and elementary charge (e)\nconstants = {\"pi\": np.pi, \"g\": 9.81, \"c\": 3e8, \"e\": 1.6e-19}\n\n# print de names -not the values- of the constants in the dictionary\nprint(constants.keys())\n\n# Calculate gravity of an object with mass of 14 kg\nmass = 14 # kg\nF_z = mass * constants[\"g\"]\nprint(f\"Gravity of an object with {mass} kg is: {F_z} N\")\n\n# Dictionary with results of a measurement \nmeasurement = {\"U\": 1.5, \"I\": 75e-3} # U in V, I in A\n\n# Add resistance to dictionary\nmeasurement[\"R\"] = measurement[\"U\"] / measurement[\"I\"]\n\nprint(f'The resistance was: {measurement[\"R\"]:.2f} \\u03A9')\n
\n(ecpc) > python dictionaries.py\ndict_keys(['pi','g', 'c', 'e'])\nGravity of an object with 14kg is: 137.34 N\nThe resistance was: 20.00 \u03a9\n
"},{"location":"vervolg-python/#tuples-args-kwargs","title":"Tuples, * args, ** kwargs","text":"In Python zijn tuple
's een soort alleen-lezen list
's. Een tuple is een immutable6 object. Daarom worden ze vaak gebruikt wanneer lijstachtige objecten altijd dezelfde vorm moeten hebben. Bijvoorbeeld een lijst van $(x, y)$-co\u00f6rdinaten zou je zo kunnen defini\u00ebren:
coords = [(0, 0), (1, 0), (0, 1)]\n
Hier is coords[0]
gelijk aan (0, 0)
. Je kunt nu niet dit co\u00f6rdinaat uitbreiden naar drie dimensies met coords[0].append(1)
en dat is waarschijnlijk precies wat je wilt voor een lijst met tweedimensionale co\u00f6rdinaten. Ook is dit object veel compacter dan een dict
: coords = [{\"x\": 0, \"y\": 0}, {\"x\": 1, \"y\": 0}, {\"x\": 0, \"y\": 1}]\n
Hier zijn tuples dus best handig, al moet je dus wel onthouden in welke volgorde de elementen staan. Dat is voor $(x, y)$-co\u00f6rdinaten niet zo'n probleem maar kan in andere situaties lastiger zijn.7 Tuples ondersteunen tuple unpacking. Je kunt het volgende doen: (x, y, z) = (2, 3, 4)\n
Na deze operatie geldt $x = 2$, $y = 3$ en $z = 4$. Je mag zelfs de haakjes weglaten voor nog compactere notatie: x, y, z = 2, 3, 4\n
Op deze manier kan een functie ook meerdere argumenten teruggeven die je vervolgens uit elkaar plukt: def get_measurement():\n ... # perform measurement\n return voltage, current\n\n\nvoltage, current = get_measurement()\n
Het uit elkaar plukken van argumenten kan zelfs als je een functie aanroept: def power(a, b):\n return a ** b\n\n\n# regular function call\npower(2, 7)\n\n# function call with tuple unpacking\nargs = 2, 7\npower(*args)\n
Wat zelfs werkt is dictionary unpacking. Je kunt aan functies ook argumenten bij naam meegeven -- de volgorde maakt dan niet uit en je maakt in je programma expliciet duidelijk welke argumenten je meegeeft. Dat werkt zo: # regular function call\npower(b=7, a=2)\n\n# function call with dictionary unpacking\nkwargs = {\"b\": 7, \"a\": 2}\npower(**kwargs)\n
args
Gegeven de lijst odds = [1, 3, 5, 7, 9]
, print de waardes uit deze lijst op \u00e9\u00e9n regel, zoals hieronder weergegeven: Terminal
1 3 5 7 9\n
Je mag er niet vanuit gaan dat de lijst altijd 5 elementen bevat. Uitwerkingen odds = [1, 3, 5, 7, 9]\n\n# What is the difference between printing with and without a star?\nprint(*odds)\n\nprint(odds)\n
"},{"location":"vervolg-python/#set","title":"Set","text":"Als laatste willen we nog de aandacht vestigen op set
's: een unieke verzameling van objecten. Ieder element komt maar \u00e9\u00e9n keer voor in een set:
l = [1, 2, 2, 3, 5, 5]\nset(l)\n# {1, 2, 3, 5}\n
Je moet even oppassen: de {}
-haakjes worden gebruikt voor zowel sets als dictionaries. Omdat een dictionary (key: value) paren heeft en een set losse elementen kan Python het verschil wel zien: is_set = {1, 2, 3, 4}\nis_dict = {1: 1, 2: 4, 3: 9, 4: 16}\n
Dat gaat alleen mis als je een lege set wilt maken. Daarvoor zul je expliciet de set()
-constructor moeten gebruiken: is_dict = {}\nis_set = set()\n
Je kunt elementen toevoegen aan een set met .add()
en sets gebruiken om verzamelingen met elkaar te vergelijken. Komen er elementen wel of niet voor in een set? Is de ene set een subset van de andere set? Enzovoorts. Zie daarvoor verder de documentatie."},{"location":"vervolg-python/#comprehension","title":"Comprehension","text":"Door gebruik te maken van een list comprehension kun je de for-loop in \u00e9\u00e9n regel opschrijven:
from math import sin\nx = [0.00, 1.05, 2.09, 3.14, 4.19, 5.24, 6.28]\ny = [sin(u) for u in x]\n
Er is in veel gevallen tegenwoordig geen groot verschil met een for-loop qua snelheid. In andere gevallen is de list comprehension net wat sneller. Als je lijsten niet te lang zijn is het makkelijker (en sneller) om een list comprehension te gebruiken in plaats van je lijst \u00e9\u00e9rst naar een array te veranderen en er dan mee verder te rekenen. Als je lijst w\u00e9l lang is of je weet al dat je meerdere berekeningen wilt uitvoeren kan dat wel: import numpy as np\nx = [0.00, 1.05, 2.09, 3.14, 4.19, 5.24, 6.28]\nx = np.array(x)\ny = np.sin(x)\n
Kortom: berekeningen met arrays zijn sneller, maar for-loops (en list comprehensions) zijn veelzijdiger. Het is zelfs mogelijk om een if
-statement op te nemen in je list comprehension. Bijvoorbeeld:
pdf.py filenames = [\"test.out\", \"text.pdf\", \"manual.pdf\", \"files.zip\"]\npdfs = [name for name in filenames if name.endswith(\".pdf\")]\nprint(f\"{pdfs=}\")\n
(ecpc) > python pdf.py\npdfs=['text.pdf', 'manual.pdf']\n
In een for-loop heb je daar meer ruimte voor nodig. Naast list comprehensions heb je ook set comprehensions8 en dict comprehensions.
array, for-loops en comprehensions
Voer, door een script te schrijven, de volgende opdrachten uit:
- Maak een lijst van de getallen 1 tot en met 10.
- Gebruik een 'gewone' for-loop om een lijst te maken van de derdemachtswortel van de getallen.
- Maak nogmaals een lijst van de derdemachtswortel van de getallen maar gebruik nu list comprehension.
- Gebruik tot slot arrays om de lijst met derdemachtswortels van de getallen te maken.
Uitwerkingen for_loop.py
import numpy as np\n\nnumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n\n# use a for loop to create a list with cube root of numbers\ncube_root = []\nfor number in numbers:\n answer = number ** (1 / 3)\n cube_root.append(answer)\n\n\n# use list comprehension to create a list with cube root of numbers\ncube_root_comprehension = [n ** (1 / 3) for n in numbers]\n\n# use numpy arrays to create a list with cube root of numbers\nnumbers = np.array(numbers)\ncube_root_array = numbers ** (1 / 3)\n\nprint(cube_root)\nprint(cube_root_comprehension)\nprint(cube_root_array)\n
\n(ecpc) > python for_loop.py\n[1.0, 1.2599210498948732, 1.4422495703074083, 1.5874010519681994, 1.7099759466766968, 1.8171205928321397, 1.912931182772389, 2.0, 2.0800838230515904, 2.154434690031884]\n[1.0, 1.2599210498948732, 1.4422495703074083, 1.5874010519681994, 1.7099759466766968, 1.8171205928321397, 1.912931182772389, 2.0, 2.0800838230515904, 2.154434690031884]\n[1. 1.25992105 1.44224957 1.58740105 1.70997595 1.81712059 1.91293118 2. 2.08008382 2.15443469]\n
Lambda functions Generators Dunder methods Decorators"},{"location":"vervolg-python/#lambda-functions","title":"Lambda functions","text":"In Python zijn functies ook objecten. Je kunt ze bewaren in een lijst of dictionary, of je kunt ze meegeven als parameter aan een andere functie. Dat kan heel handig zijn! Stel je hebt een lijst met verschillende soorten fruit die je wilt sorteren op alfabet:
a = [\"kiwi\", \"banana\", \"apple\"]\nprint(sorted(a))\n
(ecpc) > python sort.py \n['apple', 'banana', 'kiwi']\n
Dat gaat heel makkelijk met de ingebouwde sorted()
-functie. Je kunt aan deze functie ook een key
-parameter meegeven; een \u00e1ndere functie die gebruikt wordt om te bepalen waarop gesorteerd moet worden. Zo kun je sorteren op de lengte van de fruitnamen door simpelweg de len()
-functie als parameter mee te geven: a = [\"kiwi\", \"banana\", \"apple\"]\n\nprint(len(\"apple\"))\nprint(sorted(a, key=len))\n
(ecpc) > python length.py \n5\n['kiwi', 'apple', 'banana']\n
Als je wilt sorteren op de tweede letter van de naam -- waarom niet? -- dan kun je zelf een functie defini\u00ebren en gebruiken: a = [\"kiwi\", \"banana\", \"apple\"]\n\ndef second_letter(value):\n return value[1]\n\nprint(second_letter(\"lemon\"))\nprint(sorted(a, key=second_letter))\n
(ecpc) > python second_letter.py \ne\n['banana', 'kiwi', 'apple']\n
Lambdafuncties zijn bedacht om je een hoop typewerk te besparen. Je kunt korte functies in \u00e9\u00e9n regel opschrijven en gebruiken, zolang het maar een geldige expression is. G\u00e9\u00e9n if-then-else, maar de meeste andere dingen mogen wel. Bijvoorbeeld: a = [\"kiwi\", \"banana\", \"apple\"]\n\nsquared = lambda x: x ** 2\nprint(squared(4))\n\nsecond_letter = lambda value: value[1]\nprint(sorted(a, key=second_letter))\n
(ecpc) > python lamda.py \n16\n['banana', 'kiwi', 'apple']\n
Aangezien de definitie van een lambdafunctie zelf ook een expression is kun je het sorteren op de tweede letter zelfs in \u00e9\u00e9n regel doen: a = [\"kiwi\", \"banana\", \"apple\"]\n\nprint(sorted(a, key=lambda value: value[1]))\n
(ecpc) > python one_line.py \n['banana', 'kiwi', 'apple']\n
Lambdafuncties kun je ook gebruiken om te fitten aan een bepaald model. Je definieert je model dan in \u00e9\u00e9n regel met een lambdafunctie:
# from lmfit import models\nf = lambda x, a, b: a * x + b\nmodel = models.Model(f)\nfit = model.fit(y, x=x)\n
Het is hierbij wel belangrijk dat lmfit
er vanuit gaat dat de eerste variabele in de functiedefinitie de onafhankelijke variabele ($x$-as) is. Dit is verder geen Pythonlimitatie. Je kunt de functies ook bewaren in een dictionary voor later gebruik.
lambda
Maak een dictionary models
met functies voor een lineaire functie linear
gegeven door $y = ax + b$, een kwadratische functie quadratic
gegeven door $y = ax^2 + bx + c$ en een sinusfunctie sine
gegeven door $a + b\\sin(cx + d)$. Hierna moet de volgende code werken:
f = models['linear']\nf(5, a=2, b=3)\n# 13\n
Maak een grafiek van de sinusfunctie op het domein $[0,\\, 2\\pi]$ met parameters $a=1$, $b=2$, $c=2$ en $d=\\frac{\\pi}{2}$. Uitwerkingen import numpy as np\nfrom numpy import pi\nimport matplotlib.pyplot as plt\n\n# dictionary with linear, quadratic and sine function\nmodels = {\n \"linear\": lambda x, a, b: a * x + b,\n \"quadratic\": lambda x, a, b, c: a * x ** 2 + b * x + c,\n \"sine\": lambda x, a, b, c, d: a + b * np.sin(c * x + d),\n}\n\n# test the next piece of code\nf = models[\"linear\"]\nprint(f(5, a=2, b=3))\n\n# Graph of sine function on domain [0, 2pi] with parameters a=1, b=2, c=2, d=0.5pi\nx = np.linspace(0, 2 * pi, 100)\nf = models[\"sine\"]\n\nplt.plot(x, f(x, a=1, b=2, c=2, d=0.5 * pi))\nplt.show()\n
"},{"location":"vervolg-python/#generators","title":"Generators","text":"Als een functie een serie metingen verricht kan het lang duren voordat de functie de resultaten teruggeeft. Laten we die functie even perform_measurements()
noemen. Het is soms lastig als de rest van het programma daarop moet wachten voordat een analyse kan worden gedaan, of een melding aan de gebruiker kan worden gegeven. Het kan dan gebeuren dat je je programma draait en je dan afvraagt: doet hij het, of doet hij het niet? Je kunt dit oplossen door print()
-statements in je programma op te nemen, maar dit is niet zo netjes. Als je perform_measurements()
inbouwt in een tekstinterface die ook stil moet kunnen zijn? Of als je de functie gaat gebruiken vanuit een grafisch programma waarin je geen tekst wilt printen, maar een grafiek wilt opbouwen? Je moet dan steeds perform_measurements()
gaan aanpassen. Een ander probleem kan optreden wanneer je langdurige metingen doet die ook veel geheugen innemen. Wachten op de hele meetserie betekent dat het geheugen vol kan lopen. Lastig op te lossen!
Of\u2026 je maakt gebruik van een generator function: een functie die tussendoor resultaten teruggeeft. Dat kan door gebruik te maken van yield
in plaats van return
. De rest gaat automatisch. Maar: je moet wel even weten hoe je omgaat met de generator. Stel, we willen de kwadraten berekenen van een reeks getallen tot een bepaald maximum:
def calculate_squares_up_to(max_number):\n \"\"\"Calculate squares of all integers up to a maximum number\"\"\"\n squares = []\n for number in range(max_number):\n squares.append(number ** 2)\n return squares\n\nprint(calculate_squares_up_to(5))\n
(ecpc) > python squares.py \n[0, 1, 4, 9, 16]\n
De functie berekent eerst alle kwadraten, voegt ze toe aan een lijst en geeft vervolgens de lijst met uitkomsten terug. Een generator definieer je als volgt:
def calculate_squares_up_to(max_number):\n \"\"\"Generate squares of all integers up to a maximum number\"\"\"\n for number in range(max_number):\n yield number ** 2\n
Lekker kort, want we hoeven geen lijst bij te houden! Als je de functie aanroept krijg je geen resultaat terug, maar een generator. Als je de waardes wil zien dan gebruik je next()
, als volgt: square_generator = calculate_squares_up_to(5)\nnext(square_generator)\n# 0\nnext(square_generator)\n# 1\n...\nnext(square_generator)\n# 16\nnext(square_generator)\n# StopIteration\n
Als de generator is uitgeput (de for-loop is afgelopen, de functie sluit af) dan geeft Python een StopIteration
exception en crasht het programma -- tenzij je de exception afvangt. Het werkt, maar het is niet helemaal ideaal. Makkelijker is om de generator te gebruiken in een loop: for square in calculate_squares_up_to(5):\n print(\"Still calculating...\")\n print(square)\n
(ecpc) > python squares.py \nStill calculating...\n0\nStill calculating...\n1\nStill calculating...\n4\nStill calculating...\n9\nStill calculating...\n16\n
Dit kan ook in list comprehensions. En als je toch wilt wachten op alle resultaten, dan kan dat eenvoudig met squares = list(calculate_squares_up_to(5))
.
generators
Schrijf een generator function die het vermoeden van Collatz illustreert. Dat wil zeggen: beginnend bij een getal $n$, genereer het volgende getal als volgt: is het getal even, deel het dan door twee; is het getal oneven, vermenigvuldig het met 3 en tel er 1 bij op. Enzovoorts. Sluit de generator af als de uitkomst gelijk is aan 1. Dat is het vermoeden van Collatz: ongeacht met welk geheel getal je begint, je komt altijd op 1 uit. Als voorbeeld, beginnend bij het getal 3 krijg je de reeks 3, 10, 5, 16, 8, 4, 2, 1.
Uitwerkingen collatz.py
def Collatz(x):\n \"\"\"Illustrates Collatz's conjecture\n\n Starts with x generates next number when x is not equal to 1.\n Next number is x devided by 2 if x is even and x times 3 + 1 if x is odd.\n Collatz suspects that you always end with number 1 despite of the starting value.\n\n Args:\n x (int): starting value\n\n Yields:\n int: next number in the sequence\n \"\"\"\n yield x\n while x != 1:\n if x % 2 == 0:\n # x is even and is divided by 2\n x = x // 2 # dubble // makes it an integer\n else:\n # x is odd, multiply by 3 and add 1\n x = 3 * x + 1\n yield x\n\n\nprint(\"print the values of generator with next:\")\ncollatz_generator = Collatz(3)\nprint(next(collatz_generator))\nprint(next(collatz_generator))\nprint(next(collatz_generator))\nprint(next(collatz_generator))\nprint(next(collatz_generator))\nprint(next(collatz_generator))\nprint(next(collatz_generator))\nprint(next(collatz_generator))\n# print(next(collatz_generator)) # gives StopIteration exception\n\nprint(\"print values of generator without next:\")\nfor number in Collatz(28):\n print(number)\n
\n(ecpc) > python collatz.py\nprint the values of generator with next:\n3\n10\n5\n16\n8\n4\n2\n1\nprint the values of generator without next:\n28\n14\n7\n22\n11\n34\n17\n52\n26\n13\n40\n20\n10\n5\n16\n8\n4\n2\n1\n
"},{"location":"vervolg-python/#dunder-methods","title":"Dunder methods","text":"Hoe weet Python eigenlijk wat de lengte is van een string? Of hoe je getallen optelt? Voor operatoren als + - * / **
wordt eigenlijk een method aangeroepen. bijvoorbeeld __add__()
voor +
, en __mul__()
voor *
. Een ingebouwde functie als len()
roept stiekem de method __len__()
aan en print()
print de uitvoer van __str__()
. Zulke methodes worden dunder methods9 of magic methods genoemd. We kunnen zelf bijvoorbeeld een vector introduceren waarbij we de operatoren voor onze eigen doeleinden gebruiken 25. We defini\u00ebren het optellen van vectoren en de absolute waarde (norm) van de vector:
class Vector:\n def __init__(self, x, y):\n self.x = x\n self.y = y\n\n def __add__(self, other):\n new_x = self.x + other.x\n new_y = self.y + other.y\n return Vector(new_x, new_y)\n\n def __abs__(self):\n return (self.x ** 2 + self.y ** 2) ** .5\n
De speciale __init__()
methode zorgt voor de initialisatie van de class en de eerste parameter die alle methodes meekrijgen verwijst naar zichzelf en wordt dus gewoonlijk self
genoemd.10 Met de regel self.x = x
wordt de parameter x
bewaard voor later gebruik. Je kunt de class gebruiken op de volgende manier: Terminal
>>> v1 = Vector(0, 1)\n>>> v2 = Vector(1, 0)\n>>> abs(v1)\n1.0\n>>> abs(v2)\n1.0\n>>> abs(v1 + v2)\n1.4142135623730951\n>>> (v1 + v2).x, (v1 + v2).y\n(1, 1)\n>>> v1 + v2\n<__main__.Vector object at 0x7fdf80b3ae10>\n>>> print(v1 + v2)\n<__main__.Vector object at 0x7fdf80b45450>\n
In de eerste regels maken we twee vectoren v_1 en v_2 en berekenen de lengtes11 ||v_1||, ||v_2|| en ||v_1 + v_2||. Ook kunnen we de co\u00f6rdinaten van de som bekijken. Het gaat mis als we de somvector willen printen of willen kijken wat voor object het is. We krijgen technisch juiste, maar totaal onbruikbare informatie terug. Dit lossen we op met het defini\u00ebren van __str__()
, gebruikt door str()
en dus ook print()
, en __repr__()
, gebruikt door repr()
en de Python interpreter.12 class Vector:\n ...\n def __repr__(self):\n return f\"Vector: ({self.x}, {self.y})\"\n\n def __str__(self):\n # roept __repr__ aan\n return repr(self)\n
Terminal>>> v1 + v2\nVector: (1, 1)\n>>> print(v1 + v2)\nVector: (1, 1)\n
We raden je aan altijd een zinnige __str__
en __repr__
te defini\u00ebren. Vaak hebben classes geen dunder methods nodig (behalve __repr__
en __str__
).
"},{"location":"vervolg-python/#decorators","title":"Decorators","text":"Functies zijn ook objecten in Python. Je kunt ze, zoals we eerder gezien hebben, meegeven als argument of bewaren in een dictionary. Ook kun je functies in functies defini\u00ebren en functies defini\u00ebren die functies teruggeven. Vaag13. Ik moet hier altijd weer even over nadenken en daarom mag je dit stukje overslaan. Om decorators te gebruiken, hoef je niet per se te weten hoe ze werken.
Decorators worden vaak gebruikt om het gedrag van een functie aan te passen.
Stel je hebt een functie die eenvoudig twee getallen vermenigvuldigd. Je wilt deze functie, zonder hem van binnen te veranderen, aanpassen zodat hij altijd het kwadraat van de vermenigvuldiging geeft. Dus niet $a\\cdot b$, maar $(a\\cdot b)^2$. Dat kan als volgt:
def f(a, b):\n return a * b\n\n\ndef squared(func, a, b):\n return func(a, b) ** 2\n\nf(3, 4)\n# 12\nsquared(f, 3, 4)\n# 144\n
Het werkt, maar we moeten er wel steeds aan denken om squared()
aan te roepen en dan \u00f3\u00f3k nog de functie f()
als eerste argument mee te geven. Lastig. Maar omdat functies objecten zijn kan dit ook: def squared_func(func):\n def inner_func(a, b):\n return func(a, b) ** 2\n\n return inner_func\n\n\ng = squared_func(f)\ng(3, 4)\n# 144\n
Hier gebeurt iets geks\u2026 Om te begrijpen wat hier gebeurt moeten we een beetje heen en weer springen. In regel 8 roepen we de functie squared_func(f)
aan. In regel 5 zien we dat die functie een andere functie teruggeeft -- die niet wordt aangeroepen! In regel 8 wordt die functie bewaard als g
en pas in regel 9 roepen we hem aan. De functie g()
is dus eigenlijk gelijk aan de functie inner_func()
die in regels 2--3 gedefinieerd wordt. De aanroep in regel 9 zorgt er uiteindelijk voor dat in regel 3 de oorspronkelijke functie f(a, b)
wordt aangeroepen en dat het antwoord gekwadrateerd wordt. Dit is echt wel even lastig.
In deze opzet moet de inner_func(a, b)
nog weten dat de oorspronkelijke functie aangeroepen wordt met twee argumenten a
en b
. Maar ook dat hoeft niet. We hebben immers argument (un)packing met *args
:
def squared_func(func):\n def inner_func(*args):\n return func(*args) ** 2\n\n return inner_func\n
En nu komt het: in Python kun je de decorator syntax gebruiken om je functie te vervangen door een iets aangepaste functie. In plaats van: f = squared_func(f)\n
op te nemen in je code kun je de functie meteen `decoraten' als volgt: @squared_func\ndef f(a, b):\n return a * b\n\nf(3, 4)\n# 144\n
Als je meer wilt weten over hoe decorators werken en hoe je je eigen decorators kunt maken, dan vind je een uitgebreide uitleg in Primer on Python Decorators 26. Deze tutorial heb je niet per se nodig voor de volgende opdracht.
decorators
Schrijf en test een decorator die werkt als een soort logboek. Als je een functie aanroept die gedecoreerd is print dan een regel op het scherm met het tijdstip van de aanroep, de parameters die meegegeven werden \u00e9n de return value van de functie.
Uitwerkingen decorators.py.py
import datetime\n\n\ndef log(func):\n def inner(*args, **kwargs):\n return_value = func(*args, **kwargs)\n\n print(40 * \"-\")\n print(f\"Logging function call at {datetime.datetime.now()}.\")\n print(f\"Function was called as follows:\")\n print(f\"Arguments: {args}\")\n print(f\"Keyword arguments: {kwargs}\")\n print(f\"And the return value was {return_value}\")\n print(40 * \"-\")\n\n return return_value\n\n return inner\n\n\n@log\ndef f(a, b):\n return a * b\n\n\nf(3, 4)\nf(3, b=4)\n
\n(ecpc) > python decorators.py.py\n----------------------------------------\nLogging function call at year-month-date hours:minutes:seconds\nFunction was called as follows:\nArguments: (3, 4)\nKeyword arguments: {}\nAnd the return value was 12 ---------------------------------------- ----------------------------------------\nLogging function call at year-month-date hours:minutes:seconds\nFunction was called as follows:\nArguments: (3,)\nKeyword arguments: {'b': 4}\nAnd the return value was 12\n----------------------------------------\n
"},{"location":"vervolg-python/#modules","title":"Modules","text":"Als je een nieuw script begint te schrijven staat alle code in \u00e9\u00e9n bestand. Dat is lekker compact, maar heeft ook nadelen. Als je je experiment of programma gaat uitbreiden kan het erg onoverzichtelijk worden. Ook zul je al je wijzigingen steeds in dit bestand moeten doen terwijl je je code van eerdere experimenten misschien wel wilt bewaren. Mogelijk kopieer je steeds je script naar een nieuw bestand, maar dat is niet erg DRY.14 Als je dan bijvoorbeeld een functie of class wilt aanpassen, moet dat nog steeds op heel veel plekken. Daarom is het handig om gebruik te maken van modules.
Eenvoudig gezegd is een module een stuk Python code dat je kunt importeren en gebruiken. Meestal worden er in een module handige functies en classes gedefinieerd:
math.py import math\nprint(math.sqrt(2))\nprint(math.pi)\nprint(math.sin(.5 * math.pi))\n
\n(ecpc) > python math.py\n1.4142135623730951\n3.141592653589793\n1.0\n
Door de math
module te importeren hebben we opeens de beschikking over het getal $\\pi$ en de sinus- en wortelfunties.
Je kunt je eigen code ook importeren, maar hier moet je wel even opletten. Stel, we hebben een bestand square.py
:
square.py def square(x):\n return x**2\n\n\nprint(f\"The square of 4 is {square(4)}\")\n
\n(ecpc) > python square.py\nThe square of 4 is 16\n
De uitvoer is zoals verwacht. Maar nu willen we in een nieuw script, count_count.py
, de functie importeren en gebruiken:
count_count.py import square\n\nprint(f\"The square of 5 is {square.square(5)}\")\n
\n(ecpc) > python count_count.py\nThe square of 4 is 16\nThe square of 5 is 25\n
square.square
Waarom staat er in bovenstaande code nu opeens square.square()
in plaats van gewoon square()
?
Uitwerkingen Omdat je uit de module square.py
de functie square()
gebruikt.
Maar nu is er een probleem met de uitvoer van dit script: zowel het kwadraat van 4 als van 5 wordt geprint.
Tijdens het importeren wordt alle code die aanwezig is in square.py
ook daadwerkelijk gerund. Er zijn twee manieren om dit op te lossen:
- Alle 'extra' code verwijderen uit de module (
square.py
) - De code in de module alleen laten runnen als de module als script wordt aangeroepen, maar niet wanneer de module wordt ge\u00efmporteerd
De eerste oplossing is lang niet altijd wenselijk. Voor de tweede oplossing pas je square.py
als volgt aan: square.py
def square(x):\n return x**2\n\n\nif __name__ == \"__main__\":\n print(f\"The square of 4 is {square(4)}\")\n
Wanneer je een python script runt is de speciale variabele __name__
gelijk aan de string __main__
. Maar als je een module importeert is __name__
gelijk aan de naam van de module; in dit geval square
. Met bovenstaande constructie wordt de code alleen uitgevoerd wanneer de module direct gerund wordt: (ecpc) > python square.py \nThe square of 4 is 16\n(ecpc) > python count_count.py \nThe square of 5 is 25\n
Het if __name__ == '__main__'
-statement wordt heel veel gebruikt in Python modules.
modules
- Maak zelf de bestanden
square.py
en just_count.py
aan. ECPC
\u251c\u2500\u2500 square.py
\u251c\u2500\u2500 just_count.py
\u2514\u2500\u2500 \u2022\u2022\u2022 - Run
just_count.py
zonder het if __name__ == '__main__'
-statement. - Run
just_count.py
met het if __name__ == '__main__'
-statement. - Voeg
print(f\"{__name__ = }\")
toe bovenaan square.py
. - Run
square.py
en kijk wat __name__
is. - Run dan nu
just_count.py
. Zie hoe de speciale variabele __name__
verandert.
"},{"location":"vervolg-python/#packages","title":"Packages","text":"In Python zijn packages collecties van modules. Ook krijg je automatisch namespaces. Dat wil zeggen, wanneer je functies en modules uit een package importeert zitten ze niet in \u00e9\u00e9n grote vormeloze berg, maar in een soort boomstructuur. Dat betekent dat namen niet uniek hoeven te zijn. Er zijn duizenden bibliotheken beschikbaar voor python (numpy
, scipy
, matplotlib
, etc.) en die mogen allemaal een module test
bevatten. Namespaces zorgen ervoor dat je ze uniek kunt benaderen:
import numpy.test\nimport scipy.test\n
In bovenstaande code zijn numpy
en scipy
afzonderlijke namespaces. Ook zijn numpy.test
en scipy.test
afzonderlijke namespaces. De namen van bijvoorbeeld variabelen en functies binnen die modules zullen nooit met elkaar in conflict komen. Wij gaan in deze cursus onze code ook in packages stoppen. Op die manier kun je een softwarebibliotheek opbouwen voor je experiment en die code makkelijker delen met andere onderzoekers. Een pakket is opgebouwd zoals hieronder weergegeven:
\u2514\u2500\u2500 my_project_folder
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 script.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 my_package
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 package1
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 module1.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 module2.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 package2
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 module3.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 module4.py
Iedere package bestaat uit een directory met een __init__.py
-bestand.15
De verschillende modules uit het figuur hierboven kun je als volgt importeren en gebruiken in het bestand script.py
(we gaan er even vanuit dat iedere module een functie some_function()
bevat): script.py
# module direct importeren\nimport my_package.package1.module1\nmy_package.package1.module1.some_function()\n\n# losse module vanuit een package importeren\nfrom my_package.package1 import module2\nmodule2.some_function()\n\n# module importeren onder een andere naam\nimport my_package.module4 as m4\nm4.some_function()\n
In deze cursus gaan we ook packages maken. Feitelijk hoeven we een python script dus alleen maar in een map te stoppen en in diezelfde map een lege __init__.py
aan te maken.
Waarschuwing
Let op: als je de __init__.py
vergeet dan lijkt alles het alsnog te doen. Maar je maakt nu een implicit namespace package waarbij bepaalde directories toch weer op een grote hoop gegooid worden. Geloof me, echt niet handig.16 Namespace packages kunnen handig zijn voor grote projecten, maar dat is het dan ook wel. Wij gaan hier niet verder op in. Kortom: let op en gebruik altijd een __init__.py
.
Packages
In deze opdracht ga je oefenen met het aanmaken van packages, modules en het importeren en aanroepen daarvan.
- Maak in de map
ECPC
een package models
met twee modules: polynomials.py
en tests.py
. - In de
polynomials
-module maak je een functie line(x, a, b)
die de vergelijking voor een lijn voor ons berekent: $y = ax + b$. -
In de tests
-module maak je een functie test_line()
die het volgende doet:
- gebruik de
line()
-functie uit de polynomials
-module om de $y$-waarde uit te rekenen voor een bepaald punt bij een gegeven $a$ en $b$. - Vergelijk die berekende waarde met de waarde die het volgens jou moet zijn (met de hand nagerekend).
- Print
TEST PASSED
als het klopt, en TEST FAILED
als het niet klopt.
-
Maak een bestand practice-packages.py
die:
- Een grafiek maakt van jouw lijn. Bepaal zelf het domein en de waardes voor $a$ en $b$.
- De test uitvoert door de
test_line()
-functie aan te roepen. - Pas je
line()
-functie eventjes aan om te kijken of je test ook echt werkt. Bijvoorbeeld: bij $y = ax$ zou je TEST FAILED
moeten zien.
Uitwerkingen De mappen structuur ziet er als volgt uit:
\u2514\u2500\u2500 ECPC
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 \u2022\u2022\u2022 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 practice-packages.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 models
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 __init__.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u251c\u2500\u2500 polynomials.py
\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u2514\u2500\u2500 tests.py
polynomials.py
def line(x, a, b):\n y = a * x * b\n return y\n
tests.pyfrom models.polynomials import line\n\n\ndef test_line():\n actual = line(2, 4, 3)\n expected = 11\n\n if actual == expected:\n print(\"TEST PASSED\")\n else:\n print(\"TEST FAILED\")\n
practice-packages.pyimport numpy as np\nfrom models.polynomials import line\nfrom models import tests\nimport matplotlib.pyplot as plt\n\nx = np.arange(0, 28)\na = 1\nb = 7\n\nplt.plot(x, line(x, a, b))\nplt.show()\n\ntests.test_line()\n
Relatieve en absolute imports Als je in een module een andere module wilt importeren dan zijn daarvoor twee opties: relatieve en absolute imports. Relatief wil zeggen: importeer module1 uit dezelfde directory, of ten opzichte van deze directory (..
betekent een directory hoger bijvoorbeeld). Bij een absolute import moet je de volledige locatie binnen het package opgeven. Als voorbeeld, stel dat module1
uit het figuur hierboven de modules module2
en module3
wil importeren:
# module1.py\n\n# relative imports\nfrom . import module2\nfrom ..package2 import module3\n\n# absolute imports\nfrom my_package.package1 import module2\nfrom my_package.package2 import module3\n
Absolute imports zijn wat meer werk, maar je maakt wel heel duidelijk welke module je wilt importeren. Relative imports zorgen in de praktijk regelmatig voor -- soms lastig te vinden -- bugs. Als je tegen problemen aanloopt: gebruik dan absolute imports. De Standard Library en de Python Package Index"},{"location":"vervolg-python/#de-standard-library-en-de-python-package-index","title":"De Standard Library en de Python Package Index","text":"Voor Python zijn ontzettend veel bibliotheken beschikbaar die het leven een stuk aangenamer maken. Voor een gedeelte daarvan geldt dat ze altijd aanwezig zijn als je Python ge\u00efnstalleerd hebt. Deze set vormt de standard library 23. Om te voorkomen dat je zelf het wiel uitvindt is het goed om af en toe door de lijst te bladeren zodat je een idee krijgt wat er allemaal beschikbaar is. Ziet het er bruikbaar uit? Lees dan vooral de documentatie! Tip: vergeet de built-in functions niet.
Standard Library
- Zoek de The Python Standard Library lijst op in bijvoorbeeld de Python documentatie.
- Welke bibliotheken heb je al eerder gebruik van gemaakt?
- Kies een bibliotheek uit die jouw aandacht trekt en neus door de documentatie.
- Ga terug naar de lijst en bekijk de Built-in functions, welke functie kende je nog niet maar lijkt je wel heel handig?
Verder zijn er nog eindeloos veel packages beschikbaar gesteld door programmeurs, van hobbyist tot multinational. Deze kunnen centraal gepubliceerd worden in de Python Package Index 17. Je kunt daar vaak ook zien hoe populair een package is. Dit is een belangrijke indicatie voor de kwaliteit en bruikbaarheid van een package.
PyPI
Later in de cursus leren jullie werken met Poetry daarmee is het gemakkelijk om je eigen project op PyPI te zetten. Andere studenten gingen jullie al voor:
- Ga naar pypi.org en zoek naar het project gammaspotter.
"},{"location":"vervolg-python/#exceptions","title":"Exceptions","text":"Exceptions zijn de foutmeldingen van Python. Je krijgt ze als je bijvoorbeeld probeert te delen door nul
divide.py print(1/0)\n
\n(ecpc) > python divide.py\nTraceback (most recent call last):\n File \"devide.py\", line 1, in < module >\n print(1/0)\n ~^~\nZeroDivisionError: division by zero\n\n
of wanneer je een typefout maakt:
particle.py s = \"particle\"\ns.upler()\n
\n(ecpc) > python particle.py\nTraceback (most recent call last):\n File \"particle.py\", line 2, in < module >\n s.upler()\n ^^^^^^^\nAttributeError: 'str' object has no attribute 'upler'. Did you mean: 'upper'?\n
Merk op dat je een exception met traceback meestal van onder naar boven leest. Onderaan staat de foutmelding (exception) en daar boven een traceback: een kruimelpad van w\u00e1\u00e1r in de code het probleem optrad; onderaan de regel waarin het echt fout ging, en naar boven toe alle tussenliggende functies en bibliotheken met bovenaan het hoofdprogramma.
Exception
number_input.pynumber_input = input(\"Give a number: \")\nnumber_multiply = 2.8\nprint(number_input * number_multiply)\n
- Neem het script hierboven over en voer het uit.
- Wat voor soort error geeft Python terug?
- In welke regel van het script zit volgens de Traceback het probleem?
- Leg in eigen woorden uit wat het probleem is.
Uitwerkingen - Python geeft een TypeError terug.
- Het probleem zit in regel 3:
print(number_input * number_multiply)
- De functie
input()
geeft een string terug. Daardoor is number_input
ook een string. Een string behoort tot het type sequences. Het is een reeks van elementen, '28' is een reeks van '2' en '8', 'abc' is een reeks van 'a', 'b' en 'c' en ook [0,1,2] is reeks van '0', '1', '2'. Zelfs als je in dit script het getal 8 zou invoeren dan is number_input een sequence met maar een element: '8'. Een sequence kan je vermenigvuldigen, maar niet met en float, alleen met een interger. Kijk maar eens wat er gebeurd als je number_multiply = 3
neerzet. Wat gebeurd er als je 'abc' met 3 vermenigvuldigd? En kun je ook 3 * [0,1,2] printen?
Exceptions afvangen Een exception kan vervelend zijn. Het is een beetje jammer als je bijvoorbeeld tijdens een langdurige meting telkens een weerstand aan het uitrekenen bent ($R = \\frac{U}{I}$) en de stroomsterkte $I$ wordt na anderhalf uur heel eventjes nul. Je programma crasht en je metingen zijn weg. Zoek de fout (niet altijd makkelijk!) en probeer het nog eens.
Je kunt exceptions afvangen en afhandelen met een try...except
blok:
def R(U, I):\n try:\n R = U / I\n except ZeroDivisionError:\n R = \"Inf\"\n return R\n
print(R(10, 2))\n# 5.0\nprint(R(10, 0))\n# Inf\n
Ook kun je zelf exceptions maken. Stel je schrijft een programma om een oscilloscoop uit te lezen dat twee kanalen heeft om de spanning te meten. Kanaal 0 en kanaal 1. Het programma moet gebruikt kunnen worden door andere studenten in de onderzoeksgroep dus het kan nu eenmaal gebeuren dat iemand niet op zit te letten -- niet jij, jij let altijd goed op. Een andere student die een programma schrijft en jouw code gebruikt wil een spanning meten op kanaal 2, het was immers een tweekanaals oscilloscoop. Maar kanaal 2 bestaat niet. Sommige oscilloscopen klagen dan niet maar geven een random getal terug. Dit kan leiden tot heel vervelende en lastig te achterhalen fouten in het experiment. Met dat idee in je achterhoofd kun je code schrijven die controleert op het kanaalnummer en een exception geeft:
# we maken een subclass van de 'standaard' Exception\nclass InvalidChannelException(Exception):\n pass\n\ndef get_voltage(channel):\n if channel not in [0, 1]:\n raise InvalidChannelException(f\"Use channel 0 or 1, not {channel}\")\n ...\n return voltage\n
Met deze uitvoer in het geval dat er iets mis gaat: voltage = get_voltage(1)\nprint(voltage)\n# 1.0\n
voltage = get_voltage(2)\nprint(voltage)\n
(ecpc) > get_voltage.py \nTraceback (most recent call last):\nFile \"get_voltage.py\", line 1, in < module >\n get_voltage(2)\nFile \"exception_channel.py\", line 6, in get_voltage\n raise InvalidChannelException(f\"Use channel 0 or 1, not {channel}\")\nInvalidChannelException: Use channel 0 or 1, not 2\n
Je kunt op deze manier voorkomen dat iemand dagen kwijt is aan het overdoen van achteraf verkeerd gebleken metingen. Ook kun je 'vage' foutmeldingen omzetten in duidelijkere foutmeldingen:
class NoCurrentError(Exception):\n pass\n\n\ndef R(U, I):\n try:\n R = U / I\n except ZeroDivisionError:\n raise NoCurrentError(\"There is no current flowing through the resistor.\")\n return R\n
In plaats van een ZeroDivisionError
krijg je nu een NoCurrentError
. Je programma crasht nog steeds (wellicht niet handig) maar de foutmelding is nu wel specifiek voor het probleem en kan in de rest van je programma wellicht beter afgevangen en opgelost worden. Misschien beter dan niet crashen en een mogelijk foute waarde doorgeven. Die afweging zul je zelf moeten maken. exceptions
De volgende code berekent een gemiddelde van een lijst getallen:
def average(values):\n return sum(values) / len(values) \n
Er is alleen geen foutafhandeling en dat kan leiden tot exceptions. De volgende aanroepen zorgen voor een crash (probeer ze allemaal uit!): average([])\naverage(4)\naverage(\"12345\")\n
Pas de functie average()
zodanig aan dat bij bovenstaande aanroepen slechts een waarschuwing wordt geprint. Vang daartoe de exceptions netjes af en geef de waarde None
terug wanneer een gemiddelde niet berekend kan worden. Dus bovenstaande drie aanroepen krijgen None
terug terwijl er een waarschuwing wordt geprint. Uitwerkingen exceptions.py
def average(values):\n try:\n average = sum(values) / len(values)\n except TypeError:\n average = None\n print(\"Input is not correct type\")\n except ZeroDivisionError:\n average = None\n print(\"Input is empty\")\n\n return average\n\n\nprint(average([1, 2, 3]))\n\naverage([])\n# # gives: ZeroDivisionError: division by zero\n\naverage(4)\n# # gives: TypeError: 'int' object is not iterable\n\na = average(\"12345\")\n# # gives: TypeError: unsupported operand type(s) for +: 'int' and 'str'\n# print(a)\n
\n(ecpc) > python exceptions.py\n2.0\nInput is empty\nInput is not the correct type\nInput is not the correct type\n
-
We gaan ervan uit dat iedereen bekend is met recente versies van Python en we gaan niet in op de -- soms ingrijpende -- veranderingen die de taal heeft ondergaan. Python 2 is dood. Leve Python 3!\u00a0\u21a9
-
Tenzij je al veel zelf hebt geprogrammeerd in Python, buiten de cursussen om.\u00a0\u21a9
-
De programmertaal C ligt dichter bij machinetaal dan Python en is daarmee veel sneller maar ook veel minder geavanceerd.\u00a0\u21a9
-
Echt. De sinus van 2000 $x$-waardes berekenen kostte NumPy in een test 11.6$\\micro$s en de for-loop wel 1357.7$\\micro$s.\u00a0\u21a9
-
Strikt genomen is dit niet helemaal waar. Je kunt een nieuwe array cre\u00ebren door meerdere arrays aan elkaar te plakken. Maar een eenvoudige append()
-method bestaat niet voor arrays.\u00a0\u21a9
-
Letterlijk: onveranderbaar.\u00a0\u21a9
-
Daar is bijvoorbeeld de collections.namedtuple()
dan weer handig voor.\u00a0\u21a9
-
Notatie hetzelfde, maar gebruik nu {
}-haakjes.\u00a0\u21a9
-
Dunder staat voor double underscore, de twee lage streepjes die om de naam heen staan.\u00a0\u21a9
-
Maar dat is niet verplicht, je mag in principe zelf een naam kiezen. Doe dat echter niet.\u00a0\u21a9
-
Absolute waarde of beter, norm, van een vector is eenvoudig gezegd haar lengte.\u00a0\u21a9
-
Het verschil tussen de twee is subtiel. De Pythondocumentatie geeft aan dat de __repr__
altijd ondubbelzinnig moet zijn, terwijl de __str__
vooral leesbaar moet zijn. Voor eenvoudige objecten zijn ze veelal gelijk.\u00a0\u21a9
-
Calmcode doet een goeie poging om dit rustig uit te leggen, kijk daarvoor op https://calmcode.io/decorators/functions.html \u21a9
-
DRY staat voor Don't Repeat Yourself, een belangrijk principe in software engineering.\u00a0\u21a9
-
Dat bestand is vaak leeg, maar kan code bevatten die gerund wordt zodra het package wordt ge\u00efmporteerd.\u00a0\u21a9
-
En wat mij betreft: een fout dat zoiets \u00fcberhaupt kan in Python. Zen of Python: explicit is better than implicit. \u21a9
-
Python Software Foundation. Python package index. URL: https://pypi.org.\u00a0\u21a9\u21a9
-
Ivo van Vulpen and Martijn Stegeman. Wetenschappelijk programmeren. 2020. URL: https://progns.proglab.nl/syllabus.\u00a0\u21a9
-
Anthony Scopatz and Kathryn D. Huff. Effective Computation in Physics. O'Reilly Media, Inc., 2015. URL: https://www.oreilly.com/library/view/effective-computation-in/9781491901564/.\u00a0\u21a9
-
Allen Downey. Think Python. Green Tea Press, 2nd edition edition, 2015. URL: https://greenteapress.com/wp/think-python-2e/.\u00a0\u21a9
-
Tim Peters. Zen of python. URL: https://groups.google.com/d/msg/comp.lang.python/B_VxeTBClM0/L8W9KlsiriUJ.\u00a0\u21a9
-
Chaitanya Baweja. Contemplating the zen of python. URL: https://medium.com/better-programming/contemplating-the-zen-of-python-186722b833e5.\u00a0\u21a9
-
Python Software Foundation. The python standard library. URL: https://docs.python.org/3/library/.\u00a0\u21a9\u21a9
-
Real Python. Real python: python tutorials. URL: https://realpython.com.\u00a0\u21a9
-
Malay Agarwal. Operator and function overloading in custom python classes. URL: https://realpython.com/operator-function-overloading/ (visited on 2020-06-25).\u00a0\u21a9
-
Geir Arne Hjelle. Primer on python decorators. 2018. URL: https://realpython.com/primer-on-python-decorators/.\u00a0\u21a9
"},{"location":"zonnecel/","title":"Zonnecel","text":"De toenemende behoefte aan energie heeft het zoeken naar nieuwe energiebronnen belangrijk gemaakt. Zonne-energie is \u00e9\u00e9n van de veelbelovende, niet-conventionele bronnen. Zonne-energie is echter niet meteen bruikbaar en moet eerst omgezet worden naar warmte of elektrische energie. De omzetting van zonne-energie naar een bruikbare vorm van energie kan gedaan worden door een zonneboiler of een zonnecel. In de komende sessies staat de zonnecel centraal. Je gaat allerlei eigenschappen van zonnecellen onderzoeken en proberen te verklaren.
"},{"location":"zonnecel/#de-fotovoltaische-zonnecel","title":"De fotovolta\u00efsche zonnecel","text":"Stralingsenergie van de zon is een vorm van energie die niet erg nuttig is voor de meeste toepassingen. Om de energie van de zon nuttig te kunnen gebruiken, moet de straling omgezet worden. Een mogelijkheid daartoe is de fotovolta\u00efsche zonnecel. In de zonnecel maken fotonen uit het zonlicht geladen (elektrische) deeltjes vrij die via metaalcontacten op de zonnecel door een extern circuit kunnen stromen om daar hun elektrische energie af te geven. Zolang er licht valt op de zonnecel gaat het proces van vrijmaken van elektronen door en wordt er een elektrische stroom geproduceerd.
"},{"location":"zonnecel/#werking","title":"Werking","text":"Werking van een zonnecel. Een foton met voldoende energie kan een elektron-gat-paar maken. Door de grenslaag tussen het n-type silicium en het p-type-silicium kan het elektron alleen linksom stromen, door het externe circuit, en het gat alleen rechtsom.
De werking van de zonnecel is schematisch weergegeven in de figuur hieronder. Een zonnecel bestaat uit twee soorten siliciumkristallen, een bovenlaag van het n-type silicium en een tweede, dikkere laag van het p-type silicium. In het n-type silicium kunnen elektronen gemakkelijk bewegen, terwijl in het p-type silicium de gaten (positieve lading) makkelijk kunnen bewegen. Tussen het p- en n-type silicium ontstaat een grenslaag, welke een barri\u00e8re vormt voor zowel de elektronen als de gaten. Deze zogenoemde pn-junctie is de basis van de huidige elektronica en heeft vele toepassingen, zo ook in de zonnecel.
In een zonnecel is de n-laag zo dun dat het zonlicht de grenslaag kan bereiken. Als er nu een foton op de grenslaag valt, en het foton heeft voldoende energie, dan maakt dat foton een elektron-gat-paar. Kijkend naar de figuur kunnen de elektronen door de grenslaag niet rechtsom bewegen en de gaten niet linksom. Het elektron gaat nu linksom stromen en het gat rechtsom. Er ontstaat dus een elektrische stroom. Na doorlopen van het externe circuit recombineert het elektron weer met het gat in het p-type silicium. De maximale stroom die gaat lopen wordt bepaald door het aantal elektron-gat-paren dat gevormd wordt. De maximale spanning die over de zonnecel komt te staan wordt bepaald door de energie die daarvoor nodig is (bedenk dat $[U] = J/C$!).
Om een elektron-gat-paar in een silicium zonnecel te maken is een energie nodig van 1.12 eV (elektronvolt). De energie van een foton ($E_f$) is gelijk aan \\begin{equation} E_f = \\frac{hc}{\\lambda} \\end{equation} waar $h$ staat voor de constante van Planck ($h \\approx 4.136 \\cdot 10^{-15} \\text{ eV} \\cdot \\text{s}$), $c$ staat voor de snelheid waarmee licht zich voortplant ($c \\approx 2.998 \\cdot 10^8$ ms$^{-1}$) en $\\lambda$ staat voor de golflengte van het licht. Dit betekent dat een foton met een golflengte van ongeveer \\begin{equation} \\lambda = \\frac{(4.136 \\cdot 10^{-15} \\text{ eV} \\cdot \\text{s}) \\cdot (2.998 \\cdot 10^8 \\text{ ms}^{-1})}{1.12 \\text{ eV}}\\approx 1100 {\\rm nm} \\end{equation} in staat is om een elektron-gat-paar te maken. Fotonen met een golflengte groter dan 1100 nm hebben een lagere energie dan 1.12 eV en daarvoor is de zonnecel niet gevoelig. Fotonen met een kortere golflengte dan 1100 nm hebben een hogere energie dan nodig is. Zij maken wel een elektron-gat-paar, maar het overschot aan energie wordt niet omgezet in elektrische energie, deze energie gaat verloren als warmte.
Op YouTube staat de volgende video met uitleg over de werking van de zonnecel: How do solar cells work?.
"},{"location":"zonnecel/#vereenvoudigde-modelbeschrijving","title":"Vereenvoudigde modelbeschrijving","text":"De werking van een zonnecel hangt sterk samen met de werking van een diode. Een diode heeft de bijzondere eigenschap dat afhankelijk van de polariteit over de diode het \u00f3f geen stroom door laat en dus een oneindige hoge weerstand heeft, \u00f3f alle stroom doorlaat en bij benadering een weerstand van 0 heeft. Preciezer gezegd: voor een diode geldt dat de stroom die doorgelaten wordt, afhangt van de spanning over de diode. De stroom door een diode, $I_d$, wordt (bij benadering) gegeven door
\\begin{equation} I_d = I_0 \\left( {\\rm e}^{\\frac{eU}{kT}} - 1 \\right), \\end{equation} waarbij $e$ de elektronlading is ($e \\approx 1.602 \\cdot 10^{-19}$ C), $U$ de spanning over de diode, $k$ de Boltzmannconstante ($k \\approx {1.381} \\cdot 10^{-23}$ JK$^{-1}$) en $T$ de temperatuur. $I_0$ is de lekstroom van de diode. Als de spanning over de diode negatief is, geldt dat $\\exp \\left( \\frac{eU}{kT} \\right) \\ll 1$ en is $I_d \\approx - I_0 \\approx 5-7 \\; \\mu$A en dus bij benadering 0. Als de spanning over de diode positief is groeit de stroom exponentieel en is de weerstand van de diode bij benadering 0. Dit gedrag wordt ge\u00efllustreerd in de figuur hieronder.
Links het symbool waarmee een diode weergegeven wordt in een schakeling en rechts een $IU$-karakteristiek van een diode.
Een vereenvoudigde voorstelling van een zonnecel met daarop aangesloten een belastingsweerstand $R_b$. $I_L$ is de stroom opgewekt door elektron-gat-paren, $I_d$ is de stroom die door de diode loopt en $I$ is de stroom die door belastingsweerstand $R_b$ loopt, die aangesloten is op de zonnecel.
Voor een eerste benadering kun je een zonnecel voorstellen als een speciale stroombron, zoals weergegeven is in bovenstaande figuur. In deze schakeling is ook de belastingsweerstand $R_b$ over de zonnecel getekend. De stroom die geleverd wordt door de zonnecel, $I$, hangt af van de stroom ten gevolge van het aantal elektron-gat-paren dat gemaakt wordt door het zonlicht, $I_{L}$, en de stroom door de diode, $I_d$. Dus: \\begin{equation} I = I_L - I_d. \\end{equation} Met behulp van de diodevergelijking kun je bovenstaande vergelijking verder uitschrijven. In de exponent voor de diode komt er echter nog een factor $n$ bij die samenhangt met de materiaaleigenschappen van de zonnecel. Waarden van $n$ liggen typisch tussen 1 en 5, afhankelijk van het type zonnecel. Voor het type zonnecel waarmee je in dit experiment zult werken is $n$ ongeveer 10-15. De stroom die de zonnecel levert wordt nu bij benadering gegeven door
\\begin{equation} I = I_{L} - I_d = I_{L} - I_0 \\left( {\\rm e}^{ \\frac{e U}{nkT}} - 1 \\right). \\end{equation}
"},{"location":"zonnecel/#iu-karakteristiek","title":"I,U-karakteristiek","text":"In de praktijk zul je altijd metingen doen aan zonnepanelen, waarbij zonnecellen in het paneel samengebracht zijn. De spanning die over een zonnepaneel staat hangt onder andere af van het aantal zonnecellen dat in serie geschakeld is. De stroom dat een zonnepaneel kan leveren wordt bepaald door het aantal elektron-gat-paren dat gemaakt wordt of, anders gezegd, door het aantal fotonen dat geabsorbeerd wordt. Het is echter niet zo dat je zonder meer kunt stellen dat wanneer er zonlicht op een zonnepaneel valt er een maximale spanning over het paneel staat en dat de stroom toeneemt als de lichtintensiteit toeneemt.
Het is daarom zinvol om, voordat je aan een experiment begint, het gedrag van een zonnepaneel te onderzoeken. In eerste instantie doe je dit door te kijken naar de $I,U$-karakteristiek van het zonnepaneel. Zo'n karakteristiek is weergegeven in onderstaand figuur:
Als je naar de $I,U$-karakteristiek kijkt, zie je dat het zonnepaneel zich bij lage spanningen gedraagt als een niet-ideale stroombron. Als je rond de maximale spanning kijkt, zie je dat het zonnepaneel zich daar vrijwel gedraagt als een niet-ideale spanningsbron.
De stroom die geleverd kan worden door een zonnecel uitgezet tegen de spanning $U_\\text{PV}$ geleverd door de zonnecel. Hier staat PV voor PhotoVoltaic cell.
"},{"location":"zonnecel/#prb-karakteristiek","title":"P,Rb-karakteristiek","text":"Het is bij zonnepanelen natuurlijk interessant om naar het elektrisch vermogen te kijken dat een zonnepaneel kan leveren. Het geleverd vermogen door een zonnepaneel hangt af van de materiaaleigenschappen van het paneel. Om een zo hoog mogelijk vermogen te kunnen leveren moet het zonnepaneel een zo hoog mogelijke stroom en spanning leveren. Belangrijk ook is dat het vermogen afhangt van de belasting door het circuit. Met andere woorden: bij verschillende weerstandswaardes wordt een ander vermogen geleverd. Ook is er een optimale weerstand waarbij het vermogen maximaal is:
Het vermogen dat geleverd kan worden door een zonnecel uitgezet tegen de belasting (weerstand) van het circuit. Er is duidelijk een maximum in het vermogen bij een optimale weerstand.
"},{"location":"zonnecel/#fill-factor","title":"Fill factor","text":"De kwaliteit van een zonnecel/-paneel wordt experimenteel vaak aangeduid met de fill factor $FF$. De fill factor wordt gegeven door \\begin{equation} FF = \\frac{P_{max}}{P_{T_{max}}} = \\frac{I_{Pmax} \\cdot U_{Pmax}}{I_{sc} \\cdot U_{oc}}, \\end{equation} waarbij $P_{max}$ het maximaal vermogen is wat een zonnecel/-paneel levert en $P_{T_{max}}$ het theoretisch maximaal vermogen is. $I_{sc}$ is de kortsluitstroom (bij een belastingsweerstand $R_b$ gelijk aan 0) en $U_{oc}$ de open klemspanning (wanneer het zonnepaneel niet belast wordt). $I_{Pmax}$ en $U_{Pmax}$ zijn de waarden voor respectievelijk de stroom en spanning waarbij het geleverd vermogen maximaal is.
"},{"location":"zonnecel/#maximum-power-point-tracking","title":"Maximum power point tracking","text":"De optimale weerstand waarbij het vermogen dat geleverd wordt door een zonnecel maximaal is, is helaas geen constante. Deze weerstandswaarde is afhankelijk van verschillende condities waarbij de belangrijkste de lichtintensiteit op de zonnecel is. Dat betekent dat, zonder aanpassingen, het vermogen dat geleverd wordt door de zonnecel meestal veel lager is dan je zou wensen.
Voor zonnepanelen die elektriciteit leveren aan het lichtnet is dit een groot probleem. Allereerst wil je je investering zo snel mogelijk terugverdienen en ook daarna wil je dat de opbrengst maximaal is. Ten tweede is het zo dat de weerstand van het lichtnet bijzonder klein is. Het vermogen dat daardoor geleverd wordt is ook heel klein. Dit wordt opgelost door \u2014 envoudig gezegd \u2014 de verbinding tussen het zonnepaneel en het lichtnet vele malen per seconde aan en uit te schakelen. Hierdoor voelt het zonnepaneel als het ware een weerstand. Deze weerstand is afhankelijk van de hoeveelheid tijd dat het paneel niet aan het lichtnet is geschakeld. Door slim te schakelen kan de weerstand z\u00f3 gekozen worden dat het geleverde vermogen maximaal is. Als de lichtintensiteit wijzigt kan ook de weerstand worden aangepast. Dit heet maximum power point tracking.
"}]}
\ No newline at end of file
diff --git a/sitemap.xml.gz b/sitemap.xml.gz
index 62591ec45731006cc4ae96a60bb00efc36789763..b42227d6130227675e66d5447b6df9ce48884fdc 100644
GIT binary patch
delta 13
Ucmb=gXP58h;Am(GpU7ST033S+D*ylh
delta 13
Ucmb=gXP58h;9xKgo5)@P02kr|SpWb4