Lektion 8: Introduktion till klasser
Moment: | Klasser |
Begrepp som introduceras: | Klasser, __init__- och __str__-metoder |
Arbetssätt: | Arbeta gärna tillsammans med någon, men skriv egen kod. Diskutera med varandra!
Försök svara på de frågor som har givna svar innan du tittar på svaret. Fråga handledarna om det är något svar du inte förstår! |
Uppskattad arbetstid: | 4 timmar. |
Redovisning: | Ingen obligatorisk redovisning. |
Klasser
I alla tidigare lektioner har vi talat om objekt av olika slag (t.ex. turtle-objekt, list-objekt, sträng-objekt, tupel-objekt). I denna lektion ska vi beskriva hur man definierar egna klasser av objekt.
Objekt kan ha både egenskaper (data) och metoder. Ett turtle-objekt, till exempel, har bl.a. egenskaperna position, riktning, storlek och färg samt metoder, d.v.s. en sorts funktioner, som hämtar information om eller påverkar ett speciellt objekt (forward, left, ...).
En klass kan sägas vara en ritning eller beskrivning av vilka data som objekten ska ha och vilka metoder som ska finnas.
Exempel 1: Tärningar
Antag att vi vill skapa ett tärningsobjekt. En tärning har många egenskaper t ex färg, storlek, material, antal sidor och värde. Vilka egenskaper man väljer att representera beror naturligtvis på vad man ska ha objekten till. Här tänker vi att vi ska ha tärningsobjekten i någon form av spelsammanhang och väljer då egenskaperna antal sidor och aktuellt värde.
Vi vill också kunna skapa flera olika tärningar med olika antal sidor.
Vi börjar med att deklarera en tärningsklass och skapa två tärningsobjekt:
Kod | Utskrift |
---|---|
class Dice:
pass
d1 = Dice()
d2 = Dice()
print(d1)
print(d2)
|
<__main__.Dice object at 0x10099d048>
<__main__.Dice object at 0x10099d0f0>
|
-
Klasser definieras med ordet
class
följt av det namn vi vill ha på klassen samt ett kolon. - Klassnamn ska börja på stor bokstav.
-
Efter den raden följer en eller flera indenterade rader med klassens innehåll.
I detta fall har vi inte lagt till något från början.
Vi måste ändå ha en indenterad rad för vilket man använder nyckelordet
pass
till. -
Uttrycket
Dice()
skapar ett tärningsobjekt och returnerar en referens till det (jämför hur vi skapadeTurtle
-objekt). -
Även om utskriften kanske verkar kryptisk så antyder den i alla fall att vi har två olika
tärningsobjekt — de har ju olika adresser (koden efter
at
).
Vi har ännu inte lagt in några egenskaper som antal sidor och värden.
För att göra detta använder man en speciell ("magisk") metod med namnet__init__
.
I detta fall passar följande bra:
Kod | Utskrift |
---|---|
class Dice:
def __init__(self, sides):
self.sides = sides
self.value = random.randint(1, self.sides)
d1 = Dice(6)
d2 = Dice(12)
print('d1: ', d1.sides, d1.value)
print('d2: ', d2.sides, d2.value)
|
d1: 6 1
d2: 12 10
|
-
Eftersom klassen nu har ett innehåll tar vi bort
pass
. -
Initieringsmetoden (ibland kallad "konstruktor") måste heta
__init__
och haself
som första parameter. Därefter kan man lägga till de parametrar man vill. - Observera indenteringen!
-
Tilldelningssatserna
self.namn = värde
skapar egenskaper med angivet namn och värde.
Egenskaperna kallas också attribut eller instansvariabler.
Konstruktorn lägger alltså in två egenskaper varav värdet till den ena ges av en parameter och det andra av slumpfunktionen. - Observera att
sides
ochself.sides
är två olika saker. Den första är en parameter, den andra ett attribut.
Attributen lagras i objektet och existerar så länge objektet existerar. Parametern, däremot, finns bara med det namnet medan initieringsmetoden körs, precis som alla andra parametrar till funktioner och metoder.
Genom att spara parameterns värde i ett attribut finns värdet alltid tillgängligt i objektet.
Det hade gått lika bra att använda något annat namn på parametern. -
När man skapar objekten (t ex
Dice(6)
) anger man bara de argument som man själv lagt till. Parameternself
läggs till automatiskt.
Metoden __str__
I utskrifterna i ovanstående exempel har vi explicit fått hämta
värden på de olika egenskaperna.
I detta fall är det ju bara två stycken, men om det blir fler
vore det trevligt att ha ett standardsätt för att skapa en sträng från ett objekt.
Fördelen blir att man då exempelvis kan skriva print(d1)
och få objektet utskrivet på
ett snyggt sätt.
För detta använder man en annan "magisk" metod: __str__
Kod | Utskrift |
---|---|
class Dice:
def __init__(self, sides):
self.sides = sides
self.value = random.randint(1, self.sides)
def __str__(self):
return f'Sidor: {self.sides:2d}, värde: {self.value:2d}'
d1 = Dice(6)
d2 = Dice(12)
print('d1: ', d1)
print('d2: ', d2)
|
d1: Sidor: 6, värde: 6
d2: Sidor: 12, värde: 11
|
self
.
Som nämndes ovan måste man använda self.
för att komma åt attributen.
Strängens innehåll bestämmer man själv.
Egna metoder
Ovanstående metoder hade föreskrivna namn och betydelser. Man kan också lägga till egna metoder. I detta fall vore det bra med en metodroll
för att slå tärningen,
d.v.s. ge den ett nytt slumpmässigt värde:
Kod | Utskrift |
---|---|
class Dice:
def __init__(self, sides):
self.sides = sides
self.value = random.randint(1, self.sides)
def __str__(self):
return f'Sidor: {self.sides:2d}, värde: {self.value:2d}'
def roll(self):
self.value = random.randint(1, self.sides)
d1 = Dice(6)
d2 = Dice(12)
for i in range(5):
d1.roll()
d2.roll()
print(f'{d1.value:2d}, {d2.value:2d}')
|
6, 10
5, 12
5, 7
6, 3
3, 10
|
Exempel 2: Pokertärningar
När man spelar tärningspoker brukar man använda 5 tärningar som man slår på en gång. För att representera en uppsättning pokertärningar kan vi använda en lista med 5 tärningar. En klass för en uppsättning pokertärningar kan då göras så här:Kod | Utskrift |
---|---|
class PokerDice:
def __init__(self):
self.dice_list = []
for i in range(5):
self.dice_list.append(Dice(6))
def __str__(self):
return str(sorted([d.value for d in self.dice_list]))
def roll(self):
for d in self.dice_list:
d.roll() # Använder rollmetoden i Dice
print('Pokertärningar:')
pd = PokerDice()
for i in range(10):
pd.roll()
print(pd)
|
Pokertärningar:
[1, 3, 3, 3, 4]
[1, 1, 1, 2, 5]
[2, 4, 5, 5, 6]
[1, 1, 1, 3, 3]
[2, 2, 2, 5, 6]
[1, 3, 4, 6, 6]
[1, 2, 3, 4, 6]
[1, 3, 4, 6, 6]
[1, 2, 3, 5, 5]
[1, 1, 3, 4, 6]
|
- Initieringsmetoden skapar en lista med 5 tärningsobjekt med 6 sidor.
-
Metoden
__str__
gör en lista med de olika tärningarnas värden, sorterar den och returnerar den i sträng-form.
Observera hur vi använder listbyggare för att skapa returvärdet. -
Metoden
roll
utnyttjarroll
-metoden i klassenDice
.
Övningar
-
Ibland kan man vilja ha ett annat antal än 5. Modifiera koden så att man kan ange hur många tärningar man vill ha.
Endast init-metoden behöver ändras:
def __init__(self, number_of_dice): self.dice_list = [] for i in range(number_of_dice): self.dice_list.append(Dice(6))
-
Skriv en metod som returnerar antalet tärningar!
def number_of_dice(self): return len(self.dice_list)
-
Metoden
__str__
utnyttjar en listbyggare för att skapa resultatet. Skriv den utan att använda denna facilitet.res =[] for d in self.dice_list: res.append(d.value) return str(sorted(res))
-
Skriv
__init__
-metoden med hjälp av en listbyggare.def __init__(self): self.dice_list = [Dice(6) for i in range(5)]
-
Skriv en klass
Rectangle
. En rektangel ska ha en bredd, en höjd och en position i x-y-planet. Förse klassen med standardmetoderna__init__
och__str__
. Skriv också en metod som returnerar rektangelns yta samt en metod som ritar rektanglen med hjälpTurtle
-paketet.import turtle class Rectangle: def __init__(self, width, height, xpos, ypos): self.width = width self.height = height self.xpos = xpos self.ypos = ypos def __str__(self): return f'Rectangle({self.width}, {self.height}, ' + \ f'{self.xpos}, {self.ypos})' def area(self): return self.width*self.height def draw(self): t = turtle.Turtle() t.penup() t.goto(self.xpos, self.ypos) t.pendown() for x in range(2): t.forward(self.width) t.left(90) t.forward(self.height) t.left(90) r = Rectangle(200, 100, 0, 0) print(r) r.draw()
Rektangelns position har tolkats som det nedre vänstra hörnet.
Fråga
Hur många timmar har du arbetat med denna lektion?
Gå till nästa lektion eller gå tillbaka