Övningstillfälle 4 Klasser och objekt (s. 221 ff.) Syfte 1: En naturlig fortsättning på koncepten abstraktion och inkapsling! Funktion (återanvändning av skyddad, säker och testad kod) Modul (återanvändning på filnivå) Klass (återanvändning av en skyddad 1 mängd variabler och funktioner som representerar någon typ av objekt) Syfte 2: Att fokusera på de typer av objekt ett program ska hantera är designmässigt naturligare än att fokusera på en mängd funktioner. Möjliga typer av objekt kan vara: en "anställd", en "bil", ett "rymdskepp" (i ett spel t.ex. Obs att objekten inte måste motsvara fysiska objekt, tänk t.ex. "lager" (t.ex. list; dictionary), "schema", "diagram", "bankkonto", "faktura", "särfall" (ja, exceptions är objekt). Terminologi: En klass är en ritning / beskrivning / mall för en typ av objekt (i själva verket så ligger innebörden av "klass" väldigt nära innebörden av "typ", och är i flera avseenden t.o.m. ekvivalent. För övrigt är klass-konceptet en utveckling av ett äldre koncept som kallades ADT = Abstrakt datatyp). Enligt CS' ordlista är en instans "ett objekt som förverkligar egenskaperna hos en klass". 2 Med den definitionen vill man uppenbarligen skilja på objekt av en klass och objekt i allmänhet (där man kan tänka sig att även int:s, float:s etc ingår). Men vanligen syftar objekt på just objekt av en klass, varför man även kan säga att ett objekt är en instans av en klass. Från klassen (=beskrivningen) kan man generera objekt (=instanser av klassen), och det är vanligt att man säger att man "instansierar" klassen. (Redan "instans" känns som svengelska, men "instansiera" är det ingen tvekan om i Sverige är det ju rätt vanligt att ta engelska benämningar rakt av vilket iofs ibland kan vara en fördel! Benämningen kommer från engelskans "instance" där det helt enkelt betyder "exempel". Jfr. "exemplifiera".) Klasser och objekt står alltså i ett potentiellt "ett till många"-förhållande: EnKlass Instans 1 Instans 2 Instans 3 Instans 4 Objekt kan innehålla två saker: instansvariabler ("tillstånd") och instansmetoder ("beteende"). En instansvariabel är en variabel som tillhör ett objekt. En metod är en funktion som tillhör ett objekt. Vi har redan sett exempel på detta. Ta t.ex. en lista: den har ett tillstånd (som bestäms av dess innehåll och som vi kommer åt med indexeringsoperationen [] 3 ); och den har metoder, t.ex. lista.append(x). Obs punkt-notationen: för klasser och objekt kommer vi åt både variabler och metoder med punktnotationen. Allt som kommer efter en punkt brukar kallas för attribut. I uttrycket O.x är x alltså ett attribut tillhörande O. Obs att man ofta slarvar med språkbruket och utelämnar "instans"-prefixet, och bara säger variabler och metoder. I de flesta sammanhang finns kanske ingen risk för missförstånd, men 1 Skyddet uppstår egentligen först med att man använder s.k. privata variabler! 2 http://cstjanster.idg.se/sprakwebben/ord.asp?ord=objektorientering 3 Jag misstänker för övrigt att indexeringsoperationen bara en syntaktiskt dold form av ett metodanrop
vi kommer senare troligen gå in på något som kallas klassvariabler och klassmetoder (som alltså inte är samma sak som instansvariabler och instansmetoder!) Några exempel (fler exempel finns på http://docs.python.org/tutorial/classes.html) Några exempel i Python-tolken >>> t = object() t sätts till ett nytt objekt (av klassen object) Jfr. lista = list() som skapar en ny lista >>> t.x = 'En instansvariabel' t.x sätts till en sträng >>> t.x evaluera t.x 'En instansvariabel' Nu definierar vi en klass själva, och anropar den sedan i tolken [I filen "MyClass.py" (t.ex.)] 4 class MyClass: """A simple example class""" nr = 12345 def f(self): return 'hello world' >>> import MyClass >>> m = MyClass() >>> m.nr 12345 >>> print m.f() hello world Definiera klassen MyClass Doksträng (poppar upp vid instansiering) MyClass har en (klass)variabel nr MyClass har en metod (funktion) f() som returnerar strängen Men vad är parametern self? Jag återkommer till det! Nu kör vi i tolken Modulen (!) MyClass importeras (och därmed även klassdefinitionen) Skapa en ny instans (objekt) av MyClass Evaluera instansvariabeln i (av m) Skriv ut resultatet av metoden f() (av m) Men nu görs anropet utan något argument för parametern self ännu skummare Ok, dags att kommentera I Python MÅSTE alla metoder ha self som första parameter! Parametern self refererar till det egna/ägande objektet och via den får vi tillgång till alla instansvariabler och instansmetoder (s. 226, 231). Men det är däremot Python självt som tar hand om att skicka det faktiska argumentet till parametern self. Därav detta märkliga att man anger en parameter som man aldrig skickar. Python skickar den parametern "bakom kulisserna". 5 class Counter: c = 0 def get_next(self): self.c += 1 Definiera en räknar-klass med instansvariabel c (initialiserad till 0) Definiera en metod som returnerar nästa tal 4 Finns det en risk att låta modul- och klassnamn vara samma ifall man gör from module import *? 5 Teknisk kommentar som säkerligen är överkurs: Det känns som om man låter programmeraren på detta sätt underlätta implementationen av Python-tolken (och kompilator) att göra transformationen "ett_objekt.en_metod()" "en_metod(ett_objekt)". Men hur kan man motivera att programmeraren ska behöva bry sig om den underliggande implementationen? Kanske finns någon bra förklaring? Man kan jämföra med Java där det inte finns behov av någon parameter "self" eftersom det istället finns ett nyckelord "this" som gör samma sak. (I Java måste dock all kod finnas i en klass, vilket gör att man slipper problemet med att this/self kunde vara odefinierad.)
def get_previous(self): self.c -= 1 (Obs hur vi kommer åt c via self!) Definiera en metod som returnerar föregående tal Vad händer om vi refererar till c istället för self.c? 6 Ofta vill man initialisera ett objekts instansvariabler med vissa värden. Av den anledningen finns möjligheten att skriva en initialiseringsmetod som tar emot initialvärden vid instansieringen (s. 227 f.). Denna metod motsvarar det som kallas "konstruktor" i andra objektorienterade språk (dvs. den metod som "skapar" objekten/instanserna): class Counter: def init (self, start, delta): self.c = start self.d = delta def get_next(self): self.c += d def get_previous(self): self.c -= d ----------------------- >>> cc = Counter(10, 5) >>> print cc.get_next() 15 Definiera en räknar-klass med init-metod (init omgärdat av dubbla understreck) som tar två(!) argument: start, som är det värde räknaren kommer att börja på, och delta = hur stora steg räknaren tar. Definiera en metod som returnerar nästa tal Spara det nya värdet Och returnera det Definiera en metod som returnerar föregående värde Skapa ett Counter-objekt med initialvärde 10, och steglängd 5 Vad är nästa värde? (Obs igen att vi inte anger något argument för parametern self) Aha Ett par saker att lägga märke till: - get_next() påverkar en instansvariabel enligt en bestämd regel - Om det är viktigt att användaren av vår klass inte själv går in och ändrar direkt på instansvariabeln c (vilket det ofta kan vara), så finns det skäl att tvinga användaren att gå via metoder (inkapsling!). Finns skäl att återkomma till detta och s.k. privata instansvariabler! Critter (eller Tamaguchi?) (s. 222 ff.): class Critter: def init (self, myname): self.name = myname def talk(): print 'Hej, jag heter', name >>> bubba = Critter("Bubba") >>> bubba.talk() Hej, jag heter Bubba Definiera klassen Critter Kom ihåg: metoden tar ett(!) argument, som sparas i objektet (Vad skulle hända om vi bara skrev name = myname, utan self?) Skapa en instans av Critter med init-param Anropa talk() på objektet 6 Python antar att c är en lokal variabel, varför vi skulle få "reference before assignement"-fel
Menystrukturer (relevant inför lab 5): Ibland finns det skäl att göra textbaserade program med menyer (eller?). Programmet befinner sig i ett tillstånd som motsvaras av vilken meny som senast skrevs till skärmen. Men hur ska man "få in" dessa tillstånd i ett program? Ofta är det en fördel att ordna menyer i en hierarkisk struktur, dvs. där en meny kan leda till flera submenyer, men där en submeny alltid leder tillbaks till en unik övergripande meny (en s.k. "trädstruktur" med "förgreningar" ännu ett exempel på ett "ett till många"-förhållande ;). Annars finns risk för att användare går vilse bland menyerna. Om antalet menyer är rimligt litet kan man "hårdkoda" tillstånden m.h.a. funktioner som innehåller en "while True:". Den konstruktionen gör att programmet stannar i en meny ända tills funktionen antingen anropar en annan funktion, eller gör en return (vilket avbryter en pågående while-sats). (Några av er kanske utnyttjade möjligheten att göra "while true:" i lab 4, uppgift 1, i kombination med break-instruktionen). Att programmet befinner sig i en menyfunktion motsvarar då att programmet befinner sig i det tillstånd som menyn representerar. Detta är något snyggare och stiligare än att spara tillståndet i en variabel och sedan köra med en if-sats. Speciellt om menystrukturen skulle haft fler nivåer en två, så lär det bli ganska fult att samla alla menyer i en och samma funktion, och särskilja dem med en megastor if-sats. def huvud_meny(): while True: [Skriv ut huvudmenyn] [Om användaren valde att gå till en submeny, som avser objekt x, anropa funktionen som tar hand om submenyn med argumentet x] def en_submeny(x): while True: [Skriv ut submenyn] [Om användaren valde att utföra något på x, utför det] [Annars, om den valde att gå tillbaks till huvudmenyn, gör return] Ett exempel i pseudokod Obs att om en submeny behöver tillgång till ett specifikt objekt: skicka som argument hellre än använda global variabel! huvudmeny(): om användaren väljer submenyn: anropa submeny() submeny(): om användaren väljer huvudmenyn: anropa huvudmeny() Fråga: Vad är problemet ifall man enbart använder sig av nya funktionsanrop och aldrig av return, enligt pseudokoden till vänster? (För att förstå det behöver man veta vad som händer "under ytan" vid ett funktionsanrop).
Övning: Flygflotta Tänkt scenario: Välkommen flygbolagssimulatorn V.1 Det är dag nr. 0 Huvudmeny: 1. Översikt flygflotta 2. Köp flygplan 3. Inspektera flygplan 4. Personaladministration 5. Simulera ett dygn 6. Avsluta Välj: 1 Kapital: 700 Mkr Antal flygplan: 2 Antal anställda: 7 Id, modell, år, flygplats, status ------------------------------------ F1, Boeing 737, 1994, ARN-LHR, OK F2, MD-90, 1997, ARN-JFK, OK 0 Varning(ar) Tryck enter för att fortsätta [Huvudmenyn skrivs ut igen] Välj: 3 [=inspektera flygplan] Vilket flygplan: Id, modell, år, flygplats, status ------------------------------------ 1. F1, Boeing 737, 1994, ARN-LHR, OK 2. F2, MD-90, 1997, ARN-JFK, OK Välj: 1 Inspektionsmeny för: F1, Boeing 737, 1994, ARN-LHR, OK Besättning: 5 st Nästa översyn senast: Dag 15 1. Utöka besättning 2. Minska besättning 3. Utför översyn 4. Ändra flygdestination 5. Sälj flygplan 6. Gå till huvudmenyn [Användaren väljer 2] Vem ska tas bort? 0. Eva 1. Måns [Osv] 5. Gå tillbaks till föregående meny [Användaren gör en borttagning och går till föregående meny, och väljer "Utför översyn"] Utför översyn av F1, nästa översyn ska ske dag nr 50 [Användaren går tillbaks till huvudmenyn och väljer "köp flygplan"] Marknaden har följande att erbjuda: 0. MD-90, 1998, för 300 Mkr [Användaren gör ett köp, går till huvudmenyn och väljer "Översyn flygflotta"] Kapital: 400 Mkr Antal flygplan: 3 Antal anställda: 7 Id, modell, år, flygplats, status ------------------------------------ F1, Boeing 737, 1994, ARN-LHR, OK F2, MD-90, 1997, ARN-JFK, OK F3, MD-90, 1997, undefined, VARNING: odefinierad destination, för lite besättning 1 varning(ar)
Menystrukturen: Huvudmenyn Köp flygplan Inspektera flygplan Simulera Välj flygplan Utöka besättning Minska besättning Ändra destination Klasserna: Flygbolaget Egenskaper: - Alla flygplan - Alla anställda - Kapital --------------------------------------------------- Beteenden: - Lägg till och ta bort flygplan (= köp/sälj) - Lägg till och ta bort anställda (anställ/avskeda) - Öka (eller minska) kapitalkontot - Simulera ett dygn Flygplan Egenskaper: - Id, Modell, Konstruktionsår, Destination, Status - Besättning (den konkreta besättningen) - Specificerade min-max krav på antal i besättning - När nästa översyn måste genomföras ----------------------------------------------------- Beteenden: - ta bort från besättning, lägg till besättning - ändra destination - utföra flygningar, om planets status är OK Allmänna funktioner som har med flyg att göra (ligger inte in någon specifik klass men hör till flygmodulen): - Flygplansgenerator (som kan skapa flygplan som marknaden har att erbjuda) - Persongenerator (som kan generera tillgänglig arbetskraft) Huvudprogrammet: Globala variabler: Ett flygbolag ---------------------------- Globala funktioner: Menyfunktioner Inläsningsfunktion "Flight.py" class Airline: