Klassid ja objektid¶
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(f"Student {self.name} leaves the {self.college} and starts studying in {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 f"{self.name} studies in {self.college}"
def change_college(self, college):
"""
Change the college students attends.
:param college:
:return none
"""
print(f"Student {self.name} leaves the {self.college} and starts studying in {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.