Introduktion till programmering Föreläsning 12: Klasser och objekt 1 1
Vad är en punkt? Matematikerns definition: en position i ett (tvådimensionellt) plan, karaktäriserad av en x-koordinat och en y-koordinat. Notation: (x,y) och om p är en punkt kan vi skriva px och py för att beteckna respektive koordinat I Python: vi skulle kunna använda en tupel (x,y). Men i stället för px och py skulle vi få skriva p[0] och p[1]... I Python: vi kan använda ett dictionary {'x' : x, 'y' : y}. Men px och py måste då skrivas p['x'] och p['y']... I Python: vi kan definiera en klass Point, sådan att om p tillhör denna klass så kan vi skriva p.x och p.y för att beteckna respektive koordinat 2 2
Klasser Definition av klassen Point: class Point: x = 0.0 y = 0.0 Att skapa ett objekt (en instans) av denna klass: p = Point() Att komma åt attributen x och y: print "x is", p.x, "and y is", p.y Att uppdatera attributen: p.x = 1.0 p.y = 3.5 3 3
Om objekt Vi känner igen notationen p.x och p.y från importerade namn, t ex math.pi och string.lowercase Objekt skiljer sig dock från moduler i och med att vi kan skapa hur många punkter vi vill, men bara importera (säg) modulen math en gång import math pi 3.14159 e 2.71878 import math p1 = Point() math p2 = Point() x 2.56 p1 y 3.14159 p1.x = 2.56 p2 p1.y = math.pi y 0.0 x 1.0 p2.x = 1.0 y 0.0 y = p2.y 4 4
Om objekt Vi säger att våra punkt-objekt är instanser av klassen Point. Oftast används termen objekt just som synonym till klass-instans (och inte i betydelsen "data i allmänhet") Objekt liknar listor och dictionaries i så motto att de är uppdateringsbara och att alias-fenomen därför kan uppstå Speciellt: när vi skickar objekt som funktionsargument skickas egentligen bara adresserna till deras plats i minnet Ny definition av vår funktion distance från kap 5: def distance(p1, p2): dx = p2.x - p1.x dy = p2.y = p1.y return math.sqrt(dx*dx + dy*dy) 5 5
Samma eller lika? "Olle och jag har samma skor" Vi menar antagligen att skorna är lika i någon mening (storlek, färg, modell,...) Att Olles skor tappar sulan påverkar inte mina skor "Olle och jag har samma mamma" Här menar vi att en och samma person har två roller Om Olles mamma bryter benet så betyder det automatiskt att min mamma också gjort det "Olle och jag har samma vikt" Våra vikter har samma värde, ett decimaltal Men tal kan inte mutera, så samma eller lika kan kvitta 6 6
Som objekt Olle skor mamma vikt 95.2 storlek sula modell färg benstatus jag skor mamma vikt 95.2 storlek sula modell färg 45 'hel' 'xp' 'gul' 'ok' 45 'hel' 'xp' 'gul' 7 7
Samma eller lika objekt Varför är distinktionen mellan samma och lika viktig? Svar: därför att likhet mellan Python-objekt är definierad som samma plats i minnet! Testet Olle.mamma == jag.mamma ger alltså det förväntade resultatet True, medan Olle.skor == jag.skor blir False, oberoende av skornas färg, storlek, etc (se figur föregående sida) Observera att detta beteende skiljer objekt från andra muterbara data som listor och dictionaries, där == snarare betyder "lika" än "samma" 8 8
Exempel Likhetstest för listor och objekt >>> x1 = [1,2,3] >>> x2 = [1,2,3] >>> x1 == x2 True därför att 1==1, 2==2 och 3==3 >>> p1 = Point() >>> p1.x = 1 >>> p1.y = 2 >>> p2 = Point() >>> p2.x = 1 >>> p2.y = 2 >>> p1 == p2 därför att p1 och p2 ligger på olika plats i minnet False 9 9
Likhetsoperatorn Vi säger att likhetsoperatorn == testar grund likhet ("samma") för objekt (jämför objektens plats), och djup likhet ("lika") för övriga data (jämför datainnehållet) Om vi vill göra tvärtom, t ex testa djup likhet för objekt: def equalpoints(p1, p2): return (p1.x == p2.x) and (p1.y == p2.y) Eller testa grund likhet för (säg) listor: def samelist(x1, x2): return id(x1) == id(x2) Not: den inbyggda funktionen id() ger platsen i minnet för ett godtyckligt argument (Ett annat lämpligt namn för samelist vore isalias) 10 10
Exempel >>> x1 = [1,2,3] >>> x2 = [1,2,3] >>> p1 = Point() >>> p1.x = 1 >>> p1.y = 2 >>> p2 = Point() >>> p2.x = 1 >>> p2.y = 2 >>> print x1==x2, p1==p2 True False >>> print samelist(x1,x2), equalpoints(p1, p2) False True 11 11
Exempel (fortsättning från föregående sida) >>> z = x1 >>> print z==x1, samelist(z, x1) True True Ofånkomligt faktum: grund likhet implicerar djup likhet Dvs: om två variabler pekar ut samma plats i minnet så måste det innehåll man kan hitta via de två variablerna vara lika 12 12
En klass för rektanglar Låt oss definiera en rektangel som en punkt i planet (övre vänstra hörnet) samt en bredd och en höjd Uttryckt som en Python-klass: class Rectangle: corner = Point() width = 0.0 height = 0.0 Att skapa en instans: box = Rectangle() box.corner.x = 5.0 box.width = 100.0 box.height = 200.0 13 13
Tillståndsdiagram box corner width height 100.0 200.0 x y 5.0 0.0 Observera hur punktnotationen motsvarar att grafiskt följa pilar exempelvis box.corner.y 14 14
Att returnera nya instanser En funktion som beräknar centrum av en rektangel: def findcenter(box): p = Point() p.x = box.corner.x + box.width/2 p.y = box.corner.y + box.height/2 return p Centrum är en punkt, alltså returnerar vi en punkt! När findcenter returnerar försvinner visserligen namnet p, men inte adressen som p står för! Jämför: def fun(x): 27 "försvinner inte" bara för att p = 27 ett namn som står för 27 gör det return 27 15 15
Objekt är muterbara Vi använder mutering när vi ändrar attributens initialvärden: p = Point() p.x = 3.0 p.y = 9.4 Vi kan naturligtvis också låta objekt mutera längre fram: def growrect(box, w, h): box.width = box.width + w box.height = box.height + h Notera avsaknaden av return-sats funktionens enda syfte är att orsaka en effekt (förändringen av det okända objektet som här går under aliaset box) 16 16
Exempel Användning av growrect: bob = Rectangle() bob.width = 100.0 bob.height = 200.0... growrect(bob, 20, 30) Notera likheten mellan objekt och stack-ramar! Stackdiagram: top-level bob growrect box w h 20 30 corner width height 100.0 200.0 x y 5.0 0.0 17 17
Objektkopiering Vi har sett hur alias-effekten kan undvikas (om man så önskar) genom kopiering En kopia av listan x fås av uttrycket x[:] En kopia av dictionary d fås av uttrycket d.copy() En kopia av ett godtyckligt objekt (en klass-instans): >>> import copy >>> p1 = Point() >>> p2 = copy.copy(p1) >>> print p1==p2, equalpoints(p1, p2) False True 18 18
Objekt som innehåller objekt Kopiering av rektangeln bob: b2 = copy.copy(bob) bob corner width height 100.0 200.0 x y 5.0 0.0 100.0 200.0 corner width height b2 Lurigt resultat: objektet bob.corner är fortfarande samma objekt som b2.corner, trots kopieringen! Viktigt: funktionen copy.copy() utför grund kopiering Önskas kopior av allt: använd copy.deepcopy() 19 19
Exempel Ett annat sätt att "växa" rektanglar: returnera en kopia, utan sido-effekter def grownrect(box, w, h): import copy newbox = copy.deepcopy(box) newbox.width = newbox.with + w newbox.height = newbox.height + h return newbox Jämför med den tidigare growrect och observera return-satsen, samt att vi här inte orsakar någon mutering av den rektangel box står för (Not: förändringen av funktionsnamnet är avsiktlig!) 20 20