[] is not []

Egy kollegám szembesült ma vele, hogy az bizony nem igaz, hogy:

>>> [] is []
False

Gyorsan rájött segítség nélkül is, hogy a [] kódrészlet mindig új objektumot hoz létre, két különböző új objektum meg természetesen nem ugyanaz. Ezzel kapcsolatban viszont eszembe jutott egy trükkösebb eset.

Nézzük az alábbi egyszerű függvényt:

def proba(elem, l = []):
  l.append(elem)
  return l

Sima ügy - gondolnánk -, kap egy elemet, és opcionálisan egy listát, az elemet hozzáfűzi a listához, majd a már kibővített listát visszaadja. Ha nem adtunk meg listát, akkor egy üres listához "fűzi hozzá" az elemet, és így egy egyelemű listát kapunk vissza. Nézzük meg, hogy valóban ez történik-e:

>>> proba(3, [1,2])
[1, 2, 3]
>>> proba("a", [])
['a']
>>> proba(1)
[1]
>>> proba(2)
[1,2]
# micsoda?
>>> x = proba(3)
>>> x
[1,2,3]
>>> x.append(4)
>>> proba(5)
[1,2,3,4,5]
# ?!?

Bizony nem teljesen az történik, amit elsőre gondolnánk. Az alapértelmezett lista ugyanis a függvény deklarációjakor jön létre, és nem minden egyes lefutásakor, ha később módosítjuk, akkor bizony a függvény által használt alapértelmezett listát módosítjuk. Ez nem csak listákra, de minden mutable - azaz módosítható - objektumra igaz. Számokat, sztringeket, None-t, tuple-t viszont nyugodt szívvel használhatunk alapértelmezett értéknek. Próbáljuk tehát elkerülni az ilyen eseteket, íme egy lehetőség:

def proba(elem, l = None):
  if not l:
    l = [] # persze itt lehetne "return [elem]" is
  l.append(elem)
  return l

Ezt a sideeffectet tudatosan kihasználni pedig alávaló disznóság, senki se tegye!

31jan.

1 Comment for [] is not []

  1. slink says:

    Nagyon hasznos, amit írsz, mindazonáltal nekem még sohasem jutott eszembe, hogy argumentumnak alapértékként üres listát adjak át. Talán azért, mert még nem láttam soha Python kódot, ahol ilyet írtak volna (a poszt fényében érthető módon), talán azért, mert nem pythonic, vagy talán lispes megrögzöttség nálam, hogy a None-ra (NIL) üres listaként gondolok.

    Másfelől erre a problémára a legtöbb kézikönyvben felhívják a figyelmet, a standard tutorialban is kiemelten szerepel. Mindazomáltal értékes poszt, immáron magyar nyelven is dokumentált viselkedés.

    Talán az okozhat meglepetést, hogy egy üres lista literál viselkedik így. Ha egy olyan példát említettél volna, ahol más kifejezést adsz meg alapértelmezett értéknek, talán jobban belátható.

    <<< def log(msg, timestamp=time.time()):
    ...     print "%s %s" % (msg, timestamp)
    ... 
    <<< log("ok")
    ok 1296665560.0
    <<< log("hiba")
    hiba 1296665560.0
    

    Az ok pedig az, amit írsz. Erről található egy hosszabb kifejetés is az Advanced Python or Understanding Python c. előadásban. Hogy-hogy nem a téma a StackOverflow-n is terítékre került már.

Szólj hozzá