Objektovo orientované programovanie: Jankov študentský systém
Janko začal študovať v Košiciach a hneď v prvom semestri dostal zadanie: vytvoriť jednoduchý systém, ktorý bude evidovať študentov, ich predmety a výsledky. Najskôr chcel všetky údaje uložiť do samostatných premenných, no rýchlo zistil, že pri desiatkach študentov by v nich vznikol chaos.
Rozhodol sa preto použiť objektovo orientované programovanie. Údaje a funkcie, ktoré s nimi pracujú, spojí do jedného celku — objektu.
Trieda a objekt
Trieda je plán, podľa ktorého Python vytvára objekty. Janko začal prázdnou triedou Student:
class Student:
pass
Z tejto triedy už môže vytvoriť konkrétny objekt:
janko = Student()
Student je trieda, zatiaľ čo janko je jej inštancia — samostatný objekt uložený v pamäti.
Prázdny študent však systému veľmi nepomôže. Každý potrebuje meno, vek a študijný program. Tieto údaje nastaví metóda __init__, ktorá sa spustí automaticky pri vytvorení objektu.
class Student:
def __init__(self, name, age, program):
self.name = name
self.age = age
self.program = program
Parameter self označuje objekt, s ktorým metóda práve pracuje. Výrazy self.name, self.age a self.program vytvárajú jeho atribúty.
Janko teraz dokáže vytvoriť viacerých študentov rovnakým spôsobom:
janko = Student("Janko", 20, "Počítačové siete")
martina = Student("Martina", 21, "Aplikovaná matematika")
Oba objekty vznikli podľa rovnakej triedy, no každý má vlastné údaje. K atribútom sa pristupuje bodkovou notáciou:
print(janko.name)
print(martina.program)
janko.age = 21
Metódy: čo objekt dokáže
Deň pred odovzdaním si Janko uvedomil, že systém údaje iba uchováva. Študent by sa mal vedieť aj predstaviť, zapísať si predmet a zobraziť svoje predmety. Do triedy preto pridal metódy:
class Student:
def __init__(self, name, age, program):
self.name = name
self.age = age
self.program = program
self.courses = []
def introduce(self):
print(f"Som {self.name} a študujem program {self.program}.")
def enroll(self, course):
if course not in self.courses:
self.courses.append(course)
Metóda patrí triede, ale pomocou self pracuje s údajmi konkrétneho objektu:
janko.enroll("Programovanie")
janko.enroll("Počítačové siete")
janko.introduce()
Takto objekt spravuje vlastný stav. Kód, ktorý používa triedu, nemusí riešiť, ako sú údaje uložené.
Atribút spoločný pre všetkých
Meno, vek aj predmety sa medzi študentmi líšia. Univerzita je však pre všetkých rovnaká, preto ju Janko uložil ako atribút triedy:
class Student:
university = "Technická univerzita v Košiciach"
def __init__(self, name, age, program):
self.name = name
self.age = age
self.program = program
self.courses = []
K spoločnej hodnote môže pristúpiť cez triedu aj cez objekt:
print(Student.university)
print(janko.university)
Atribúty vytvorené pomocou self patria jednotlivým inštanciám. Atribút university patrí triede a zdieľajú ho všetky jej inštancie.
Dedičnosť: z obyčajného študenta asistent
Jankov projekt zaujal vyučujúceho natoľko, že mu ponúkol miesto pomocného asistenta v laboratóriu. Asistent je stále študent, má však navyše pridelené laboratórium. Namiesto kopírovania celej triedy Janko použil dedičnosť:
class TeachingAssistant(Student):
def __init__(self, name, age, program, laboratory):
super().__init__(name, age, program)
self.laboratory = laboratory
TeachingAssistant je podtrieda triedy Student. Volanie super().__init__() použije konštruktor rodičovskej triedy a potom podtrieda pridá vlastný atribút.
assistant = TeachingAssistant(
"Janko",
21,
"Počítačové siete",
"Laboratórium sieťových technológií",
)
Objekt assistant má všetky atribúty a metódy študenta, navyše však pozná svoje laboratórium.
Prepísanie metódy
Pôvodné predstavenie už asistentovi nestačilo. V podtriede preto Janko vytvoril metódu s rovnakým názvom:
class TeachingAssistant(Student):
def __init__(self, name, age, program, laboratory):
super().__init__(name, age, program)
self.laboratory = laboratory
def introduce(self):
super().introduce()
print(f"Zároveň pomáham v laboratóriu {self.laboratory}.")
Podtrieda tým prepísala zdedenú metódu. Najprv cez super() použije pôvodné predstavenie a potom ho rozšíri o novú informáciu.
Špeciálne metódy
Pri testovaní Janko skúsil vypísať celý objekt:
print(janko)
Výsledok nebol veľmi užitočný. Python však pozná špeciálnu metódu __str__, ktorá určuje, ako sa má objekt zobraziť používateľovi:
class Student:
university = "Technická univerzita v Košiciach"
def __init__(self, name, age, program):
self.name = name
self.age = age
self.program = program
self.courses = []
def __str__(self):
return f"{self.name} — {self.program}"
Po tejto zmene príkaz print(janko) vypíše:
Janko — Počítačové siete
Na ladenie sa používa aj metóda __repr__. Jej výsledok má byť presný a užitočný najmä pre programátora:
def __repr__(self):
return (
f"Student(name={self.name!r}, age={self.age!r}, "
f"program={self.program!r})"
)
Iterovanie cez predmety
Janko chcel prechádzať zapísané predmety priamo pomocou cyklu. Do triedy pridal metódu __iter__:
def __iter__(self):
return iter(self.courses)
Objekt sa teraz správa ako kolekcia predmetov:
for course in janko:
print(course)
Výsledky ako v slovníku
Nakoniec bolo treba evidovať aj body zo skúšok. Janko pridal slovník scores a dve špeciálne metódy:
class Student:
def __init__(self, name, age, program):
self.name = name
self.age = age
self.program = program
self.courses = []
self.scores = {}
def __getitem__(self, subject):
return self.scores[subject]
def __setitem__(self, subject, score):
self.scores[subject] = score
Vďaka __getitem__ a __setitem__ môže s výsledkami pracovať podobne ako so slovníkom:
janko["Programovanie"] = 95
print(janko["Programovanie"])