Övningstillfälle 5 Lite kort repetition om klasser och objekt class EnKlass(KlassAttÄrvaFrån): klassvariabel = 5 def init (self) KlassAttÄrvaFrån. init (self) self.instansvariabel = 10 s = 'klassvariabel= '+str(enklass.klassvariabel) s +=', instansvariabel='+str(self.instansvariabel) Först definitionen av klassens namn och vad den ärver. Vi får alla metoder som finns i superklassen. Sedan kommer vanligen eventuella klassvariabler, som alltså finns en gång i klassen, och alltså är gemensam för alla instanser! Konstruktorn Man bör anropa superklassens init. Sätter en instansvariabel, som alltså kan ha olika värde för varje instans Vi överskuggar str () metoden och returnerar klassvariabelns och instansvariabelns värden i strängform Exempel: "Bil" (typ av fordon), "Mazda 6" (bilmodell), "Pappas Mazda 6" "Bil" är ett allmänbegrepp; det är "Mazda 6" också, men det är ett mer specifikt begrepp. "Pappas Mazda 5" är däremot (en bestämd beskrivning av) ett enskilt ting. 1 Begreppet "Bil" motsvaras i Python (lämpligen) av en klass; "Mazda 6" (modellen) motsvaras av en subklass som ärver av superklassen Bil. "Pappas Mazda 6" är en enskild bil, eller ett enskilt objekt, dvs. (lämpligen) en instans av Mazda6 (och därmed samtidigt en instans av Bil). Egenskap Aktuell mängd bensin Bensintankens kapacitet Plats (Location) Antal dörrar Ägare såvida vi inte menar bilmodellens "ägare" Antal växlar Hos vad? och bör därför vara instanseller klassvariabel i vilken klass? Är en egenskap hos alla enskilda bilar (instanser), men olika bilar av samma modell har inte mängden bensin gemensamt => instansvariabel i Bil Är visserligen en egenskap hos varje enskild bil; men alla bilar av samma modell har samma kapacitet => egenskap hos modellen => klassvariabel i Mazda6 Hos alla enskilda bilar, alltså en instansvariabel i Bil. Alla enskilda bilar har ett visst antal dörrar, men alla bilar av en viss modell har samma antal dörrar, alltså en klassvariabel i Mazda6 Hos alla enskilda bilar och är därmed lämpligen en instansvariabel i Bil Alla Mazda 6 "ägs" av Mazda => egenskap hos modellen => en klassvariabel i Mazda6 Egenskap hos modellen, dvs. klassvariabel i 1 Förhållandet mellan begrepp och de enskilda ting som faller under ett begrepp är ett gammalt filosofiskt problem. Här intresserar vi oss dock för hur detta praktiskt berör klasser respektive objekt.
Mazda6 Antal sittplatser Hos modellen => klassvariabel i Mazda6 Max tillåtet antal passagerare Modellen => klassvariabel i Mazda6 såvida vi inte menar för alla bilar Egenskap hos alla bilar i allmänhet (i Sverige får en bil ha max förare + 8 passagerare) => klassvariabel i Bil Minimikrav som t.ex. krav på belysning, Alla bilar i allmänhet (alla bilar ska uppnå de styrning, speglar, bromsar lagstadgade kraven för bilfordon) => klassvariabler i Bil Kört antal mil (milräknare) Hos alla enskilda bilar => instansvariabel i Bil Registrerings-id (registreringsskylt) Hos alla enskilda bilar => instansvariabel i Bil Modellbeteckningen "Mazda 6" Hos modellen => klassvariabel i Mazda6 Benämningen "Bil" Alla bilar i allmänhet kan benämnas "Bil" => klassvariabel i Bil Kommentar: Det är inte alltid självklart om en egenskap ska vara instans- eller klassvariabel, ej heller i vilken klass den ska finnas. Det beror nämligen på hur man har tänkt sig att klasshierarkin kan kommat att byggas ut. Kan man t.ex. tänka sig Lådbil som en subklass till Bil? Om ja, så blir en hel del av variablerna ovan irrelevanta för den klassen och man kanske får tänka om. Fråga 1: Om du är osäker på om en variabel bör vara en instans- eller klassvariabel, vad bör du välja? (Eller omvänt, vilket val kan leda till störst problem?) 2 Fråga 2: En av fördelarna med klassvariabler är att de tar mindre plats. Antag att du har en list-variabel som innehåller 1000 heltal. Hur mycket minne kommer gå åt om listan är en klassvariabel? Hur mycket går åt om varje instans har en separat (dvs. inte referenser till en och samma lista) lista och antalet instanser är 10.000? 3 class Car: MAX_PASSENGERS = 9 # Inklusive förare TERM = 'Bil' # benämning def init (self, owner, plate): self.owner = owner self.plate = plate self.milage = 0 Definiera klassen Bil Med bl.a. information om tillåtet antal passagerare och benämning (konstanta klassvariabler) Konstruktor 2 Du bör låta den vara en instansvariabel, då detta aldrig kan leda till direkt logiska fel som hindrar programmet från att fungera, dvs. det gör inget (logiskt sett) att vi lagrar en variabel onödigt många gånger genom att lägga den i varje instans. Om variabeln däremot kommer anta olika värden för olika instanser, blir det helt fel om vi hanterar det genom en klassvariabel som är gemensam för alla instanser. 3 Ett heltal tar upp minst två byte. Klassvariabeln upptar då ca 2 kilobyte, medan instansvariablerna tillsammans upptar > 2 kilobyte gånger 10.000 instanser = 20 megabyte (ganska onödigt om vi vet att alla instanser kommer att ha exakt samma lista). 4 En sådan metod kallas abstrakt, och därmed borde klassen också anses vara abstrakt. Typiskt för statiska OOspråk som Java, är att det inte skulle vara möjligt/tillåtet att skapa instanser av en sådan klass, men i Python går det bra, men om metoden anropas, riskerar vi att krascha! Dessutom finns ingen mekanism i Python som verkligen tvingar programmeraren att implementera ofullständiga metoder. Mönstret går igen ganska ofta: Python är ett "slappt" språk, som låter programmeraren slappa, men riskerar därmed att släppa igenom osäkra och dåligt skrivna program. Läs http://code.activestate.com/recipes/266468/ för att se ett märkligt försök att på egen hand simulera abstrakta metoder. Eventuellt är detta löst i Python v. 3?
self.fuel_tank = 0 return Car.TERM def get_owner(self): return self.owner def get_nr_doors(self): # Gör ingenting och ska därmed # implementeras av subklasser pass class Mazda6(Car): GEARS = 6 NR_DOORS = 4 MAX_TANK = 50 def init (self, owner, plate): # Obs anropa superklassens konstruktor Car. init (self, owner, plate) def get_nr_doors(self): return NR_DOORS return 'En ' + str(mazda6.nr_doors) + \ ' dörrars ' + Car. str (self)+ \ ', som är en Mazda 6' + \ ', och ägs av ' + self.get_owner() b = Car('Olle', 'ABC 345') print 'b är en', b m = Mazda6('pappa', 'XYZ 123') print 'm är en', m Sträng-metod get-metoder (variablerna bör vara privata) Obs, en sådan här definition kan vara bra för att tvinga alla subklasser att leva upp till ett visst gränssnitt, men gör klassen själv ofullständig. 4 Definiera klassen Mazda6 som ärver av Car Med några klassvariabler (som lämpligen betraktas som konstanter - VERSALER) Konstruktor, som i sin tur anropar superklassens konstruktor! VIKTIGT!! Obs att vi då anger argumentet self. 5 Implementation av get_nr_doors() Överskugga sträng-metoden med en specialiserad metod Skapa instanser! b är en Bil m är en En 4 dörrars Bil, som är en Mazda 6, och ägs av pappa Exempel på metoder som finns i klassen object och som vi kan överskugga: object. init (self[,...]) 6 Called when the instance is created. The arguments are those passed to the class constructor expression. If a base class has an init () method, the derived class s init () method, if any, must explicitly call it to ensure proper initialization of the base class part of the instance; for example: BaseClass. init (self, [args...]). object. str (self) Called by the str() built-in function and by the print statement to compute the informal string representation of an object. object. cmp (self, other) Called by comparison operations if rich comparison (see above) is not defined. Should return a negative integer if self < other, zero if self == other, a positive integer if self > other. If no 5 Det kan tyckas bisarrt att vi helt plötsligt, till skillnad från förut, ska ange argumentet self. Det har att göra med hur Python fungerar och att vi anropar superklassens init som en klassmetod (inte instansmetod). 6 http://docs.python.org/reference/datamodel.html (ganska tung artikel men säger mycket om hur Python fungerar)
cmp (), eq () or ne () operation is defined, class instances are compared by object identity ( address ) object. hash (self) Called by built-in function hash() and for operations on members of hashed collections [ ]. hash () should return an integer. The only required property is that objects which compare equal have the same hash value; [ ] 7 En av de diagnostiska datorprovsfrågorna på hemsidan, 8 illustrerar detta tydligt: Om vi i definitionen av klassen Abborre skriver class Abborre(Fisk): innebär det att A: Klassen Abborre ärver attribut och metoder från klassen Fisk. B: Klassen Fisk ärver attribut och metoder från klassen Abborre. C: Klassen Abborre ärver enbart attributen från klassen Fisk. D: Klassen Fisk ärver enbart metoderna från klassen Abborre. X: Inget av ovanstående. Jag kan avslöja att enligt facit är rätt svar A, och för vilket vettigt OO-språk som helst skulle man också otvetydigt svara A men är det här verkligen helt sant för Python? För metoderna är det onekligen så, men hur är det med instansvariabler? Låt oss se efter class Fisk: Definiera klassen Fisk def init (self): self.location = 'Mälaren' Med instansvariabel location class Haj(Fisk): def init (self): Fisk. init (self) self.species = 'Haj' class Abborre(Fisk): def init (self): self.species = 'Aborre' f = Haj() print f.location f = Abborre() print f.location Definiera klassen Haj som ärver av Fisk Anropa superklassens init Lägg till en instansvariabel Abborre ärver också av Fisk. Strunta i att anropa superklassens init Lägg till en instansvariabel Provkörning Fungerar utmärkt (skriver ut Mälaren) Detta borde väl också fungera, Abborre har ju ärvt Fisk. Men NEJ! Det kraschar med "location not defined". Instansvariabeln har alltså INTE ärvts! Ovanstående behöver förstås en kommentar. Hur kommer det sig att location inte är definierad i Abborre? Jo, för att instansvariabeln skapas dynamiskt först när konstruktorn / init verkligen körs. Och vad händer när vi instansierar Abborre? Jo, konstruktorn för Abborre körs, men konstruktorn i Fisk körs INTE! För att den ska köras måste vi explicit säga till om det, så som illustrerat i klassen Haj. Jag repeterar vad som står i dokumentationen för init: 7 hash () är intimt kopplad till eq () (eller cmp ()). Obs att objektet ska vara omuterbart! 8 Första frågan, prov nr 5.
If a base class has an init () method, the derived class s init () method, if any, must explicitly call it to ensure proper initialization of the base class part of the instance; for example: BaseClass. init (self, [args...]). Den här märkligheten uppstår pga. Pythons dynamiska natur (instansvariablerna skapas först i init). Även i andra OO-språk, som Java, är det av intresse att anropa superklassens konstruktor för att få hjälp med initialiseringen. Däremot så ärvs instansvariablerna i den mening att de alltid existerar i subklassen (men måhända oinitialiserade). Vi kan konstatera att enbart utifrån raden "class Abborre(Fisk):" kan vi faktiskt inte veta om instansvariablerna ärvs! Fler frågor Vad skapas av satsen nedan? <pre> barnfilm = Film("Lilla Kycklingen", 81) <s1> A: Ett objekt med två klassattribut. </s1><s2> B: Ett objekt med två statiska metoder. </s2><s3> C: Bara ett objekt, vi kan inte få ut information om objektets attribut eller metoder från ovanstående sats. </s3><s4> D: Ett objekt med två metoder. Betrakta följande kod: class Skolan(object): def init (self,adress): self.adress = adress def ret_adress(self): return self.adress class Tekniska(Skolan): return "teknis" s=skolan("osquars backe 2") k=tekniska("lindstedsv 5") print k.ret_adress() Vad kommer att hända när programmet körs? <s> <s1> A: Skrivs på skärmen "Osquars backe 2" </s1><s2> B: Programmet kommer att krascha för att metodern <code>ret_adress</code> finns inte definierad i klassen Tekniska. </s2><s3> C: Skrivs på skärmen "teknis" </s3><s4> D: Skrivs på skärmen "Listedsvägen 5" Varför är det bra att skapa egna moduler?
<s1> A: Modulerna kan återanvändas i andra program. </s1><s2> B: Man slipper använda variabler. </s2><s3> C: Felsökning förenklas. </s3><s4> D: Progrmmet blir överskådligare. Anta att vi håller på att definiera en metod i klassen Semla, där <pre> class Semla(Bakverk): Hur gör man för att anropa en metod i Bakverk inifrån Semla? <s1> A: Importerar en modul. </s1><s2> B: Definierar en ny klass. </s2><s3> C: Tar bort parametrarna i metodanropet. </s3><s4> D: Använder funktionen super. Tkinter och grafik (s. 291 ff.): from Tkinter import * import random root = Tk() c = Canvas(root) x1, y1 = 0, 0 # från-koordinater x2, y2 = None, None # till-koordinater for i in range(100): x2 = random.randrange(100) y2 = random.randrange(100) c.create_line(x1, y1, x2, y2) x1, y1 = x2, y2 c.pack() root.mainloop() print 'slut' Skapa rotobjektet Skapa en Canvas = rityta (obs det genomgående mönstret för s.k. widgets i Tkinter: första argumentet är en referens till den omgärdande behållaren) Rita en linje (slumpmässig slutpunkt) Slutpunkten blir vår nya utgångspunkt pack(), "placerar" objektet Här börjar Tkinter jobba med att framställa grafiken. Problem: anropet till mainloop() "blockerar", 9 vilket gör att 'slut' inte skrivs ut förrän vi stänger fönstret. Vad göra? Det finns två lösningar till ovanstående problem: 1) Man skapar egna s.k. "trådar" som i princip är parallella exekveringar, där vi alltså kan göra något samtidigt som mainloop() kör. (Kanske inte så aktuellt till att börja med) 2) Vi definierar s.k. call-back-metoder, som anropas av systemet (Python), och därmed ger oss kontroll när användaren har gjort något intressant på skärmen, t.ex. tryckt en knapp. Kallas även "Event-Driven Programming". Exempel med alternativ 2: 9 Man säger att ett anrop "blockerar" om programexekveringen stannar. Anrop till input() är ett annat exempel.
from Tkinter import * class App: def init (self, master): frame = Frame(master) frame.pack() self.button = Button(frame, text="quit", fg="red", command=master.destroy) self.button.pack(side=left) self.hi_there = Button(frame, text="hello", command=self.say_hi) self.hi_there.pack(side=left) def say_hi(self): print "hi there, everyone!" root = Tk() app = App(root) root.mainloop() En klass som representerar våra miniapplikation Den har en ram med en quit-knapp med callback till master.destroy (stänger applikationen) 10 och en hej-knapp med callback till self.say_hi 11 Här definierar vi say_hi som alltså kommer att anropas när användaren klickar hejknappen! Detta anrop blockerar som vanligt, men knapparna kommer att ge oss nödvändig kontroll när de trycks! 10 Se http://www.daniweb.com/forums/thread66698.html# för destroy, men det kanske finns bättre sätt? 11 Obs, när funktionens namn inte följs av parenteser så är det en metod/funktions-referens och inte ett anrop (själva anropet gör Tkinter när knappen trycks). Man kan göra anropet explicit via ett lambdauttryck, men det kanske känns ännu konstigare: command=lambda:self.say_hi().