Naloga

Želvi dodaj metodi

  • stamp(), ki naredi odtis želve, torej izriše želvo, ki ostane izrisana tudi, ko gre želva naprej), in
  • clearStamps(), ki pobriše vse odtisnjene želve s trenutne slike

Program  t.forward(10) t.stamp() t.left() t.forward(100) t.turn(45) t.forward(20) t.stamp() t.right() t.forward(40) t.left() t.forward(40) t.right() t.forward(40) t.stamp() t.right() t.forward(40) t.hide()   nariše  . Če nato rečemo clearStamps(), želvice izginejo: 

Zahteva: metodi morata pravilno delovati tudi, če imamo več želv, ki se odtiskujejo in brišejo svoje odtise!

Namig: stamps naj vse, kar riše, shranjuje v seznam, clearStamps pa pobriše narisano.

Tolažba: metodi imata po štiri vrstice, in še v __init__ dodamo eno, pa smo.

Rešitev

Najprej je bilo potrebno v funkcijo __init__ dodati seznam, v katerega bomo shranjevali odtise želv. Dodamo, recimo

self.stamps = []

Funkciji za odtis želve in brisanje odtisov sta takšni

def stamp(self): angle = radians(90 - self.angle) body = risar.krog(self.x, self.y, 5, risar.zelena, 3) head = risar.krog(self.x+5*cos(angle), self.y-5*sin(angle), 2, risar.zelena, 3) self.stamps.append(body) self.stamps.append(head) def clearStamps(self): for stamp in self.stamps: stamp.hide() self.stamps = []

(Funkcija je dve vrstici daljša, kot sem obljubil, ker oba kroga zaradi pregledanosti najprej spravim v body in head ter od ondod v seznam. Šlo bi tudi self.stamps.append(risar.krog(self.x, self.y, 5, risar.zelena, 3))...)

Način, na katerega brišemo odtise, sicer ni čisto pravilen, saj so odtisi še vedno na sliki, le vidni niso. Lepše bi bilo turtle.risar.widget.scene.removeItem(stamp), a to očitno presega namen vaje in količino informacije, ki sem vam jo dal.

Druge rešitve

Pogosto sem videval tole

def stamp(self): self.stamps.append(risar.krog(self.body.x(), self.body.y(), 5, risar.zelena, 3)) self.stamps.append(risar.krog(self.head.x(), self.head.y(), 2, risar.zelena, 3)) OK. S tem, ko namesto self.x jemljemo kar self.body.x() in tako naprej, se izognemo temu, da v okviru te funkcije računamo položaj glave in telesa. Ni mi všeč, nimam pa kakih otipljivih argumentov, zakaj. Najbrž je ravno tako OK.

Rešitev, ki mi res ni všeč, bila pa je precej pogosta, je tale

def stamp(self): stamp=Turtle() stamp.fly(self.x,self.y,self.angle) self.stamps.append(stamp) def clearStamps(self): for i in self.stamps: i.hide()

Razlog, da je ne maram, je v tem, da za seboj ne pušča le preprostih objektov, krogov, temveč kar cele želve. Njena prednost bi se pokazala, če bi se nenadoma premislili in želv ne bi več risali s krogi temveč s kvadrati: metode stamp nam kljub temu ne bi bilo potrebno spreminjati, saj ničesar ne riše sama. Slabost je v tem, da so želve bolj zapletena reč od krogov in požrejo več pomnilnika. Res pa nam je v tem primeru za teh par bajtov popolnoma vseeno. Moji argumenti proti tej rešitvi so bolj načelni kot praktični.

Nerodnosti

Pustite self.head in self.body pri miru! Načelen razlog je v tem, da dodajamo odtise in to nima zveze z glavo in telesom (razen tega, da slučajno enako izgleda). Praktičen je v tem, da se na ta način prehitro zaplezate in morate reševati probleme z želvo, ki izginja.

Tipičen primer takega gre nekako tako:

def stamp(self): self.stamps.append(self.body) self.stamps.append(self.head) self.body = risar.krog(0, 0, 5, risar.zelena, 3) self.head = risar.krog(0, 0, 2, risar.zelena, 3) self.update() Torej: trenutno telo in glavo želve shranimo v seznam, želvo pa narišemo znova. To ... nekako ni lepo, ker spreminjamo trenutno glavo v odtis. In stvari, ki so konceptualno zgrešene, se potem, ko programiramo naprej, pogosto izkažejo tudi kot praktično nerodne. Večina tistih, ki je goljufala na ta način, je tudi pozabila poklicati self.update(), tako da je bila želva do prvega premika celo narisana v zgornjem levem kotu.

Spolzka tla pa so tule. V __init__ dodamo

self.stamps=[(self.body,self.head)]

Metodo stamp napišemo kot

def stamp(self): self.body=risar.krog(0,0,5,risar.rdeca,3) self.head=risar.krog(0,0,2,risar.rdeca,3) self.update() self.stamps.append((self.body,self.head)) In zdaj imamo zmedo: zadnji element seznama stamps je trenutna slika želve. Se pravi: stamps vedno vsebuje vse odtise in še želvo za povrh. Na to moramo paziti pri brisanju, ki ga pravilno napišemo takole: def clearStamps(self): for a,b in self.stamps[:-1]: a.hide() b.hide() self.stamps=[] To sicer deluje, je pa kar nerodno.

Resne napake

Zelo narobe je tole

stamps=[] class Turtle: ... ... def stamp(self): stamp=Turtle() stamp.fly(self.x,self.y,self.angle) stamps.append(stamp) ... ... Seznam odtisov je lastnost želve, zato mora biti definiran znotraj razreda, ne izven njega! Če bi imeli več želv, bi si te delile skupni seznam, kar bi bilo narobe!

Poleg tega sem kot nepravilno rešene naloge štel tistih nekaj rešitev, ki so imele namesto metod funkcije, saj je bil cilj naloge učenje objektnega programiranja.

Izpeljevanje

Nekateri ste iz razreda Turtle izpeljali nov razred, ki zna delati odtise. To se pravilno naredi tako:

class TurtleStamp(Turtle): def __init__(self): Turtle.__init__(self) self.stamps = [] def stamp(self): .... in tako naprej, enako kot prej

Pomembno je torej, da ima izpeljani razred svoj __init__, ki pokliče podedovanega, nato pa nastavi self.stamps. O tem, kako se kliče podedovane metode, kadar jih povozimo z novimi, na prejšnjih predavanjih nisem govoril, saj ni bilo časa. Poleg tega način, na katerega sem to naredil zgoraj, ni več v modi, vendar onega, pravega, ne moremo uporabiti, ker bi bilo za to potrebno malenkost spremeniti razred Turtle. Kogar zanima, naj si ogleda; na predavanjih pa bomo že še omenili.

Nekateri so namesto, da bi definirali svoj __init__, naredili takole

class TurtleStamp(Turtle): stamps = [] def stamp(self): .... in tako naprej, enako kot prej

To ni prav, ker je v tem primeru stamps lastnost razreda in ne objekta. Tudi to je nekaj, o čemer na predavanjih nisem posebej govoril in niti ne nameravam; to je ekvivalentno razrednim spremenljivkam (class variable) v C++ in presega predavanja iz Programiranja 1. Za tiste, ki jih zanima, pa je tu primer. class A: l = [] def __init__(self): self.k = [] def dodaj(self, i): self.l.append(i) self.k.append(i)

Zdaj je l skupen vsem objektom razreda A, k pa ima vsak objekt posebej. Preverimo:

>>> a = A() >>> b = A() >>> >>> a.dodaj(42) >>> a.l [42] >>> a.k [42] >>> b.l [42] >>> b.k [] >>> >>> b.dodaj(43) >>> a.l [42, 43] >>> a.k [42] >>> b.l [42, 43] >>> b.k [43]
마지막 수정됨: 수요일, 14 12월 2011, 10:26 PM