Osa 10

Objektorienterade programmeringstekniker

En klass kan innehålla en metod som returnerar ett objekt av samma klass. Nedan har vi t.ex. klassen Produkt, vars metod reaprodukt returnerar ett nytt Produkt-objekt med samma namn som det ursprungliga men med ett pris som är 25 % lägre:

class Produkt:
    def __init__(self, namn: str, pris: float):
        self.__namn = namn
        self.__pris = pris

    def __str__(self):
        return f"{self.__namn} (pris {self.__pris})"

    def reaprodukt(self):
        nedsatt = Produkt(self.__namn, self.__pris * 0.75)
        return nedsatt
banan1 = Produkt("Banan", 2.99)
banan2 = banan1.reaprodukt()
print(banan1)
print(banan2)
Exempelutskrift

Banan (pris 2.99) Banan (pris 2.2425)

Låt oss gå igenom syftet med variabeln self: inom en klassdefinition hänvisar den till själva objektet. Vanligtvis används den för att hänvisa till objektets egna egenskaper, dess attribut och metoder. Variabeln kan också användas för att hänvisa till hela objektet, till exempel om själva objektet måste returneras till klientkoden. I exemplet nedan har vi lagt till metoden billigare i klassdefinitionen. Den tar en annan Produkt som sitt argument och returnerar det billigare av de två:

class Produkt:
    def __init__(self, namn: str, pris: float):
        self.__namn = namn
        self.__pris = pris

    def __str__(self):
        return f"{self.__namn} (pris {self.__pris})"

    @property
    def pris(self):
        return self.__pris

    def billigare(self, produkt):
        if self.__pris < produkt.pris:
            return self
        else:
            return produkt
banan = Produkt("Banan", 2.99)
apelsin = Produkt("Apelsin", 3.95)
ananas = Produkt("Ananas", 5.25)

print(apelsin.billigare(banan))
print(apelsin.billigare(ananas))
Exempelutskrift

Banan (2.99) Apelsin (3.95)

Även om det här fungerar bra är det ett mycket specialiserat fall av att jämföra två objekt. Det skulle vara bättre om vi kunde använda Pythons jämförelseoperatorer direkt på dessa Produkt-objekt.

Överlagring av operatorer

Python innehåller några speciellt namngivna inbyggda metoder för att arbeta med standardoperatorerna för aritmetik och jämförelse. Tekniken kallas operatörsöverlagring (eng. operator overloading). Om du vill kunna använda en viss operator på instanser av självdefinierade klasser kan du skriva en speciell metod som returnerar det korrekta resultatet av operatorn. Vi har redan använt den här tekniken med __str__ metoden: Python vet att leta efter en metod som heter så här när en strängrepresentation av ett objekt efterfrågas.

Låt oss börja med operatorn > som talar om för oss om den första operanden är större än den andra. Klassdefinitionen Produkt nedan innehåller metoden __gt__, som är en förkortning av greater than. Denna speciellt namngivna metod ska returnera det korrekta resultatet av jämförelsen. Specifikt ska den returnera True om och endast om det aktuella objektet är större än det objekt som skickas som ett argument. De kriterier som används kan bestämmas av programmeraren. Med aktuellt objekt menar vi det objekt som metoden anropas på med punkt . notationen

class Produkt:
    def __init__(self, namn: str, pris: float):
        self.__namn = namn
        self.__pris = pris

    def __str__(self):
        return f"{self.__namn} (pris {self.__pris})"

    @property
    def pris(self):
        return self.__pris

    def __gt__(self, annan_produkt):
        return self.pris > annan_produkt.pris

I implementationen ovan returnerar metoden __gt__ True om priset på den aktuella produkten är högre än priset på den produkt som skickas som argument. I annat fall returnerar metoden False.

Nu finns jämförelseoperatorn > tillgänglig för användning med objekt av typen Produkt:

apelsin = Produkt("Apelsin", 4.90)
banan = Produkt("Banan", 3.95)

if apelsin > banan:
    print("Apelsin är större")
else:
    print("Banan är större")
Exempelutskrift

Apelsin är större

Som nämnts ovan är det upp till programmeraren att bestämma vilka kriterier som ska gälla för att avgöra vad som är störst och vad som är minst. Vi kan t.ex. bestämma att ordningen inte ska baseras på pris, utan i stället ska vara alfabetisk enligt namn. Detta skulle innebära att banan nu skulle vara "större än" apelsin, eftersom "banan" kommer senare alfabetiskt.

class Produkt:
    def __init__(self, namn: str, pris: float):
        self.__namn = namn
        self.__pris = pris

    def __str__(self):
        return f"{self.__namn} (pris {self.__pris})"

    @property
    def pris(self):
        return self.__pris

    @property
    def namn(self):
        return self.__namn

    def __gt__(self, annan_produkt):
        return self.namn > annan_produkt.namn
apelsin = Produkt("Apelsin", 4.90)
banan = Produkt("Banan", 3.95)

if apelsin > banan:
    print("Apelsin är större")
else:
    print("Banan är större")
Exempelutskrift

Banan är större

Fler operatorer

Här har vi en tabell som innehåller de vanliga jämförelseoperatorerna, tillsammans med de metoder som måste implementeras om vi vill göra dem tillgängliga för användning på våra objekt:

OperatorTraditionell meningMetodens namn
<Mindre än__lt__(self, annan)
>Större än__gt__(self, annan)
==Lika med__eq__(self, annan)
!=Inte lika med__ne__(self, annan)
<=Mindre eller lika med__le__(self, annan)
>=Större eller lika med__ge__(self, annan)

Du kan också implementera några andra operatorer, inklusive följande aritmetiska operatorer:

OperatorTraditionell meningMetodens namn
+Addition__add__(self, annan)
-Subtraktion__sub__(self, annan)
*Multiplikation__mul__(self, annan)
/Division (bråktal)__truediv__(self, annan)
//Division (heltal)__floordiv__(self, annan)

Fler operatorer och metodnamn finns lätt att hitta på nätet. Kom också ihåg dir-instruktionen för att lista de metoder som är tillgängliga för användning på ett visst objekt.

Det är mycket sällan nödvändigt att implementera alla aritmetiska operatorer och jämförelseoperatorer i dina egna klasser. Division är t.ex. en operation som sällan är meningsfull utanför numeriska objekt. Vad skulle resultatet av att dividera ett Student-objekt med tre, eller med ett annat Student-objekt bli? Trots detta är vissa av dessa operatorer ofta mycket användbara även i egna klasser. Valet av metoder att implementera beror på vad som är vettigt, med tanke på egenskaperna hos dina objekt.

Låt oss ta en titt på en klass som modellerar en enda anteckning. Om vi implementerar metoden __add__ i vår klassdefinition blir additionsoperatorn + tillgänglig för våra Anteckning-objekt:

from datetime import datetime

class Anteckning:
    def __init__(self, dtm: datetime, inlagg: str):
        self.dtm = dtm
        self.inlagg = inlagg

    def __str__(self):
        return f"{self.dtm}: {self.inlagg}"

    def __add__(self, annan):
        # Datumet för den nya anteckningen är aktuell tid
        nytt_inlagg = Anteckning(datetime.now(), "")
        nytt_inlagg.inlagg = self.inlagg + "och " + annan.inlagg
        return nytt_inlagg
inlagg1 = Anteckning(datetime(2016, 12, 17), "Kom ihåg att köpa presenter")
inlagg2 = Anteckning(datetime(2016, 12, 23), "Kom ihåg att hämta en gran")

# Nu kan dessa anteckningar bli sammanlagda med + operatorn - detta kallar metoden __add__ i Anteckning-klassen
bada = inlagg1 + inlagg2
print(bada)
Exempelutskrift

2020-09-09 14:13:02.163170: Kom ihåg att köpa presenter och Kom ihåg att hämta en gran

En strängrepresentation av ett objekt

Du har redan implementerat en hel del __str__-metoder i dina klasser. Som du vet returnerar metoden en strängrepresentation av objektet. En annan ganska liknande metod är __repr__ som returnerar en teknisk representation av objektet. Metoden __repr__ är ofta implementerad så att den returnerar den programkod som kan exekveras för att returnera ett objekt med identiskt innehåll som det aktuella objektet.

Funktionen repr returnerar denna tekniska strängrepresentation av objektet. Den tekniska representationen används även när metoden __str__ inte har definierats för objektet. Exemplet nedan kommer att göra detta tydligare:

class Person:
    def __init__(self, namn: str, alder: int):
        self.namn = namn
        self.alder = alder

    def __repr__(self):
        return f"Person({repr(self.namn)}, {self.alder})"
person1 = Person("Anna", 25)
person2 = Person("Peter", 99)
print(person1)
print(person2)
Exempelutskrift

Person('Anna', 25) Person('Peter', 99)

Lägg märke till hur metoden __repr__ själv använder repr-funktionen för att hämta den tekniska representationen av strängen. Detta är nödvändigt för att inkludera tecknen ' i resultatet.

Följande klass har definitioner för både __repr__ och __str__:

class Person:
    def __init__(self, namn: str, alder: int):
        self.namn = namn
        self.alder = alder

    def __repr__(self):
        return f"Person({repr(self.namn)}, {self.alder})"

    def __str__(self):
        return f"{self.namn} ({self.alder} år)"
person = Person("Anna", 25)
print(person)
print(repr(person))
Exempelutskrift

Anna (25 år) Person('Anna', 25)

Det är värt att nämna att med datastrukturer, såsom listor, använder Python alltid __repr__-metoden för strängrepresentationen av innehållet. Detta kan ibland se lite förvirrande ut:

personer = []
personer.append(Person("Anna", 25))
personer.append(Person("Peter", 99))
personer.append(Person("Maja", 55))
print(personer)
Exempelutskrift

[Person('Anna', 25), Person('Peter', 99), Person('Maja', 55)]

Loading
Loading

Iteratorer

Vi vet att for-satsen kan användas för att iterera genom många olika datastrukturer, filer och samlingar av objekt. Ett typiskt användningsfall skulle kunna vara följande funktion:


def rakna_positiva(lista: list):
    n = 0
    for foremal in lista:
        if foremal > 0:
            n += 1
    return n

Funktionen går igenom objekten i listan ett efter ett och håller reda på hur många av objekten som var positiva.

Det är också möjligt att göra sina egna klasser itererbara. Detta är användbart när klassens huvudsyfte handlar om att lagra en samling objekt. Klassen Bokhylla från ett tidigare exempel skulle vara en bra kandidat, eftersom det skulle vara vettigt att använda en for-loop för att gå igenom böckerna på hyllan. Detsamma gäller för, exempelvis, ett studentregister. Att kunna iterera genom samlingen av studenter kan vara användbart.

För att göra en klass itererbar måste du implementera iteratormetoderna __iter__ och __next__. Vi återkommer till detaljerna i dessa metoder efter följande exempel:


class Bok:
    def __init__(self, namn: str, forfattare: str, sidor: int):
        self.namn = namn
        self.forfattare = forfattare
        self.sidor = sidor

class Bokhylla:
    def __init__(self):
        self._bocker = []

    def tillsatt_bok(self, bok: Bok):
        self._bocker.append(bok)

    # Initialiseringsmetoden för iteratorn
    # Här bör iterationsvariabeln(eller variablerna) initialiseras
    def __iter__(self):
        self.n = 0
        # Metoden returnerar en referens till själva objektet eftersom
        # iteratorn är implementerad inom samma klassdefinition
        return self

    # Metoden returnerar nästa föremål inom objektet
    # Ifall inga föremål är kvar, åstadkomms StopIteration-händelsen
    def __next__(self):
        if self.n < len(self._bocker):
            # Väljer det aktuella föremålet från listan inom objektet
            bok = self._bocker[self.n]
            # Öka räknaren med ett
            self.n += 1
            # ... och returnera det aktuella föremålet
            return bok
        else:
            # Inga fler böcker
            raise StopIteration

Metoden __iter__ initialiserar iterationsvariabeln eller variablerna. I det här fallet räcker det med att ha en enkel räknare som innehåller index för det aktuella objektet i listan. Vi behöver också metoden __next__, som returnerar nästa objekt i iteratorn. I exemplet ovan returnerar metoden objektet med index n från listan i Bokhylla-objektet, och iteratorvariabeln inkrementeras också.

När alla objekt har genomgåtts utlöser metoden __next__ undantaget StopIteration. Processen skiljer sig inte från andra undantag, men det här undantaget hanteras automatiskt av Python och dess syfte är att signalera till koden som anropar iteratorn (t.ex. en for-loop) att iterationen nu är över.

Vår bokhylla är nu redo för iteration, till exempel med en for-loop:


if __name__ == "__main__":
    b1 = Bok("Livet av en Python", "Peter Python", 123)
    b2 = Bok("Den gamle och Java", "Ernest Hemingjava", 204)
    b3 = Bok("C-värdheter på nätet", "Karl Kodare", 997)

    hylla = Bokhylla()
    hylla.tillsatt_bok(b1)
    hylla.tillsatt_bok(b2)
    hylla.tillsatt_bok(b3)

    # Skriver ut namnet på alla böcker
    for bok in hylla:
        print(bok.namn)
Exempelutskrift

Livet av en Python Den gamle och Java C-värdheter på nätet

Loading
Du har nått slutet av den här delen! Fortsätt till nästa del:

Se dina poäng genom att klicka på cirkeln nere till höger av sidan.