102 Objektid ja klassid
Kuigi Python toetab mitut programmeerimise paradigmat, on neist kõige enam rõhutatud just objekt-orienteeritud programmeerimise (OOP) paradigmat.
Võib öelda, et OOP-i kasutamine tähendab programmikoodi teatud kindlal viisil organiseerimist. Selle lähenemise juures leiavad kasutamist
mõisted nagu klass ja objekt, teemasse enam süvenedes ka kapseldamine, polümorfism, pärimine, modulaarsus, abstraktsioon. Viimaseid
nimetatakse OOP tehnikateks.
OOP-i juures on tavaline rääkida klasside kirjeldamisest ja nende kirjelduste alusel objektide loomisest ning manipuleerimisest. Objektidega
manipuleerimine leiab aset klassi kirjelduses antud meetodite (põhimõtteliselt funktsioonide) alusel. Klassi kirjelduse alusel luuakse objekte,
millistel on ühesugused omadused ja tegevused (meetodid).
Vaata alljärgnevast videost, mis on objekt-orienteeritud programmeerimine, mis on klassid ja objektid ning vaata näiteid erinevatest OOP tehnikatest.
Järgneva näite pideva täiendamisega püüame samm-sammult antud teema olulised ideed edasi anda.
Oletame, et tahame koostada programmi, mis simuleerib tavalise koera käitumist. Mida oskab tavaline koer?
Eeldame, et iga tavaline koer oskab haukuda, veereda ja tervitada oma omanikku. Samal ajal, tavaline koer ei oska rääkida (üsna
loogiline, eks). Selleks, et koostada sellist programmi, defineerime klassi Dog
(mille alusel saab hiljem programmis just samade, klassis kirjeldatud,
oskustega koeri luua):
# Defining the Dog class class Dog: # Code
On teada, et iga tavaline koer oskab haukuda, veereda ja tervitada oma omanikku:
class Dog: def bark(self): print("Bark!") def roll(self): print("*rolling*") def greet(self): print("Hey, master") def speak(self): print("I cannot!")
Klassi Dog
kirjelduses on neli funktsiooni, mida OOP lähenemise puhul nimetatakse klassi meetoditeks. Pöörame tähelepanu, et iga
meetod kasutab parameetrit nimega self
, millest räägime natuke hiljem. Meie klass kirjeldab tavalise koera
käitumist – on meetodid, mis paiknevad klassi sees ning kirjeldavad iga tavalise koera oskusi. See ongi abstraktsioon. S.t. klass (meil Dog
)
on kui piparkoogivorm ning objektid kui antud vormiga tehtud piparkoogid.
Nüüd, kui meil on klassis tavalise koera käitumine kirjeldatud, saab luua objekti – klassi isendi (instance).
Loome kaks koera: Clyde
ja Jenkins
.
class Dog: def bark(self): print("Woof!") def roll(self): print("*rolling*") def greet(self): print("Greetings, master") def speak(self): print("I cannot!") # Creating the Dog class instance and saving it to the variable <clyde> clyde = Dog() clyde.bark() # --> Woof! clyde.roll() # --> *rolling* clyde.greet() # --> Greetings, master clyde.speak() # --> I cannot! # Creating another Dog instance jenkins = Dog() jenkins.bark() # --> Woof! jenkins.roll() # --> *rolling* # .. And other methods # .. Infinite objects can be created this way, all implementing the same methods defined in our class
Tuleb välja, et vaatamata sellele, et clyde
ja jenkins
on erinevad koerad, oskavad mõlemad teha asju,
mida meie definitsiooni järgi oskab iga tavaline koer. Nii, nagu reaalses elus umbes ongi – näiteks iga koer oskab haukuda
sõltumata tema liigist.
Klassil võib olla ka konstruktor, mis kujutab endast erilist, fikseeritud nimega, meetodit klassi sees. See fikseeritud nimi on järgmine:
__init__().
class Dog: # Class constructor def __init__(self): # Code here
Konstruktor ja selle sees olev kood käivitatakse vaid isendi (objekti) loomisel. Näiteks tahame, et klassi Dog
isendi loomisel oleks igale klassi objektile antud oma nimi. Teeme nii, et meie konstruktor võtaks sisse teise argumendi,
name
, ja salvestaks selle uue muutuja self.name
sisse.
class Dog: # Class constructor def __init__(self, name): self.name = name def bark(self): print("Woof!") def roll(self): print("*rolling*") def greet(self): print("Greetings, master") def speak(self): print("I cannot!") # Returns the current dog's name def get_name(self): return self.name # Returns the reference to the current dog instance def get_this_dog(self): return self # Creating the Dog class instance and giving it name dog1 = Dog("Clyde") dog1.bark() # --> Woof! dog1.roll() # --> *rolling* dog1.greet() # --> Greetings, master dog1.speak() # --> I cannot! print(dog1.get_name()) # --> Clyde print(dog1.get_this_dog()) # --> (Example) <__main__.Dog object at 0x0119D710> print(dog1) # --> (Example) <__main__.Dog object at 0x0119D710> # Creating some more Dog instances dog2 = Dog("Jenkins") dog2.bark() # --> Woof! dog2.roll() # --> *rolling* dog2.greet() # --> Greetings, master dog2.speak() # --> I cannot! print(dog2.get_name()) # --> Jenkins print(dog2.get_this_dog()) # --> (Example) <__main__.Dog object at 0x0143D710> print(dog2) # --> (Example) <__main__.Dog object at 0x0143D710> dog3 = Dog("Robbie") print(dog3.get_name()) # --> Robbie print(dog3.get_this_dog()) # --> (Example) <__main__.Dog object at 0x01CED710>
Seega, vaatamata sellele, et iga koer oskab haukuda, veereda ja tervitada, on kõik koerad erinevad objektid, nagu
reaalelus ongi. Muutuja self
iseloomustab antud juhul just seda konkreetset koera, kelle objekt on loodud,
self
on viide sellele objektile. Seda on näha, kui printida välja meie Dog
klassil olev meetod get_this_dog
või kui lihtsalt üritada välja printida objekti ennast, nagu on üleval tehtud.
Nagu näha, on erinevatel objektidel erinev viide, mis tähendabki, et näiteks Jenkins ja Clyde ei ole üks ja sama
koer, vaid kaks erinevat.
Kui meie loome muutuja, pannes self
juurde prefiksina, nagu tegime self.name
korral, siis saame nn
isendimuutuja, mis on nähtav igale klassi meetodile:
class Dog: # Class constructor def __init__(self, name): # Instance variable self.name = name def bark(self): print(self.name + " says: Woof!") def roll(self): print("*rolling*") def greet(self): print(self.name + " greets you!") def speak(self): print("I cannot") # Returns the current dog's name def get_name(self): return self.name # Returns the current dog instance def get_this_dog(self): return self dog1 = Dog("Clyde") dog1.bark() # --> Clyde says: Woof! dog1.greet() # --> Clyde greets you! print(dog1.get_name()) # --> Clyde print(dog1) # --> Reference dog2 = Dog("Jenkins") dog2.bark() # --> Jenkins says: Woof! dog2.greet() # --> Jenkins greets you! print(dog2.get_name()) # --> Jenkins
Klassil võib olla ka klassimuutuja, mis on samal moel nähtav igale klassi meetodile:
class Dog: # Class variable name = "Some name" # Class constructor def __init__(self, new_name): # Assign class variable to new value Dog.name = new_name def bark(self): print(Dog.name + " says: Woof!") def roll(self): print("*rolling*") def greet(self): print(Dog.name + " greets you!") def speak(self): print("I cannot") # Returns the current dog's name def get_name(self): return Dog.name # We do not need to create an object to access the class variable print(Dog.name) # --> Some name dog1 = Dog("Clyde") dog1.bark() # --> Clyde says: Woof! dog1.greet() # --> Clyde greets you! print(dog1.get_name()) # --> Clyde print(Dog.name) # --> Clyde dog2 = Dog("Jenkins") dog2.bark() # --> Jenkins says: Woof! dog2.greet() # --> Jenkins greets you! print(dog2.get_name()) # --> Jenkins print(Dog.name) # --> Jenkins # Assign new value to the variable name Dog.name = "Some random dog name" print(Dog.name) # --> Some random dog name
Isendimuutuja ja klassimuutuja erinevad teineteisest selle poolest, et klassimuutuja eksisteerib, on
kättesaadav ja muudetav ilma objekti loomiseta, kuid samal ajal isendimuutuja eksisteerib ainult koos oma objektiga.
Veel mõni lihtne näide. Oletame, et me tahame koostada klassi, mis kirjeldaks tavalist tudengit. Igal tudengil
on olemas nimi ja kool, kus ta õpib. Samas on suurem osa tudengeid laisad, aga mitte kõik. Ehk igal tudengil
on kolm parameetrit: nimi, kool ja laiskus, mis on vaikimisi tõene.
class Student: """Student class.""" def __init__(self, name, college, is_lazy=True): """ Class constructor. :param name: student name :param college: student college :param is_lazy: is student lazy? (by default yes) """ self.name = name self.college = college self.is_lazy = is_lazy
Tudengitel on alati mingi kodutöö. Eeldame, et tudeng saab teha homset kodutööd ainult siis, kui ta ei ole laisk.
def is_homework_done(self): """ Did the student do the homework for tomorrow? :return: string """ if self.is_lazy: return "Homework? Pff, I have TV shows to watch." return "Homework is done!"
Lisaks sellele eeldame, et iga tudeng saab oma kooli vahetada.
def change_college(self, college): """ Change the college student attends. :param college: :return none """ print("Student {} leaves the {} and starts studying in {}".format(self.name, self.college, college)) self.college = college
Ja samal moel saab tudeng mõistuse pähe võtta ja enam mitte laisk olla (või vastupidi):
def change_laziness(self): """ Is the student still lazy/not lazy? :return: none """ self.is_lazy = not self.is_lazy
Siis, pannes kõik kokku, saame:
class Student: """Student class.""" def __init__(self, name, college, is_lazy=True): """ Class constructor. :param name: student name :param college: student college :param is_lazy: is student lazy? (by default - yes) """ self.name = name self.college = college self.is_lazy = is_lazy def is_homework_done(self): """ Did the student do the homework for tomorrow? :return: string """ if self.is_lazy: return "Homework? Pff, I have TV shows to watch." return "Homework is done!" def get_college(self): """ Get the college student attends. :return: college """ return "{} studies in {}".format(self.name, self.college) def change_college(self, college): """ Change the college students attends. :param college: :return none """ print("Student {} leaves the {} and starts studying in {}".format(self.name, self.college, college)) self.college = college def get_name(self): """ Get the student name. :return: name """ return self.name def get_laziness(self): """ Is the student lazy? :return: true/false """ return self.is_lazy def change_laziness(self): """ Is the student still lazy/not lazy? :return: none """ self.is_lazy = not self.is_lazy # Some examples st1 = Student("Alice", "Tallinn University of Technology") print(st1) # --> (Example) <__main__.Student object at 0x01E3DB30> print(st1.get_name()) # --> Alice print(st1.get_college()) # --> Tallinn University of Technology print(st1.get_laziness()) # --> True print(st1.is_homework_done()) # --> Homework? Pff, I have TV shows to watch. print() # Alice decides she is more into art than computers, so she leaves TUT and starts studying in Estonian Academy of Arts. # Output: Student Alice leaves the Tallinn University of Technology and starts studying in Estonian Academy of Arts st1.change_college("Estonian Academy of Arts") print(st1.get_college()) # --> Estonian Academy of Arts # She is still lazy though print(st1.get_laziness()) # --> True print() st2 = Student("Mary", "Massachusetts Institute of Technology", False) print(st2.get_name()) # --> Mary print(st2.get_college()) # --> Massachusetts Institute of Technology print(st2.get_laziness()) # --> False print(st2.is_homework_done()) # --> Homework is done! # It is weekend now, so Mary can be a bit lazier than usual st2.change_laziness() print(st2.is_homework_done()) # --> Homework? Pff, I have TV shows to watch.
Vaatleme lisaks üht sisseehitatud meetodit, mis teeb meie elu veidi lihtsamaks. Meetodi nimi on __str__
. Meetod käivitub
siis, kui me proovime objekti välja printida ja tagastab meie poolt määratud stringi tavalise viite asemel. Vaatame,
kuidas see meetod töötab Student
klassi näitel.
class Student: """Student class.""" def __init__(self, name, college, is_lazy=True): """ Class constructor. :param name: student name :param college: student college :param is_lazy: is student lazy? (by default - yes) """ self.name = name self.college = college self.is_lazy = is_lazy def __str__(self): """ Returns name of the student when invoked. :return: student name """ return self.name # <...Other methods skipped...> # Some examples st1 = Student("Alice", "Tallinn University of Technology") print(st1) # --> Alice print() st2 = Student("Mary", "Massachusetts Institute of Technology", False) print(st2) # --> Mary
Ehk nüüd ei ole meil vaja luua eraldi meetodit get_name
tudengi nime tagastamiseks, nime on võimalik saada lihtsalt
objekti printimisel. __str__
ei pea tagastama ainult nime, antud juhul näiteks võiks ta tagastada ka kooli, kus tudeng õpib.
Lisalugemist
- Ülevaade koos näidetega klasside ja objektide kohta W3Schools Python Classes and Objects peatükis.
- Klasside ja objektide kasutamise põhimõtted ja näited GeeksforGeeks Python Classes and Objects artiklis.