11B: De scope van een variabele

Les 11 kent drie delen A, B, C die in een willekeurige volgorde kunnen worden doorgewerkt.

In deze les leggen we het begrip scope (geldigheidsgebied) van een variabele uit. Het belangrijkste idee is, dat het mogelijk is om twee verschillende variabelen te hebben met dezelfde naam maar verschillende "scopes". Dit maakt het makkelijker om programma's te schrijven en te verbeteren, in het bijzonder grote programma's.

Een voorbeeld van een "scope" van een variabele

In dit voorbeeld is een deel van ons programma de volgende functie:

def square(x):
   value = x * x
   return value
Dit is de functie die het kwadraat van het getal x berekent; bijvoorbeeld: square(5) geeft 25 als resultaatwaarde. Veronderstel dat we square aanroepen vanuit een ander deel van het programma waarin een variabele value voor een ander doel wordt gebruikt:

# hier begint het hoofdprogramma
value = 17
fivesquared = square(5)
print(fivesquared, value)
De vraag is wat er in dit scenario zal worden afgedrukt? Er zijn twee mogelijkheden, wanneer we niet weten hoe Python werkt:

  1. Een mogelijkheid is dat Python herkent dat de tweevalue-variabelen op de een of andere manier "gescheiden moeten worden". Dus, de aanroep vansquarezal 25 teruggeven, maar de waarde vanvaluein het hoofdprogramma blijft 17, en we zien als uitvoer afgedrukt worden 25 17.
  2. De andere mogelijkheid is dat bij het uitvoeren van square, het de bestaande waarde van value overschrijft met 25, en we als uitvoer krijgen 25 25.

Laten we nagaan wat er daadwerkelijk gebeurt!

We zien dat het gedrag van Python overeenkomt met de beschrijving in mogelijkheid nr 1. Het zit zo: wanneer je een functie aanroept, kun je geen variabelen wijzigen die buiten de functie zijn gedefinieerd. In plaats daarvan zullen alle toekenningsopdrachten inwerken op een "lokale" variabele die enkel bestaat binnen de huidige functie-oproep. Meer bepaald: wanneer Python begint met een functie uit te voeren, maakt het een nieuw frame aan speciaal voor die uitvoering van die functie. Alle toekenningen die binnen die uitvoering gebeuren, komen terecht in dat frame. Het frame verdwijnt, en alle lokale variabelen gaan onherroepelijk verloren, wanneer de functie-uitvoering eindigt. De reden dat Python en de meeste andere talen werken met frames, is dat het daardoor veel gemakkelijker is grote programma's te schrijven met vele functies. In het bijzonder is het daardoor mogelijk een nieuwe functie te schrijven en daarin elke willekeurige variabelenaam te gebruiken (inclusief de gewone zoals result, temp en i) zelfs wanneer zij elders voor andere doelen worden gebruikt.

Kijk naar "Step 6 van 8" hierboven. Merk op dat er twee verschillende variabelen zijn met de naam value: één geassocieerd met de huidige uitvoering van functie square, en één daarbuiten (in het "globale" frame).

Meerkeuze-opgave: Erbinnen en erbuiten
Wat is de uitvoer van het volgende programma?

x = "erbuiten"
def xReplace(value):
  x = value
xReplace("erbinnen")
print(x)
Correct! De opdracht x = value verandert alleen de lokale versie van variabele x geassocieerd met de uitvoering van de functie. De globale x verandert niet (en de functie xReplace is dus nutteloos en verandert nooit iets).

Een vergelijkbaar concept als een scope is een namespace; een namespace is een soort scope voor een module. Dus als je een module importeert (zoals met bijvoorbeeld import math) overlapt een variabele gedefinieerd door die module (zoals math.pi) niet met een variabele pi in je eigen programma, omdat ze verschillende namespaces hebben. (Als je echter import pi from math gebruikt, zal wel een eigen globale variabele overschreven worden.)

Regels voor scopes: globaal en lokaal

Er zijn situaties waarin we variabelen van globale en lokale scope door elkaar willen gebruiken. Een voorbeeldje is als je een bepaalde variabele eenmalig een waarde wil geven aan het begin van het programma, maar je wil deze variabele nog wel gebruiken in de rest van je programma.

Voorbeeld
Een globale functie binnen een functie uitlezen

De lokale scope bevat hier geen variabele genaamd favorietePizza, maar dit leidt niet tot een foutmelding. Wanneer Python een variabele in de lokale scope niet kan vinden wordt gekeken of er in de globale scope wel een variabele met die naam bestaat. In dit geval wordt favorietePizza inderdaad in de globale scope gevonden.

In de bovenstaande voorbeelden zijn bestelPizzas en xReplace syntactisch bijna helemaal identiek. Waarom maakt Python een lokale variabele x in xReplace, maar geen nieuwe favorietePizza in bestelPizzas? De regel in Python is als volgt: als je alleen leest uit de variabele wordt er geen nieuwe lokale variabele gemaakt; schrijf je echter ergens in de functie naar de variabele, dan wordt deze variabele in de hele functie als lokaal beschouwd. (Dit is de meest voorkomende oorzaak van de foutmelding UnboundLocalError: zie ook [1, 2] in de officiële Python-documentatie.)

Functie-argumenten worden altijd als lokale variabelen beschouwd:

Het gebruik van global

Zoals zo vaak werkt bovenstaande aanpak in 99% van de gevallen. Er zijn echter gevallen te bedenken waar je echt de waarde van een globale variabele wilt veranderen binnen een functie. Python stelt je in staat dit te doen door middel van de opdrachtglobal. Hieronder staat een aangepaste versie van het eerdere xReplace-voorbeeld.

Zoals we eerder besproken vereist lezen van een variabele niet dat je global gebruikt; dit is alleen nodig als je wilt schrijven.

Je hebt deze les nu afgerond en kun je door naar de volgende.