Zoeken…


Veranderlijk versus onveranderlijk

Er zijn twee soorten typen in Python. Onveranderlijke typen en veranderlijke typen.

Immutables

Een object van een onveranderlijk type kan niet worden gewijzigd. Elke poging om het object te wijzigen zal resulteren in een kopie.

Deze categorie omvat: gehele getallen, drijvers, complex, tekenreeksen, bytes, tupels, bereiken en frozensets.

Laten we, om deze eigenschap te markeren, spelen met de ingebouwde id . Deze functie retourneert de unieke identificatie van het object dat als parameter is doorgegeven. Als de id hetzelfde is, is dit hetzelfde object. Als het verandert, is dit een ander object. (Sommigen zeggen dat dit eigenlijk het geheugenadres van het object is, maar pas op voor hen, ze komen van de donkere kant van de kracht ...)

>>> a = 1
>>> id(a)
140128142243264
>>> a += 2
>>> a
3
>>> id(a)
140128142243328

Oké, 1 is geen 3 ... Nieuws, misschien niet. Dit gedrag wordt echter vaak vergeten als het gaat om complexere typen, met name tekenreeksen.

>>> stack = "Overflow"
>>> stack
'Overflow'
>>> id(stack)
140128123955504
>>> stack += " rocks!"
>>> stack
'Overflow rocks!'

Aha! Zien? We kunnen het wijzigen!

>>> id(stack)
140128123911472

Nee. Hoewel het lijkt alsof we de tekenreeks met de variabele stack kunnen wijzigen, maken we eigenlijk een nieuw object dat het resultaat van de aaneenschakeling bevat. We worden voor de gek gehouden omdat het oude object nergens heen gaat, dus het wordt vernietigd. In een andere situatie zou dat duidelijker zijn geweest:

>>> stack = "Stack"
>>> stackoverflow = stack + "Overflow"
>>> id(stack)
140128069348184
>>> id(stackoverflow)
140128123911480

In dit geval is het duidelijk dat als we de eerste string willen behouden, we een kopie nodig hebben. Maar is dat zo duidelijk voor andere types?

Oefening

Nu, wetende hoe een onveranderlijk type werkt, wat zou je zeggen met het onderstaande stukje code? Is het wijs?

s = ""
for i in range(1, 1000):
    s += str(i)
    s += ","

Mutables

Een object van een veranderlijk type kan worden gewijzigd en het wordt ter plaatse gewijzigd. Er worden geen impliciete kopieën gemaakt.

Deze categorie omvat: lijsten, woordenboeken, bytearrays en sets.

Laten we doorgaan met spelen met onze kleine id functie.

>>> b = bytearray(b'Stack')
>>> b
bytearray(b'Stack')
>>> b = bytearray(b'Stack')
>>> id(b)
140128030688288
>>> b += b'Overflow'
>>> b
bytearray(b'StackOverflow')
>>> id(b)
140128030688288

(Als een kanttekening gebruik ik bytes die ascii-gegevens bevatten om mijn punt duidelijk te maken, maar onthoud dat bytes niet zijn ontworpen om tekstuele gegevens te bevatten. Moge de kracht me vergeven.)

Wat hebben we? We maken een bytearray, wijzigen deze en met behulp van de id kunnen we ervoor zorgen dat dit hetzelfde object is, gewijzigd. Geen kopie ervan.

Natuurlijk, als een object vaak wordt aangepast, doet een veranderlijk type het veel beter dan een onveranderlijk type. Helaas wordt de realiteit van dit pand vaak vergeten wanneer het het meest pijn doet.

>>> c = b
>>> c += b' rocks!'
>>> c
bytearray(b'StackOverflow rocks!')

Oke...

>>> b
bytearray(b'StackOverflow rocks!')

Nog een seconde ...

>>> id(c) == id(b)
True

Inderdaad. c is geen kopie van b . c is b .

Oefening

Nu begrijp je beter welke bijwerking wordt geïmpliceerd door een veranderlijk type, kun je uitleggen wat er misgaat in dit voorbeeld?

>>> ll = [ [] ]*4 # Create a list of 4 lists to contain our results
>>> ll
[[], [], [], []]
>>> ll[0].append(23) # Add result 23 to first list
>>> ll
[[23], [23], [23], [23]]
>>> # Oops...

Veranderlijk en onveranderlijk als argumenten

Een van de belangrijkste gevallen waarin een ontwikkelaar rekening moet houden met veranderbaarheid, is het doorgeven van argumenten aan een functie. Dit is erg belangrijk, omdat dit de mogelijkheid voor de functie bepaalt om objecten te wijzigen die niet tot het bereik behoren, of met andere woorden als de functie bijwerkingen heeft. Dit is ook belangrijk om te begrijpen waar het resultaat van een functie beschikbaar moet worden gemaakt.

>>> def list_add3(lin):
    lin += [3]
    return lin

>>> a = [1, 2, 3]
>>> b = list_add3(a)
>>> b
[1, 2, 3, 3]
>>> a
[1, 2, 3, 3]

Hier is de fout om te denken dat lin , als parameter voor de functie, lokaal kan worden gewijzigd. In plaats daarvan, lin en a verwijzing naar hetzelfde object. Omdat dit object kan worden gewijzigd, wordt de wijziging ter plekke uitgevoerd, wat betekent dat het object waarnaar wordt verwezen door zowel lin als a wordt gewijzigd. lin hoeft niet echt te worden geretourneerd, omdat we al een verwijzing naar dit object hebben in de vorm van a . a en b eindigen op hetzelfde object.

Dit gaat niet hetzelfde voor tupels.

>>> def tuple_add3(tin):
    tin += (3,)
    return tin

>>> a = (1, 2, 3)
>>> b = tuple_add3(a)
>>> b
(1, 2, 3, 3)
>>> a
(1, 2, 3)

Aan het begin van de functie, tin en a verwijzing naar hetzelfde object. Maar dit is een onveranderlijk object. Dus wanneer de functie probeert om het te wijzigen, tin ontvangt een nieuw object met de wijziging, terwijl a houdt een verwijzing naar de oorspronkelijke object. In dit geval is het retourneren van tin verplicht, anders gaat het nieuwe object verloren.

Oefening

>>> def yoda(prologue, sentence):
    sentence.reverse()
    prologue += " ".join(sentence)
    return prologue

>>> focused = ["You must", "stay focused"]
>>> saying = "Yoda said: "
>>> yoda_sentence = yoda(saying, focused)

Opmerking: reverse werkt op zijn plaats.

Wat vindt u van deze functie? Heeft het bijwerkingen? Is de terugkeer noodzakelijk? Wat is de waarde van het saying na het telefoontje? Van focused ? Wat gebeurt er als de functie opnieuw wordt opgeroepen met dezelfde parameters?



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow