2013. január 13., vasárnap

02 - Változók, típusok

Még mielőtt akárminek is nekikezdenék, szeretném jelezni, hogy igazodva a kor igényeihez, ez a bejegyzés 2016. február 20-án újraírásra került. Az eredeti bejegyzésben még a Python 2.7.3-ba beépített típusokat ismertettem, egyébként hiányosan. Azóta gyakorlatilag az összes jelentős csomag Python 3 kompatibilis verziója elkészült, és magam is napi rendszerességgel Python 3 nyelvű fejlesztéssel foglalkozok.

Ha el szeretnénk tárolni a program futása során valami adatot a számítógép memóriájában, akkor változókat (variable) kell használnunk. A változó létrehozását deklarálásnak (declare) nevezik. Programozási nyelvtől függően lehet, hogy azt is definiálni kell, hogy milyen típusú adatot tárolnánk a változóban, mert ettől is függ a szükséges memória mérete. A Python szerencsére egy dinamikus programozási nyelv (dynamic programming language), azaz egy változó tetszőleges típusú adatot tárolhat. Viszont a Python erősen típusos nyelv (strongly typed), ami azt jelenti, hogy nem lehet csak úgy összevissza a különböző típusú adatokat egy műveletben használni.

Hétköznapibb példa lehet az, ha a változókat úgy képzeljük el, mint egy halom üres fiókot. Kinyitunk egyet, és belerakunk egy tetszőleges értéket. A fiókokat nevekkel látjuk el, így tudunk majd hivatkozni a bennük tárolt értékekre. A fiókoknak természetesen van méretük is, így akármekkora adatot nem lehet tárolni bennük.

Ebben a bejegyzésben a változók deklarálásáról, és a Python néhány beépített típusáról lesz szó.

Python típusok

Két fajtáját különböztetném meg a Python beépített típusainak. Ezek a primitívek és az objektumok. A primitív típusok egy értéket jelölnek. Ezek például a logikai értékek és a számok. Az objektumok összetett adattípusok, amik általában más, primitív típusokat kombinálnak.

Lássuk a leggyakoribb fajtákat:

A primitívek:

  • bool
    Boolean, vagy logikai érték. Ennek a típusnak pontosan két érték felel meg, a logikai igaz (True) és a hamis (False).
  • int
    Integer, vagy egész szám. Ezek tetszőleges nagyságú pozitív vagy negatív értékek, tizedesjegyek nélkül.
  • float
    Lebegőpontos szám, vagy tizedestört. Az ilyen típusú értékek néhány tizedesjegyig pontosak, és nem tudnak tetszőleges nagy egészrészt tárolni.
  • None
    A semmi. Néha bizony egy művelet végeredménye a nagy büdös semmi. Ennek a típusnak egy érték felel meg, ami stílusosan a None.

Az objektumok:

  • str
    String, vagy karakterlánc. Mindenféle szöveges érték ilyen típusú.
  • list
    Lista. Több különböző típusú adatot lehet benne egymás után felsorolva tárolni.
  • dict
    Dictionary, vagy asszociatív tömb. Ebben is több különböző típusú adatot lehet tárolni, de az adatokat saját egyedi azonosító kulccsal érhetjük el.

Azért hogy ne szabaduljon el ennek a bejegyzésnek a mérete, egyelőre a(z általam tapasztalt) leggyakoribb típusokat soroltam fel. Egy másik bejegyzésben visszatérünk még a tuple, set, bytes vagy a complex típusokra.

Most hogy tisztában vagyunk vele, hogy milyen típusú adatokkal dolgozhatunk ideje változókat is létrehozni, amibe ezek az adatok belekerülhetnek. Ahogy említettem, a dinamikus típusosság miatt az adat definiálja a változó típusát. Ha a változóba a program futása során más típusú adatot teszünk bele, akkor onnantól az lesz az érvényes.

Változók létrehozása

Változót pofon egyszerű létrehozni. Csupán annyit kell tenni, hogy leírunk egy szót, ami a változó neve lesz, és ezt egyelővé tesszük egy értékkel.

Példával sokkal egyszerűbb:

my_variable = 0

Gépeljük be a fenti sort egy új Python fájlba. Ezzel létre is hoztunk egy változót, aminek a my_variable lett a neve, és a 0 értéket tároltuk el benne. Ezt úgy is szokták mondani hogy inicializáltuk a változót.

Ha lefuttatod a fenti sort tartalmazó Python fájlt, akkor látszólag semmi se történik, mert nem jelenik meg semmi se a konzolban. A valóságban viszont létrejött a változó, megkapta az értékét, de semmi olyan utasítás nem volt ami kiírt volna akármit is, elfogyott a tennivaló is, ezért minden memóriát visszaadott a program az oprendszernek és szépen kilépett.

A változók elnevezéseinek meg kell felelniük annak a szabálynak, hogy csak az angol ABC kis- és nagybetűit, számjegyeket, valamint a _ karaktert tartalmazhatják. Ezen kívül megkötés még, hogy a változó neve nem kezdődhet számjeggyel, és nem állhat csak számokból sem.

Konkrét típusú változó létrehozása

Mivel a változó típusa a változóban tárolt érték típusával egyenlő, ezért adott értékekkel konkrét típusúvá tehetünk egy változót. A típus lehet úgynevezett explicit vagy implicit.

Explicit típusok:

  • bool := bool()
  • int := int()
  • float := float()
  • None := None
  • str := str()
  • list := list()
  • dict := dict()

A felsorolást úgy kell értelmezni, hogy ha például egy változóban int (egész szám) adatot akarunk tárolni, akkor ahhoz a változót az int()-el kell egyenlővé tenni:

my_number = int()

Implicit típusok:

  • bool := True vagy False
  • int := 0 vagy tetszőleges egész szám
  • float := 0.0 vagy tetszőleges tört szám
  • None := None
  • str := '' vagy tetszőleges szöveg, 2db aposztróf vagy idézőjel között (angolul string literal)
  • list := [] vagy tetszőleges felsorolás (angolul list literal)
  • dict := {} vagy tetszőleges kulcs-érték párok (angolul dict literal)

Ezt a felsorolást pedig úgy kell értelmezni, hogy ha például egy változóban int (egész szám) adatot akarunk tárolni, akkor a változót a 0 értékkel kell egyenlővé tenni:

my_number = 0

A fenti eseteket azért hívjuk implicitnek, mert a kézzel beírt adat alakja egyértelműen meghatározza a típust is úgy magától.

Változó értékének kiírása

Elég lehangoló hogy minden szó nélkül véget és a programunk, akármit is írunk bele, ezért egy picit előre haladunk, és megmutatom hogy hogyan lehet kiírni a konzolba egy változó értékét. Ehhez egyszerűen a print() függvényt kell alkalmazni.

my_number = 1234
print(my_number)

Ha ezt begépeljük egy Python fájlba, és lefuttatjuk, akkor a képernyőn az 1234 üzenet jelenik meg, majd kilép a program. Egyelőre elég annyit tudni tehát, hogy ha változót akarunk kiírni, akkor a print() zárójelei közé a változó nevét kell begépelni.

Primitívek

A fenti jó pár sor után bizonyára világos, hogy hogyan kell int-et létrehozni. Következzen pár rövid példa az összes típusra

bool

false_value = False
true_vale = True
int_as_false = bool(0)

Mivel kétféle logikai érték van, ezért itt rövid a példa. A False érték felel meg a hamis, és a True az igaz értéknek. A 3. sorban látható egy érdekes dolog, a 0 átalakítása logikai értékké. Ez talán még adja magát, hogy False lesz (ha kiírjuk az int_as_false értékét, akkor a képernyőn a False jelenik meg).

Vegyük ezt a példát inkább

true_or_false = bool('False')
print(true_or_false)

Ha a 'False' str-t bool-á alakítjuk, akkor mit kapunk eredményül? Meglepő lehet, de True-t.

Ennek az az oka, hogy minden tetszőleges értékhez tartozik egy-egy logikai érték is. Az ökölszabály az, hogy a 0 vagy az üres objektumok False értékűek. Minden más True.

true_or_false = bool('')
print(true_or_false)

Így jelenik meg a False a képernyőn.

int

positive = +1234
negative = -2016
print(positive)
print(negative)

Egymás alatt az 1234 és a -2016 sorok jelennek meg a konzolban. Ezen szerintem nincs sok magyarázni való.

float

one = 1.0
print(one)
print(one + 0.1 + 0.1)

Deklaráltuk a one változót, ami egy tört és az 1.0 értéket tárolja. A 2. sorban kiírtuk az értékét. A 3. sor viszont érdekes, ugyanis nem az jelenik meg a konzolban hogy 1.2 hanem hogy 1.2000000000000002. Ez nagyon szokatlan lehet, de ahogy korábban mondtam, a tört számok pontossága nem végtelen. Ahogy a kis törteket összeadjuk, összeadódik a “pontatlanság” is, és ha már kellően nagy ez az érték, akkor megjelenik a tört végén mint hiba. Az esetek nagy részében ez nem okoz gondot, hisz elég ránézni, a 16. tizedesjegynél van először hiba, de megvan az a terület (tudományos, pénzügyi), ahol ez nagyon is nem mindegy. Aggodalomra semmi ok, mert megvannak az eszközök a precíz tört műveletekre is (de ezzel jó ideig nem kell foglalkozni).

None

nothing = None

A None nem egy hosszú történet. Ez jelenti a Python-ban a “semmit”. Olyankor jöhet jól, ha szükségünk van arra, hogy létezzen egy változó, de a programban csak később teszünk valódi adatot ebbe a változóba. Vagy jelezhetünk vele hibát is (ami nem ajánlott, mert létezik rá dedikált módszer, viszont nem lehet elmenni a mellett, hogy a Pythonban beépítve is vannak olyan algoritmusok, amik akár hibát is jelezhetnek így).

Objektumok

Az objektumok, még ha egyelőre csak 3-ról is van szó, nagyon izgalmasak. Egyelőre viszont maradjunk az egyszerű dolgoknál

str

text = 'Hello World!'
text2 = "It's me speaking"
text3 = 'Magyar szöveg Python 3-ból"

Az str típusú értékeket '' (aposztróf) közé kell írni (angolul ez a string literal), ez látható az 1. sorban. Megengedett a "" (idézőjel) használata is. A '' között tetszőleges mennyiségű szöveg állhat, speciális karakterekkel, számokkal, és néhány vezérlőkarakter is megengedett.

Kilépő- és vezérlő karakterek

Vegyük azt az esetet, amikor egy str-ben aposztrófot akarunk írni. Az előző példából a text2 értékét írom át.

text2 = 'It's me speaking'

Látható ennek a kódrészletnek a színezéséből is, hogy az 'It' piros, viszont utána fekete a szöveg, és a sor végén egy árva piros aposztróf áll. Ha ezt a szöveget ilyen formában beírjuk egy fájlba, és futtatjuk, akkor SyntaxError hibát kapunk.

A probléma a miatt keletkezik, hogy egy stringet pontosan két aposztróf vagy idézőjel közé kell írni. Na most mivel a folyó szövegben volt aposztróf, így az zárta a stringet. Ami szöveg utána következik, az már kód, és a sor végén egy árva ' marad, aminek hiányzik a párja, tehát szét van burványolva az egész, nincs meg senkinek se a párja. Ezek azok a szituációk, amikor olyan karakterek vannak a stringben, amik akár forráskódnak is megfelelnek. Ezek elkerüléséhez “ki kell léptetni” (string escape) a speciális karaktereket. Ezt a \ karakterrel lehet megtenni, ami ilyenkor nem kerül nyomtatásra.

A hibás kódot így lehet javítani ezzel:

text2 = 'It\'s me speaking'

A \ a kilétető karakter, ami az utána következő egyetlen karaktert megvédi, így nem szakad meg a string. Fontos, mivel a \ mindig kiléptetést jelet, ezért ha azt akarjuk, hogy a szövegben megjelenjen ez a karakter, akkor ezt a karaktert is ki kell léptetni. Mutatom a példát:

prompt = 'C:\\Users\\'
print(prompt)

Ha ezt lefuttatjuk, akkor a konzolban megjelenik a C:\Users\ szöveg.

Ha tudjuk, hogy aposztróf lesz majd a stringben, akkor érdemes inkább idézőjelek közé írni a szöveget. Ez fordítva is igaz. Így egyszerűen csak olvashatóbb marad a forráskód.

list

A list egy felsorolt adattípus, azaz az értéke egy lista, amiben tetszőleges elemek lehetnek.

Minden programozó eljut arra a pontra, amikor felismeri hogy:

Akarok írni egy programot, és sok változó értéket akarok tárolni, de nem tudom előre mennyit. És annyira természetellenesnek tűnik deklarálni 100 változót, hogy valtozo00, valtozo01, …. . Nincs erre valami jobb megoldás?

Amikor ilyen szituációban találod magad, akkor kell list-et használni. A létrehozása nagyon egyszerű:

numbers = [1, 1, 2, 3, 5, 8, 11]
my_list = [0, [1, "eggs"], [2, "apples"]]

A numbers innentől tömb, amiben a vesszővel elválasztott számok szépen libasorban követik egymást. A my_list egy kicsit bonyolultabb már. Ebben először egy 0-ás számjegy, majd megint egy tömb van, amiben egy 1 értékű int, utána egy str következik. A tömbnek vége is van, és a my_list 3. eleme egy újabb tömb. Ez jól szemlélteti, hogy list-ben akármi, akár újabb list is lehet.

Elemek elérés

Az érdekes kérdés az, hogy oké hogy készítettem két listát, de hogyan tudom kiolvasni a lista 3. elemének az értékét? A válasz az index operátor.

numbers = [1, 1, 2, 3]
third_number = numbers[2]
print(third_number)

A fenti kód 2. sorában történik az indexelés, ami formailag annyi, hogy a változó nevének a végén szögletes zárójelek közé kell írni az elérendő indexet. A tömbök, és úgy általában a felsorolt típusok első elemének az indexe a 0. (0-based indexing). Tehát ahhoz hogy a tömb harmadik elemét elérjem, a 2. indexen levő értékre van szükségem, és ez is látható a 2. sor végén. A third_number változó pedig felveszi ezt az értéket. Tehát a numbers elemszáma nem változik.

dict

Mondjuk hogy van egy list-ünk, és ebből kellene valamilyen egyértelműen azonosítható tulajdonság alapján egy konkrét értéket kiválasztani. Ahhoz hogy ez sikerüljön olyan programot kell írni ami egyesével végignyálazza a tömb összes elemét, és ellenőrzi, hogy annál az elemnél vagyunk-e amit keresünk. Ha minden értéket végig kell vizsgálni, akkor onnantól nem kiválasztásról, hanem keresésről beszélünk, ami időigényes (a kiválasztáshoz képest). Ha ilyen problémával találkozol, akkor valószínűleg dict-et kell használnod.

Ebben a típusban saját magunk által tetszőlegesen definiált kulcsokhoz tudunk értékeket társítani. Legjobb lesz ha a list-es összehasonlítás mellett maradunk, úgy könnyű lesz megérteni.

Készítsünk egy tömböt amiben több személy nevét és születési évét tároljuk:

people = [[1990, 'Alice'], [1991, 'Bob'], [1992, 'Carol']]

A kérdés: ki született 1992-ben?

A válaszhoz, ahogy nekünk embereknek, úgy a számítógépnek is tételesen végig kell olvasnia az összes elemet.
De mondhatjuk azt is, hogy a születési év alapján akarjuk kiválasztani a személyek neveit, tehát jó lenne, ha a születési év egy kulcs, a hozzá tartozó érték pedig a személy neve lenne. Ez leírva így néz ki:

people = {1990: 'Alice', 1991: 'Bob', 1992: 'Carol'}

Eléggé hasonló a list-es példához. Az érték kikérése pedig pontosan ugyan olyan: az index operátort kell használni. Tehát ha az 1990-ben született személy nevére vagyok kíváncsi, akkor:

people = {1990: 'Alice', 1991: 'Bob', 1992: 'Carol'}
oldest = people[1990]
print(oldest)

Ha ezt lefuttatjuk, akkor az Alice felirat jelenik meg a konzolban. Fasza nem?

Típuskonverzió

A nyelv dinamikus működése miatt gond nélkül leírhatjuk azt hogy:

my_variable = 1234
my_variable = False
print(my_variable)

és a képernyőn megjelenik a False üzenet, de az erős típusosság miatt ha azt írjuk be hogy:

apples = 5 + 'alma'
print(apples)

akkor nagy lóbaszó hibaüzenet kapunk csak.

Az ilyen szituációk kezeléséről gondoskodik a típuskonverzió (type conversion vagy casting) művelete. A fenti példában egy számhoz próbáltam egy szöveget hozzáragasztani, de a képernyőn csak annyi jelent meg hogy TypeError: unsupported operand type(s) for +: 'int' and 'str', ami pont azt jelenti, hogy int-et str-el összeadni nem lehet.

'5' nem egyenlő 5-el!

Átkozottul fontos lesz azt fejben tartani, hogy mikor mi kerül a képernyőre kiíratásra. Ugyanis a képernyőn nem látszódik már, hogy egy számot, vagy egy olyan szöveget írunk ki, ami számjegyeket tartalmaz, viszont a Pythonnak ez pont nem mindegy.

Egy str-ben gyakorlatilag akármit eltárolhatunk, amit a képernyőn képesek vagyunk megjeleníteni karakter formájában, de attól, hogy egy string minden egyes karaktere egy számjegy, a Python még nem változtatja át a str-t magától int-é.

Ahhoz tehát, hogy a korábbi, 5 + 'alma' problémát megoldjuk, el kell érni, hogy az összeadás jel két oldalán azonos típusú adat legyen.

Explicit típuskonverzió

Explicit típuskonverzió az, amikor valamilyen jól látható módon jelezzük, hogy változzon meg egy érték típusa valami mássá. Ez a típuskonverzió legnyilvánvalóbb formája, de van néhány (nagyon kevés) olyan eset is, amikor a Python mégis képes magától a típusok módosítására. Ilyen például amikor egész és tört számokat adunk össze.

A konverziót függvények fogják elvégezni, amiket pár bekezdéssel feljebb már felsoroltam, az “Explicit típusok” nevű listában.

Azok a függvények, de úgy általában minden függvénye a Pythonban, írásmódjukban nagyon hasonlítanak az közönséges változókra, de van egy nagy különbség köztük: zárójellel végződnek. De a függvényekről majd később beszélünk …

Egy érték típusának az átalakításához csak írjuk be az értéket (ami természetesen változó is lehet) a zárójelek közé, pont úgy, mint ahogy a print()-el kiírtuk az értéket a képernyőre. A függvény előállít egy új értéket az alapján, amit a zárójelei között kapott, de olyan típussal, amilyen konverziót a függvény elvégez.

A sok magyarázat után itt az ideje hogy csináljunk is valamit. Ideje megoldani a problémát:

apples = str(5) + 'alma'
print(apples)

Az 1. sorban az 5-öt, ami egy int, beraktuk az str() zárójelei közé, így átalakult az 5 '5'-é, majd ezt összeadjuk az 'alma' str-el. Így az összeadás mindkét oldalán azonos típus van, és két str összeadásának az eredménye a bal- és jobb oldali string összeragasztva.

A fenti példa meglehetősen buta, hisz ezzel a lendülettel azt is írhattam volna hogy:

apples = '5' + 'alma'

és ez teljesen jogos. A valóságban igazából a változókban szereplő értékek típusát kell gyakran állítani, mert gyakori, hogy nem mi rendelkezünk róla, hogy a változóban milyen típusú érték van. Ilyen például, ha a felhasználó gépelt be valamit, vagy fájlból olvasunk.

Ennek szellemében, az alábbi eset sokkal-sokkal gyakoribb

some_variable = 5
apples = str(some_variable) + 'alma'

Mikor jön el a konverzió ideje?

Jogos lehet a kérdés, hogy mégis meddig kell megtartani egy változót egy adott típussal, és mikor kell átalakítani egy másikká. Én azt a szabályt alkalmazom, hogy ritkán használt típust minél később, gyakran használtat pedig minél előbb módosítok.

Ha egy változó értékére más típussal van szükségem, de viszonylag ritkán, akkor abban a ritka pillanatban egyszer elvégzem az átalakítást. Ha egy változó értékére más típussal nagyon gyakran van szükség, mondjuk onnantól hogy beolvastam egy fájlból, egészen addig, amíg kilép a program, akkor közvetlenül a fájl beolvasása után elvégzem az átalakítást, és a konvertált értéket használom onnantól végig.

Konstansok

Elég jól átrágtuk magunka ennek a résznek a nehezén, úgyhogy levezetésül pár szó a konstansokról.

A konstansok gyakorlatilag a változók ellentettjei. Amikor leírod hogy:

text = 'Hello World!'
print(text)

Akkor tiszta sor hogy a text az egy változó, és hogy a 'Hello World!' egy string. De ez a str egy konstans is, azért, mert kézzel egy pontos, konkrét értéket gépeltél be a forráskódba.

Sokkal jobban érződik ez, ha nem akarsz balra-jobbra tárolgatni egy stringet, csak ki akarod írni a konzolba. Az így néz ki:

print('Hello World!')

Látszik, azt a 'Hello World!'-öt nem változóból nyertük; be van írva, nem lehet megváltoztatni, az egy konstans.

Zárás

Bele akartam kezdeni a kifejezések témakörébe, de azt hiszem hogy ez a következő epizódra marad. Egyelőre viszont már tudunk változókat deklarálni, és adatokat rakni beléjük, úgyhogy jó úton haladunk, na!

Ez a bejegyzés a Python tutorialom egyik része. Az összes rész listája itt fellelhető.

-slp

Nincsenek megjegyzések:

Megjegyzés küldése