DD1320 Tillämpad Datalogi

Föreläsning X: Klasser och objekt i Python

Vad är ett objekt?

Klasser

Den del av programmet där man definierar attribut och metoder för sina kallas en klass.
Klassen fungerar som mall för objekt
I klassen beskrivs attribut och metoder.
Alla metoder har parametern self som används för att komma åt de egna attributen
Klassen definieras med class Klassnamn(object): följt av en indenterad samling metoder.

class Bok(object):

    def __init__(self, titel, forfattare, utg, isbn = "-"):
        self.titel = titel
        self.forfattare = forfattare
        self.utg = utg
        self.isbn = isbn


I metoden __init__ definierar vi objektets attribut och ger dom värden. Här är attributen:
self.titel
self.forfattare
self.utg
self.isbn
När vi vill skapa ett Bok-objekt skickar vi in värden till de fyra parametrarna titel, forfattare, utg och isbn.
Här blir godnattsaga en referens till det nyskapade Bok-objektet.
Observera att self inte skickas med!
Det är bara inuti klassens metoder vi kan använda self.

godnattsaga = Bok("The BFG", "Roald Dahl", 1982, "0-224-02040-4")

Parametern isbn har ett defaultvärde ("-") som används om vi bara skickar med tre parametrar.

kursbok = Bok("Pythonkramaren II", "Henrik Eriksson", 2010)

Fler metoder

Vi kan definiera metoder med och utan parametrar (men alla metoder måste ha parmetern self).
Om vi vill komma åt något av objektets attribut gör vi det visa self.
Vi kan definiera metoder med och utan returvärde (en metod utan returvärde returnerar alltid None).

    def byt_isbn(self, isbn):
        self.isbn = isbn

    def visa_titel(self):
        print self.titel

    def hur_gammal(self):
        ar = 2011 - self.utg
        return ar

En metod anropas allid via ett objekt:
objekt.metod()
Exempel:

kursbok.visa_titel()

En husdjursklass

class Husdjur(object):
    """ Ett virtuellt husdjur """
    def __init__(self, djurnamn):
        """Konstruktorn, initierar attributen namn, glad och hunger."""
        self.namn = djurnamn
        self.glad = 0
        self.hunger = 0
        
    def visaStatus(self):
        """Visar husdjurets namn och hur det mår."""
        print "\n", self.namn, "är",
        if self.glad > 0 and self.hunger < 3:
            print "glad: (^_^)"
        else:
            print "ledsen: (T_T)"

    def banna(self):
        """Ger husdjuret bannor. glad minskas."""   
        print "\n - Fy på dig", self.namn, "!"
        self.glad -= 3

    def mata(self, mat):
        """Ger husdjuret mat. hunger minskas."""
        print
        for i in range(mat):
            print "GLUFS",
        self.hunger -= mat

    def leka(self):
        """Leker med husdjuret. glad och hunger ökar"""
        self.glad += 1
        print "\n~~~~~~~~~~~ WHEEEEEEE! ~~~~~~~~~~~"
        self.hunger  += 3

    def avsked(self):
        """Skriver ut avskedet."""
        print
        print "Hejdå,", self.namn, "kommer att sakna dig!"

Kommentarer som omges av tre citattecken blir automatiskt hjälpkommentarer (som visas om man skriver help(Husdjur) i Python).
Den som kör huvudprogrammet får själv välja husdjurets namn. Men attributen glad och hunger sätts alltid till 0 när ett objekt skapas.

def main(): 
    djurnamn = raw_input("Vad vill du döpa ditt husdjur till? ")
    djur = Husdjur(djurnamn)
    djur.visaStatus()
    svar = raw_input(" Vill du \n  banna \n  mata \n  leka med \n ditt husdjur? " )
    while svar:
        if svar[0]=="m":
            bullar = input("Hur många bullar? ")
            djur.mata(bullar)    
        elif svar[0]=="b":
            djur.banna()
        elif svar[0]=="l":
            djur.leka()
        else:
            print "Hursa? "
        djur.visaStatus()
        svar = raw_input(" Vill du \n  banna \n  mata \n  leka med \n ditt husdjur? " )
    djur.avsked()

Tecknet "\n" betyder radbyte.
if svar[0]=="m": innebär att vi bara tittar på första tecknet i strängen svar. Det spelar alltså ingen roll om den som kör programmet skriver m, mata, mums eller matematik - allt som börjar på "m" kommer att tolkas som "mata".

Flera objekt i en lista

Vitsen med att definiera en klass är att man sedan har en mall för objekt som man kan utnyttja för hur många objekt som helst.

I nästa exempel skapar vi flera husdjursobjekt som läggs i en lista. Vi börjar med att titta på huvudprogrammet:

def main():

    n = input("Hur många husdjur vill du ha? ")

    buren = Bur(n)
    buren.visa()
    svar = raw_input(" Vill du att dina husdjur ska \n  bannas \n  få mat \n  leka \n  mingla? " )
    while svar:
        if svar[0]=="f":
            bullar = input("Hur många bullar var? ")
            buren.mata(bullar)    
        elif svar[0]=="b":
            buren.banna()
        elif svar[0]=="l":
            buren.leka()
        elif svar[0]=="m":
            buren.mingel()
        else:
            print "Hursa? "
        buren.visa()
        svar = raw_input(" Vill du att dina husdjur ska \n  bannas \n  få mat \n  leka \n  mingla? " )
    buren.avsked()

Det ser nästan likadant ut som main() i det förra programmet!
Skillnaden är att alla metoder görs på ett Bur-objekt istället för ett Husdjursobjekt.

class Bur(object):
# Flera virtuella husdjur i en bur

    def __init__(self, n):
        """Skapar en lista med n stycken Husdjur"""
        self.lista = []
        for i in range(n):
            self.lista.append(Husdjur())

    def banna(self):
        """Skäller på alla djur"""
        for djur in self.lista:
            djur.banna()
            
    def mata(self, bullar):
        """Ger varje djur bullar"""
        for djur in self.lista:
            djur.mata(bullar)

    def leka(self):
        """Leker med varje djur"""
        for djur in self.lista:
            djur.leka()
            
    def mingel(self):
        """Låter husdjuren träffas och ev föröka sig"""
        print "Nu är det mingel i Buren!"
        n = len(self.lista)
        for i in range(n):
            djur1 = self.lista[i]
            for j in range(i+1,n):
                djur2 = self.lista[j]
                if djur1 != djur2 and djur1.kontakt(djur2):
                    self.lista.append(Husdjur(djur1.namn(),djur2.namn()))

    def visa(self):
        """Visar alla husdjur i Buren"""
        n  = len(self.lista)
        print "\n*** Det finns", n, "husdjur i Buren: ***"
        for djur in self.lista:
            print djur
        print "******************************************\n"
        
    def avsked(self):
        for djur in self.lista:
            djur.avsked()

Bur-klassen innehåller en lista med Husdjursobjekt: self.lista
Metoden __init__ fungerar så här:
Vi skapar först en tom lista. Sen går vi n varv i en for-slinga, och i varje varv skapar vi ett Husdjursobjekt och lägger det sist i listan (med listmetoden append).

I metoderna banna, leka, mata, visa och avsked går vi igenom alla Husdjur i listan och bannar, leker, osv med vart och ett av dom.
Metoden mingel är speciell eftersom två Husdjursobjekt får interagera!
I nästlade for-slingor (den ena inuti den andra) ser vi till att varje husdjur får chans att träffa varje annat husdjur (som speed-dating). Anropet djur1.kontakt(djur2) returnerar True om djur1 får kontakt med djur2, och då får dom en unge tillsammans, som läggs till listan.
Men hur kan en metod involvera två olika objekt? Och vad är self då?

    def  kontakt(self, kompis):
        """Testar om kontakt uppstår mellan detta husdjur och dess kompis"""
        if (self.kon == kompis.kon):
            if (self.__preferens == "samma") and (kompis.__preferens == "samma"):
                print "Puss!"
                return True
        elif (self.kon != kompis.kon):
            if (self.__preferens == "annat") and (kompis.__preferens == "annat"):
                print "Puss!"
                return True
        else:
            return False

I metoden kontakt är det det anropande objektet (djur1) som blir self i metoden. Parametern (djur2) kallas kompis inuti metoden. Då går det att jämföra objektens attribut för att se om dom gillar varann.

    def __str__(self):
        """Returnerar en sträng som beskriver husdjuret"""  
        beskrivning = self.namnet + " är "
        if self.glad > 5:
            beskrivning += "glad: (^_^)"
        else:
            beskrivning += "ledsen: (T_T)"
        if self.hunger > 3:
            beskrivning += " och hungrig!"
        else:
            beskrivning += " och mätt."
        return beskrivning

I den här versionen av Husdjur har vi bytt ut metoden visaStatus() mot en liknande metod som heter __str__(). Att vi valt ett så underligt namn beror på att det här är en metod som Python känner igen (precis som __init__). Om metoden __str__() finns definierad i en klass så kommer print-satsen att anropa den automatiskt om man försöker skriva ut ett objekt. Då vill det till att metoden returnerar en lämplig sträng som beskriver objektet.

    def __init__(self, namn1="", namn2=""):
        """Ger husdjurets attribut slumpade värden"""
        self.namnet = choice("BCFKR" )+choice("iouy")+\
                    choice("nst")*2+choice("aey")
        if namn1:
            self.namnet = namn1 + "-" + namn2 + "-" + self.namnet
        self.glad = randrange(10)
        self.hunger = randrange(3)
        self.kon = choice(("hona","hane"))
        self.__preferens = choice(("samma","annat"))

Metoden __init__() är också ändrad. Här slumpar vi fram värden på attributen. Dessutom finns det två extra-parametrar med default-värden: namn1 och namn2. Där är det tänk att man ska kunna skicka med föräldrarnas namn, som sätts ihop med barnets namn så att man kan se att Binne-Rytta-Kenny är barn till Binne och Rytta.