Observera att denna sida är under utveckling!

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>
Kommentarer:
  1. Klasser definieras med ordet class följt av det namn vi vill ha på klassen samt ett kolon.
  2. Klassnamn ska börja på stor bokstav.
  3. 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.
  4. Uttrycket Dice() skapar ett tärningsobjekt och returnerar en referens till det (jämför hur vi skapade Turtle-objekt).
  5. Ä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
Kommentarer:
  1. Eftersom klassen nu har ett innehåll tar vi bort pass.
  2. Initieringsmetoden (ibland kallad "konstruktor") måste heta __init__ och ha self som första parameter. Därefter kan man lägga till de parametrar man vill.
  3. Observera indenteringen!
  4. 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.
  5. Observera att sides och self.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.
  6. När man skapar objekten (t ex Dice(6)) anger man bara de argument som man själv lagt till. Parametern self 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
Metoden, som definierar hur objektet ska uttryckas som en sträng, ska bara ha parametern 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 metod roll 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
Om man redan har en klass kan man sedan i sin tur använda den i en annan klass, som i följande exempel.

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]
Kommentarer:
  1. Initieringsmetoden skapar en lista med 5 tärningsobjekt med 6 sidor.
  2. 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.
  3. Metoden roll utnyttjar roll-metoden i klassen Dice.

Övningar

  1. 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. Svar
    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))
    
  2. Skriv en metod som returnerar antalet tärningar! Svar
        def number_of_dice(self):
            return len(self.dice_list)
    
  3. Metoden __str__ utnyttjar en listbyggare för att skapa resultatet. Skriv den utan att använda denna facilitet. Svar
    res =[]
    for d in self.dice_list:
        res.append(d.value)
    return str(sorted(res))
    
  4. Skriv __init__-metoden med hjälp av en listbyggare. Svar
        def __init__(self):
            self.dice_list = [Dice(6) for i in range(5)]
    
  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älp Turtle-paketet. Svar
    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

Valid CSS!