Adatszerkezetek#
Az adatszerekezetek vagy adatstruktúrák adattípusok tárolását teszik lehetővé. Az adatszerkezet is egy típus, így egy adatszerkezet egy vagy több további adatszerkezetet is tárolhat. Az adatszerkezetek a következő alapműveleteket mindenképpen lehetővé teszik:
az adatszerkezet elemeihez való hozzáférés,
új adatot hozzáadása az adatszerkezethez (beszúrás),
és onnan elemet vagy elemeket eltávolítása (törlés).
További műveletek is természetesen lehetésgesek, például keresés vagy rendezés az adatszerkezetben. Az adatszerkezetek abban különböznek, hogy a különböző típusú műveleteket, milyen komplexitású algorimussal valósítják meg. Megfelelő adattípus választása egy adott probléma esetén a program tervezés egy fontos eleme. Az adatszerkezetek, a hozzáférést, beszúrás és törlést különböző komlpexitás mellett biztosítják. Komplexitáson itt a művelet végrehajtásához szükséges futási idő és memória igényt értünk első sorban.
A Python nyelv számos alapértelmezett adatszerkezetet támogat; ezek közül a listát, szótárat és vektort ismertetjük.
Lista (list
)#
A lista különböző elemeket tartalmazó gyüjtemény. Melynek elemeit az elemhez tartozó indexszel érhetjük el. A lista különböző indexeknél különböző típusokkal térhet vissza: a lista nem csak egy adott adattípusot tartalmazhat.
Kiegészítő anyag
A lista egy egész számú indexszhez megadja az indexszen tárolt értéket. Matematikailg ez magadható a következő leképezéssel:
\(\mathbb{Z} \rightarrow \mathbb{D}\),
ahol \(\mathbb{D}\) az összes adattípus halmaza.
Lista létrehozása#
Nézzünk egy példát egy lista típusú változó létrehozására:
my_list = [1, 2, 3, 4, 5]
print(my_list)
print(type(my_list))
[1, 2, 3, 4, 5]
<class 'list'>
Feladat
Vizsgáljuk meg a lista létrehozásának szintaxisát. Azonosítsuk a következőeket:
Milyen karakterekkel jelőljük hogy egy listát hozzunk létre?
Milyen módon soroljuk fel a lista elemeit?
Mi a lista típus neve a Pythonban (
type
eredménye)?
A lista adatszerkezet a más nyelvekből megszokott tömb típusnak feleltethető meg, azzal a fontos különbséggel, hogy a lista bármilyen adatípust, bármilyen kombinációban tud tárolni:
my_list = ["I", "am", 22, "! That is ", True]
print(my_list)
['I', 'am', 22, '! That is ', True]
Mivel a list bármit tartalmazhat ezért egy másik listát is:
list_1 = ["1", 2, "3", 4]
list_2 = ["2nd list", "contains 1st", list_1]
print(list_2)
['2nd list', 'contains 1st', ['1', 2, '3', 4]]
Az üres lista egy olyan list, mely nem tartalmaz egyetlen elemet:
empty_list = []
print(empty_list)
[]
Műveletek listán#
A lista hossza a listát tartalmazó elemek száma, melyet a len
függvény segítségével kaphatunk meg:
n = len(list_2)
print(n)
3
Az üres lista hossza 0:
empty_list = []
print(len(empty_list))
0
A list egyik elemét indexeléssel érhetjük el:
fun_str = ["Programming", "is", "fun", "!"]
print(fun_str[0]) # lista első eleme, vegyük észre 0-tól indexelünk
print(fun_str[1]) # lista második eleme
Programming
is
Figyelem
Vegyük észre, hogy a Pythonban az indexszelés 0-tól kezdődik.
Lehetőség az utolsó elemtől visszafele történő indexelésre, amennyibe negatív indexszet adunk meg:
fun_str = ["Programming", "is", "fun", "!"]
print(fun_str[-1]) # lista utolsó eleme
print(fun_str[-2]) # lista utolsó előtti eleme
!
fun
A fenti kód kifejezhető a len
függvény segítségével következő módon:
fun_str = ["Programming", "is", "fun", "!"]
print(fun_str[len(fun_str)-1])
print(fun_str[len(fun_str)-2])
!
fun
A fenti kifejezés teljesen ekvivalens a negatív indexszeléssel, ezért a negatív indexszelés egyszerűsíti a kódot. Az ilyen nyelvi megoldásokat az angol irodalom “syntax sugar”-nek, vagy magyarul szinatkszis cukornak nevezni.
Matlabhoz hasonlóan használhatunk tartományt is a lista egyik részhalmazának lekérdezéséhez; ezt slicing-nak vagy szeletelésnek hívjuk. A tartományt a :
operátor segítéségvel definiálhatjuk:
fun_str = ["Programming", "is", "fun", "!"]
print(fun_str[0:2]) # 0 és 2 indeszek közötti elemek (a 2-es indexű elem nem tartozik bele)
print(fun_str[1:3]) # 1 és 3 indeszek közötti elemek (a 2-es indexű elem nem tartozik bele)
print(fun_str[1:]) # elemek az egyes indexű elemtől az utolsó elemig
print(fun_str[0:-1]) # elemek 0-tól az utolsó indeszig (az utolsó elem nem tartozik bele)
print(fun_str[:-1]) # ha nem adunk meg kezdő ideszeket, akkor az automatikusan 0. eredmény ugyanaz mint az előző sorban`
['Programming', 'is']
['is', 'fun']
['is', 'fun', '!']
['Programming', 'is', 'fun']
['Programming', 'is', 'fun']
Figyelem
Figyeljünk rá, hogy az a:b
tartományba a b
indexű elem nem tartozik bele, így a matematikában megszokott jelölésmóddal a tartomány: [a, b)
. Ez a konvenció azonos a range
függvénynél ismertettel.
Lehetőség bizonyos lépésközzel végighaladni a tartományon:
fun_str = ["Programming", "Programming", "is", "is", "always", "not", "fun", "!", "!"]
print(fun_str[0:-1:2]) # minden második elem a 0 és az utolsó indeszek közötti (az utolsó elem nem tartozik bele)
print(fun_str[0::2]) # minden második elem a 0 és az utolsó indeszek közötti (az utolsó elem beletartozik)
print(fun_str[::2]) # ha nem adunk meg kezdő ideszeket, akkor az automatikusan 0. eredmény ugyanaz mint az előző sorban
print(fun_str[::3]) # minden harmadik elem a 0 és az utolsó indeszek között (az utolsó elem beletartozik)
['Programming', 'is', 'always', 'fun']
['Programming', 'is', 'always', 'fun', '!']
['Programming', 'is', 'always', 'fun', '!']
['Programming', 'is', 'fun']
Az indeszek a slicing kifejezésben lehetnek változók is:
fun_str = ["Programming", "is", "fun", "!"]
from_idx = 0
to_idx = len(fun_str)
print(fun_str[from_idx:to_idx])
['Programming', 'is', 'fun', '!']
append
paranccsal új elemet tudunk hozzáfűzni a listához:
fruits = ["apple", "banana", "cherry"]
fruits.append("orange")
print(fruits)
['apple', 'banana', 'cherry', 'orange']
Figyelem
Vegyük észre, hogy az append
függvény a listát helyben (magát a lista tartalmát) változtatja meg. Magának az append
függvénynek nincs visszatérési értéke, ezért az alábbi kód None
-t for kiírni:
fruits = ["apple", "banana", "cherry"]
fruits = fruits.append("orange")
print(fruits) # Eredmény: None
extend
paranccsal két listát tudunk összefűzni:
fruits = ["apple", "banana", "cherry"]
fruits.extend(["orange", "lime"])
print(fruits)
['apple', 'banana', 'cherry', 'orange', 'lime']
Ahogy korábban láttuk, az összeadás műveletét általánosíthatjuk, és nem csak számok között értelmezhetjük. Példát a szövegek esetén láttunk egy példát, ahol az összeadás két szöveg összefűzését (konkatenációját) jelentette. Hasonlóan, itt is, két lista között is értelmezhetjük az összeadást; ekkor azok összefűzését jelenti. Ez egy egyszerűsítés (syntax sugar) az extend
függvény használata helyett:
fruits = ["apple", "banana", "cherry"] + ["orange", "lime"]
print(fruits)
['apple', 'banana', 'cherry', 'orange', 'lime']
További az összeadással össefűzött elemek egy új listába kerülnek, és az eredeti listák nem változnak meg:
fruits = "apple", "banana", "cherry"
veggies = "carrot", "broccoli",
healthy = fruits + veggies
print(fruits)
print(veggies)
print(healthy)
('apple', 'banana', 'cherry')
('carrot', 'broccoli')
('apple', 'banana', 'cherry', 'carrot', 'broccoli')
Feladat
Mit ír ki a következő program?
fruits = ["apple", "banana", "cherry"]
veggies = ["carrot", "broccoli"]
healthy = fruits.extend(veggies)
print(fruits)
print(veggies)
print(healthy)
Vigyázzunk arra, hogy ne keverjük az append
és extend
parancsokat:
fruits = ["apple", "banana", "cherry"]
fruits.extend("orange")
print(fruits)
['apple', 'banana', 'cherry', 'o', 'r', 'a', 'n', 'g', 'e']
Feladat
Próbáljuk megmagyarázni mi történik a fenti kódban! Gondoljunk vissza az automatikus típuskonverzióra, illetve hogy a hogyan konvertálódna egy szöveg listává!
Egy példa arra amikor valószínűsíthetően az append
függvény van használva extend helyett:
fruits = ["apple", "banana", "cherry"]
fruits.append(["orange"])
print(fruits)
['apple', 'banana', 'cherry', ['orange']]
Lista logikai kifejezésben#
Nem üres list True
értékként értelmezett:
example_list = ['a', 'b']
if example_list:
print("True")
else:
print("False")
True
… és ennek fordítottja; üres lista hamisként értelmezett:
example_list = []
if example_list:
print("True")
else:
print("False")
False
Feladat
Mi az eredménye az alábbi kódnak?
example_list = [False]
if example_list:
print("True")
else:
print("False")
Szótár (dict
)#
A szótár, vagy más néven asszociatív tömb, különböző elemeket tartalmazó gyüjtemény, melynek elemeit az elemhez tartozó kulccsal érhetjük el. A szótár így kulcs-érték párokat tartalmaz. A szótár különböző kulcsoknál különböző típusokot adhat: az érték adattípusai kulcsonként változhatnak. Megkötés viszont, hogy a kulcsoknak megfelelő adattípusúaknak kell lennie: mi egyelőre azzal a szabállyal élünk, hogy egyszerű típusnak kell lennie.
Szótár létrehozása#
Egy példa szótár létrehozására, ahol a kulcs szöveg:
car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964,
}
print(car)
print(type(car))
{'brand': 'Ford', 'model': 'Mustang', 'year': 1964}
<class 'dict'>
A szótár tehát hasonlóképpen működik mint a hagyományos szótárak. Például egy angol-magyar szótár esetén, egy adott angol szóhoz megkapható a magyar megfelelője.
Feladat
Vizsgáljuk meg a szótár létrehozásának szintaxisát. Azonosítsuk a következőeket:
Milyen karakterekkel jelőljük hogy egy listát hozzunk létre?
Milyen módon adjuk meg a kulcsot?
Milyen módon adjuk meg a kulcshoz tartozó értéket?
Mi a szótár típus neve a Pythonban (
type
eredménye)?
Többszörös kulcs esetén az utolsó előfordulás lesz használva:
car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964,
"year": 2020
}
print(car)
{'brand': 'Ford', 'model': 'Mustang', 'year': 2020}
Nem csak szöveg, hanem más típus is szerepelhet kulcsként:
numbers = {
0: "nulla",
1: "egy",
2: "kettő",
}
print(numbers)
{0: 'nulla', 1: 'egy', 2: 'kettő'}
A kulcsoknak nem kell azonos típusúaknak lennie (de úgynevezett hashable típusúnak kell lennie, mellyel most nem foglalkozunk, és elfogadjuk, hogy egyszerű típusnak kell lennie):
numbers = {
0: "nulla",
"one": "egy",
2: "kettő",
}
print(numbers)
{0: 'nulla', 'one': 'egy', 2: 'kettő'}
Végül nézzük meg az üres szótárat, melynek nincs egyetlen kulcs-érték párja sem:
empty_dict = {}
print(empty_dict)
{}
Műveletek szótáron#
Elemhez való hozzáférés a kulcson keresztül lehetséges a [
és ]
használatával hasonlóan a lista szintaxisához:
car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964,
}
print(car['brand'])
Ford
Az elem hozzáférése után az értéket is módosíthatjuk:
car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964,
}
car["model"] = "Focus"
print(car)
{'brand': 'Ford', 'model': 'Focus', 'year': 1964}
Kiegészítő anyag
A list esetén a matemtaikai leképzést a következő alakban adtuk meg korábban:
\(\mathbb{Z} \rightarrow \mathbb{D}\).
A szótár esetén az index szerepét, mely csak egész számot vehet fel, a szöveg veheti át, így a szótár a következő leképezéssel megadható:
\(\mathbb{S} \rightarrow \mathbb{D}\),
ahol \(\mathbb{S}\) a szövegek halmaza. Valójában bármilyen típus szerepelhet kulcsként, így pontosabb az alábbi kifejezés:
\(\mathbb{H} \rightarrow \mathbb{D}\),
ahol \(\mathbb{H} \subset \mathbb{D}\) úgynevezett hashable adattípusok halmaza. Tehát a szótárra a lista általánosításaként is tekinthetünk. Bizonyítandó, az alábbi kódban egész számokat használunk kulcsként, majd egy elemére hivatkozunk. Vegyük észre, hogy az elem elérése szintaktikailag ugyanúgy nézz ki mint a lista esetén:
car = {
0: "Ford",
1: "Mustang",
2: 1964,
}
print(car[0])
Új elemet a szótárhoz, vagyis új kulcs-érték párt, az új kulcsra való hivatkozással tudjuk megtenni:
car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964,
}
car['owner'] = "Anna"
print(car)
{'brand': 'Ford', 'model': 'Mustang', 'year': 1964, 'owner': 'Anna'}
Ez azt jelenti, hogy új szótárat szintaktikailag üres szótárból is feltölthetünk. Például az alábbi kód ugyanazt a szótárt hozza létre, mint feljebb:
car = {}
car["brand"] = "Ford"
car["model"] = "Mustang"
car["year"] = "1964"
car["owner"] = "Anna"
print(car)
{'brand': 'Ford', 'model': 'Mustang', 'year': '1964', 'owner': 'Anna'}
A szótár típuson lévő pop
függvény segítségével törölhető egy kulcs-érték pár:
car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964,
}
car.pop('year')
print(car)
{'brand': 'Ford', 'model': 'Mustang'}
A fenti példában a year
kulcsot töröltük.
Figyelem
Ha nem létező elemet próbálunk törölni hibát kapunk, például:
car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964,
}
car.pop('owner')
Szótár logikai kifejezésben#
Nem üres szótár True értéként értelmezett:
example_list = {'a': 1, 'b': 2}
if example_list:
print("True")
else:
print("False")
True
Üres szótár pedig hamis:
example_list = {}
if example_list:
print("True")
else:
print("False")
False
Listák és szótárak egymásba ágyazva#
Mivel a szótár egy típus szerepelhet mint a lista egy eleme. Nézzük meg az alábbi példát, ahol egy listába tettünk két szótárat. A lista elemére hivatkozva a megfelelő szótárt kapjuk vissza:
cars = [{
"brand": "Ford",
"model": "Mustang",
"year": 1964,
}, {
"brand": "Ford",
"model": "Focus",
"year": 2018,
}]
print(cars) # a list kiírása
print(cars[0]) # a lista első szótárának kiírása
print(cars[1]) # a lista második szótárának kiírása
[{'brand': 'Ford', 'model': 'Mustang', 'year': 1964}, {'brand': 'Ford', 'model': 'Focus', 'year': 2018}]
{'brand': 'Ford', 'model': 'Mustang', 'year': 1964}
{'brand': 'Ford', 'model': 'Focus', 'year': 2018}
Kérdés, hogy hogyan tudunk a listában lévő szótár egyik kulcsára hivatkozni. Először lekérdezhejtük a szótárat, és beletehetjük azt egy változóba. Ezt a változót használhatjuk ezután, hogy a szótár egyik kulcsára hivatkozzunk:
cars = [{
"brand": "Ford",
"model": "Mustang",
"year": 1964,
}, {
"brand": "Ford",
"model": "Focus",
"year": 2018,
}]
mustang = cars[0]
print(mustang["year"])
1964
A kód azonban egyszerűsíthető: a mustang
változó elhagyható az alábbi módon:
cars = [{
"brand": "Ford",
"model": "Mustang",
"year": 1964,
}, {
"brand": "Ford",
"model": "Focus",
"year": 2018,
}]
print(cars[0]["year"])
1964
A fenti kód utolsó sorának cars[0]["year"]
kifejezésében a következő történik:
cars[0]
visszaadja a lista első szótárát,ezután ezen a szótáron a
year
kulcsot lekérdezzük,és az eredmény kiírásra kerül.
Úgy is elképzelhetjük, hogy a cars[0]
eredménye egy ideiglenes változóba kerül, melyre a ["year"]
hivatkozik.
Mivel a lista is egy típus, ezért lehet érték egy szótárban, például:
car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964,
"exam": [1992, 1998, 2005, 2009]
}
print(car)
{'brand': 'Ford', 'model': 'Mustang', 'year': 1964, 'exam': [1992, 1998, 2005, 2009]}
Hasonlóan a fentiekhez, a car szótár exam kulcsához tartozó lista második elemének a lekérdezése megfogalmazható a következőképpen:
print(car["exam"][1])
1998
Feladat
Az alábbi kódrészletben a students
lista három szótárat tartalmaz. A szótárak a tanulók neveit a name
kulcsban, valamint a zh-n elért eredményeiket a tests
kulcsban tárolja. A tests
kulcshoz tartozó lista a zh-k érdemjegyei a zh-k sorrendjében. Adjunk kódot ami választ ad a következő kérdésekre:
Mi a
students
lista első szótárában található diák neve?Mi a
students
lista utolsó szótárában található diák neve?Mi Béla második zh-jának az eredménye?
Mi Anna utolsó zh-jának az eredménye?
students = [
{"name": "Anna", "tests": [4, 4, 5]},
{"name": "Béla", "tests": [3, 2, 4]},
{"name": "Cecíllia", "tests": [3, 2, 5]}
]
Vektor (tuple
)#
A tuple
véges elemű rendezett elemek listája. Nagyon hasonló a listához, a legfontosabb különbség, hogy a tuple
elemei nem változtathatóak meg. Így ez az adatszerkezet valamilyen elemek statikus gyüjteménye. Akkor használatos, amikor előre ismert hány és milyen elemet akarunk tárolni; ilyen eset például amikor egy függvénynek több visszatérési értéke van.
tuple
létrehozása#
Példa egy tuple létrehozására:
fruits = ("apple", "banana", "cherry")
print(type(fruits))
<class 'tuple'>
Feladat
Vizsgáljuk meg a tuple
létrehozásának szintaxisát. Azonosítsuk a következőeket:
Milyen karakterekkel jelőljük hogy egy
tuple
t hozzunk létre?Milyen módon soroljuk fel a tuple elemeit?
A tuple a listához hasonlóan bármilyen más típust tárolhat, így listát, vagy szótárat is:
numbers = (1, "two", ["three", "four"])
print(numbers)
(1, 'two', ['three', 'four'])
Korábban tárgyaltuk a többszörös értékadást, mely így nézett ki:
fruit1, fruit2, fruit3 = "apple", "banana", "cherry"
print(fruit1, fruit2, fruit3)
apple banana cherry
Ha az "apple", "banana", "cherry"
oldalt egy változóba irányítjuk, akkor annak típusa tuple
lesz:
fruits = "apple", "banana", "cherry"
print(fruits)
print(type(fruits))
('apple', 'banana', 'cherry')
<class 'tuple'>
Hasonlóan, korábban láttunk példát arra, hogy egy függvénye több visszatérési értékkel rendelkezik, például:
def circle_params(r):
K = 2*r*3.14145
T = r**2*3.14145
return K, T
K, T = circle_params(10)
Ezekután nem meglepő, hogy a visszatérsi típus, ha nincs tagonként kifejtve, akkor tuple
t kapunk:
def circle_params(r):
K = 2*r*3.14145
T = r**2*3.14145
return K, T
result = circle_params(10)
print(result)
print(type(result))
(62.82899999999999, 314.145)
<class 'tuple'>
Figyelem
Ha a függvénynek több visszatérési értéke van, akkor a függvény hívásakor, vagy nem adunk meg visszatérési értéket, vagy egyet adunk meg, ami tuple típusú lesz, vagy mindegyiket meg kell adni. Nincs lehetőség csak részben megadni bal oldali változót, ezért az alábbi kód utolsó sorában hibát fogunk kapni.
def circle_params(r):
K = 2*r*3.14145
T = r**2*3.14145
d = 2*r
return K, T, d
circle_params(5) # ok
K, T, d = circle_params(5) # ok
result = circle_params(5) # ok
K, result = circle_params(5) # hiba: ValueError: too many values to unpack (expected 2)
Műveletek tuple
n#
A tuple
hosszát a szokásos módon tudjuk lekérdezni:
fruits = "apple", "banana", "cherry"
print(fruits)
print(len(fruits))
('apple', 'banana', 'cherry')
3
A tuple egy elemét a listához megszokott módon tudjuk elérni:
fruits = "apple", "banana", "cherry"
print(fruits[0])
print(fruits[-1])
apple
cherry
Figyelem
Ahogy korábban említettük a list elemei nem megváltoztathatóak, így egy elemének hivatkozása az értékadás bal oldalán nem szerepelhet. Például az alábbi kód futtatása esetén hibát kapunk:
fruits = "apple", "banana", "cherry"
fruits[0] = "apricot" # hiba: TypeError: 'tuple' object does not support item assignment
Lehetőség van slicing típusú elérésre is ugynolyan módon, ahogy a lista esetén már láttuk:
fun_str = ("Programming", "is", "fun", "!")
print('Típus:', type(fun_str))
print(fun_str[0:2]) # 0 és 2 indeszek közötti elemek (a 2-es indexű elem nem tartozik bele)
print(fun_str[1:3]) # 1 és 3 indeszek közötti elemek (a 2-es indexű elem nem tartozik bele)
print(fun_str[1:]) # elemek az egyes indexű elemtől az utolsó elemig
print(fun_str[0:-1]) # elemek 0-tól az utolsó indeszig (az utolsó elem nem tartozik bele)
print(fun_str[:-1]) # ha nem adunk meg kezdő ideszeket, akkor az automatikusan 0. eredmény ugyanaz mint az előző sorban`
Típus: <class 'tuple'>
('Programming', 'is')
('is', 'fun')
('is', 'fun', '!')
('Programming', 'is', 'fun')
('Programming', 'is', 'fun')
tuple
ket az összeadással tudjuk összefűzni, hasonlóan a listához:
fruits = "apple", "banana", "cherry"
veggies = "apple", "banana", "cherry"
healthy = fruits + veggies
print(healthy)
('apple', 'banana', 'cherry', 'apple', 'banana', 'cherry')
Figyelem
A list esetén használt extend
vagy append
nem elérhető, ezért a következő kód hibát fog adni. Ennek fő oka, hogy ezek a függvények a listát magát változtatják meg, és mint már említettük, a tuple
megváltoztathatatlan.
fruits = "apple", "banana", "cherry"
veggies = "apple", "banana", "cherry"
fruits.append(veggies) # hiba: AttributeError: 'tuple' object has no attribute 'append'
Mutable és immutable típusok#
Nézzük meg a következő példát:
fun = 'Programming is fun!'
not_fun = fun
print(fun)
print(not_fun)
not_fun = 'Programming is not fun'
print(fun)
print(not_fun)
Programming is fun!
Programming is fun!
Programming is fun!
Programming is not fun
Minden a megszokottak szerint történik: lértehozunk a fun
változót, majd annak értékét odaadjuk a not_fun
változónak. Ezután a not_fun
változót felülírjuk. Ezután nézzük meg ugyanezt a kódot listákkal:
fun = ['Programming', 'is', 'fun', '!']
not_fun = fun
print(fun)
print(not_fun)
not_fun[1] = 'is not' # változtatás
print(fun)
print(not_fun)
['Programming', 'is', 'fun', '!']
['Programming', 'is', 'fun', '!']
['Programming', 'is not', 'fun', '!']
['Programming', 'is not', 'fun', '!']
Meglepő módon azt tapasztaljuk, hogy a not_fun
változón végzett változtatás, lásd not_fun[1] = 'is not'
, hatással van a fun
változóra is.
Mi történik? A megoldás, hogy a fun
változó nem magát a listát, hanem a listára mutató referenciát tartalmazza. Mit jelent ez? A lista valamilyen memóriaterületen foglal helyet. Ennek a memóriaterületnek van valamilyen címe. Amikor egy lista definíció az értékadás jobb oldalán szerepel, a lista a memóriában létrejön, és ennek a memóriának a címe kerül az értékadás bal oldalán található változóban értékadásra. Ez a cím nem látható: a Python elrejti előlünk, és a listát úgy használjuk, mintha a változó maga lenne a lista. Azonban a not_fun = fun
értékadáskor a fun
változóban található referencia kerül a not_fun
változóba, így a fun
és not_fun
változók tulajdonképpen ugyanarra a listára mutatnak. Ezért az egyiken történő változtatás hatással van a másikra is!
Egyik következménye a fentieknek, hogy a not_fun
változón végzett egyéb műveletek is hatással vannak a fun
változóra, így például konkatencáiónál:
fun = ['Programming', 'is', 'fun', '!']
not_fun = fun
print(fun)
print(not_fun)
not_fun.append('Not really...')
print(fun)
print(not_fun)
['Programming', 'is', 'fun', '!']
['Programming', 'is', 'fun', '!']
['Programming', 'is', 'fun', '!', 'Not really...']
['Programming', 'is', 'fun', '!', 'Not really...']
Pythonban minden változó referencia. De az első példában láthattuk, hogy a szöveg esetén nem volt a fenti példához hasonló problémánk. Ez hogy lehetséges? Ha belegondolunk, a szám típusok, vagy például a szöveg típus esetén nem tudjuk a változó mögött lévő objektumot (memória tartalmat) olyan módon megváltoztatni, mint azt a lista append
vagy extend
függvényeinek segítségével tettük. Minden olyan esetben, amikor úgy tűnik egy szám, vagy szöveg változásra került, igazából a változó feltűnik egy értékadás jobb oldalán, így új objektum jön létre a memóriában. Az ilyen adattípusokat, melyek értékei nem megváltoztathatóak megváltoztathatatlan (immutable) típusoknak nevezzük; ezzel szemben az olyan típusokat amelyek megváltozthatóak (mutable) típusnak hívjuk. Az eddig tanul típusok esetén az osztályozás a kövekező:
Immutable típusok:
int
,float
,boolean
,string
,tuple
Mutable típusok:
list
,dict
Sekély és mélymásolás#
mutable típusok esetén tehát problémába ütközünk, amikor egy másolatot szeretnénk készíteni. Ebben a példában két változót szeretnénk, amely két különböző szöveget tárolna:
fun = ['Programming', 'is', 'fun', '!']
not_fun = fun
not_fun[1] = 'is not'
print(fun)
print(not_fun)
['Programming', 'is not', 'fun', '!']
['Programming', 'is not', 'fun', '!']
Tehát a not_fun
listát nem tudjuk megváltoztatni anélkül, hogy a fun
változó ne változzon. Ezt úgy mondjuk, hogy a not_fun = fun
egy sekély másolás, mivel csak referenciát másol. Ha egy teljes másolatot akarunk készíteni egy mutable
típus mögött lévő objektumról, akkor a copy()
függvényt kell meghívnunk a változón. Ezt hívjuk mély másolásnak:
fun = ['Programming', 'is', 'fun', '!']
not_fun = fun.copy()
not_fun[1] = 'is not'
print(fun)
print(not_fun)
['Programming', 'is', 'fun', '!']
['Programming', 'is not', 'fun', '!']
Feladat
A fentiek szótárakra is igazak. Mutassuk meg a fenti mutable tulajdonságokat szótárral!
Mutable típusok függvényekben#
Nézzük meg az alábbi kódot, amely egy nagy számra cseréli a bemeneti változót:
def make_big(x):
x = 100000
print(x)
a = 0
make_big(a)
print(a)
100000
0
Semmi meglepő nem történt, mivel az x
változó hatásköre csak a függvényen belül érvényesül, ezért a globális névtérben létrehozott a
változót nem írja felül.
Ezzel szemben nézzük meg az alábbi kódot:
def make_big(x):
x[0] = 100000
print(x)
a = [0]
make_big(a)
print(a)
[100000]
[100000]
Meglepő módon azt tapasztaljuk, hogy a globális változó értéke megváltozott.
Mi történik? Mindkét esetben úgy képzelhetjük, hogy a make_big
függvény x
paramétere, egy, a függvény látókörébe tartozó változó, amelynek értéke a függvény hívásakor felveszi az a
változó értékét, oly módon hogy az a
mögötti referencia bemásolásra kerül az x
változóba. Az első esetben a x = 100000
sornál az x
változó felvesz egy új referenciát ami az új 10000
értékre mutat. Ez egy új referencia, ezért a globális a
nem változik meg. Ezzel szemben a lista esetén az a
és x
változók ugyanarra a listára mutató referenciák, ezért az x
lista elemein történő változások hatással vannnak a globális névtérben létrehozott listára is.
Ez azt is jelenti, hogy nem csak az elemeken, hanem magán a listán végzett változtatás is hatással van a globális névtérben található a
változóra:
def make_big(x):
x.append(100000)
print(x)
a = [0]
make_big(a)
print(a)
[0, 100000]
[0, 100000]
A fentiek további következménye, hogyha az x
változót, amely egy listára mutató referenciát táról, egy értékadással felülírjuk, azaz egy új listára mutató referenciát hozunk létre, akkor az már nem fog változást okozni a globális névtérben található a
változóra:
def make_big(x):
x = [100000]
print(x)
a = [0]
make_big(a)
print(a)
[100000]
[0]
A x = [100000]
művelet ugyanaz, mint a x = 100000
sor az első példánkban.
Az is
kulcsszó#
Az is
kulcszóval ellenőrízhetjük, hogy két változó mögötti referenciák ugyanarra az objektumra mutat-e. Ez immutable típus esetén ugyan az mint a ==
összehasonlító operátor:
if (not False or True) is True:
print('True')
True
mutable típusok esetén viszont különböző, nézzünk két példát. A két lista megegyezik az alábbi példában:
shopping_list_jane = ['bread', 'milk']
shopping_list_joe = ['bread', 'milk']
if shopping_list_jane == shopping_list_joe:
print('It is true, so they are married!')
else:
print('It is false, so they are not married!')
It is true, so they are married!
Azonban a két változó mögötti referencia különbözik:
shopping_list_jane = ['bread', 'milk']
shopping_list_joe = ['bread', 'milk']
if shopping_list_jane is shopping_list_joe:
print('It is true, so they are married!')
else:
print('It is false, so they are not married!')
It is false, so they are not married!