Alap adatszerkezetek Pythonban

Kategória: Python.
Alkategória: Adatkezelés Pythonban.

Ezen az oldalon a legfontosabb Python adatszerkezeteket nézzük meg.

Lista

A listában az elemek egy jól meghatározott sorrendben szerepelnek.

Létrehozás

Emlékeztetőül: listát szögletes zárójelekkel tudunk létrehozni:

fruits = ["apple", "orange", "banana", "pear", "mandarin"]

Hivatkozás

A címzés szögletes zárójelbe írt indexszel történik. Ezzel a módszerrel ki le tudjuk kérni az adott indexű elemet, ill. módosítani is tudjuk. Az indexelés 0-tól indul:

print(fruits[1]) # orange
fruits[1] = "peach"
print(fruits) # ['apple', 'peach', 'banana', 'pear', 'mandarin']

Az indexekkel intervallumot is megadhatunk, :-tal elválasztva:

print(fruits[1:3]) # ['peach', 'banana']

Sőt, értéket is adhatunk, kicsit különleges módon:

fruits[1:3] = ["grape", "lemon"]
print(fruits) # ['apple', 'grape', 'lemon', 'pear', 'mandarin']

Negatív indexeléssel a lista végén található elemeket érhetjük el; -1 jelenti utolsót, -2 az utolsó előttit stb.:

print(fruits[-1]) # mandarin
print(fruits[-3:-1]) # ['lemon', 'pear']

A harmadik index paraméter a lépésköz:

fruits = ["apple", "orange", "banana", "pear", "mandarin"]
print(fruits[1:5:2]) # ['orange', 'pear']
print(fruits[::2]) # ['apple', 'banana', 'mandarin']

A listát felbonthatjuk változókra az alábbi módon:

fruits = ["apple", "banana", "orange"]
fruit1, fruit2, fruit3 = fruits
print(fruit1) # apple
print(fruit2) # banana
print(fruit3) # orange

Az ún. kicsomagoló operátor (unpacking operator) segítségével az asterix karakterrel (*) listát is létrehozhatunk:

fruits = ["apple", "banana", "orange", "mandarin", "watermelon"]
fruit1, *fruits2, fruit3 = fruits
print(fruit1)  # apple
print(fruits2) # ['banana', 'orange', 'mandarin']
print(fruit3)  # watermelon

Ellenőrzés

Annak ellenőrzése, hogy egy elem a lista része-e:

fruits = ["apple", "banana", "orange", "apple"]
"banana" in fruits     # True
"watermelon" in fruits # False

Az index() függvény visszaadja az adott elem első előfordulásának indexét:

print(fruits.index("orange")) # 2
print(fruits.index("apple"))  # 0

Ha az elem nincs benne a listában, akkor ValueError hibát ír ki.

A count() függvény megszámolja, hogy egy elem hányszor fordul elő a listában.

print(fruits.count("apple")) # 2

Hozzáadás

Hozzáfűzni elemet az append() függvénnyel tudunk:

fruits.append("plum")
print(fruits) # ['apple', 'orange', 'banana', 'pear', 'mandarin', 'plum']

Beszúrni adott indexre ("jobbra tolva" a többit) az insert() függvénnyel lehet:

fruits.insert(2, "watermelon")
print(fruits) # ['apple', 'orange', 'watermelon', 'banana', 'pear', 'mandarin', 'plum']

Egy listát az extend() függvénnyel tudunk hozzáfűzni egy másikhoz:

new_fruits = ["kiwi", "pineapple"]
fruits.extend(new_fruits)
print(fruits) # ['apple', 'orange', 'watermelon', 'banana', 'pear', 'mandarin', 'plum', 'kiwi', 'pineapple']

Két listát a + operátorral tudunk összefűzni. Az eredmény egy harmadik lista lesz:

fruits1 = ["apple", "banana", "orange"]
fruits2 = ["peach", "watermelon"]
fruits3 = fruits1 + fruits2
print(fruits3) # ['apple', 'banana', 'orange', 'peach', 'watermelon']

A * művelettel lehet megsokszorozni a listát:

fruits = ["apple", "banana", "orange"]
print(3 * fruits)
['apple', 'banana', 'orange', 'apple', 'banana', 'orange', 'apple', 'banana', 'orange']

Törlés

Törölni elemeket szintén többféleképpen lehetséges. A remove() függvénynek át tudjuk adni magát az elemet:

print(fruits) # ['apple', 'orange', 'watermelon', 'banana', 'pear', 'mandarin', 'plum', 'kiwi', 'pineapple']
fruits.remove("pear")
print(fruits) # ['apple', 'orange', 'watermelon', 'banana', 'mandarin', 'plum', 'kiwi', 'pineapple']

Az utolsó elemet a pop() hívással tudjuk törölni. Ez a függvény visszaadja a törölt elemet:

fruits.pop() # 'pineapple'
print(fruits) # ['apple', 'orange', 'watermelon', 'banana', 'mandarin', 'plum', 'kiwi']

Paraméterül megadhatjuk, hogy melyik indexű elemet szeretnénk törölni:

fruits.pop(2) # 'watermelon'
print(fruits) # ['apple', 'orange', 'banana', 'mandarin', 'plum', 'kiwi']

A del szintén törli az elemet, de az nem adja vissza:

del fruits[1]
print(fruits) # ['apple', 'banana', 'mandarin', 'plum', 'kiwi']

A clear() függvény törli az összes elemet:

fruits.clear()
print(fruits) # []

Iterálás

A lista elemein többféleképpen léphetünk végig: a más programozási nyelvekben megszokott index és foreach jellegű iterációk mellett itt létezik egy sajátos módszer, az ún. list comprehension. Lássunk mindegyikre példát! Célszerűen induljunk ki az alapállapotból:

fruits = ["apple", "orange", "banana", "pear", "mandarin"]

Index segítségével történő végigiterálás:

for i in range(len(fruits)):
    print(fruits[i])

Tömörebb és hatékonyabb megoldást kínál a foreach:

for fruit in fruits:
    print(fruit)

A list comprehension egysoros megoldást kínál:

[print(fruit) for fruit in fruits]

Ennek van visszatérési értéke is, ami egy lista. Pl.:

lens = [len(fruit) for fruit in fruits]
print(lens) # [5, 6, 6, 4, 8]

Feltételt is írhatunk mögé, ezzel szűrhetünk:

[fruit for fruit in fruits if ("e" in fruit)] # ['apple', 'orange', 'pear']

Az eredmény azok a gyümölcsnevek, melyekben van e betű. A kettő kombinálható is:

[len(fruit) for fruit in fruits if ("e" in fruit)] # [5, 6, 4]

Itt tehát az eredmény egy olyan tömb, amely azon gyümölcsök neveinek hosszait tartalmazzák, amelyekben van e betű.

Helyben rendezés

A listán a sort() függvényt végrehajtva tudjuk lerendezni az elemeket:

print(fruits) # ['apple', 'orange', 'banana', 'pear', 'mandarin']
fruits.sort()
print(fruits) # ['apple', 'banana', 'mandarin', 'orange', 'pear']

Fordított sorrendet a reverse=True paraméterrel tudunk elérni:

fruits.sort(reverse=True)
print(fruits) # ['pear', 'orange', 'mandarin', 'banana', 'apple']

A key paraméterrel megadhatjuk azt is, hogy mi alapján rendezzen. A stringek esetében ez alapértelmezésben a lexikografikus sorrend, de pl. a szavak hossza szerint is le tudjuk rendezni, ha megadjuk a key=len paramétert (itt akár saját függvényt is használhatnánk):

fruits.sort(key=len)
print(fruits) # ['pear', 'apple', 'orange', 'banana', 'mandarin']

A reverse() hívással is meg tudjuk a sorrendet:

fruits.reverse()
print(fruits) # ['mandarin', 'banana', 'orange', 'apple', 'pear']

Nem helyben rendezés

A sorted() hívás nem helyben rendez:

fruits = ["apple", "orange", "banana", "pear", "mandarin"]
fruits_sorted = sorted(fruits)
print(fruits) # ['apple', 'orange', 'banana', 'pear', 'mandarin']
print(fruits_sorted) # ['apple', 'banana', 'mandarin', 'orange', 'pear']

Másolás

Ha egy listát értékül adunk egy másik változónak, akkor az fizikailag ugyanarra a listára hivatkozik. Ha pl. megváltoztatunk a listában egy elemet az egyik listában, akkor a másikban is megváltozik:

fruits1 = ["apple", "orange", "banana", "pear", "mandarin"]
fruits2 = fruits1
fruits1[0] = "pear"
print(fruits1) # ['pear', 'orange', 'banana', 'pear', 'mandarin']
print(fruits2) # ['pear', 'orange', 'banana', 'pear', 'mandarin']

Ha nem ezt szeretnénk, akkor használhatjuk a copy() függvényt, amely másolatot készít a listából. Így az egyik lista megváltoztatása nem lesz hatással a másikra:

fruits3 = fruits1.copy()
fruits1[0] = "watermelon"
print(fruits1) # ['watermelon', 'orange', 'banana', 'pear', 'mandarin']
print(fruits3) # ['pear', 'orange', 'banana', 'pear', 'mandarin']

Elem n-es (tuple)

Hasonló műveleteket tudunk ezzel is végrehajtani, mint a listákkal. Ellentétben a listákkal, ez nem megváltoztatható. Külön nincs nem megváltoztatható típus a Pythonában, ezt a típust tudjuk arra a célra használni.

Megváltoztatni az alábbi trükkel tudjuk:

t = ("apple", "orange", "banana")
l = list(t)
l[1] = "peach"
t = tuple(l)
print(t) # ('apple', 'peach', 'banana')

A megváltoztató műveletek (elem beszúrása, módosítása, törlése) itt nem alkalmazhatóak. Viszont az alábbiak itt is működnek:

  • Index szerinti címzés (szakasz és negatív index is).
  • Felbontás; az asterix karakter is.
  • Az ellenőrző műveletek (in operátor, count() és index()).
  • A különféle iteráló műveletek, beleértve a list comprehension-t is.

Halmaz

A halmazban egy elem egyszer szerepelhet, és a sorrend sem biztosított.

Létrehozás

fruits = {"apple", "orange", "banana", "orange"}
print(fruits) # {'banana', 'orange', 'apple'}

Hivatkozás

A halmaz elemeit nem tudjuk címezni így utólag nem is tudjuk megváltoztatni.

Ellenőrzés

Az in operátor a halmazok esetén is működik:

fruits = {"apple", "orange", "banana", "cherry", "mandarin"}
"apple" in fruits # True
"peach" in fruits # False

Hozzáadás

A listához hasonlóan itt is az add() függvénnyel történik:

fruits.add("pineapple")

Törlés

A remove() függvénnyel, az elem megadásával történik:

fruits.remove("orange")

A pop() művelettel ki tudunk vennie egy elemet, de hogy melyiket, az nem garantált:

print(fruits.pop()) # pineapple
print(fruits)       # {'banana', 'apple'}

A discard() függvénnyel is elemet tudunk törölni. Lényeges különbség a remove() és a discard() között az, hogy ha az elem nincs a halmazban, akkor ez utóbbi nem dob hibát:

fruits = {"apple", "orange", "banana", "cherry", "mandarin"}
fruits.remove("apple")
fruits.remove("apple")   # KeyError
fruits.discard("orange")
fruits.discard("orange") # OK

A clear() függvénnyel tudjuk törölni a halmaz minden elemét:

fruits.clear()
print(fruits) # set()

Iterálás

Végigiterálni - indexelés híján - csak a foreach módszerrel tudunk:

for fruit in fruits:
    print(fruit)

A list comprehension technika itt is működik:

fruits = {"apple", "orange", "banana", "cherry", "mandarin"}
{fruit.upper() for fruit in fruits if "e" in  fruit} # {'APPLE', 'ORANGE', 'CHERRY'}

Rendezés

A halmazokat nem lehet rendezni.

Másolás

Másolni itt is a copy() metódussal tudunk. Érdekessége, hogy már itt is kijön az, hogy a halmazban a sorrend nem garantált; az eredményben nem ugyanaz a sorrend, mint a forrásban:

fruits = {"apple", "orange", "banana", "cherry", "mandarin"}
fruits_copy = fruits.copy()
print(fruits)      # {'orange', 'banana', 'apple', 'cherry', 'mandarin'}
print(fruits_copy) # {'orange', 'cherry', 'banana', 'mandarin', 'apple'}

Halmaz műveletek

Unió

Egy halmazhoz az update() függvénnyel tudunk hozzáadni egy másik halmaz elemeit:

fruits = {"apple", "orange", "banana"}
new_fruits = {"cherry", "apple", "pineapple"}
fruits.update(new_fruits)
print(fruits) # {'orange', 'cherry', 'pineapple', 'banana', 'apple'}

Paraméterül nemcsak halmazt, hanem más iterálható adatstruktúrát is átadhatunk:

fruits = {"apple", "orange", "banana"}
new_fruits = ["cherry", "apple", "pineapple"]
fruits.update(new_fruits)
print(fruits) # {'orange', 'banana', 'apple', 'cherry', 'pineapple'}

Ha egyik halmazt sem szeretnénk megváltoztatni, akkor az union() függvényt használhatjuk; ez esetben az eredmény egy új halmaz lesz:

fruits1 = {"apple", "orange", "banana"}
fruits2 = {"cherry", "apple", "pineapple"}
fruits3 = fruits1.union(fruits2)
print(fruits1) # {'banana', 'orange', 'apple'}
print(fruits2) # {'cherry', 'pineapple', 'apple'}
print(fruits3) # {'orange', 'cherry', 'pineapple', 'banana', 'apple'}

Metszet

Itt is kétféle módszer van. Az intersection_update() megváltoztatja a halmazt:

fruits = {"apple", "orange", "banana"}
new_fruits = {"cherry", "apple", "pineapple"}
fruits.intersection_update(new_fruits)
print(fruits) # {'apple'}

Az intersection() eredménye egy új halmaz:

fruits1 = {"apple", "orange", "banana"}
fruits2 = {"cherry", "apple", "pineapple"}
fruits4 = fruits1.intersection(fruits2)
print(fruits4) # {'apple'}

Különbség

A difference_update() megváltoztatja a halmazt:

fruits = {"apple", "orange", "banana"}
new_fruits = {"cherry", "apple", "pineapple"}
fruits.difference_update(new_fruits)
print(fruits) # {'banana', 'orange'}

A difference() új halmazt hoz létre:

fruits1 = {"apple", "orange", "banana"}
fruits2 = {"cherry", "apple", "pineapple"}
fruits5 = fruits1.difference(fruits2)
print(fruits5) # {'banana', 'orange'}

Szimmetrikus különbség

A symmetric_difference_update() eredménye a szimmetrikus különbség az egyik halmazban:

fruits = {"apple", "orange", "banana"}
new_fruits = {"cherry", "apple", "pineapple"}
fruits.symmetric_difference_update(new_fruits)
print(fruits) # {'orange', 'banana', 'cherry', 'pineapple'}

A symmetric_difference() új halmazt hoz létre:

fruits1 = {"apple", "orange", "banana"}
fruits2 = {"cherry", "apple", "pineapple"}
fruits6 = fruits1.symmetric_difference(fruits2)
print(fruits6) # {'orange', 'banana', 'cherry', 'pineapple'}

Halmaz lekérdezések

Az issuperset(), issubset() és isdisjoint() hívásokkal azt tudjuk lekérdezi, hogy egy halmaz bővebb-e, mint egy másik részhalmaza-e ill. diszjunktak-e:

{"apple", "banana", "cherry"}.issuperset({"apple", "cherry"}) # True
{"apple", "banana", "cherry"}.issuperset({"apple", "peach"})  # False
{"apple", "banana"}.issubset({"apple", "cherry", "banana"})   # True
{"apple", "banana"}.issubset({"cherry", "banana"})            # False
{"apple", "banana"}.isdisjoint({"cherry", "banana"})          # False
{"apple", "banana"}.isdisjoint({"cherry", "pear"})            # True

Módosíthatatlan halmaz

A frozenset() módosíthatatlan halmazhoz létre. Tehát úgy viszonyul a halmaz a frozenset-hez, mint a lista a tuple-höz. Azok a halmazműveletek működnek, amelyek nem változtatják meg a halmazt, amelyek viszont megváltoztatnák, azok a műveletek hiányoznak.

unmodifiabe_set = frozenset({"apple", "orange", "banana"})
print(unmodifiabe_set) # {'banana', 'apple', 'orange'}
unmodifiabe_set.add("peach") # hiba

Szótár

Angolul dictionary, innen adódik a dict típus megnevezés. Egyéb elnevezések: asszociatív tömb (associative array), szimbólumtábla (symbol table), térkép (map). Rendezetlen kulcs-érték párokat tartalmaz. A kulcs és az érték is bármilyen típusú lehet; akár vegyíteni is lehet őket.

Létrehozás

fruits = {"apple": "alma", "banana": "banán", "orange": "narancs"}
print(fruits) # {'apple': 'alma', 'banana': 'banán', 'orange': 'narancs'}

Hivatkozás

Kétféleképpen is hivatkozhatunk egy elemre: szögletes zárójellel ([…]) és get() hívással is:

print(fruits["apple"])     # alma
print(fruits.get("apple")) # alma

Ellenőrzés

Azt tudjuk ellenőrizni, hogy egy adott kulcsú elem benne van-e az asszociatív tömbben:

"apple" in fruits # True
"alma" in fruits  # False

Az értékek között is végrehajthatjuk a tartalmazás vizsgálatot:

"alma" in fruits.values()
"körte" in fruits.values()

Hozzáadás

Új elemet a következőképpen tudunk felvenni:

fruits["apricot"] = "sárgabarack"

Módosítani is tudjuk a már meglévő elemet:

fruits["apricot"] = "kajszibarack"

Egy elemnek eltérő típusa is lehet, pl. halmaz:

fruits["apricot"] = {"sárgabarack", "kajszibarack"}
print(fruits["apricot"]) # {'kajszibarack', 'sárgabarack'}

Hasonló módon egy kulcsnak az értéke lehet egy újabb dict, egymásba ágyazva.

Egyszerre több elemet is hozzáadhatunk ill. módosíthatunk az update() segítségével:

fruits.update({"watermelon": "görögdinnye", "pear": "körte"})
print(fruits) # {'apple': 'alma', 'banana': 'banán', 'orange': 'narancs', 'apricot': {'kajszibarack', 'sárgabarack'}, 'watermelon': 'görögdinnye', 'pear': 'körte'}

Törlés

Elemet a pop() hívással tudunk törölni:

fruits.pop("apple") # 'alma'
print(fruits) # {'banana': 'banán', 'orange': 'narancs', 'apricot': {'kajszibarack', 'sárgabarack'}}

Tehát visszaadja az adott kulcsú elem értékét, és törlni az adott kulcsú elemet.

Iterálás

Induljunk ki az eredetileg létrehozott szótárból:

fruits = {"apple": "alma", "banana": "banán", "orange": "narancs"}

Ha foreach módszerrel végigiterálunk rajta, akkor a kulcsokon lépked végig:

for fruit in fruits:
    print(fruit)

melynek eredménye:

apple
banana
orange

Explicit megadhatjuk, hog a kulcsokon szeretnénk végiglépkedni a keys() meódussal:

for fruit in fruits.keys():
    print(fruit)

Az eredménye ugyanaz.

Az értékeken a következőképpen tudunk végiglépkedni:

for value in fruits.values():
    print(value)

Eredmény:

alma
banán
narancs

A kulcs-érték párokat úgy is kiírhatjuk, hogy végigiterálunk a kulcsokon, és egyesével lekérdezzük az értékeket. Ám nagyobb tömb esetén ez lassú. Érdemes rögtön a kulcs-érték párokat lekérdezni a cilkuson belül:

for k, v in fruits.items():
    print(str(k) + " : " + str(v))

Ennek eredménye:

apple : alma
banana : banán
orange : narancs

Rendezés

Nem lehet rendezni.

Másolás

Az értékadás ill. a copy() hasonlóan működik itt is, mint a

fruits_view = fruits
fruits_copy = fruits.copy()
fruits["apple"] = "ALMA"
print(fruits["apple"])      # ALMA
print(fruits_view["apple"]) # ALMA
print(fruits_copy["apple"]) # alma

Defaultdict

Az dict típusban nincs alapértelmezett érték, ráadásul ha egy olyan kulcsot kérünk le, amely nincs benne a szótárban, akkor KeyError hibát kapunk. A defaultdict kezeli a hiányzó kulcsú elemeket is. Használata kissé körülményes. Alapértelmezésben nincs betöltve, azt importálni kell. A konstruktorban egy ún. factory-t kell megadni, ami lehet egy lambda függvény is, fix visszatérési értékkel. Ráadásul az elemeket is egyesével lehet csak megadni. Egy példa:

from collections import defaultdict
fruits_default = defaultdict(lambda: "missing")
fruits_default["apple"] = "alma"
fruits_default["orange"] = "narancs"
fruits_default["banana"] = "banán"
print(fruits_default["apple"])    # alma
print(fruits_default["mandarin"]) # missing

Counter

A szótárak egyik gyakori alkalmazási területe a számlálás: például egy listában összeszámoljuk, hogy melyik elem hányszor fordul elő. Hagyományos módon ezt a következőképpen tudjuk megoldani:

fruit_counter = {}
for fruit in ["apple", "orange", "banana", "apple", "banana"]:
    if fruit in fruit_counter:
        fruit_counter[fruit] += 1
    else:
        fruit_counter[fruit] = 1
print(fruit_counter) # {'apple': 2, 'orange': 1, 'banana': 2}

Elegánsabb megoldást nyújt a Counter, ami a dict leszármazottja:

from collections import Counter
fruit_counter = Counter()
for fruit in ["apple", "orange", "banana", "apple", "banana"]:
    fruit_counter[fruit] += 1
print(fruit_counter) # Counter({'apple': 2, 'banana': 2, 'orange': 1})
print(dict(fruit_counter)) # {'apple': 2, 'orange': 1, 'banana': 2}

Vegyes gyűjtemény témák

Közös műveletek

Az alábbi táblázat a 4 alap gyűjtemény típus közös eljárásait mutatja be. Az alkalmazott konvenciók:

  • c: collection
  • l: list
  • t: tuple
  • s: set
  • d: dict
  • e: element
  • k: key
  • v: value
  • i: index
list tuple set dict
létrehozás [3, 2] (3, 2) {3, 2} {3:9, 2:4}
indexelés l[i] t[i] - d[k]
lekérdezés e in l e in t e in s k in d
index lekérdezése l.index(e) t.index(e) - -
elemek megszámolása l.count(e) t.count(e) - -
elem hozzáadása l.append(e)
l.insert(i, e)
- s.add(e) d[k]=v
elemek hozzáadása helyben l.extend(c) - s.update(c) d1.update(d2)
két gyűjtemény összefűzése l1 = l2 + l3 t1 = t2 + t3 s1 = s2.union(s3)
s1 = s2 | s3
d1 = d2 | d3
metszet - - s1 = s2.intersection(s3)
s1.intersection_update(s2)
-
különbség - - s1 = s2.difference(s3)
s1 = s2.intersection_update(s3)
-
szimmetrikus különbség - - s1 = s2.symmetric_difference(s3)
s1.symmetric_difference_update(s2)
-
egy elem törlése l.remove(e)
l.pop(i)
l.pop()
- s.remove(e)
s.pop()
d.pop(k)
kiürítés l.clear() - s.clear() d.clear()
iterálás for e in l for e in t for e in s for k in d
for k, v in d
rendezés l.sort() - - -
másolás l.copy() t.copy() s.copy() d.copy()

A c3 = c2 | c3 módszer csak a 3.9-es verziótól kezdve működik.

Vessző a felsorolás végén

A tuple életre keltett egy igen érdekes lehetőséget Pythonban: megengedi a vesszőt a felsorolások végén. Lássuk az alapproblémát! Emlékeztetőül: a tuple-t zárójelekbe tesszük:

a = (3, 2)
print(type(a)) # <class 'tuple'>

Viszont mi a helyzet az egy elemű tuple-lal?

b = (3)
print(type(b)) # <class 'int'>

Ez így csak egy zárójelbe írt szám. Ha azt szeretnénk kifejezni, hogy ez egy egyelemű tuple, akkor vesszőt kell a végére tennünk:

c = (3,)
print(type(c)) # <class 'tuple'>

Ez nem csak egy elemű tuple-k esetén működik:

d = (3, 2,)
print(type(d)) # <class 'tuple'>

Ahogy azt láthattuk, az egyes struktúrák szintaxisa nagyban hasonlít egymásra a Python-ban. A vessző sem kivétel, a többi gyűjtemény típusban is használhatjuk, pl. a listák esetén így:

e = [3, 2,]
print(type(e)) # <class 'list'>

Ez viszont "önálló életre" keltette ezt a lehetőséget. Látszólag felesleges vesszőt tenni a felsorolás végére, sőt, igénytelennek tűnik, viszont ha egy felsorolásban mindegyik elem külön sorba kerül, akkor van jelentősége. Nézzük meg, mi történik vessző használata nélkül! Ha beszúrunk a végére egy új sort, akkor az utolsó előtti (korábban utolsó) elem után vesszőt kell tennünk. Előtte:

fruits = [
    "apple",
    "banana",
    "orange"
]

Utána:

fruits = [
    "apple",
    "banana",
    "orange",
    "peach"
]

Csakhogy ez esetben már az a sor is módosultnak minősül, annak ellenére, hogy a vessző logikailag az új sor miatt kerül oda. A verziókövetők viszont nem ismerik a módosítást, azok úgy modellezik, hogy egy sor eltűnt, egy másik megjelent. Azaz egyetlen elem beszúrása miatt a módosulás mértéke 3 sor lesz:

-     "orange"
+     "orange",
+     "peach"

Ha viszont vesszőt teszünk a sor végére, azaz:

fruits = [
    "apple",
    "banana",
    "orange",
]

ill.

fruits = [
    "apple",
    "banana",
    "orange",
    "peach",
]

akkor a különbség már egyetlen sor lesz:

+     "peach",

E probléma kezelésére egyébként már történtek kísérletek. Pl. a Microsoft módszere az SQL rendszereiben az a konvenció, hogy a vesszőt nem az előző elem után, hanem a következő elem elé teszi. Az viszont olyan szempontból zavaró, hogy egy másik, talán sokkal erősebb konvenciót üt: nevezetes azt, hogy a szó és az azt követő vessző között nincs szóköz pont amiatt, hogy ne kerülhessen új sorba a vessző, míg a vessző után van. Emiatt a Python megoldását sokkal jobban tartom. Kezdetben furcsa volt a végén a vessző, de hozzá lehet szokni. Ha egyszer készítek egy saját programozási nyelvet, ezt biztos, hogy beleteszem :-)

Van egy különleges lehetőség a Pythonban, amivel sehol máshol nem találkoztam: egy felsorolást befejezhetünk vesszővel. Tehát az 1, 2, 3, 4 és az 1, 2, 3, 4, egyenértékű.

Iterátorok

Mindegyik gyűjtemény típusnál láthattuk, hogy végig tudunk rajtuk lépkedni. Ezt a módszert iterálásnak hívjuk. Mi magunk is tudunk készíteni iterátort. Ehhez:

  • Egy osztályban meg kell valósítani két függvényt:
    • __iter__(self): ezzel inicializáljuk az iterátort
    • __next__(self): ezzel léptetjük az iterátort
  • Az így keletkezett osztályt át kel adni az iter() konstruktornak.
  • Végül a next() hívásokkal tudunk végiglépkedni rajta.

Példaként lássuk a Fibonacci számok iterátorként történő megvalósítását:

class Fibonacci:
    def __iter__(self):
        self.x = 0
        self.y = 1
        return self
 
    def __next__(self):
        result = self.y
        next = self.x + self.y
        self.x = self.y
        self.y = next
        return result
 
fibonacci = iter(Fibonacci())
for i in range(10):
    print(next(fibonacci))

Az igazi gyűjteményeknél nem kellett a next() azok viszont végesek. Ha azt szeretnénk, hogy a saját iterátorunk is véges legyen, akkor az utolsó elem után a StopIteration kivételt kell dobni:

class Fibonacci:
    def __iter__(self):
        self.x = 0
        self.y = 1
        self.n = 0
        return self
 
    def __next__(self):
        self.n += 1
        if (self.n <= 10):
            result = self.y
            next = self.x + self.y
            self.x = self.y
            self.y = next
            return result
        else: 
            raise StopIteration
 
for x in iter(Fibonacci()):
    print(x)

Felsorolás

Meglepő, de alapértelmezésben nincs felsorolás típus a Pythonban. Helyette használhatjuk az enum modulból az Enum osztályt:

import enum
 
class Day(enum.Enum):
    MONDAY = 1,
    TUESDAY = 2,
    WEDNESDAY = 3,
    THURSDAY = 4,
    FRIDAY = 5,
    SATURDAY = 6,
    SUNDAY = 7,
 
def print_day(day):
    if (day == Day.MONDAY):
        print("hétfő")
    elif (day == Day.TUESDAY):
        print("kedd")
    elif (day == Day.WEDNESDAY):
        print("szerda")
    elif (day == Day.THURSDAY):
        print("csötörtök")
    elif (day == Day.FRIDAY):
        print("péntek")
    elif (day == Day.SATURDAY):
        print("szombat")
    elif (day == Day.SUNDAY):
        print("vaásnap")
 
print_day(Day.THURSDAY) # csütörtök

Ez nem igazán kényelmes, mert meg kell adni a felsorolt elemek számértékeit. Ezen valamelyest segít az auto():

class Day(enum.Enum):
    MONDAY = enum.auto(),
    TUESDAY = enum.auto(),
    WEDNESDAY = enum.auto(),
    THURSDAY = enum.auto(),
    FRIDAY = enum.auto(),
    SATURDAY = enum.auto(),
    SUNDAY = enum.auto(),

Ezen még egyet javíthatunk:

from enum import Enum, auto
 
class Day(Enum):
    MONDAY = auto(),
    TUESDAY = auto(),
    WEDNESDAY = auto(),
    THURSDAY = auto(),
    FRIDAY = auto(),
    SATURDAY = auto(),
    SUNDAY = auto(),

Ennél jobban viszont sajnos nem sikerült egyszerűsítenem.

Folyam

Angolul stream. Alapvető fontosságú, és talán kicsit meglepő, de a Pythonban ilyen nincs olyan mértékben, mint a számos más programozási nyelvben, ld. pl. Java vagy Scala. Ám a 3 legfontosabb függvény a Pythonban is elérhető, amire szinten minden visszavezethető:

  • map(func, collection): a gyűjtemény elemeit alakítja át. Az adott gyűjtemény (collection) elemein végrehajt egy műveletet. A func vagy egy egy paraméteres függvény, vagy - gyakrabban - egy lambda kifejezés. Az eredmény egy olyan gyűjtemény, ami azt új értékeket tartalmazza.
  • filter(predicate, collection): szűr. Az adott gyűjtemény elemein végrehajt egy logikai műveletet. A predicate, ami egy olyan függvény vagy lambda kifejezés, ami egy értéket vár és egy logikai értéket ad vissza. Ahol az eredmény True, az megmarad, a többi nem.
  • reduce(func, collection[, init]): végrehajt egy redukáló műveletet. Itt a func egy olyan függvény, ami két paramétert vár, és egy ugyanolyan típusú értéket ad vissza. Ezeket hajtja végre úgy, hogy először veszi az első kettőt, utána az eredményt és a következőt, és így tovább, a végéig. Ha megadunk egy kezdeti értéket (init), akkor első lépésben nem az első kettőn hajtja végre, hanem az init értéken és az elsőn.

Megjegyzés: a map() és a filter() beépített függvények, a reduce()-hoz viszont kell egy betöltés:

from functools import reduce

Lássunk egy kicsit talán erőltetett példát (itt TODO magamnak: egy kevésbé erőltetett, de kellően általános példát találni):

  • adott egy tömb,
  • annak elemeit négyzetre emeljük (általános esetben itt akármilyen bonyolult feltételt elképzelhetünk),
  • végrehajtunk egy szűrést úgy, hogy csak a 40-nél kisebb értékek maradnak benne (általános esetben itt is lehet bonyolultabb, így nem használhatjuk ki azt, hogy már az eredeti tömbön meg lehetne határozni),
  • végül összeadjuk az eredményt (és itt is lehet akármilyen bonyolult a művelet, tehát ne használjuk ki pl. a beépített add() függvényt).

Először lássuk ezt hagyományos, iteratív módon!

my_numbers = [4, 3, 8, 2, 5, 7]
 
my_squares = []
for i in my_numbers:
    my_squares.append(i*i)
 
my_smalls = []
for i in my_squares:
    if i < 40:
        my_smalls.append(i)
 
sum = 0
for i in my_smalls:
    sum = sum + i
 
print(sum)

Aki idáig eljutott, annak nem lenne szabad, hogy újat mondjon ez a kód. Most lássuk mindezt az imént megtanult függvényekkel:

from functools import reduce
 
my_numbers = [4, 3, 8, 2, 5, 7]
 
def my_square(x):
    return x * x
 
my_squares = map(my_square, my_numbers)
 
def my_less(x):
    return x < 40
 
my_smalls = filter(my_less, my_squares)
 
def my_add(x, y):
    return x + y
 
sum = reduce(my_add, my_smalls, 0)
 
print(sum)

Itt függvényeket adunk át. Figyeljük meg: a my_square, a my_less és a my_add után hívó oldalon sincs zárójel, tehát nem az eredményt adjuk át paraméterként, hanem magát a függvényt. A reduce harmadik paramétere () opcionális.

De ez még eléggé "nyakatekert", hiányzik belőle az "elegancia". Valójában nem fontos nekünk nevet adni a függvénynek, és egyszerűbb kódot eredményez, ha egy lambdával helyettesítjük. Valójában a folyamok és a lambda kifejezések "kéz a kézben" járnak (pl. Java-ban egyszerre vezették be ezeket az elemeket, a 8-as verzióban). Lássuk ugyanezt lambda kifejezésekkel:

from functools import reduce
 
my_numbers = [4, 3, 8, 2, 5, 7]
my_squares = map(lambda x: x*x, my_numbers)
my_smalls = filter(lambda x: x < 40, my_squares)
sum = reduce(lambda x, y: x + y, my_smalls, 0)
print(sum)

Ez már sokkal tömörebb! És valójában ugyanolyan érthető maradt a kód.

Végül lássuk az igazi "hardcore" funkcionális megoldást:

from functools import reduce
print(reduce(lambda x, y: x + y, filter(lambda x: x < 40, map(lambda x: x*x, [4, 3, 8, 2, 5, 7])), 0))

Itt tehát nem hoztunk létre ideiglenes változókat. A valóságban egyébként tipikusan ily módon alkalmazzuk a folyamokat. Viszont sajnos a Pythonban nincs meg az a láncolt szintaxis, ami más programozási nyelvekben megtalálható, ami rontja az olvashatóságot, mivel lényegében jobbról balra kell haladni. Pl. már így is nehéz észrevenni, hogy a hova tartozik, de gondoljunk egy, a valósághoz jobban hasonlító esetet, amely tele van tűzdelve tucatnyi átalakítással és szűréssel.

A Python és a Java képzeletbeli harcában igen sok érvet lehet felhozni a Python mellett, itt viszont a Python programozóknak el kell ismerniük azt, hogy a Java megoldása sokkal átgondoltabb és olvashatóbb:

System.out.println(Arrays.asList(4, 3, 8, 2, 5, 7).stream()
        .map(x -> x * x)
        .filter(x -> x < 40)
        .reduce(0, (x, y) -> x + y));
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License