ProgrammatieBackend ontwikkelaar

Leg uit hoe closures werken in Python, hoe ze verschillen van normale functies en wat hun praktische toepassingen zijn?

Slaag voor sollicitatiegesprekken met de Hintsage AI-assistent

Antwoord.

Geschiedenis van de vraag

De term "closure" is ontleend aan functioneel programmeren en is vanaf het begin in Python aanwezig. Closures stellen een functie in staat om de omgeving waarin ze zijn gemaakt te onthouden, zelfs als ze buiten die omgeving worden aangeroepen. Dit concept biedt flexibiliteit en maakt de implementatie van veel patronen mogelijk, waaronder functie-fabrieken en lui berekeningen.

Probleem

In Python zijn functies first-class objecten. Soms is het nodig dat een geneste functie variabelen uit de scope van de omvattende functie gebruikt, zelfs nadat deze is beëindigd. De gewone lexicale scope garandeert dit niet bij de terugkeer van de functie. Wanneer zo'n functie verwijst naar de omgevingsvariabelen van zijn creatie, ontstaat er een closure.

Oplossing

Een closure ontstaat wanneer de binnenste functie verwijst naar variabelen die in de buitenste functie zijn gedefinieerd, en die buitenste functie de binnenste functie teruggeeft. Dit wordt vaak gebruikt om functie-fabrieken te creëren, toestand te kapselen zonder klassen, en functies te construeren met parameters "on the spot".

Voorbeeldcode:

def make_multiplier(factor): def multiplier(x): return x * factor return multiplier mul2 = make_multiplier(2) mul3 = make_multiplier(3) print(mul2(10)) # 20 print(mul3(10)) # 30

Belangrijke kenmerken:

  • Een closure slaat de waarden van omgevingsvariabelen op, zelfs als de buitenste functie al is beëindigd.
  • De toestand in de geneste functie is in wezen privé en kan niet rechtstreeks van buitenaf worden gewijzigd.
  • Om een niet-immutable variabele in de buitenste functie binnen de closure te wijzigen, wordt het sleutelwoord nonlocal gebruikt.

Misleidende vragen.

Kan een closure veranderlijke toestand tussen aanroepen behouden als de variabele binnen de geneste functie wordt gewijzigd?

Ja, als je het sleutelwoord nonlocal in de geneste functie gebruikt. Zonder nonlocal creëert een toewijzing een nieuwe lokale variabele, zonder de buitenste te wijzigen.

def counter(): count = 0 def inc(): nonlocal count count += 1 return count return inc c = counter() print(c()) # 1 print(c()) # 2

Is het mogelijk om privé-variabelen in Python te implementeren met behulp van closures in plaats van klassen?

Ja, een closure biedt een eenvoudige implementatie van "privé" variabelen die niet van buitenaf toegankelijk zijn, tenzij getters/setters worden aangeboden in de geneste functie.

Worden closures alleen op functies toegepast? Kan een closure met lambda's in Python worden georganiseerd?

Ja, een closure kan ook worden gevormd met lambda-expressies, aangezien ze vergelijkbaar zijn met 'def' in hun binding van lexicale variabelen.

def make_power(n): return lambda x: x ** n square = make_power(2) cube = make_power(3) print(square(4)) # 16 print(cube(2)) # 8

Typische fouten en anti-patronen

  • Verwachten dat een closure automatisch een externe variabele wijzigt wanneer deze van binnenuit wordt gewijzigd zonder nonlocal.
  • Veranderlijke objecten binnen een closure vastleggen en deze muteren zonder de complicaties bij debugging te beseffen.
  • Een loop gebruiken om functies in een closure te creëren zonder de juiste binding van variabelen (valkuil "alle functies zien de laatste waarde van de variabele").

Voorbeeld uit het leven

Negatieve case

Een functie-fabriek die handlers in een loop vormt, gebruikt de loopvariabele binnen de closure:

handlers = [] for i in range(3): def handler(x): return x + i handlers.append(handler) print([h(10) for h in handlers]) # [12, 12, 12]

Voordelen:

  • Eenvoudig, weinig code.

Nadelen:

  • Alle handlers verwijzen naar dezelfde variabele i, de laatste waarde 2 — gedrag dat onvoorspelbaar is voor de meeste mensen.

Positieve case

Een standaardargument is gebruikt om de waarde "vast te leggen":

handlers = [] for i in range(3): def handler(x, j=i): return x + j handlers.append(handler) print([h(10) for h in handlers]) # [10, 11, 12]

Voordelen:

  • Binding van de benodigde waarde.
  • Voorspelbaar gedrag.

Nadelen:

  • Je moet je deze nuance herinneren en het closure handmatig aanpassen, wat de onderhoudbaarheid van de code bemoeilijkt.