Kategória: Python.
Alkategória: Adatkezelés Pythonban.
Table of Contents
|
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));