Enhavolisto

Antaŭa temo

11. Enigo - eligo

Sekva temo

13. Eraroj kaj esceptoj

12. Klasoj

12.1. Objektoj

Atentigo

la vorto ‘objekto’ en Pitono ne nepre signifas klasan instancon. Je Pitono, preskaŭ ĉio estas objekto (ekz. ĉiuj datumaj tipoj, moduloj, kaj funkcioj). Ĉi tiu provizas semantikojn por importado kaj renomado.

Ne ĉiuj tipoj en Pitono estas klasoj: oni ne povas uzi primitivajn tipojn kiel bazajn klasojn por etendi ilin. Dosieroj estas tipoj, sed ne estas klasoj. Tamen, ĉiuj Pitonaj tipoj kunhavas komunajn semantikojn kiu estas ‘objekta’.

Fakte, klasoj mem estas objektoj, en la pli larĝa senco de la vorto.

12.2. Enkonduko al klasoj

La plej simpla formo de klasa difino estas:

class KlasNomo(object):
     <instrukcio-1>
     .
     .
     <instrukcio-N>

Oni devas plenumi klasajn difinojn, same kiel funkciajn difinojn (def instrukcioj) por aktivigi ilin (oni povas plenumi klasan difinon nur kondiĉe aŭ en funkcio, sed kutime klasoj okazas je la komenco de la modulo).

Praktike, la instrukcioj en klasa difino kutime estos funkciajn difinojn, sed aliaj instrukcioj estas permesitaj, kaj malofte utilaj - plu poste. La funkciaj difinoj en klaso kutime havas specialan formon de argumenta listo - refoje, plu poste.

Kiam oni komencas plenumi klasan difinon, nova nomospaco estas kreita, kaj uzita kiel loka amplekso - do, ĉiu asignoj al lokaj variabloj estas en ĉi tiu klasa nomospaco. Specife, la funkciaj difino kaj la funkcia nomo estas kunligitaj en la klasa nomospaco. Vidu leciono Nomospacoj kaj valorizado por pli da informoj pri nomospacoj kaj ampleksoj.

Kiam oni sukcese plenumas klasan difinon, klasa objekto estas kreita. Ĉi tiu estas volvaĵo ĉirkaŭ la enhavoj de la nomospaco de la klasa difino. La originala nomospaco (aktiva ĵus antaŭ la komenco de la klasa plenumo) estas remetita kaj la klasa objekto kaj la klasa nomo estas kunligitaj en ĉi tiu nomospaco.

12.2.1. Klasaj objektoj

Klasaj objektoj subtenas du tipojn da agadoj: atribuajn referencojn kaj generadon.

Atribuaj referencoj uzas la norman Pitonan sintakson: obj.nomo. Validaj atribuaj nomoj estas ĉiuj nomoj kiu estis en la klasa nomospaco kiam la klasa objekto estis kreita. Do, se la klasa difino aspektis jene:

class MiaKlaso:
   "Simpla ekzemplo klasa"
   i = 12345
   def f(q):
      sal = 'saluton mondo'
      return sal

Tiam MiaKlaso.i and MiaKlaso.f estas validaj atribuaj referencoj, redonante entjeron kaj funkcian objekton, respektive. Oni povas ankaŭ asigni al klasaj atribuoj, do oni povas ŝanĝi la valoron de MiaKlaso.i per asigno. Aliflanke, la variablo sal ne estas atingebla.

Oni kreas klasan instancon per uzo de funkcia signaro, ekz. (supozu la klason supre)

v = MiaKlaso()

kreas novan instancon de la klaso kaj asignas ĉi tiun instancan objekton al la loka variablo v.

12.2.2. Instancaj objektoj

Kio oni povas fari per instancaj objektoj? La solaj agadoj kiun instancaj objektoj komprenas estas atribuoj referencoj. Estas du tipoj.

La unua estas datumaj atribuoj (en aliaj programlingvoj, ĉi tiuj estas ‘instancaj variabloj’ aŭ ‘datumaj membroj’). Datumaj atribuoj, same kiel lokaj variabloj, estas kreitaj kiam oni asignas al ilin. Ekz. (supozu la supran ekzemplon) la sekvaj instrukcioj onidebligos la valoron 16:

v.kvanto = 1
# kreu datuman atribuon
while v.kvanto < 10:
     v.kvanto = v.kvanto * 2
     print v.kvanto   #videbligu rezulton
     del v.kvanto     #senvalorigu ĝin

La dua estas metodoj. Metodo estas funkcion kiu apartenas al objekto (ne nepre al klasaj instancoj - ekz. listoj havas metodojn). Tamen, en la cetero de ĉi tiu sekcio mi uzas la vorton ‘metodo’ aŭ ‘metoda objekto’ nur signifi metodon de klase instancaj objektoj, krom se mi eksplicite kontraŭdiras.

Validaj metodaj nomoj de instanca objekto dependas de ĝia klaso. Laŭ difino, ĉiuj atribuoj de klaso kiu estas funkciaj objektoj difinas respondajn metodojn de la instancoj. Do, en nia ekzemplo, v.f estas valida metoda referenco, ĉar MiaKlaso.f estas funkcio. Aliflanke, v.i ne estas valida metoda referenco, ĉar MiaKlaso.i ne estas funkcio.

Sed v.f ne estas la sama kiel MiaKlaso.f - v.f estas metoda objekto; MiaKlaso.f estas funkcia objekto.

12.2.3. Metodaj objektoj

Kutime, oni vokas metodon tuje, ekz.:

v.f()

En nia ekzemplo, ĉi tiu redonos la ĉenon ‘saluton mondo’. Tamen, oni ne necese devas voki metodon tuje: v.f estas metoda objekto, kaj oni povas stapli ĝin kaj voki ĝin poste, ekz.:

vf = v.f
while 1:
   print vf()

Ĉi tiu daŭrigos videbligi ‘saluton mondo’ denove kaj denove.

Rimarku, ke mi vokis la metodon v.f sen argumento, sed la funkcia difino postulas argumenton. La specialaĵo de metodoj estas, ke la objekto mem estas sendita kiel al unua argumento de la funkcio. Se oni sendas argumentojn al la metodo, la objekto estas aldonita kiel la unua argumento. En nia ekzemplo, la voko v.f() estas precize egala al MiaKlaso.f(v). Ĝenerale, instanco.metodo(arg1, arg2, k.t.p.) egalas klaso.funkcio(instanco, arg1, arg2, k.t.p.).

Angle, oni kutime nomas la unua argumento self. Ĉi tiu estas nur kutimo; la vorto self ne havas signifon al Pitono (sed aliaj programoj eble anticipas la uzon de ĉi tiu nomo). En ĉi tiu instruilo, mi anstataŭe uzos la esperantan vorton mem.

Metodoj povas voki aliajn metodojn, ekz.:

class Sako:
    def malplenigu(mem):
        mem.datumoj = []
    def aldonu(mem, q):
        mem.datumoj.append(q)
    def aldonu_dufoje(mem, q):
        mem.aldonu(q)  # vokas alian metodon 'aldonu'
        mem.aldonu(q)

La kreado de instanco fakte kreas malplenan objekton. Ofte, oni volas, ke datumaj objektoj estas kreitaj en specifaj statoj. Por fari tion, klaso povas difini specialan metodon nomiĝas __init__.

Se klaso difinas metodon __init__, la kreado de nova instanco aŭtomate plenumas la metodon __init__ por tiu instanco.

En la Sako ekzemplo (supre), ni povas aldoni:

def __init__(mem):
    mem.malplenigu()

Sekve, kiam ni kreas novan instancon

s = Sako()

la funkcio malplenigu kreas malplenan liston datumoj.

Kompreneble, la metodo __init__() povas havi argumentojn. Tiuokaze, argumentoj donitaj dum la instanca kreado estas senditaj al la metodo __init__, ekz.:

>>> class Komplekso:
... def __init__(mem, reelparto, imagparto):
...     mem.r = reelparto
...     mem.i = imagparto
...
>>> x = Komplekso(3.0,-4.5)
>>> x.r, x.i
(3.0, -4.5)

12.2.4. Datumaj atribuoj

Datumaj atribuoj estas haveblaj al metodoj kaj ordinaraj uzantoj. Pitono ne havas kapablecon por tute kaŝi datumojn. Klasaj datumaj atribuoj estas netuŝeblaj nur per konsento.

Uzantoj devus zorge uzi datumojn atribuojn. Uzantoj povas korupti metodojn per disrompo de iliaj datumaj atribuoj. Rimarku, ke uzanto povas aldoni iliajn proprajn datumajn atribuoj al instanca objekto sen malfavora rezulto, se nomaj konfliktoj estas evitindaj. Bona noma sistemo ŝparos multajn problemojn ĉi tiajn (vidu Nomado de instancaj objektoj sube).

Ne ekzistas mallongigojn en metodoj por referenci datumajn atribuojn aŭ aliajn metodojn. Ĉi tiu manko pligrandigas la klarecon de metodoj. Ne estas ebleco de konfuzo inter lokaj variabloj kaj instancaj variabloj.

Bona regulo: Ne rekte tuŝu datumajn atribuojn. Anstataŭe, uzu metodon resendi aŭ ŝanĝi valoron.

12.2.4.1. Nomado de instancaj objektoj

Datuma atribuo subpremas samnoman metodan atribuon. Por eviti akcidentajn nomajn konfliktojn, uzu ian noman ŝablonon kiu diferencigas la nomojn. Ekzemploj - majuskligu metodajn nomojn aŭ prefiksu nomojn de datumaj atribuoj kun malgranda unika ĉeno (eble unu substreko) aŭ uzu verbojn por metodoj kaj substantivojn por datumaj atribuoj.

12.2.4.2. Klasa ekzemplo

Starigi generalan stakan kapablecon per klaso (el “Programming Python”, verkita de Mark Lutz):

class Stako(object):
    def __init__(mem):
        #starigu stakon(liston)por ĉiu instanco
        mem.stako = []

    def puŝu(mem, objekto):
        #aldonu al la fino de la stako(listo)
        mem.stako.append(objekto)

    def elprenu(mem):
        supro = mem.stako[-1] #supro = la lasta
        del mem.stako[-1]     #forigu la lasta
        return supro

    def malplena(mem):
        return not mem.stako

La klaso povas esti en importota modulo nomita StakoKlaso:

>>> from StakoKlaso import Stako
>>> astako_1 = Stako()    #kreu unuan stakan instancon
>>> stako_1.puŝu('ĉeno')  #aldonu stakan eron
>>> stako_1.puŝu(123)     #aldonu denove

>>> stako_1.stako
['ĉeno', 123]
>>> stako_2 = Stako()     # dua instanco

>>> stako_2.puŝu(stako_1.elprenu()) #movu
>>> stako_1.stako, stako_2.stako
(['ĉeno'], [123])
>>> if not stako_1.malplena():
...     a = stako_1.elprenu()
...
>>> a
'ĉeno'