OOP konstruktor, 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.
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.
Konstruktor¶
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
Kuid peab siiski silmas pidama, et konstruktor ei pea ilmtingimata olema nii kerge ja lühike. On teada, et koerale võib õpetaja erinevaid trikke, oletame nüüd, et meie koera objektil on trikkid mida koer oskab teha kujul list sõnedest, kus iga sõne on trikk nt „jump“, „lay“, „catch ball“, „flip“. Kuidas seda lisada koera objektile, arvestades, et koerad on ju erinevad? Samuti kuidas lisada trikki koerale või just eemaldada triki, kui koer enam ei oska seda teha :)?
Seega, lisame uue muutuja koerale, mis on algselt tühi list, sest eks koer alguses ju ühtegi trikki ei oska teha.
class Dog:
# Class constructor
def __init__(self, name):
# Instance variable
self.name = name
self.tricks = [] # initially the dog knows no tricks
... rest of code
Nüüd peame lisama paar funktsiooni, mis lisaks uue trikki koerale, kui antud koer õpis uue triki ära. Ja samuti võiks igaks juhuks teha ka funktsiooni, mis eemaldab triki, kui koer seda enam ei oska. Samuti lisame ikka kontrolli, et trikid ei korduks.
class Dog:
# Class constructor
def __init__(self, name):
# Instance variable
self.name = name
self.tricks = [] # initially the dog knows no tricks
# Note that first param here is the self,
# it is used to access instance variables
# Add trick
def add_trick(self, trick):
if trick not in self.tricks:
self.tricks.append(trick)
# Remove trick
def remove_trick(self, trick):
if trick in self.tricks:
self.tricks.remove(trick) # remove by value
... rest of code
Nüüd, tänu sellele, et me lisasime uue listi muutujana ja paar uut funktsiooni, et selle listiga opereerida, saame panna kõik kokku ja luua paar koera ning õpetada nendele paar trikki ning uurida, mis trikke nad oskavad. Aga enne seda võiks teha funktsiooni, mis tagastab meile trikid loetaval kujul
class Dog:
... rest of code
# Display tricks
def display_tricks(self):
print(f"{self.name} knows these tricks:")
for trick in self.tricks:
print(trick) # print every trick on a new line
print("\n")
... rest of code
Kõik koos:
class Dog:
# Class constructor
def __init__(self, name):
# Instance variable
self.name = name
self.tricks = [] # initially the dog knows no tricks
# Add trick
def add_trick(self, trick):
if trick not in self.tricks:
self.tricks.append(trick)
# Remove trick
def remove_trick(self, trick):
if trick in self.tricks:
self.tricks.remove(trick) # remove by value
# Display tricks
def display_tricks(self):
print(f"{self.name} knows these tricks:")
for trick in self.tricks:
print(trick) # print every trick on a new line
print("\n")
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
dog1.add_trick("flip")
dog1.add_trick("dance")
dog1.display_tricks()
# Clyde knows these tricks:
# flip
# dance
dog1.add_trick("flip") # no change
dog1.display_tricks()
# Clyde knows these tricks:
# flip
# dance
dog2 = Dog("Jenkins")
dog2.bark() # --> Jenkins says: Woof!
dog2.greet() # --> Jenkins greets you!
print(dog2.get_name()) # --> Jenkins
dog2.add_trick("jump")
dog2.add_trick("crawl")
dog2.add_trick("sing")
dog2.remove_trick("sing")
dog2.display_tricks()
# Jenkins knows these tricks:
# jump
# crawl
Nüüd asume koera trikide võistlusel. Iga koer, oskab kindlad trikid ja samuti on erineval koeraomanikul erinev programm võistluseks. Oletame nüüd kaks olukorda: 1) Kõikidele koeraomanikele jagatakse info, kus on pythoni plaanis dict, kus võti on koera nimi ning väärtus on trikid, mida koer võiks teha antud võistluse voorus. 2) Igal koeraomanikul on oma personaalne programm oma koerale. Nüüd lisame tingimuse, et see võib olla 1) või 2) variant, ning mõlemat peab arvestama. Peame muutma meie konstruktori ja tegema seda mõnevõrra targemaks.
class Dog:
# Class constructor
def __init__(self, name, tricks, global_program, individual_program):
# Instance variable
self.name = name
self.tricks = tricks # assume that every dog knows some tricks
self.tricks_for_program = []
# add only those tricks to program tricks that the dog actually knows
# add tricks to program that are for this given dog
if self.name in global_program:
tricks_for_this_dog = global_program[self.name]
for trick in tricks_for_this_dog:
if trick in self.tricks and trick not in self.tricks_for_program:
self.tricks_for_program.append(trick)
# add tricks from personal program
for trick in individual_program:
if trick in self.tricks and trick not in self.tricks_for_program:
self.tricks_for_program.append(trick)
...rest of code
Ülalolevalt näitelt saab aru, et tegelikult võib klassi konstruktoris teha palju erinevaid tegusid. Konstruktor ei ole ilmtingimata selleks, et klassisisestele muutujatele anda väärtusi, konstruktoris võib välja kutsuda nii funktsioone kui ka näiteks luua veel objekte jpm.