Metoder i egna klasser
Klasser som endast innehåller dataattribut skiljer sig inte så mycket från ordlistor. Nedan kan du se två sätt att modellera ett bankkonto, först med en klassdefinition och sedan med hjälp av en ordlista.
# Exempel 1: bankkonto med klassdefinition
class Bankkonto:
def __init__(self, kontonummer: str, agare: str, saldo: float, arsranta: float):
self.kontonummer = kontonummer
self.agare = agare
self.saldo = saldo
self.arsranta = arsranta
peters_konto = Bankkonto("12345-678", "Peter Python", 1500.0, 0.015)
# Exempel2: bankkonto med ordlista
peters_konto = {"kontonummer": "12345-678", "agare": "Peter Python", "saldo": 1500.0, "arsranta": 0.0}
Med en ordlista är implementeringen mycket kortare och enklare. Med en klass är strukturen däremot mer "hårt bunden", så vi kan förvänta oss att alla Bankkonto
-objekt är strukturellt lika. Dessutom är en klass också namngiven. Klassen Bankkonto
refereras till när ett nytt bankkonto skapas, och objektets typ är Bankkonto
, inte dict.
En annan stor fördel med klasser är att de förutom data även kan innehålla funktionalitet. En av de vägledande principerna för objektorienterad programmering är att ett objekt används för att komma åt både den data som är kopplad till ett objekt och funktionaliteten för att bearbeta denna data.
Metoder i klasser
En metod är ett underprogram eller en funktion som är knuten till en specifik klass. Vanligtvis påverkar en metod bara ett enda objekt. En metod definieras inom klassdefinitionen och den kan komma åt klassens dataattribut precis som vilken annan variabel som helst.
Låt oss fortsätta med klassen Bankkonto
som introducerades ovan. Nedan har vi en ny metod som lägger till ränta på kontot:
class Bankkonto:
def __init__(self, kontonummer: str, agare: str, saldo: float, arsranta: float):
self.kontonummer = kontonummer
self.agare = agare
self.saldo = saldo
self.arsranta = arsranta
# Metoden lägger till den årliga räntat till saldot
def lagg_till_ranta(self):
self.saldo += self.saldo * self.arsranta
peters_konto = Bankkonto("12345-678", "Peter Python", 1500.0, 0.015)
peters_konto.lagg_till_ranta()
print(peters_konto.saldo)
1522.5
Metoden lagg_till_ranta
multiplicerar saldot på kontot med den årliga ränteprocenten och lägger sedan till resultatet till det aktuella saldot. Metoden verkar bara på det objekt som den anropas på.
Låt oss se hur detta fungerar när vi har skapat flera instanser av klassen:
# Klassen Bankkonto är definierat såsom i förra exemplet
peters_konto = Bankkonto("12345-678", "Peter Python", 1500.0, 0.015)
pernillas_konto = Bankkonto("99999-999", "Pernilla Pythonson", 1500.0, 0.05)
pers_konto = Bankkonto("1111-222", "Per Persson", 1500.0, 0.001)
# Vi tillsätter ränta till Peters och Pernillas konton, men inte Pers
peters_konto.lagg_till_ranta()
pernillas_konto.lagg_till_ranta()
# Vi skriver ut alla
print(peters_konto.saldo)
print(pernillas_konto.saldo)
print(pers_konto.saldo)
1522.5 1575.0 1500.0
Som du kan se ovan läggs den årliga räntan endast till på de konton som metoden anropas på. Eftersom den årliga räntan är olika för Peters och Paulas konton, blir resultatet olika för dessa två konton. Saldot på Pernillas konto ändras inte, eftersom metoden lagg_till_ranta
inte anropas på objektet pernillas_konto
.
Inkapsling
Inom objektorienterad programmering dyker ordet klient upp då och då. Det används för att hänvisa till ett kodavsnitt som skapar ett objekt och använder dem med hjälp av de metoder som motsvarande klass ger möjlighet till. När data som finns i ett objekt endast används genom de metoder som definierats i klassen, garanteras objektets interna integritet. I praktiken innebär detta att t.ex. en Bankkonto
-klass erbjuder metoder för att hantera saldo
-attributet, så att saldot aldrig nås direkt av klienten. Dessa metoder kan sedan verifiera att saldot till exempel inte tillåts gå under noll.
Ett exempel på hur detta skulle fungera:
class Bankkonto:
def __init__(self, kontonummer: str, agare: str, saldo: float, arsranta: float):
self.kontonummer = kontonummer
self.agare = agare
self.saldo = saldo
self.arsranta = arsranta
# Metoden tillsätter den årliga räntat till saldot av kontot
def lagg_till_ranta(self):
self.saldo += self.saldo * self.arsranta
# Den här metoden "tar ut" pengar från kontot
# Metoden returnerar True ifall det lyckades, och False ifall det misslyckades
def uttag(self, uttagssumma: float):
if uttagssumma <= self.saldo:
self.saldo -= uttagssumma
return True
return False
peters_konto = Bankkonto("12345-678", "Peter Python", 1500.0, 0.015)
if peters_konto.uttag(1000):
print("Uttaget lyckades, kontots saldo är nu", peters_konto.saldo)
else:
print("Uttaget lyckades inte, saldot var otillräckligt")
# Vi försöker på nytt
if peters_konto.uttag(1000):
print("Uttaget lyckades, kontots saldo är nu", peters_konto.saldo)
else:
print("Uttaget lyckades inte, saldot var otillräckligt")
Uttaget lyckades, kontots saldo är nu 500.0 Uttaget lyckades inte, saldot var otillräckligt
Att bibehålla objektets interna integritet och erbjuda lämpliga metoder för att säkerställa detta kallas inkapsling. Tanken är att objektets inre arbete är dolt för klienten, men objektet erbjuder metoder som kan användas för att komma åt den data som lagras i objektet.
Att lägga till en metod innebär inte att attributet automatiskt döljs. Även om klassdefinitionen Bankkonto
innehåller metoden uttag
för att ta ut pengar, kan klientkoden fortfarande komma åt och ändra attributet saldo
direkt:
peters_konto = Bankkonto("12345-678", "Peter Python", 1500.0, 0.015)
# Vi försöker lyfta 2000
if peters_konto.uttag(2000):
print("Uttaget lyckades, kontots saldo är nu", peters_konto.saldo)
else:
print("Uttaget lyckades inte, saldot var otillräckligt")
# Vi "tvingar" ett uttag på 2000
peters_konto.saldo -= 2000
print("Saldot är nu:", peters_konto.saldo)
Det är möjligt att dölja dataattributen från klientkoden, vilket kan bidra till att lösa detta problem. Vi återkommer till detta ämne i nästa del.
Som avslutning på detta avsnitt tittar vi på en klass som modellerar en spelares personliga bästa. Klassdefinitionen innehåller separata valideringsmetoder som kontrollerar att de argument som skickas är giltiga. Metoderna anropas redan i konstruktorn. Detta säkerställer att det skapade objektet är sunt internt.
from datetime import date
class PersonligtRekord:
def __init__(self, spelare: str, dag: int, manad: int, ar: int, poang: int):
# Standardvärden
self.spelare = ""
self.datum = date(1900, 1, 1)
self.poang = 0
if self.namn_ok(spelare):
self.spelare = spelare
if self.dtm_ok(dag, manad, ar):
self.datum = date(ar, manad, dag)
if self.poang_ok(poang):
self.poang = poang
# Hjälparmetoder som kollar att argumenten är giltiga
def namn_ok(self, namn: str):
return len(namn) >= 2 # Namnet ska vara minst två tecken
def dtm_ok(self, dag, manad, ar):
try:
date(ar, manad, dag)
return True
except:
# Ett undantag ifall datumet inte är giltigt
return False
def poang_ok(self, poang):
return poang >= 0
if __name__ == "__main__":
resultat1 = PersonligtRekord("Peter", 1, 11, 2020, 235)
print(resultat1.poang)
print(resultat1.spelare)
print(resultat1.datum)
# Datumet är inte giltigt
resultat2 = PersonligtRekord("Pernilla", 4, 13, 2019, 4555)
print(resultat2.poang)
print(resultat2.spelare)
print(resultat2.datum) # Skriver ut standardvärdet 1900-01-01
235 Peter 2020-11-01 4555 Pernilla 1900-01-01
I exemplet ovan anropades även hjälpmetoderna via parameternamnet self
när de användes i konstruktorn. Det är också möjligt att inkludera /statiska/ metoddefinitioner i klassdefinitioner. Dessa är metoder som kan anropas utan att det någonsin skapas en instans av klassen. Vi återkommer till detta i nästa del.
Parameternamnet self
används endast när man hänvisar till /objektets egenskaper som en instans av klassen/. Det gäller både dataattributen och de metoder som är knutna till ett objekt. För att göra terminologin mer förvirrande kallas dataattributen och metoderna tillsammans ibland helt enkelt för objektets attribut, vilket är anledningen till att vi i detta material ofta har angett dataattribut när vi menar de variabler som definieras inom klassen. Det är här terminologin hos vissa Python-programmerare skiljer sig något från den terminologi som mer allmänt används inom objektorienterad programmering, där attribut vanligtvis bara hänvisar till dataattributen hos ett objekt.
Det är också möjligt att skapa lokala variabler inom metoddefinitioner utan att hänvisa till self
. Detta bör du göra om det inte finns något behov av att komma åt variablerna utanför metoden. Lokala variabler inom metoder har inga speciella nyckelord; de används precis som alla vanliga variabler som du har stött på hittills. .
Så här skulle det till exempel fungera:
class Bonuskort:
def __init__(self, namn: str, saldo: float):
self.namn = namn
self.saldo = saldo
def lagg_till_bonus(self):
# Nu är variabeln bonus en lokal variabel, inte ett
# data attribut till objektet
# Den kan inte nås genom ett objekt
bonus = self.saldo * 0.25
self.saldo += bonus
def lagg_till_superbonus(self):
# Variabeln superbonus är också en lokal variabel.
# Vanligtvis är hjälpvariabler lokala variabler eftersom
# det inte finns något behov av att komma åt dem från andra
# metoder i klassen eller direkt via ett objekt.
superbonus = self.saldo * 0.5
self.saldo += superbonus
def __str__(self):
return f"Bonuskort(namn={self.namn}, saldo={self.saldo})"
Se dina poäng genom att klicka på cirkeln nere till höger av sidan.