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)
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))
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")
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")
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:
Operator | Traditionell mening | Metodens 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:
Operator | Traditionell mening | Metodens 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)
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)
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))
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)
[Person('Anna', 25), Person('Peter', 99), Person('Maja', 55)]
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)
Livet av en Python Den gamle och Java C-värdheter på nätet
Se dina poäng genom att klicka på cirkeln nere till höger av sidan.