Python klase i objekti

Klase donose malo nove sintakse, tri nova objekta, kao i nešto malo nove semantike.

Sintaksa za definiciju klase

Najjednostavniji oblik definicije klase izgleda ovako:

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

Definicija klase, slično definiciji funkcije (def naredba) mora biti izvršena pre njene upotrebe. (Vi možete definiciju klase staviti bilo gde u programu, na primer u if naredbi, ili unutar funkcije).

U praksi, naredbe unutar definicije klase, će najčešće biti definicije funkcija, ali su dozvoljene i druge naredbe - što ponekad može biti korisno. Definicije funkcija unutar klase imaju naizgled čudnu listu argumenata, što je prouzrokovano konvencijom za pozivanje metoda - o čemu će biti reči kasnije.

Kada se pokrene definisana klasa, kreira se njen namespace koji će se koristiti kao lokalni scope — tako će sve lokalne varijable ići u ovaj novi namespace. I sve definicije funkcija će takođe biti vezane za taj namespace.

Kada se napusti definisanje klase (izvršavanjem poslednje naredbe) , kreira se class object (objekat tipa klasa). To je u osnovi jedan omotač oko sadržaja namespace-a koji je kreiran definicijom klase. Originalni lokalni scope (onaj koji je bio aktivan pre nego što je započeta definicija klase) se ponovo uspostavlja, i novi objekat se uvodi sa imenom nove definisane klase.

“Class” objekti

Class objekti podržavaju dve vrste operacija: referencu na atribute i kreiranje instance (instantiation).

Referenca na atribute koristi standardnu sintaksu: obj.name. Validna imena atributa su sva imena koja se nalaze u namespace-u kada je objekat klase kreiran. Tako ako je definicija klase bila:

class MyClass:
    """A simple example class"""
    i = 12345

    def f(self):
        return 'hello world'

onda su MyClass.i i MyClass.f valide reference na atribute, koje upućuju na objekat ceo broj i na objekat fukciju, respektivno. Klasni atributi se mogu koristiti u nardbi dodeljivanja vrednosti (assignment), pa tako možete izmeniti vrednost MyClass.i. Atribut __doc__ je takođe valjan atribut, koji daje docstring koji pripada klasi: "A simple example class".

“Instance” objekti

Za kreiranje instance koristi se sintaksa kao za funkcije. Prosto zamislimo da je klasa funkcija koja vraća instancu klase.

Na primer (koristeći napred definisanu klasu MyClass):

x = MyClass()

kreira novu instancu klase i tako kreirani objekat pridružuje lokalnoj varijabli x.

U većini slučajeva klasom se kreiraju objekti koji imaju neko određeno inicijalno stanje po kojem se razlikuju od drugih objekata.

U tu svrhu koristi se specijalna metoda pod nazivom __init__, kao u sledečem primeru:

def __init__(self):
    self.data = []

Kada klasa sadrži __init__ metodu, ta metoda se automatski poziva pri kreiranju nove instance. Tako bi u gornjem primeru bila kreirana nova instanca, ali sada sa inicijalizacijom:

x = MyClass()

Naravno, metoda __init__ može imati argumente. Na primer:

class Complex:
   def __init__(self, realpart, imagpart):
   self.r = realpart
   self.i = imagpart

x = Complex(3.0, -4.5)
print(x.r, x.i)

će štampati:

(3.0, -4.5)

Šta možemo da radimo sa instance objektima? Jedine operacije sa ovim objektima jesu operacije referenciranja na njihove atribute. Postoje dve vrste validnih imena atributa: atributi podataka i metoda.

Atributi podataka odgovaraju “varijablama instanci” prema Smalltalk terminologiji, ili “data members” u C++ terminologiji. Atributi podataka ne moraju biti unapred deklarisani; slično lokalnim varijablama, oni nastaju kada im se dodeli vrednost. Na primer, ako je x instanca klase MyClass (koju smo definisali ranije), sledeći program će štampati 16, a da za sobom ne ostavi nikakv trag:

x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print(x.counter)
del x.counter

Druga vrsta reference atributa instance je metod. Metod je funkcija koja “pripada” objektu. (U Python-u, izraz “metod” ne koristi se samo za instance klase: drugi tipovi objekata mogu imati metode, Na primer, objekti tipa list imaju metode append, insert, remove, sort itd.)

Validna imena metoda u instanca objektima zavise od klase. Po definiciji, svi atributi klase koji su funkcije definišu metode njenih instanci. Tako u našem primeru, x.f je validna referenca na metod, pošto je MyClass.f funkcija, ali x.i nije jer MyClass.i nije funkcija. Vodite računa da x.f i MyClass.f nisu ista stvar: x.f je metod a MyClass.f je funkcija.

“Metod” Objekti

Metode se obično pozivaju odmah nakon što su uvezane (bound) na objekat:

x.f()

U slučaju MyClass primera, ovaj poziv će vratiti 'hello word' string. Međutim, metod ne mora biti pozvan odmah: x.f je objekat tipa metod i može biti sačuvan za kasnije pozivanje. Na primer, kod:

xf = x.f
while True:
    print(xf())

će štampati hello world bez prestanka.

Šta se zapravo dešava kada se metod pozove? Možda ste uočili da je x.f() pozvan bez argumenta iako je u definiciji funkcije f specificiran jedan argument. Šta se desilo sa tim argumentom. Naravno. Python podiže izuzetak kada se bez argumenata poziva funkcija koja zahteva argumente.

U stvari, možda ste sami pogodili odgovor: specijalna stvar je da se metodu uvek prenosi referenca na instancu objekta kao prvi argument funkcije. U našem primeru, poziv x.f() je ekvivalentan pozivu MyClass.f(x). Generalno, pozivanje metoda sa listom od n argumenata je ekvivalentno pozivanju odgovarajuće funkcije sa dodatnim argumentom (instancom objekta) na početku liste argumenata.

“Class” i “Instance” varijable

Generalno govoreći, “instance” varijable su jedinstvene za svaku instancu, a “class” varijable su zajedničke za sve intance date klase, kao u primeru:

class Dog:

    kind = 'canine'         # class variable shared by all instances

    def __init__(self, name):
        self.name = name    # instance variable unique to each instance

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind                  # shared by all dogs
'canine'
>>> e.kind                  # shared by all dogs
'canine'
>>> d.name                  # unique to d
'Fido'
>>> e.name                  # unique to e
'Buddy'

Kao što je još ranije navedeno, zajednički podaci mogu izazvati iznenađujuće efekte ako sadrže promenljive (mutable) objekte kao što su liste i rečnici.

Na primer, tricks lista u sledećem kodu ne bi trebalo da se koristi kao varijabla klase jer će biti zajednička za sve Dog instance:

class Dog:

    tricks = []             # mistaken use of a class variable

    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                # unexpectedly shared by all dogs
['roll over', 'play dead']

Korektan dizajn ove klase treba da koristi varijablu instance umesto varijablu klase:

class Dog:

    def __init__(self, name):
        self.name = name
        self.tricks = []    # creates a new empty list for each dog

    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']

“Private” varijable

U Pythonu, ne postoje privatne “instance” varijable kojima se ne može pristupati osim iz samog objekta. Međutim, postoji jedna konvencija koja se poštuje u većini Python programa: da se ime koje počinje sa donjom crtom (na primer _spam) tretira kao “non-public” bilo da se radi o varijabli ili metodi.

Pošto postoji dobar razlog za postojanje privatnih članova klase (recimo da se izbegne sudar imena sa imenima iz subclase), postoji jedan ograničeni mehanizam koji se naziva name mangling (rušenje imena). Svako ime oblika __spam (sa dve najmanje donje crte na početku i najviše jednom na kraju) se zamenjuje sa classname__spam gde je classname ime tekuće klase.

“Mangling” imena je pogodan da omogući subklasi da prekrije metod klase a da se pri tome ne remeti upotreba tog metoda unutar originalne klase. Na primer:

class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)

Napominjemo da je “mengling” pravilo napravljeno samo za izbegavanje akcidenata; i pored tog parvila moguće je pristupati i modifikovati varijable koje se smatraju privatnim. To može čak biti korisno u nekim specijalnim situacijama, kao što je na primer debagiranje.

Dodatak

Ponekad je korisno imati na raspolaganju tip podataka sličan Pascal “record” tipu, ili C “struct” tipu, gde se više podataka spaja u jednu celinu. Za to nam lepo može poslužiti “prazna” klasa kao u sledećem primeru:

class Employee:
    pass

john = Employee()  # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

Pitanja

1.Opišite podatke i metode koje smatrate neophodnim pri dizajnu jednostavnog tekst editora.

2.Napišite Python klasu “Cvet” koja ima tri “instance” varijable tipa str, int i float, koje predsravljaju naziv, broj latica i cenu cveta. Klasa treba da sadrži konstruktor metodu kojom se inicijalizuju ove varijable. Klasa treba da sadrži metode za setovanje svake od varijabli, kao i metode za dobijenje vrednosti svake od varijabli klase.

3.Pretpostavimo da ste u timu projektanata nove on-line čitaonice (e-book reader). Koje su osnovne klase i metode koje će biti potrebne za rad čitaonice? Potrebno je da klase prikažete UML dijagramima sa vezama među klasama, ali ne treba da pišete program. Vaše projektovane klase treba da omoguće da korisnici čitaonice mogu da kupe novu knjigu, da vide listu već kupljenih knjiga kao i da čitaju kupljene knjige.