Så var det då onsdag igen, och dags för ett nytt avsnitt av “Introduktion till programmering”! 😀

Introduktion till programmering

Förra veckan tittade vi på import av bibliotek och hur man jobbar mot GPIO-porten på Raspberry Pi. Idag ska vi titta lite mer på objekt!

Vi har tidigare gått igenom heltal, decimaltal, strängar/tecken, booleans och listor. Men dessa kanske inte passar i alla situationer, eller?

Om vi till exempel vill lagra ett datum, hur gör vi då? En sträng? Nja, lite väl krångligt att byta datum. Tre heltal? Osmidigt. En lista? Ptja…

Eller så använder man ett “datetime”-objekt! Detta objekt innehåller år, månad, dag, timme, minut, sekund, och mikrosekund.

Hur fungerar objekt och hur skapar man dem?

Vi gick kort igenom objekt i ett tidigare avsnitt, men nu ska vi titta mer på hur de är tänkta att fungera och hur man skapar egna.

Enkelt uttryckt kan man säga att ett objekt inom programmering är ett sätt att representera ett föremål, eller en mer avancerad datatyp, med datorns enkla datatyper.

När man arbetar med en enkel variabel gör man tilldelning och läsning direkt mot variabeln, så här:

a = 1
if a == 1:
	a = 2

När man skapar ett objekt däremot, har objektet en mängd attribut och “metoder”. Metoder är egentligen funktioner, men som enbart arbetar mot det objekt som de tillhör.

Det är ju fullt möjligt att man vill dölja vissa attribut för omvärlden, eller kanske en metod som bara används lokalt i objektet (t.ex. för beräkningar).

När man programmerar objektorienterat är det därför vanligt att man helt döljer attributen utanför objektet. Man gör dem “privata”. För att läsa och ändra attributen skapar man istället metoder.

Python har inget stöd för att göra attribut privata, men där har man istället en “standard” som säger att om ett attributsnamn börjar med understreck (“_”) är det att betrakta som privat och man skall undvika att använda det då namn eller tillgänglighet kan förändras till senare versioner av objektet.

Ett enkelt Python-objekt (en s.k. “klass”) för ett objekt som enbart lagrar ett heltal, och skriver ut det vid behov, skulle kunna se ut så här:

class Heltal:
	def __init__(self, tal=0):
		self._tal = int(tal)
	def visaTal(self):
		return self._tal

Som synes definierar vi två funktioner (eller, i detta fall, metoder), __init__ och visaTal. “__init__” är konstruktorn, som man anropar när man skapar ett nytt objekt, och visaTal skriver ut talet som lagras i objektet.

Att slänga in denna klassdefinition i Python kan göras genom att klistra in koden direkt i interpretator-fönstret (tolken), eller genom att spara den och köra den från en fil. För att testa objektet kan man skriva så här:

>>> a = Heltal(10)
>>> b = Heltal(2)
>>> c = Heltal()
>>> a, b, c
(<__main__.Heltal instance at 0x0000000002D6FBC8>, <__main__.Heltal instance at 0x0000000002D60588>, <__main__.Heltal instance at 0x0000000002D6F848>)
>>> a.visaTal(), b.visaTal(), c.visaTal()
(10, 2, 0)
>>>

Låt mig bara kort förklara varför returvärdet blir (10, 2, 0). När man hämtar flera värden på samma gång, med kommatecken mellan, returneras svaret som en array.

Det fungerar alltså inte här att direkt hämta ut datan från objekten som vi försöker göra med anropet “a, b, c”, utan man måste anropa visaTal() för att hämta ut talen. Man kan däremot skriva a._tal (även om man inte borde) och på så sätt komma åt talet. Men! Här är det som är fördelen med privata attribut. Säg att vi ändrar namnet på _tal till något annat. Då kommer ju inte a._tal fungera längre. Så om någon använder sig av vår kod och vi gör en sådan förändring kommer deras kod inte längre att fungera, om de inte använder sig av visaTal() som vi antagligen väljer att bibehålla för att inte påverka bakåtkompatibiliteten.

Det gör också att den som använder objektet inte behöver bry sig om hur objektet ser ut, utan bara vilka metoder som finns. Vi vet att vi kan skapa ett objekt och hur vi gör, och vi vet att vi kan skriva ut talet. Resten får den som skapat objektet bekymra sig för!

Vår objekt “Heltal” är ju tyvärr, i sitt nuvarande utförande, ganska oanvändbar. Det enda den gör är lagra ett heltal och visa det vid anrop. Man kan inte ändra talet efter att objektet skapats då det inte finns någon metod för det. Man kan heller ej göra beräkningar på fler Heltal och spara resultatet. För att kunna göra dessa saker behöver man fler metoder som hanterar det! Men det går vi inte in på idag.

“Getters och Setters”

De vanligaste metoderna i objekt är “get” och “set”. Varje objekt har såklart en konstruktor också, men som regel har de bara en eller några enstaka, medan getters och setters ofta finns en av varje för varje attribut man vill kunna arbeta med.

I vår klass Heltal ovan fungerar konstruktorn i princip som en setter, och även för mer invecklade objekt är det så (men när objektet skapas kan det vara betydligt fler attribut som sätts). Objektet sätter sitt attribut till det invärde “tal” som skickats med när man skapade objektet, och om man lämnade fältet tomt sätts talet till 0.

visaTal, däremot, är en “getter”. Det hämtar värdet på tal, och skickar tillbaka det till användaren. I Python behöver man inte specificera vilken typ av data som skickas tillbaka, men i Java hade samma objekt sett ut så här:

public class Heltal {
	
	private int tal;
	
	public Heltal(int tal) {
		this.tal = tal;
	}

	public int visaTal() {
		return tal;
	}
}

Här är klassen publik, precis som metoden visaTal och konstruktorn, medan heltalsvärdet är privat. Du kan också se att vi definierat vilken datatyp som returneras i “visaTal()” genom att skriva int efter public. Java är betydligt “objektsäkrare” än Python, så här skulle inte Heltal.tal fungera att använda när den är privat (men objektet kan anropa den lokalt, såklart).

self/this

I kodexemplen ovan dyker orden “self” (Python) och “this” (Java) upp. Jag tänkte bara nämna lite kort om vad det innebär. När man instansierat ett objekt vill man kunna specificera att det attribut man vill läsa eller ändra är inuti just det specifika objektet. Detta görs med “this”. För att förenkla, i Java-exemplet ovan skulle “this.tal” kunna översättas till “attributet tal i det här objektet”. Eftersom invärdet i konstruktorn också heter tal, gör man så här för att skilja dem åt.

 

Det var allt för denna gång, nästa gång fortsätter vi titta på objekt men går också in lite mer på avancerade datatyper och, om vi hinner, smartare beräkningar!