Scope és Osztály
- Osztályok - Ízelítő az osztályokról
- Elnevezések - snake_case, UpperCaseCamelCase
- Scope - általánosságban
- Built-in Namespace - Beépített névtér - bárhol elérhető objektumok
- Global Namespace - Globális névtér - Modulon belül elérhető objektumok
- Local Namespace - Lokális névtér - Lokálisan, függvény példák, katalógus
- Python osztály - Felépítés, példák, init és dunder metódusok
- Láthatóság - _ -al privátnak tekintjük
- Öröklés -
Osztály(Ős1, Ős2, ...)
, Melyik függvényt hívjam?- super - Függvény keresés egy sorrend alapján
Ezek nem kötelező feladatok, csak megoldásuk közben könyebb megtanulni a dolgokat
- Készíts osztályokat a kutyáknak, macskáknak és a medvéknek úgy, hogy ezeknek legyen ősosztálya és a legtöbb közös tulajdonságot reprezentálja.
- Injektálj be egy osztályt a kutyák és a készített ősosztály közé úgy, hogy minden konstruktor lefusson. (Tipp: super)
Osztályok
Az osztályok egy lehetőséget biztosítanak, hogy
az adatainkat és a hozzájuk kapcsolódó funkcionalitást
összecsatoljuk.
Osztályainkból különböző objektumokat tudunk létrehozni,
melyek saját egyedi adatokkal rendelkeznek.
Ezek mellett az egyes osztályainknak lehetnek leszármazottaik,
melyek az ősosztály funkcionalitásait és adatait
megöröklik.
Na de miért használnék Pythonban osztályokat?
Először is biztosítja, hogy objektum orientált szemlélettel
dolgozzunk, továbbá függvényben mikor objektumot adunk át,
akkor valójában a háttérben csak egy arra mutató pointert
visz tovább a Python, tehát sokkal hatékonyabbá válik
a program.
Továbbá az objektumainkat tudjuk a függvényen
belül módosítani és nem csak egy másolatával dolgozunk.
Elnevezések
Pythonban a függvényeinket és változóinkat szokás
snake_case
-esítve írni. Ez annyit jelent, hogy
minden betű kisbetűs és a szóköz helyett _
-k vannak.
Osztályainkat pedig UpperCaseCamelCase
írjuk, azaz
egybe, és minden szó kezdő betűjét nagy betűvel
szóköz nélkül.
Scope
Ahhoz, hogy megértsük, hogy hogyan tudunk egy objektumhoz adatot hozzá csatolni, meg kell értsük a Scope-okat is.
A Python folyamatosan menedzseli a jelenleg kezelt neveket
és hozzájuk tartozó objektumokat.
Egy katalógusként tudod ezt az egészet elképzelni.
Ezeket névtereknek (namespace) nevezzük és több
fajtáját különböztetjük meg.
Built-in Namespace - Beépített névtér
Itt vannak azok az objektumok, melyeket bármikor elérünk a kódunkban.
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', ... ]
Global Namespace - Globális névtér
Bármi, amit a futtatott programunkon definiáltunk.
Minden modulhoz készül egy ilyen.
A modulon belül bárhonnan elérjük az itt definiált
dolgokat.
Local Namespace - Lokális névtér
Minden függvényen belül egy saját névteret készít a python. Tehát ha belül definiálunk egy változót, az csak azon a funkción belül lesz elérhető.
>>> def fv():
... a = 3
... if a == 1:
... b=2
... print(a)
... print(b)
...
>>> fv()
3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in fv
UnboundLocalError: local variable 'b' referenced before assignment
Látható, hogy ennél a példánál még az if
ágon belüli b
is
létezik, viszont nem kapott értéket és emiatt Error-t kaptunk.
Például egy nem létező változóval:
def fv2():
... a = 3
... if a == 1:
... b=2
... print(a)
... print(not_existing_variable)
...
>>> fv2()
3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in fv2
NameError: name 'not_existing_variable' is not defined
Itt már azt a hibát kaptuk, hogy a változót nem is hoztuk létre.
Ezt úgy képzeld el, hogy a Python mikor sorról sorra végigment a kódon, akkor felépített minden funkción belül egy saját katalógust a változóknak és oda felírta a talált neveket. Viszont az értékeket csak akkor írta bele, ha ténylegesen kapott is.
Python osztály
Na és akkor nézzük meg, hogy hogyan néz ki egy osztály.
class OsztalyNev:
.
.
.
Hogy legyen is benne valami:
>>> class Osztalyom:
... """A simple example class""" # Dokumentáció
... i = 12345
... def f(self):
... return 'hello world'
...
Ebből tudunk egy példányt készíteni:
>>> x = Osztalyom() # Példányosítás
>>> x.i
12345
Ilyenkor meghívódott az alapértelmezett konstruktora.
Ha saját konstruktort szeretnénk, akkor a beépített __init__ függvényt tudjuk felhasználni.
A különböző objektumainknak megannyi ilyen beépített
függvénye van egyébként.
Például felültudjuk írni, hogy mi történjen,
ha az objektumot összeszorozzuk valamivel,
vagy mit írjon ki ha simán ki Printeljük és így tovább.
Ezeket Double Underscore, azaz "Dunder" metódusoknak
hívják
Részletesebben
>>> class Osztalyom:
... """Egy példa osztály"""
... i = 12345 # Minden objektum által megosztott
... def f(self):
... return 'hello world'
... def __init__(self): #
... self.data = []
Egy érdekes különbség C-től, hogy a változóinkat
nem definiáljuk közvetlen, hanem az init metódusában
vesszük fel őket.
Ha ezt nem így tennénk, akkor valójában egy statikus
minden objektum közt megosztott változót készítenénk.
A konstruktor metódusunkban feltűnhet az első paraméter. Ez az a változó, mely a jelenleg létrejövő objektumra mutat. Ha ehhez hozzáírunk új adatokat, akkor csak abban lesz benne.
>>> class Komplex:
... def __init__(self, valos, imaginarius):
... self.r = valos
... self.i = imaginarius
...
>>> x = Komplex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)
Statikus változóval ilyesmi problémába üzközhetünk bele:
class Dog:
tricks = []
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over', 'play dead']
Helyesen:
class Dog:
def __init__(self, name):
self.name = name
self.tricks = [] # Minden kutyánál saját lista lesz
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']
Erre a magyarázat, hogy ezekre az objektumokra csak mutatók
készülnek.
Tehát mikor mi definiáltuk az osztályunk akkor készítettünk
egy új listát []
, majd pedig a tricks változót rámutattuk a
listánkra.
Ezt követően mikor ebből új példányokat képeztünk,
akkor ugyanazokat a mutató értékeket kapták az egyes
leszármazottak.
Láthatóság
Pythonban az osztályokban a tagváltozók és függvények
publikusan elérhetőek és nem lehet őket priváttá tenni.
Erre egy konvenció, hogyha _
-al kezdődik a nevük, akkor
privátként kezeljük őket.
Ekkor nem lesznek privátak, ugyanúgy publikok maradnak,
viszont ha valaki hozzányúl a kódhoz, akkor nem fogja
ezeket használni, legfeljebb Getter, Setteren keresztül.
class Lathatosag:
def __init__(self, privat_info):
self.privat_info = privat_info
def get_privat_info(self):
return self.privat_info
def set_privat_info(self, uj_ertek):
self.privat_info = uj_ertek
Öröklés
Mikor egy osztályból leöröklünk, akkor a gyerekosztály
megjegyzi az ősét és később tudunk rá hivatkozni.
Az egyes függvények és változók elérhetőek
a leszármazott osztály self
-jében, viszont hogy
pontosan melyik ősosztály függvényét hívjuk, ha
egyezés van köztük az nem egyértelmű elsőre ha
például több osztályunk is van.
Nézzük is meg!
(Osztálynév(Ősosztály, ...)
)
class A:
pass
class B(A):
pass
b = B() # Példányosítottuk
print(isinstance(b, A))
# isinstance metódus ellenőrzi, hogy leszármazottja-e
# az objektumunk A-nak
# Kimenet:
# True
Akár több osztályt is megadhatunk ősosztálynak.
Tegyünk bele pár konstruktort
class A:
def __init__(self):
print("A")
class B(A):
def __init__(self):
print("B")
Mit gondolsz, példányosításkor mi fog történni?
b = B()
# Kimenet:
# B
Nem futott le az A konstruktora. Ezt két féleképp tudjuk megoldani.
class A:
def __init__(self):
print("A")
class B(A):
def __init__(self):
A.__init__(self) # Explicit meghívjuk A konstruktorát
print("B")
b = B()
# Kimenet:
# A
# B
super
Másik megoldás, hogy használjuk a beépített super
függvényt.
class A:
def __init__(self):
print("A")
class B(A):
def __init__(self):
super().__init__()
# ez super(B, self).init() -ként is írhatnánk
# alapértelmezetten az üres verzió python 3-ban
# (B, self)-re egészül
print("B")
b = B()
# Kimenet:
# A
# B
A super
függvény ilyenkor egy úgynevezett
metódus feloldási sorrendet követve végigjárja
a megadott osztálytól kezdve az ősosztályokat
és megkeresi a megfelelő függvényt, majd megáll.
Tehát a fenti példánál megtalálta A
-ban az
__init__
függvényt és abbahagyta a keresést,
majd meghívta a függvényt.
A metódus feloldási sorrendünk ez esetben:
print(B.__mro__) # Method Resolution Order
# Kimenet:
# (<class '__main__.B'>,
# <class '__main__.A'>,
# <class 'object'>)
Mikor a super
-t alapértelmezetten B
-től indítottuk,
akkor a következő A
osztályban kereste meg az __init__
függvényt.
Azt is észrevehetted, hogy alapértelmezetten az osztályaink az object osztályból öröklődnek
Na de miért használjak super
-t?
class A(object):
def __init__(self):
print('A.__init__(self) -t hívtunk')
class B(A):
def __init__(self):
print('B.__init__(self) -t hívtunk')
A.__init__(self) # Nem használtam Super-t
class C(A):
def __init__(self):
print('C.__init__(self) -t hívtunk')
super(C, self).__init__() # Használtam Supert
Ha netán úgy döntenénk, hogy ezekből az osztályokból leszeretnénk származni, de úgy, hogy A alá még helyezünk egy osztályt, így:
class Injektalt_Osztaly(A):
def __init__(self):
print('Injektalt_Osztaly.__init__(self) -t hívtunk')
super(Injektalt_Osztaly, self).__init__()
class B_bol(B, Injektalt_Osztaly):
pass
class C_bol(C, Injektalt_Osztaly):
pass
B_bol()
# Kimenet:
# B.__init__(self) -t hívtunk
# A.__init__(self) -t hívtunk
C_bol()
# Kimenet:
# C.__init__(self) -t hívtunk
# Injektalt_Osztaly.__init__(self) -t hívtunk
# A.__init__(self) -t hívtunk
Miért nem futott le a B_bol esetén?
Nézzük meg a metódus feloldási sorrendet
<class '__main__.B_bol'>,
<class '__main__.B'>,
<class '__main__.Injektalt_Osztaly'>,
<class '__main__.A'>,
<class 'object'>
B_bol
osztály után meghívódott B
konstruktora,
mert az alapértelmezett konstruktora B
-nek meghívja
a super()
függvényt.
Ezt követően B
-ben a következő hívás A
-ra fog
mutatni.
Ezt követően pedig a végére érünk és ha hívnánk
újabb super
-t, akkor már az object jönne.
Mikor a C_bol
osztályból indultunk, akkor C
osztályban a super
függvény lefutott és
az Injektalt_Osztaly
-ban megtalálta a függvényünk.
Mikör öröklünk és a super függvényunk használjuk, akkor felépít egy metódus feloldási sorrendet attól függően, hogy hogyan definiáltuk az osztályainkat. Ezt követően mikor hívogatjuk a super-eket, akkor mindig a sorrendben következő fog jönni, kivéve ha átugorjuk, mert statikusan definiáljuk, hogy melyik a következő osztály.
A super-el akár más függvényeket is megtudunk így találni.