ProgrammatiePython developer / Data Engineer

Wat gebeurt er wanneer je een muteerbaar object (zoals een lijst of dictonary) aan een functie in Python doorgeeft? Hoe kun je onverwachte wijzigingen binnen en buiten de functie vermijden?

Slaag voor sollicitatiegesprekken met de Hintsage AI-assistent

Antwoord.

Achtergrond van de vraag:

Parameteroverdracht in Python maakt gebruik van het principe 'call by object reference' (soms ook wel call by sharing genoemd). Dit betekent dat de variabele binnen de functie naar hetzelfde object in het geheugen verwijst als het argument dat van buitenaf is doorgegeven.

Probleem:

Als de functie het doorgegeven muteerbare object (zoals een lijst of dictonary) wijzigt, zijn de wijzigingen ook buiten de functie zichtbaar. Dit kan leiden tot moeilijk te traceren bugs, vooral als verwacht wordt dat de functie de invoergegevens niet zal wijzigen.

Oplossing:

Om neveneffecten te vermijden, moet je een kopie van het object maken binnen de functie of onmuteerbare datatypes gebruiken. Voor kopiëren gebruik je standaardmethoden (bijvoorbeeld list.copy() voor lijsten, dict.copy() voor dictionaries of copy.deepcopy()).

Codevoorbeeld:

def append_one(xs): xs.append(1) return xs lst = [0] append_one(lst) print(lst) # [0, 1] # Hoe wijzigingen te vermijden? Maak een kopie: def safe_append_one(xs): ys = xs.copy() ys.append(1) return ys lst2 = [0] safe_append_one(lst2) print(lst2) # [0]

Belangrijke kenmerken:

  • Overdracht van een muteerbaar object maakt het mogelijk de staat ervan zowel binnen als buiten de functie te wijzigen.
  • Om dit te voorkomen, wordt het kopiëren van gegevens gebruikt (shallow/deep copy).
  • Onmuteerbare objecten zijn beschermd tegen dergelijke wijzigingen.

Vragen met een valstrik.

Kun je er zeker van zijn dat een lijstkopie via .copy() volledig onafhankelijk is van de oorspronkelijke lijst?

Nee — .copy() maakt een oppervlakkige kopie. Als er binnenin geneste muteerbare objecten zijn, zullen wijzigingen daarin ook zichtbaar zijn in de origineel.

import copy lst = [[1, 2], [3, 4]] shallow = lst.copy() shallow[0][0] = 42 print(lst) # [[42, 2], [3, 4]] deep = copy.deepcopy(lst) deep[0][0] = 100 print(lst) # [[42, 2], [3, 4]]

Is het retourneren van een nieuw object op basis van de invoer een garantie voor afwezigheid van wijzigingen in het origineel?

Niet altijd. Als binnen het nieuwe object delen van het originele object worden gebruikt (bijvoorbeeld een verwijzing naar een intern genest lijst), kan het originele object worden gewijzigd.

def duplicate_list(xs): return xs * 2 lst = [[1], [2]] res = duplicate_list(lst) res[0][0] = 999 print(lst) # [[999], [2]]

Kunnen standaard argumentwaarden voor muteerbare objecten problemen veroorzaken bij herhaalde functie-aanroepen?

Ja — de standaardwaarde wordt slechts één keer berekend bij de definitie van de functie.

def add_item(item, container=[]): container.append(item) return container print(add_item(1)) # [1] print(add_item(2)) # [1, 2]

Typische fouten en anti-patronen

  • Wijziging van het doorgegeven muteerbare object binnen de functie zonder de gebruiker te informeren.
  • Toepassen van oppervlakkig kopiëren voor geneste datastructuren (fout met muterende geneste objecten).
  • Gebruik van muteerbare objecten als standaardwaarde in functieargumenten.

Voorbeeld uit het leven

Negatieve case

In een configuratieverwerkingsbibliotheek werd een lijst als standaardwaarde gebruikt, wat leidde tot accumulatie van elementen tussen verschillende functie-aanroepen. Gedrag was onvoorspelbaar en werd erg laat ontdekt.

Voordelen:

Minder code voor herhaalde aanroepen, zichtbare besparing van geheugen.

Nadelen:

Onzichtbaar gedrag, moeilijkheden bij het debuggen, langdurige fouten.

Positieve case

Gebruik van None als standaardwaarde en expliciet creëren van een nieuw object bij elke aanroep.

Voordelen:

Voorspelbaarheid, afwezigheid van onverwachte neveneffecten, betrouwbaarheid.

Nadelen:

Vereist bewustzijn en iets meer code.