python

Objektet dhe klasat

Objektet

Python njihet si një gjuhë programimi e orientuar në objekte. Objektet janë pjesë e jetës sonë. Një libër, një mace, një makinë janë të gjitha objekte që ne i hasim në jetën e përditshme. Deri tani ne kemi studiuar numrat, vargjet e shenjave, listat, n-shet etj., dhe ajo që nuk e kemi përmendur është se në python gjithçka është objekt. Për secilen prej tyre ne kemi thënë se janë të dhëna si edhe kemi parë metodat përkatëse.

Objektet janë koleksione të dhënash dhe metodash që veprojne mbi këto të dhëna.

Objektet pra kanë të dhëna, që në këtë kontekst quhen atribute dhe metoda që veprojnë mbi këto atribute. Atributet janë ndryshore që ruajnë të dhëna që i përkasin objekteve. Metodat janë ato që i japin jetë këtyre objekteve. Ato janë funksione që veprojnë mbi të dhënat që ka objekti për të realizuar një detyrë. Si objekt le të studiojmë një lepur. Atributet e tij mund të ishin psh. ngjyra, këmbët dhe goja. Ndërsa si metoda mund të përmendim vrapo(), ha() etj. Atributi ngjyrë mund të marrë vlerat: e bardhe, gri etj. Metoda vrapo() do të vepronte mbi atributin këmbët, ndërsa metoda ha() mbi atributin gojë. Nëse objekti do të ishte kameleoni do të kishte metodën ndryshoNgjyre() e cila do të vepronte mbi atributin ngjyrë të objektit kameleon.

Atributet dhe metodat e objekteve aksesohen me pikë. Kjo është sintaksa e hedhjes së vlerës atributit të një objekti.

objekti.atributi = vlera

Nëse do të donim të hidhnim vlerat e bardhë dhe gri për atributin ngjyra të objektit lepur atëherë sintaksa do të ishte:

>>> lepur1.ngjyra = 'e bardhe'
>>> lepur2.ngjyra = 'gri'

Siç e shohim kemi dy objekte lepur1 dhe lepur2 që kanë vlerat e atributit ngjyra perkatësisht të bardhë dhe gri. Prania e dy objekteve të llojit lepur është mëse e kuptueshme në këtë rast, pasi një lepur nuk mund të këtë dy ngjyra. Ndryshe do të ndodhte nëse do të merrnim shembullin e kameleonit, ku në këtë rast do të duhej një objekt kameleon dhe një metodë që do të ndryshonte ngjyrën e tij. Një nga lehtësitë më të mëdha të përdorimit të objekteve, siç edhe e pamë nga shembujt e thjeshtë që u përmenden, është simulimi i jetës reale në kodin e programit.

Ne këtë libër kemi folur për vargjet dhe metodat e tyre. Vargjet janë objekte; ato kanë metoda që veprojnë mbi vlerat e këtyre objekteve. Le të rikujtojmë një nga metodat e objektit varg siç është metoda count e cila jep si rezultat numrin e herëve të shfaqjes së elementit në varg.

>>> x = 'vlera e objektit'
>>> x.count('e')
3

Objekti x ka një vlerë: "vlera e objektit" dhe një metodë: count që vepron mbi këtë vlerë.

Objektet kanë një sintaksë të caktuar për tu krijuar por fillimisht na duhet të mësojmë për klasat. Klasat janë një koncept i lidhur ngushtë me objektet pasi ato nuk mund të kuptohen të shkëputura nga njëra tjetra. Klasat janë si fabrikë që prodhojnë objekte. Nëse më lart u përmend objekti lepur tani duhet të shtojmë se ky objekt i përket klasës lepur. Është pikërisht kjo klasë që "prodhon" objekte lepur të ngjyrave të ndryshme, të madhësive të ndryshme etj. Ajo që vëmë re është se klasa është një përshkrim i një grumbulli objektesh që kanë karakteristika të përbashkëta. Nuk mund të themi se një mace, një lepur apo një mollë janë objekte të së njëjtës klasë. Ata janë të gjithë objekte të klasave të ndryshme.

Objektet e një klase quhen instancat e saj. Psh lepur1 dhe lepur2 janë dy instanca të ndryshme të së njejtës klasë. Më lart përmendëm se objekti është një koleksion të dhënash dhe metodash. Tani e dimë se këto ndryshore dhe metoda të objekteve, krijohen përmes klasave. Metodat dhe ndryshoret mund të krijohen në mënyrë të tillë që t'i përkasin klasës ( dhe për rrjedhojë të kenë të njëjtën vlerë në çdo instancë të saj) ose mund të krijohen si ndryshore ose metoda që i përkasin objekteve të krijuara nga klasa ( dhe për rrjedhojë çdo instancë në këtë rast ka "kopjen" e vet të ndryshores me vlerën unike për çdo instancë). Për ta bërë më konkrete, metodat dhe ndryshoret e klasave i dallojmë pasi ato thirren duke përdorur direkt emrin e klasës ( Klasa.metode_e_klases() ose Klasa.ndryshore_e_klases) ndërsa metodat dhe ndryshoret e instancave thirren përmes emrit të një objekti ( instance) të klasës ( objekti.metode_e_instances() ose objekti.ndryshore_e_instances).

Klasat janë koleksione objektesh me karakteristika të përbashkëta.

Fjala kyçe që përdoret për të krijuar një klasë është class e pasuar nga emri i klasës që zakonisht nga programuesit është vendosur të fillojë me gërmë të madhe, por kjo nuk është e detyrueshme. Por nëse do të merrnim parasysh që jo të gjitha programet mund të shkruhen nga një programues i vetëm, duhet vendosur në këtë rast një rregullore mbi shkrimet e emrave të klasave, objekteve, ndryshoreve, vendosjen e komenteve etj. Kjo për të rritur lexueshmërinë e kodit, në rastet e punës në grup si edhe në rastet kur na duhet t'i kthehemi një programi pas disa kohesh për arsye ndryshimi apo përmirësimi.

Klasa e më poshtme është një klasë shumë e thjeshtë. Ajo përmban vetëm emrin e klasës: Makine si dhe ka një fjalë pass. Pass nga anglishtja do të thotë kalim, edhe në gjuhën e programimit python ka po të njejtin kuptim. Kjo fjalë përdoret në rastet kur klasa është bosh, nuk ka metoda apo ndryshore dhe në këtë rast i themi pythonit të kalojë leximin e kodit në rreshtat e tjerë.

>>> class Makine(object):
        pass

Për të krijuar një objekt ( instancë) të klasës përdoret sintaksa:

emriObjektit = emriKlases()

Kllapat në fund të emrit të klasës tregojnë se përveç krijimit të objektit ne mund të kalojmë edhe argumente sikur po thërrasim një funksion. Një objekt i klasës Makine do të ishte ky:

>>> objekti = Makine()

Në këtë moment kemi krijuar një instancë të klasës Makine.

Atributet e klasave

Thamë se një klasë ka edhe funksione të cilat quhen metoda. Një klasë mundëson krijimin e metodave të reja si edhe ka metodat e gatshme. Metodat janë funksione që i përkasin një klase. Metodat dhe ndryshoer e një klase quhen së bashku atributet e një klase. Për të parë se cfare atributesh ka një klasë ka dy menyra: dir dhe __dict__

Funksioni i gatshëm dir() jep si rezultat një listë me emrat e atributeve të një klase. Sintaksa për përdorimin e funksionit të gatshëm dir() është:

dir(Klasa)

Ndërsa atributi special __dict__ jep si rezultat një fjalor me emrat dhe vlerat e atributeve të një klase. Ky atribut fillon me dy viza njëra pas tjetrës, më pas fjala dict, dhe në fund dy viza të tjera. Sintaksa është:

Klasa.__dict__

Shembull:

>>> class Makine(object):
        pass
>>> dir(Makine)
['__doc__', '__module__']
>>> Makine.__dict__
{'__module__': '__main__', '__doc__': None}

Nga shembulli shohim se klasa Makine e cila nuk ka asnjë atribut të shkruar nga ne ka dy atribute te vetat: __doc__ dhe __module__. Këto janë atribute speciale të klasës të cilat i ka çdo klasë. Atributi special __doc__ jep si rezultat një varg karakteresh "dokumentacioni" të klasës i cili duhet të shkruhet nga ne, në formën e një vargu karakteresh midis thonjëzave treshe, direkt pas përcaktimit të klasës, ngjashëm me komentet, për të shpjeguar funksionin e klasës. Psh.:

>>> class Makine(object):
"""shpjegimi i funksionit te klases"""
    pass
>>> Makine.__doc__
'shpjegimi i funksionit te klases'

Atributi tjetër special __module__ tregon modulin ku ndodhet klasa, në këtë rast __main__ në një modul që po ekzekutohet direkt, në rast se klasa do të importohej nga një modul tjetër nuk do kishim të njëjtën pergjigje. Në kapitullin tjetër ku do të flasim për trashëgiminë do të qartësojmë përkatësitë e moduleve të klasave.

Atributet speciale të klasës
__name__ Emri i klasës
__doc__ Dokumentimi për klasën
__bases__ Jep një n-she për klasat mëmë të klasës ( do të shpjegohet në kapitullin tjetër tek trashëgimia)
__dict__ Atributet e klasës
__module__ Moduli ku përcaktohet klasa

Metodat e një klase janë funksione dhe si të tilla menyra e krijimit të tyre është e njëjtë me atë të funksioneve. Le të krijojmë një metodë të klasës makinë e cila printon një mesazh në ekran.

>>> class Makine(object):
        def printo(self):
            print 'Metoda printo e klases Makine'
>>> objekti = Makine()
>>> objekti.printo()
Metoda printo e klases Makine

Në fillim kemi krijuar klasën Makine e cila ka një metodë printo që merr një parametër self. Kjo metodë shfaq në ekran mesazhin "Metoda printo e klases makine" dhe siç e shohim parametri self nuk përdoret. Më pas kemi krijuar një instancë të klasës Makine me emrin objekti dhe në fund kemi thirrur metodën printo().

Ndryshorja self

Vendosja e ndryshores self si parametër tek metodat në mënyrë eksplicite, është një gjë e detyrueshme në gjuhën python ( emri i parametrit mund të jetë çfarëdo, por self përdoret me marrëveshje). Në rreshtin ku është thirrur metoda objekti.printo() ne nuk kemi kaluar asnjë argument, por për pythonin ne kemi kaluar vete objektin si argument. Këtë rresht kodi

objekti.printo()

pythoni e konverton në:

Makine.printo(objekti)

Siç e shohim python i ka kaluar një argument metodës printo(), e cila nëse nuk do ta kishte paramentrin self që ne shkruam, do të shfaqte një gabim sintakse ku do të na njoftonte se metoda jonë nuk pret asnjë argument ndërsa ne i kemi dhënë një në momentin e thirrjes së saj.

>>> class Makine(object):
        def printo():
            print 'Metoda printo e klases Makine'
>>> objekti = Makine()
>>> objekti.printo()
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    objekti.printo()
TypeError: printo() takes no arguments (1 given)

Në rastin kur metoda duhet të këtë një ndryshore kurrsesi nuk duhet menduar se kjo do të zëvendësojë parametrin self. Objekti është argumenti i parë që i kalon një metode kur thirret dhe i pergjigjet parametrit self të vendosur të parë tek metoda. Për të parë më qartë një rast kur metoda merr një parameter tjetër veç self, të shohim shembullin më poshtë.

>>> class Makine(object):
        def printo(self, emri):
            self.x = emri
            print "Kjo makine eshte", self.x
>>> objekti = Makine()
>>> objekti.printo('bmw')
Kjo makine eshte bmw

Në rreshtin kur kemi thirrur metodën i kemi kaluar vetëm një argument:

objekti.printo('bmw')

Pavarsisht se metoda printo() ka dy parametra ne i kemi kaluar vetëm një, por tashmë e kemi të qartë se argumenti i parë është vetë objekti. Pra ne këtë rast pythoni ka konvertuar:

objekti.printo('bmw')

Makine.printo(objekti,'bmw')

Dhe në këtë rast është shumë e qartë që edhe kur thërrasim metodën printo() edhe kur e krijojmë këtë metodë kemi dy ndryshore në secilin rast.

Deri tani kemi parë vetëm metodat si atribute të klasave. Ndërsa në këtë shembull pamë edhe ndryshoren self.x. Rreshti self.x = emri do të përkthehej: atributit x të instancës jepi vlerën e parametrit emri. Pra mund të themi se klasa Makine ka këto atribute: metodën e instancave printo, dhe ndryshoren e instancave x. Siç u përmend më lart atributet e instancave janë më të zakonshme se ato të klasave prandaj në vazhdim nuk do të shkruajmë më metodë instance dhe ndryshore instance por thjesht metodë dhe ndryshore.

Kur studiuam funksionet ne kalonim si argumenta listat, numrat e të tjera lloje të dhënash. Siç e pamë në këto shembuj edhe objektet mund të kalohen si argumenta ashtu si llojet e tjera të të dhënave.

Konstruktori __init__

Konstruktorët janë metoda speciale të një klase. Një nga konstruktorët më të përdorur në python është __init__. Emri init fillon me dy viza të poshtme njëra pas tjetrës dhe po kështu përfundon. Ky konstruktor thirret menjëherë pas krijimit të objektit dhe bën të mundur përdorimin e ndryshoreve. Parametri i parë që ka një konstruktor është ndryshorja self, kjo pasi siç thamë konstruktorët janë metoda dhe çdo metodë në programimin e orientuar në objekte e ka të domosdoshme përdorimin e kësaj ndryshoreje.

Ka shumë raste kur na duhet që sapo krijojmë një objekt t'i japim edhe argumenta për t'i dhënë vlerë ndryshoreve të objektit. Deri tani kemi vepruar kështu:

>>> class Shembull(object):
        def printo(self, emri):
            self.emri = emri
            printo self.emri
>>> objekti = Shembull()
>>> objekti.printo('piton')

Rreshti objekti = Shembull() bën vetëm krijimin e objektit dhe nuk mund të marrë argumenta dmth nuk mund të shkruajmë: objekti = Shembull('piton'), kjo mund të realizohet përmes konstruktorit __init__.

>>> class Makine(object):
        def __init__(self, modeli):
            self.x = modeli
        def printo(self):
            print 'Kjo makine eshte e llojit', self.x
>>> objekti = Makine('kamion')
>>> objekti.printo()
Kjo makine eshte e llojit kamion

Objekti që në krijim merr argumenta të cilat i bën të mundura për përdorim konstruktori __init__. Këto ndryshore mund të përdoren më pas nga të gjitha metodat e kësaj instance si metoda printo.

Shembull përmbledhës

Le të zhvillojme me të tej klasën Makine, duke përforcuar konceptet e objekteve, atributeve të tyre, si edhe duke përsëritur koncepte të mesuara më parë. Tek klasa jonë Makine mund të shtojmë metoda nga më të ndryshmet. Gjërat që mund të realizojmë janë psh. të tregojme emrin e pronarit të makinës, markën, litrat që mban, për sa kohë bën një rrugë të caktuar në lidhje me shpejtësinë që ka etj. Shembulli është vetëm ilustrues.

Të printojmë emrin, markën dhe sasinë e litrave të një objekti të klasës Makine. Pra objekti duhet të marrë tri argumenta: emrin, markën dhe litrat. Fillimisht duhet të krijojmë konstruktorin __init__, më pas një metodë detajet që të shfaqë emrin e pronarit të makinës, markën dhe litrat.

>>> class Makine(object):
        def __init__(self, emri, marka, litra):
            self.emri = emri
            self.litra = litra
            self.modeli = marka

        def detajet(self):
            print "%s ka makine %s me %.2f litra karburant" % (self.emri, self.modeli, self.litra)
>>> objekti = Makine('Roberti', 'modeli1', 70)
>>> objekti.detajet()
Roberti ka makine modeli1 me 70.00 litra karburant

Të krijojmë një metodë që llogarit kohën që i duhet një makine për të përshkruar një rrugë të caktuar. Gjëja e parë që na vjen ndër mend është fakti se jo çdo lloj makine ecën me të njëjtën shpejtësi. Pra një gjë që duhet t'i shtojmë kodit tonë është lloji i modeleve të makinave i shoqëruar me shpejtesitë përkatëse. Për këtë mund të perdorim fjalorët ku çiftet çelës–vlerë do të jenë marka-shpejtësia. Më pas do të krijojmë metodën për të gjetur kohën kur dimë shpejtësinë dhe rrugën që do përshkojë makina. Për këtë na vjen në ndihmë formula e lëvizjes së njëtrajtshme pa nxitim për kohën: t = l/v, ku t-koha, l-gjatësia e rrugës dhe v-shpejtësia.

>>> class Makine(object):
        modelet = {'modeli1': 85.0, 'modeli2': 80.0, 'modeli3': 75.0}

        def __init__(self, emri, marka, litra):
            self.emri = emri
            self.litra = litra
            self.modeli = marka
            self.shpejtesia = Makine.modelet[self.modeli]

        def detajet(self):
            print "%s ka makine %s me %.2f litra karburant" % (self.emri, self.modeli, self.litra)

        def per_sa_kohe(self, dist):
            koha = dist / self.shpejtesia
            print "%s e ben rrugen %dkm per %.2f ore" % (self.emri, dist, koha)
>>> obj = Makine('Roberti', 'modeli2', 70)
>>> obj.per_sa_kohe(70)
Roberti e ben rrugen 70km per 0.88 ore

Siç thamë kemi krijuar një fjalor me çiftet çelës-vlerë: marka-shpejtësia. Në këtë rresht kemi shkruar nga një shpejtësi specifike sipas llojit të makinës:

modelet = {'modeli1': 85.0, 'modeli2': 80.0, 'modeli3': 75.0}

Siç mund ta vini re, ndryshoren modelet nuk e kemi krijuar brenda konstruktorit si ndryshoret e tjera por në fillim të klasës pa filluar përkufizimet e funksioneve. Për këtë arsye kjo është një ndryshore e klasës dhe jo ndryshore e instancës siç janë ndryshoret e tjera emri, litra etj. Kjo vihet re dhe nga mënyra se si i referohmi kësaj ndryshoreje në kodin e më poshtëm ku krijojmë një ndryshore për të ruajtur shpejtësinë e instancës konkrete të makinës në bazë të modelit:

self.shpejtesia = Makine.modelet[self.modeli]

Në këtë rresht kodi shpejtësisë ia japim vlerën duke iu referuar modelit të makinës e cila përcaktohet nga marka. Marka e merr vlerën në momentin e krijmit to objektit konkret për një makinë. Interesante është të vihet re se ndryshoreve të instancës i referohemi përmes self si psh self.shpejtesia, kurse ndryshorës së klasës i referohemi përmes emrit të vetë klasës Makine.modelet.

Ndryshoren modelet e krijuam si ndryshore klase, sepse kjo është një ndryshore e cila do të mbajë një informacion që është i përbashkët për të gjitha instancat e makinave: lista e modeleve që programi ynë njeh nuk ndryshon nga instanca në instancë. Ajo që ndryshon është modeli i secilës makinë dhe kjo kalohet në momentin e krijimit të instancës me anë të argumentit marka, dhe ruhet në kodin e klasës në ndryshoren e instancës self.modeli.

Në fund kemi krijuar metodën per_sa_kohe, e cila merr si argument distancën. Kjo metodë shfrytëzon formulën fizike t = l / v. Këto të dhëna i kemi të gatshme. Distanca e rrugës jepet nga argumenti me të cilin thirret metoda: dist, ndërsa shpejtësia e cila u shpjegua më lart është e përcaktuar në kodin e klasës.

Ti shtojmë kodit edhe aftesinë për të llogaritur përfundimin apo jo të rrugës me aq litra sa përcakton programuesi pa bërë mbushje gjatë rrugës. Nëse këto litra nuk mjaftojnë, atëherë të dalë një mesazh njoftimi për mbushje të makinës. Fillimisht le të përcaktojmë njësoj siç vendosëm shpejtësinë, sesa litra harxhon secila lloj makine për çdo 100 kilometra. Psh. modeli1 - 5 litra, modeli2 – 4.5, modeli3 - 4.9.

>>> class Makine(object):
        modelet = {'modeli1': (85, 5), 'modeli2': (80, 4.5), 'modeli3': (75, 4.9)}

        def __init__(self, emri, marka, litra):
            self.emri = emri
            self.litra = litra
            self.modeli = marka
            self.shpejtesia = Makine.modelet[self.modeli][0]
            self.litra_per_100 = Makine.modelet[self.modeli][1]

        def detajet(self):
            print "%s ka makine %s me %s litra karburant" % (self.emri, self.modeli, self.litra)
            print "Kjo lloj makine ecen me %.2f km/h dhe harxhon %.2f litra per 100 km" % (self.shpejtesia, self.litra_per_100)

        def per_sa_kohe(self, dist):
            koha = float(dist) / self.shpejtesia
            konsumi = float(dist) * self.litra_per_100 / 100
            if konsumi > self.litra:
                print "%s nuk e ben dot rrugen %dkm me %.2f litra karburant" % (self.emri, dist, self.litra)
                print "Makina %s harxhon %.2f litra per %dkm rruge" % (self.modeli, konsumi, dist)
                return
            mbetur = self.litra - konsumi
            print "%s e ben rrugen %dkm per %.2f ore dhe i teprojne %.2f litra" % (self.emri, dist, koha, mbetur)
>>> obj = Makine('Robert', 'modeli2', 2)
>>> obj.per_sa_kohe(72)
Robert nuk e ben dot rrugen 72km me 2.00 litra karburant
Makina modeli2 harxhon 3.24 litra per 72km rruge
>>> obj = Makine('Robert', 'modeli2', 20)
>>> obj.per_sa_kohe(72)
Robert e ben rrugen 72km per 0.90 ore dhe i teprojne 16.76 litra

Kodit i shtuam edhe litrat që harxhon secili model makine për çdo 100 kilometra, kështu fjalori ka tashmë këtë pamje:

modelet = {'modeli1': (85, 5), 'modeli2': (80, 4.5), 'modeli3': (75, 4.9)}

Kodi ka modifikim në mënyrën sesi i referohemi shpejtësive të modeleve të ndryshme, kjo pasi modelet tashmë është një fjalor ku vlera e çdo çelësi është një n-she: shpejtësi dhe litra për 100km.

self.shpejtesia = Makine.modelet[marka][0]

Shënimi Makine.modelet[marka][0] mund të përshkruhej gjatë me fjalë kështu: nga ndryshorja modelet e klasës Makine merr vlerën që ndodhet në indeksin 0 të n-shes së çelësit marka.

self.litra_per_100 = Makine.modelet[marka][1]

Ndërsa në këtë rresht kodi, litrat që harxhon për çdo 100 kilometra makina e modelit marka, i kalojnë si vlerë ndryshores self.litra_per_100. Kjo ndryshore do të shfrytëzohet tek metoda e cila do të tregojë sesa litra harxhon një makinë për një rrugë të caktuar:

def per_sa_kohe(self, dist):
    koha = float(dist) / self.shpejtesia
    konsumi = float(dist) * self.litra_per_100 / 100
    if konsumi > self.litra:
        print "%s nuk e ben dot rrugen %dkm me %.2f litra karburant" % (self.emri, dist, self.litra)
        print "Makina %s harxhon %.2f litra per %dkm rruge" % (self.modeli, konsumi, dist)
        return
    mbetur = self.litra - konsumi
    print "%s e ben rrugen %dkm per %.2f ore dhe i teprojne %.2f litra" % (self.emri, dist, koha, mbetur)

Struktura if është përdorur për të kontrolluar nëse shoferi mund të kryejë ose jo rrugën duke marrë parasysh litrat që ka makina në nisje. Teknika e përdorur në këtë pjesë është e re. Nëse konsumi është më i madh se sasia e litrave në nisje, udhëtimi nuk mund të kryhet prandaj funksioni printon mesazhin përkatës dhe ndalon ekzekutimin e tij përmes fjalës kyçe return. Përndryshe zbatohen rreshtat e kodit pas kushtit if dhe funksioni përfundon normalisht pa pasur nevojë për një tjetër return.

Gjithashtu është shtuar edhe një pjesë kodi e cila llogarit sesa litra teprojnë pas një rruge të caktuar.

Ndërsa kodi:

print "Kjo lloj makine ecen me %.2f km/h dhe harxhon %.2f litra per 100 km" % (self.shpejtesia, self.litra_per_100)

është një printim i të dhënave, ku shtesa e re është pikërisht printimi i litrave që harxhohen për çdo 100 kilometra nga makina.

Tjeter metodë që mund t'i shtohej këtij kodi mund të jetë edhe metoda shto_litra. Në rastin kur makina nuk mund të kryejë një rrugë të caktuar mund të krijojme një metodë të re të klasës Makine që kryen këtë detyrë të thjeshtë.

>>> def shto_litra(self, shto):
    self.litra = self.litra + shto

Metoda shto_litra merr si parametër vlerën e litrave që duhen shtuar dhe ja shton këtë ndryshores ku ruhet numri i litrave në serbator.

Kodi i plotë:

>>> class Makine(object):
        modelet = {'modeli1': (85, 5), 'modeli2': (80, 4.5), 'modeli3': (75, 4.9)}

        def __init__(self, emri, marka, litra):
            self.emri = emri
            self.litra = litra
            self.modeli = marka
            self.shpejtesia = Makine.modelet[self.modeli][0]
            self.litra_per_100 = Makine.modelet[self.modeli][1]

        def detajet(self):
            print "%s ka makine %s me %s litra karburant" % (self.emri, self.modeli, self.litra)
            print "Kjo lloj makine ecen me %.2f km/h dhe harxhon %.2f litra per 100 km" % (self.shpejtesia, self.litra_per_100)

        def per_sa_kohe(self, dist):
            koha = float(dist) / self.shpejtesia
            konsumi = float(dist) * self.litra_per_100 / 100
            if konsumi > self.litra:
                print "%s nuk e ben dot rrugen %dkm me %.2f litra karburant" % (self.emri, dist, self.litra)
                print "Makina %s harxhon %.2f litra per %dkm rruge" % (self.modeli, konsumi, dist)
                return
            mbetur = self.litra - konsumi
            print "%s e ben rrugen %dkm per %.2f ore dhe i teprojne %.2f litra" % (self.emri, dist, koha, mbetur)

        def shto_litra(self, shto):
            self.litra = self.litra + shto
>>> obj = Makine('Robert', 'modeli2', 2)
>>> obj.detajet()
Robert ka makine modeli2 me 2 litra karburant
Kjo lloj makine ecen me 80.00 km/h dhe harxhon 4.50 litra per 100 km
>>> obj.per_sa_kohe(52)
Robert nuk e ben dot rrugen 52km me 2.00 litra karburant
Makina modeli2 harxhon 2.34 litra per 52km rruge
>>> obj.shto_litra(1)
>>> print obj.litra
3
>>> obj.per_sa_kohe(52)
Robert e ben rrugen 52km per 0.65 ore dhe i teprojne 0.66 litra

Klasa që kemi krijuar zbaton parimet e programimit te orientuar në objekte si dhe parimet e programimit të mirë që kemi shpjeguar në kapitullin e funksioneve. Një nga këto parime ishte fshehja e informacionit. Siç do ta shohim më gjërë në kapitullin e rradhës, në python fshehja e informacionit është një gjë që nuk imponohet nga sintaksa e python por lihet në gjykimin e programuesit. Kështu në shembullin e më sipërm ne kemi printuar direkt me anë të kodit print ojb.litra ndryshoren litra për të kontrolluar vlerën e saj nga jashtë objektit. Në gjuhë të tjera programimi, ku parimi i fshehjes së kodit imponohet nga sintaksa e gjuhës një përdorim i tillë nuk lejohet. Ndërsa në python siç e pamë kjo gjë është e lejueshme.

Megjithatë, kur themi se zbatimi i këtij parimi është në dorë të përdoruesit, kemi parasysh psh. që në kodin e mësipërm ne e përdorëm në mënyrë të drejtëpërdrejtë ndryshoren litra vetëm për të lexuar vlerën e saj. Për ta ndryshuar vlerën e kësaj ndryshoreje, ne si krijuesit e klasës Makina, vendosëm në dispozicion të përdoruesve të kësaj klase metodën shto_litra. Kjo megjithate, nuk e pengon përdoruesin e kësaj klase që ta ndryshojë vlerën e ndryshores direkt me një rresht kodi të tipit: obj.litra = 0. Për ta nxitur përdoruesin e klasës që të mos i ndryshojë në mënyrë të drejtëpërdrejtë ndryshoret e objektit, ne si krijues të klasës duhet ta bëjmë sa më të lehtë ndryshimin e tyre përmes metodave të posçme si shto_litra.

Në vijim të këtij parimi le të krijojmë një metodë të re e cila do ta lejojë përdoruesin e klasës sonë që të shtojë një model të ri makine tek ndryshorja e klasës modelet

def shtoModel(modeli, shpejtesia, litra_per_100):
    Makine.modelet[modeli] = (shpejtesia, litra_per_100)

Ky kod mund të përdoret nga përdoruesit e klasës në këtë mënyrë:

>>> Makine.shtoModel('modeli7', (80, 4))
>>> Makine.modelet
{'modeli3': (75, 4.9), 'modeli2': (80, 4.5), 'modeli1': (85, 5), 'modeli7': (80, 4)}

Vini re kjo metodë thirret direkt nga klasa Makine dhe jo nga ndonjë instancë e saj. Pra ashtu si ndryshorja modelet që është një ndryshore e vetë klasës Makine dhe jo e instancave të saj, edhe metoda shtoModel nuk ka pse të jetë e ndryshme për instanca të ndryshme. Kjo na krijon gjithashtu lehtësinë që të mund ta përdorim këtë metodë edhe pa qenë nevoja të krijojmë një instancë të klasës më parë. Për ta shtuar kodin e funksionit të mësipërm shtoModel si një metodë të klasës duhet bërë një ndryshim i vogël:

>>> class Makine(object):
        modelet = {'modeli1': (85, 5), 'modeli2': (80, 4.5), 'modeli3': (75, 4.9)}

        @staticmethod
        def shtoModel(modeli, shpejtesia, litra_per_100):
            Makine.modelet[modeli] = (shpejtesia, litra_per_100)

        def __init__(self, emri, marka, litra):
            self.emri = emri
        ... etj ...

Rreshti @staticmethod i shtuar përpara metodës është ai që na lejon të krijojmë një metodë "statike" të klasës e cila mund të përdoret pa pasur nevojë t'i kalojmë si argument të parë një instancë të metodës.

Një tjetër mënyrë për krijuar metoda të klasës, që nuk kanë nevojë për një instancë, por që marrin si argument të parë një referencë të vetë klasës është përmes @classmethod:

>>> class Makine(object):
        modelet = {'modeli1': (85, 5), 'modeli2': (80, 4.5), 'modeli3': (75, 4.9)}

        @classmethod
        def shtoModel(cls, modeli, shpejtesia, litra_per_100):
            cls.modelet[modeli] = (shpejtesia, litra_per_100)

        def __init__(self, emri, marka, litra):
            self.emri = emri
        ... etj ...

Në këtë rast në vend të parametrit self që kishin metodat e instancave, kjo metodë klase merr si parametër të parë të detyrueshëm një referencë të vetë klasës që e kemi quajtur cls. Këtë parametër mund ta përdorim brenda metodës në vend të emrit të klasës Makine. Thirrja e metodës nga jashtë është e njejtë: Makine.shtoModel(...)

Në fund, përpara se ta mbyllim këtë kapitull, le t'i kthehemi edhe një herë konceptit të fshehjes së informacionit, dhe krijimit të metodave që e lejojnë përdoruesin e klasave që ne krijojmë, të ndryshojë vlerat e ndryshoreve të instancave.

Modeli i makinës është një ndryshore kryesore për objektet që krijohen me anë të klasës Makine. Nëse kjo ndryshore manipulohet direkt nga jashtë klasës psh objekt.modeli = 'model tjeter' shkakton probleme në pjesën tjetër të klasës. Kështu psh. mund të vendoset si vlerë një model që nuk ekziston, por edhe nëse modeli ekziston, llogaritjet e metodave tona do të jenë të gabuara pasi ndryshoret e tjera si shpejtesia dhe litra_per_100 do të kenë vlerat e modelit të vjetër. Në këto raste dobia e metodave ndihmëse për të modifikuar ndryshoret është shumë e qartë:

def ndryshoModel(self, marka):
    if marka not in Makine.modelet:
        print "Modeli '%s' nuk ekziston" % marka
        return
    self.modeli = marka
    self.shpejtesia = Makine.modelet[marka][0]
    self.litra_per_100 = Makine.modelet[marka][1]