4. Funktioner

Programmeringsteknik

(Skillnad mellan versioner)
Hoppa till: navigering, sök
(Kopiera filer)
Nuvarande version (25 februari 2015 kl. 09.22) (redigera) (ogör)
 
(72 mellanliggande versioner visas inte.)
Rad 1: Rad 1:
-
==Filer==
+
{| border="0" cellspacing="0" cellpadding="0" height="30" width="100%"
 +
| style="border-bottom:1px solid #797979" width="5px" |  
 +
{{Mall:Vald flik|[[4. Funktioner|Teori]]}}
 +
{{Mall:Ej vald flik|[[4. Övningar|Övningar]]}}
 +
{{Mall:Ej vald flik|[[4. Inlämningsuppgift 2|Rondelet]]}}
 +
| style="border-bottom:1px solid #797979" width="100%"|  
 +
|}
-
<p>Information som man vill ha kvar p&aring; en dator sparas p&aring; fil. Det kan t&nbsp;ex r&ouml;ra sig om ett brev som har skapats med hj&auml;lp av ett ordbehandlingsprogram, en bild som har skapats av ett ritningsprogram eller en kamera osv. Filer sparas p&aring; l&aring;ngtidsminnen som t ex datorns skivminne (h&aring;rddisken). N&auml;r man sparat en fil p&aring; h&aring;rddisken kommer filen att finnas kvar d&auml;r &auml;ven n&auml;r man har startat om datorn eller t&nbsp;ex n&auml;r det blir str&ouml;mavbrott. D&auml;rf&ouml;r &auml;r det bra att spara den programkod man skriver p&aring; en fil f&ouml;r senare bearbetning. Dels f&ouml;r att det s&auml;llan blir r&auml;tt f&ouml;rsta g&aring;ngen, dels f&ouml;r att n&auml;r man v&auml;l provk&ouml;rt kommer man p&aring; fler saker man &ouml;nskar av programmet. </p>
 
-
<p>Filer delas upp i tre sorter:</p>
+
==Vad har man funktioner till?==
-
<p><ol>
+
Stora program är besvärligare att konstruera än små.
-
<li>Vanliga filer: som inneh&aring;ller data (t ex text eller bildinformation).
+
Men man kan göra det enklare för sig genom att dela
-
<li>Katalog eller mapp: som inneh&aring;ller andra filer.
+
upp programmet i funktioner. En funktion är som ett
-
<li>L&auml;nkar, genv&auml;gar eller alias: En fil som refererar till en fil.
+
litet delprogram i programmet.
-
</ol></p>
+
-
<p>Kataloger anv&auml;nds f&ouml;r att organisera filer och program. Kataloger kan inneh&aring;lla andra kataloger. T ex kan man spara alla filer som har skapats i samband med sommarkursen under katalogen &rdquo;sommarkurs&rdquo;. Strukturen kallas i datorsammanhang ofta f&ouml;r ett filtr&auml;d.</p>
+
[[Bild:Snigeltavling.jpg]]
-
<p>L&auml;nkar anv&auml;nds t ex f&ouml;r att man ska kunna hitta samma fil p&aring; flera st&auml;llen. En bildfil kan ta stor plats och en popul&auml;r bild kan man vilja ha med p&aring; m&aring;nga st&auml;llen. F&ouml;r att slippa kopiera bilden l&auml;gger man d&aring; ist&auml;llet l&auml;nkar till den. L&auml;nkarna tar n&auml;stan ingen plats alls.</p>
+
När man anordnar tävlingar med riktiga sniglar tar det mycket
 +
lång tid innan deltagarna kommer i mål. Vi ska istället skriva
 +
ett litet program som simulerar en tävling mellan två sniglar:
-
==Filhantering==
+
<pre>
 +
---------------------------------------------------
 +
| VEM HAR DEN SNABBASTE SNIGELN? |
 +
| |
 +
| Här får din snigel tävla mot en vältränad |
 +
| racersnigel. Skriv in namnet på din snigel |
 +
| så sätter tävlingen igång! |
 +
---------------------------------------------------
 +
Vad heter din snigel? Ebba
 +
Klara...färdiga...gå!
-
<p>P&aring; n&auml;stan alla datorer finns det ett grafiskt anv&auml;ndargr&auml;nssnitt (det du ser p&aring; sk&auml;rmen) som kan anv&auml;ndas f&ouml;r att organisera filer mha musen, kortkommandon eller ett filhanteringsprogram.</p>
+
Ebba: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @
 +
Racersnigeln: - - - - - - - - - - - - - - - - - - - - - - - - - - - @
-
<p>Filer, kataloger och l&auml;nkar har d&aring; grafiska symboler som kallas f&ouml;r ikoner. F&ouml;ljande bilder visar hur olika ikoner ser ut i ett Mac/OS X operativsystem:</p>
 
-
[[Bild:800176.jpg]] [[Bild:800177.jpg]] [[Bild:800178.jpg]]
+
Det här loppet tog en oväntad vändning, Ebba vann!
 +
</pre>
-
<p>I programmeringssammanhang &auml;r det v&auml;ldigt vanligt att anv&auml;nda tangentbordskommandon f&ouml;r filhanteringen, t ex för att utföra:</p>
 
-
<p><ol>
+
===Dela upp ett problem i funktioner===
-
<li>F&ouml;rflyttning i filtr&auml;det.
+
-
<li>Kopiering av filer.
+
-
<li>Radering av filer.
+
-
</ol></p>
+
-
<p>Ofta &auml;r den grafiska filhanteringen enklare, men i programmeringssammanhang vill man anv&auml;nda tangentbordskommandon s&aring; mycket som m&ouml;jligt eftersom det g&aring;r snabbare (n&auml;r man v&auml;l kan det).</p>
+
För att programmet ska bli enklare att skriva bestämmer vi
 +
först vilka funktioner vi ska ha. Det är inte alls självklart
 +
hur programmet ska delas upp i funktioner; samma problem
 +
kan lösas på många olika sätt! Vi vill se till att varje
 +
funktion har ett enda uppdrag.
-
==F&ouml;rflyttning i filtr&auml;d==
+
Här är ett förslag på uppdelning:
-
<p>Tangentbordskommandon anv&auml;nder man i ett terminalf&ouml;nster. För att öppna ett terminalfönster på en Windowsdator: tryck först på windows-tangenten, håll den nere och tryck därefter R. I det fönster som öppnas, skriv in CMD och tryck retur. Har du Macintosh OS X kan du se Python-tolk-instruktionerna i avsnitt 1.9 om villkor om du gl&ouml;mt hur man &ouml;ppnar ett s&aring;dant. Vi b&ouml;rjar med kommandot cd (som st&aring;r f&ouml;r <b>c</b>hange <b>d</b>irectory - byt katalog) anv&auml;nds f&ouml;r att f&ouml;rflytta sig i filtr&auml;det.</p>
+
# Skriv ut informationsrutan och läs in namnet snigeln
 +
# Simulera tävlingen och rita upp banorna
 +
# Skriv ut vem som vann
-
<p>Antag att man befinner sig i katalogen "sommarkurs" och man vet att i katalogen finns en annan katalog med namnet "test". D&aring; kan man skriva f&ouml;ljande kommando f&ouml;r att flytta sig till katalogen "test":</p>
+
Ettan och tvåan består av flera olika moment. Vi gör det
 +
ännu enklare för oss genom att dela upp i ännu mindre delar:
 +
# Skriv ut informationsrutan
 +
# Läs in namnet på en snigel
 +
# Simulera tävlingen
 +
# Rita upp en snigels bana
 +
# Skriv ut vem som vann
-
<div class="codebox"><div class="title">Terminalfönster</div>&gt;cd test</div>
+
Vi börjar med att skriva in dessa moment som kommentarer i
 +
filen snigel.py, för att vi ska hålla reda på vad som
 +
är kvar att göra. Då får vi också en kommentar för varje
 +
funktion, så att det syns vad som händer var.
 +
===Definiera en funktion===
-
<p>Det finns tv&aring; speciella namn "." och ".." som har stor betydelse f&ouml;r systemet.</p>
+
Först kommer funktionshuvudet (första raden i funktionen). Exempel:
-
<p>"." betyder aktuella katalogen.</p>
+
<pre>
 +
def visa_information():
 +
</pre>
-
<p>".." betyder katalogen som inneh&aring;ller aktuella katalogen (dvs katalogen ovanför den aktuella katalogen).</p>
+
[[Bild:Huvud.jpg|thumb|left]]
 +
Funktionshuvudet består av fyra delar.
 +
* Ordet def (talar om att en funktion ska definieras här)
 +
* Funktionens namn, gärna ett verb som beskriver vad funktionen gör
 +
* Ett par parenteser ()
 +
* Ett kolon
-
<p>"." och ".." kommer att automatiskt finnas i varje katalog.</p>
+
Under funktionshuvudet lägger man funktionkommentaren:
 +
<pre>
 +
def visa_information():
 +
''' Skriver ut informationsrutan
 +
'''
 +
</pre>
-
<p>Vad begreppet "aktuell katalog" betyder brukar framg&aring; efter ett tags användning, men en analogi &auml;r rum i ett hus. Om du g&aring;r in i ett rum, t ex ditt sovrum så är sovrummet det aktuella rummet. R&auml;knar du upp alla m&ouml;bler där, så får du ett annat resultat än om du r&auml;knar upp m&ouml;blerna i t ex k&ouml;ket. En niv&aring; upp (katalogen ovanf&ouml;r) kan du j&auml;mst&auml;lla med huset (om du tänkte på en villa) eller l&auml;genheten. En niv&aring; ner (en katalog i rummet) skulle kunna motsvaras av en garderob. I en dator finns det inga begr&auml;nsningar på antalet niv&aring;er man kan ha, men det gör det f&ouml;rst&aring;s i verkligheten.</p>
+
Sedan kommer funktionskroppen, som helt enkelt är
 +
en följd av indenterade satser som funktionen ska utföra.
 +
Så här ser hela funktionen ut:
-
<p>I samband med kommandot "cd" f&ouml;rekommer ofta tv&aring; andra viktiga kommandon "ls" och "pwd". Kommandot "ls" (som st&aring;r f&ouml;r list, lista) eller "dir" i dos anv&auml;nds f&ouml;r att lista vilka filer finns i katalogen och "pwd" (som st&aring;r f&ouml;r print working directory, skriv aktuellt katalognamn) (motsvarande pwd finns inte i dos, ist&auml;llet skrivs katalognamnet alltid ut) anv&auml;nds f&ouml;r att ta reda p&aring; namn p&aring; den aktuella katalogen. Betrakta f&ouml;ljande bild:</p>
+
<pre>
 +
def visa_information():
 +
''' Skriver ut informationsrutan
 +
'''
 +
print("""
 +
---------------------------------------------------
 +
| VEM HAR DEN SNABBASTE SNIGELN? |
 +
| |
 +
| Här får din snigel tävla mot en vältränad |
 +
| racersnigel. Skriv in namnet på din snigel |
 +
| så sätter tävlingen igång! |
 +
---------------------------------------------------""")
 +
</pre>
-
[[Bild:795530.jpg]]
+
(För att kunna skriva en sträng som sträcker sig över flera rader
 +
så startar och avslutar vi strängen med tre stycken "-tecken.
 +
Vanliga strängar, som börjar och slutar med enkelt
 +
citattecken, kan nämligen inte innehålla radbrytningar.)
-
<p>I forts&auml;ttningen visas anv&auml;ndarens inmatning i fet stil, resten av texten skrivs av datorn. N&auml;r den aktuella katalogen &auml;r sommarkurs och man skriver f&ouml;ljande kommandon i tur och ordning f&aring;r man dessa svar av datorn:</p>
+
Lägg till koden ovan i din fil snigel.py och provkör.
-
<div class="codebox-divided">
+
Men vad nu - programmet skriver inte ut något alls?
-
<div class="codecolumn1">
+
Gå vidare till nästa avsnitt för att få reda på varför!
-
<div class="title">Unix/Mac OS X</div>
+
-
<pre>&gt;pwd
+
-
sommarkurs
+
-
&gt;ls
+
-
programkod&nbsp;test
+
-
&gt;cd programkod
+
-
&gt;pwd
+
-
sommarkurs/programkod
+
-
&gt;ls
+
-
Prog.py&nbsp;Prog2.py
+
-
&gt;cd Prog.py
+
-
Prog.py: Not a directory
+
-
&gt;cd ..
+
-
&gt;pwd
+
-
sommarkurs
+
-
&gt;cd test/Katalog1
+
-
&gt;pwd
+
-
sommarkurs/test/katalog1
+
-
&gt; </pre></div>
+
-
<div class="codecolumn2">
+
-
<div class="title">DOS</div>
+
-
<pre>C:\sommarkurs>dir
+
-
06-05-01 11.45 &lt;DIR&gt; programkod
+
-
06-05-02 20:23 &lt;DIR&gt; test
+
-
C:\sommarkurs&gt;cd programkod
+
-
C:\sommarkurs\programkod&gt; dir
+
-
06-06-01 &lt;DIR&gt; .
+
-
06-06-01 &lt;DIR&gt; ..
+
-
06-06-02 10.45 1024 PROG.PY
+
-
06-05-11 11.11 2048 PROG2.PY
+
-
C:\sommarkurs\programkod&gt; cd prog.py
+
-
The directory name is invalid
+
-
C:\sommarkurs\programkod&gt; cd ..
+
-
C:\sommarkurs&gt; cd test/Katalog1
+
-
C:\sommarkurs\test\Katalog1&gt;</pre></div></div>
+
 +
==Anropa en funktion==
-
<span style="color:red">[FRÅGOR]</span>
+
Satserna i funktionskroppen utförs inte om inte funktionen
 +
anropas. Anropet ska stå längst ner i programmet, under
 +
funktionsdefinitionerna. Den delen av programmet kallar vi
 +
huvudprogrammet.
-
==Kopiera filer==
+
Anropet består bara av funktionens namn följt av ett parentespar.
 +
Om du lägger till anropet sist i ditt program ska alltihop se ut så här:
-
<p>Kommandot cp (som st&aring;r f&ouml;r copy, kopiera) eller copy i DOS anv&auml;nds f&ouml;r att kopiera filer. Anta att aktuella katalogen &auml;r sommarkurs&nbsp;och vi vill kopiera filen Prog.py som finns i katalogen sommarkurs/programkod till katalogen Katalog1.</p>
+
<pre>
 +
def visa_information():
 +
''' Skriver ut informationsrutan
 +
'''
 +
print("""
 +
---------------------------------------------------
 +
| VEM HAR DEN SNABBASTE SNIGELN? |
 +
| |
 +
| Här får din snigel tävla mot en vältränad |
 +
| racersnigel. Skriv in namnet på din snigel |
 +
| så sätter tävlingen igång! |
 +
---------------------------------------------------""")
-
<p>Efter kopieringen kommer vi att f&aring; f&ouml;ljande filstruktur:</p>
+
# Läs in namnet på en snigel
 +
# Simulera tävlingen
 +
# Rita upp en snigels bana
 +
# Skriv ut vem som vann
-
[[Bild:795531.jpg]]
+
visa_information()
 +
</pre>
-
<div class="codebox-divided" style="width: 565px;"><div class="codecolumn1"><div class="title">Unix/Mac OS X</div><pre>&gt;pwd
+
Spara i filen snigel.py och provkör!
-
sommarkurs
+
-
&gt;cp programkod/Prog.py test/Katalog1/
+
-
&gt;</pre></div><div class="codecolumn2"><div class="title">DOS</div><pre>C:\sommarkurs&gt;copy programkod\Prog.py test\Katalog1\
+
-
C:\sommarkurs&gt;</pre></div></div>
+
-
<p>Första kommandot pwd är för att säkerställa att vi är i rätt katalog, detta kommando är alltså inte nödvändigt om man vet att aktuella katalogen är sommarkurs.<br />
+
===Skicka utdata från en funktion (returvärden)===
-
Den andra kommandoraden består av följande tre delar:<br />
+
-
Själva kommandot: cp (copy i dos)<br />
+
-
Sökvägen till källan (dvs den fil man vill kopiera)<br />
+
-
Sökvägen till destinationen (dvs den katalog man vill kopiera filen till)</p>
+
-
<p>Om man vill kopiera en hel katalog med alla filer den innehåller måste man även ange flaggan r , (i dos finns ett eget kommando för detta xcopy med flaggan /s får man nästan samma effekt). Anta att vi vill kopiera hela katalogen test med alla filer som finns i den till programkod. Detta gör vi med följande kommando om den aktuella katalogen är sommarkurs:</p>
+
[[Bild:Brev.jpg|right|Ett brev]]
 +
Då ger vi oss på nästa funktion, som ska läsa in namnet på en snigel.
 +
Namnet på snigeln är utdata från funktionen. Den som kör programmet
 +
och knappar in namnet tycker förstås att det är indata, men
 +
ur funktionens synvinkel är det nåt som ska skickas ut.
-
<div class="codebox-divided" style="_width: 480px;"><div class="codecolumn1"><div class="title">Unix/Mac OS X</div><pre>cp -r test programkod</pre></div><div class="codecolumn2"><div class="title">DOS</div><pre>C:\sommarkurs>xcopy /s test programkod</pre></div></div>
+
För att skicka ut ett värde ur funktionen skriver vi ordet
 +
return följt av den variabel vi vill skicka ut värdet på.
 +
Funktionen som läser in namnet kommer att se ut så här:
-
<p>Efter kopieringen får vi följande struktur:</p>
+
<pre>
 +
def las_namn():
 +
''' Läs in namnet på en snigel
 +
returns: namnet på snigeln
 +
'''
 +
namn = input("Vad heter din snigel? ")
 +
return namn
 +
</pre>
-
[[Bild:795532.jpg]]
+
När vi ska anropa funktionen måste vi ta hänsyn till att den
 +
returnerar ett värde. Därför skriver vi anropet i en tilldelningssats,
 +
med en variabel till vänster som får ta emot det returnerade värdet.
 +
Variabeln behöver inte ha samma namn som variabeln i return-satsen
 +
inuti funktionen.
 +
<pre>
 +
din_snigels_namn = las_namn()
 +
</pre>
-
<span style="color:red">[FRÅGOR]</span>
+
När den här satsen utförs av Python kommer följande att hända. Först
 +
anropas funktionen las_namn, som läser in namnet och returnerar det.
 +
Sen kommer det returnerade värdet att lagras i variabel din_snigels_namn.
-
==Radera filer==
+
Vi lägger in den nya i filen snigel.py och provkör.
-
<p><strong>Varning:</strong> Det finns en viktig skillnad mellan terminalf&ouml;nsterbaserad filhantering och grafisk filhantering n&auml;r det g&auml;ller borttagning av filer. N&auml;r man tar bort en fil med hj&auml;lp av den grafiska filhanteraren kommer filen hamna i en speciell katalog som brukar kallas f&ouml;r papperskorg, allts&aring; filen &auml;r inte riktigt borttagen fr&aring;n datorn (vill man ta bort filen s&aring; m&aring;ste man t&ouml;mma papperskorgen). N&auml;r man anv&auml;nder terminalf&ouml;nsterbaserad filhantering f&ouml;rsvinner alla filer som tas bort f&ouml;r gott. D&auml;rf&ouml;r ska man vara v&auml;ldigt f&ouml;rsiktig n&auml;r man ta bort filer i terminalf&ouml;nstret.</p>
+
-
<p><span class="code">rm</span> (som st&aring;r f&ouml;r <b>r</b>e<b>m</b>ove) eller del i DOS &auml;r kommandot som anv&auml;nds f&ouml;r att ta bort filer. Anta att aktuella katalogen &auml;r sommarkurs och man vill ta bort filen <span class="code">Prog2.py</span>. F&ouml;ljande kommando tar bort filen <span class="code">Prog2.py</span>:</p>
+
<pre>
 +
def visa_information():
 +
''' Skriver ut informationsrutan
 +
'''
 +
print("""
 +
---------------------------------------------------
 +
| VEM HAR DEN SNABBASTE SNIGELN? |
 +
| |
 +
| Här får din snigel tävla mot en vältränad |
 +
| racersnigel. Skriv in namnet på din snigel |
 +
| så sätter tävlingen igång! |
 +
---------------------------------------------------""")
-
<div class="codebox-divided" style="_width:470px;"><div class="codecolumn1"><div class="title">Unix/Mac OS X</div><pre>&gt;rm programkod/Prog2.py
 
-
&gt;</pre></div><div class="codecolumn2"><div class="title">DOS</div><pre>C:\sommarkurs&gt;del programkod\prog2.py
 
-
C:\sommarkurs&gt;</pre></div></div>
 
-
<p>S&aring; h&auml;r kommer filstrukturen se ut efter borttagningen:</p>
+
def las_namn():
 +
''' Läs in namnet på en snigel
-
[BILD]
+
returns: namnet på snigeln
 +
'''
 +
namn = input("Vad heter din snigel? ")
 +
return namn
-
<p>Om man vill ta bort en hel katalog med alla filer i ska man i UNIX-baserade operativystem &aring;terge flaggan r till kommandot <span class="code">rm</span>.</p>
+
# Simulera tävlingen
 +
# Rita upp en snigels bana
 +
# Skriv ut vem som vann
-
<p>I Windows är det däremot annorlunda - kommandot <span class="code">del</span> som vi använde oss av ovan fungerar bara på filer, inte mappar. Istället är kommandot <span class="code">rmdir</span> det som gäller. Om mappen inte är tom måste man även ange flaggan S.</p>
+
visa_information()
 +
din_snigels_namn = las_namn()
 +
</pre>
-
<p>Antag att den aktuella katalogen &auml;r sommarkurs och man vill ta bort katalogen test som finns under katalogen programkod med f&ouml;ljande kommando:</p>
+
Men hur ska vi veta om det fungerade eller inte?
 +
Jo, vi lägger in en kontrollutskrift
 +
<pre>
 +
print("Din snigel heter alltså", din_snigels_namn)
 +
</pre>
 +
allra sist i programmet. Kontrollutskrifter är ett enkelt sätt
 +
att se om programmet fungerar som det ska, och är till stor
 +
nytta när man försöker ta reda på varför programmet inte gör
 +
som det ska.
-
<div class="codebox-divided"><div class="codecolumn1"><div class="title">Unix/Mac OS X</div><pre>&gt;rm -r programkod/test
+
Provkör igen. När programmet fungerar kan du ta bort kontrollutskriften!
-
&gt;</pre></div><div class="codecolumn2"><div class="title">DOS</div><pre>C:\sommarkurs&gt;rmdir /S programkod\test<br>C:\sommarkurs&gt;</pre></div></div>
+
-
<p>Bilden nedan visar filstrukturen efter borttagning:</p>
+
Det här är viktigt, så vi tar ett exempel till, den här gången med
 +
två returvärden. Nästa funktion ska simulera tävlingen, på det
 +
här viset:
 +
* Sniglarna startar bägge på startstrecket, dvs vid 0
 +
* Vi skriver ut Klara...färdiga...gå
 +
* Och startar själva tävlingen, som representeras av en while-slinga
 +
* I varje varv i slingan låter vi sniglarna ta varsitt skutt av slumpmässig längd
 +
* När någon av sniglarna (eller bägge) passerar mållinjen så avbryter vi
 +
* Sist returnerar vi bägge sniglarnas slutpositioner
-
[BILD]
+
<pre>
 +
def tavling():
 +
''' Simulerar en tävling mellan två sniglar
-
<span style="color:red">[FRÅGOR]</span>
+
returns: sniglarnas slutpositioner
 +
'''
 +
snigelbana1 = 0
 +
snigelbana2 = 0
 +
print("Klara...färdiga...gå! \n")
 +
while snigelbana1 < DISTANS and snigelbana2 < DISTANS:
 +
snigelbana1 += random.randrange(5)
 +
snigelbana2 += random.randrange(5)
 +
return snigelbana1, snigelbana2
 +
</pre>
-
==Flytta filer==
+
I return-satsen allra sist kan vi se att två värden
 +
returneras, åtskilda med kommatecken. Givetvis måste vi då också
 +
ta emot två värden i huvudprogrammet.
-
<p>Om man vill flytta en fil fr&aring;n en katalog till en annan katalog kan man anv&auml;nda sig av kommandot <span class="code">mv</span> (som st&aring;r f&ouml;r move) eller <span class="code">move</span> i DOS. Anta nu att den aktuella katalogen &auml;r sommarkurs och man vill flytta filen <span class="code">prog.py</span> fr&aring;n katalogen <span class="code">programkod</span> till katalogen <span class="code">test</span>. Detta g&ouml;rs med hj&auml;lp av f&ouml;ljande kommandorad:</p>
+
Den distans (i centimeter) som sniglarna ska tillryggalägga
 +
representeras av konstanten DISTANS. En variabel som ska
 +
ha ett konstant värde under hela programmet brukar man
 +
skriva med stora bokstäver, VERSALER. Python bryr sig inte om det, men
 +
andra människor som läser programmet kan se vad man menar.
 +
Vi definierar DISTANS överst i huvudprogrammet, och frågar
 +
oss om den kommer att synas inuti funktionen?
-
<div class="codebox-divided" style="_width: 500px;"><div class="codecolumn1"><div class="title">Unix/Mac OS X</div><pre>&gt;mv programkod/Prog.py test/<br>&gt;</pre></div><div class="codecolumn2"><div class="title">DOS</div><pre>C:\sommarkurs&gt;move programkod\prog.py test<br>C:\sommarkurs&gt;</pre></div></div>
+
Lägg in den nya funktionen i snigel.py, enligt nedan. Glöm inte
 +
att lägga in kontrollutskrifter av snigelbana1 och snigelbana2
 +
innan du provkör!
-
<p>Ovanst&aring;ende kommando best&aring;r av tre f&ouml;ljande delar:</p>
+
<pre>
 +
import random
-
<p><ol>
+
def visa_information():
-
<li>Sj&auml;lva kommandot: <span class="code">mv</span>
+
''' Skriver ut informationsrutan
-
<li>Hela s&ouml;kv&auml;gen till k&auml;llan: <span class="code">programkod/prog.py</span>
+
'''
-
<li>Hela s&ouml;kv&auml;gen till destinationen: <span class="code">test/</span>
+
print("""
-
</ol></p>
+
---------------------------------------------------
 +
| VEM HAR DEN SNABBASTE SNIGELN? |
 +
| |
 +
| Här får din snigel tävla mot en vältränad |
 +
| racersnigel. Skriv in namnet på din snigel |
 +
| så sätter tävlingen igång! |
 +
---------------------------------------------------""")
-
<p>S&aring; h&auml;r ser fil-strukturen ut:</p>
+
def las_namn():
 +
''' Läs in namnet på en snigel
-
[BILD]
+
returns: namnet på snigeln
 +
'''
 +
namn = input("Vad heter din snigel? ")
 +
return namn
-
<p>Observera att om man f&ouml;rs&ouml;ker flytta en fil till en plats d&auml;r det redan finns en fil med samma namn s&aring; kommer kommandot mv skriva &ouml;ver filen som finns i destinationen. Man f&aring;r bekr&auml;fta eller makulera kommandot genom att svara p&aring; en fr&aring;ga n&auml;r det &auml;r DOS men i Unix/Mac OS X kommer att filen skrivas &ouml;ver utan n&aring;gra varningar. T ex om man k&ouml;r f&ouml;ljande kommando:</p>
+
def tavling():
 +
''' Simulerar en tävling mellan två sniglar
-
<div class="codebox-divided" style="width: 600px;"><div class="codecolumn1" style="_width: 180px;"><div class="title">Unix/Mac OS X</div><pre>&gt;mv test/prog.py test/Katalog1/<br>&gt;</pre></div><div class="codecolumn2"><div class="title">DOS</div><pre>C:\sommarkurs&gt;move test\&gt;Prog.py test\Katalog1\
+
returns: sniglarnas slutpositioner
-
Overwrite C:\sommarkurs\test\Katalog1\Prog.py? (Yes/No/All): Yes
+
'''
-
C:\sommarkurs&gt;</pre></div></div>
+
snigelbana1 = 0
 +
snigelbana2 = 0
 +
print("Klara...färdiga...gå! \n")
 +
while snigelbana1 < DISTANS and snigelbana2 < DISTANS:
 +
snigelbana1 += random.randrange(5)
 +
snigelbana2 += random.randrange(5)
 +
return snigelbana1, snigelbana2
-
[BILD]
+
DISTANS = 30
 +
visa_information()
 +
din_snigels_namn = las_namn()
 +
snigelbana1, snigelbana2 = tavling()
 +
</pre>
-
<p>D&aring; kommer filen <span class="code">test/Katalog1/prog.py</span> ers&auml;ttas med filen <span class="code">test/prog.py</span></p>
+
När du fått det här att fungera så ska vi ta en ny titt på funktionen
 +
tavling. Ser du att vi gör precis samma sak med variablerna snigelbana1
 +
och snigelbana2? Först sätts variablerna till noll, sedan jämför vi
 +
bägge med DISTANS i while-slingans villkor, och inuti while-slingan
 +
ökas bägge med ett slumpat värde. Sist returneras bägge värdena.
 +
Det här kallas för <em>kodupprepning</em> och det ska man försöka
 +
undvika, av följande skäl:
 +
* Det blir mer kod att skriva, vilket ger fler felkällor
 +
* Programmet blir svårare att underhålla - när man ska införa ändringar måste man ändra för bägge variablerna
 +
* Det ser fult ut
-
<p>Man kan ocks&aring; anv&auml;nda kommandot <span class="code">mv</span> f&ouml;r att byta namn p&aring; en fil. Anta att man vill byta namn p&aring; filen <span class="code">prog.py</span> som finns under katalogen <span class="code">test/Katalog1</span> till <span class="code">prog3.py</span>, f&ouml;ljande kommando g&ouml;r detta om aktuella katalogen &auml;r <span class="code">sommarkurs</span>:</p>
+
===Skicka indata till en funktion (parametrar)===
-
<div class="codebox-divided" style="width: 700px;"><div class="codecolumn1"><div class="title">Unix/Mac OS X</div><pre>&gt;mv test/Katalog1/prog.py test/Katalog1/prog3.py
+
I det här avsnittet ska vi sona vårt stilbrott genom att skriva
-
&gt; </pre></div><div class="codecolumn2"><div class="title">DOS</div><pre>C:\sommarkurs&gt;move test/Katalog1/prog.py test/Katalog1/prog3.py
+
en generell funktion för utskrift av en snigels bana. Funktionen ska gå
-
C:\sommarkurs&gt;</pre></div></div>
+
att använda för vilken snigel som helst.
-
[BILD]
+
Utskriften av en snigelbana kan se ut så här:
 +
<code>
 +
Ebba: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @
 +
</code>
 +
Vad vill vi kunna variera från en snigel till en annan? Två saker - snigelns
 +
namn och banans längd. Säg att vår funktion heter <code>rita_banan</code>.
 +
Här följer tre olika exempel på hur en sån funktion skulle kunna anropas:
 +
<pre>
 +
rita_banan("Ebba", 31)
 +
rita_banan("Racersnigeln", 28)
 +
rita_banan(din_snigels_namn, snigelbana1)
 +
</pre>
-
<span style="color:red">[FRÅGOR]</span>
+
De två värden eller variabler som står innanför parenteserna i anropet
 +
kallas för <em>parametrar</em>. När vi definierar funktionen måste vi
 +
namnge alla parametrarna. Vi kallar dom för <code>snigelnamn</code>
 +
respektive <code>langd</code>. Så här ser funktionen ut.
 +
<pre>
 +
def rita_banan(snigelnamn, langd):
 +
''' Ritar en snigelbana
-
==Skapa kataloger==
+
param snigelnamn: namnet på snigeln
 +
param langd: längden på snigelns bana
 +
'''
 +
print(snigelnamn.rjust(12) + ":", end=" ")
 +
for i in range(1, langd):
 +
# slemspåret
 +
print ("-", end=" ")
 +
# snigeln
 +
print("@")
 +
</pre>
-
<p>Om man vill skapa nya kataloger kan man anv&auml;nda sig av kommandot <span class="code">mkdir</span> (som st&aring;r f&ouml;r make directory, skapa katalog).</p>
+
Du har nog inte sett rjust förut? Det är en strängmetod som
 +
högerjusterar strängen i givet antal positioner. Namnen kommer
 +
då att sluta i samma position (12), så att våra snigelbanor kan
 +
starta i samma läge.
-
<p>Anta att aktuella katalogen &auml;r sommarkurs och man vill skapa en katalog med namnet <span class="code">lab1</span> i katalogen programkod, f&ouml;ljande kommando g&ouml;r detta:</p>
+
De två anropen av funktionen ritaBanan kommer att se ut så här:
 +
<pre>
 +
rita_banan(din_snigels_namn, snigelbana1)
 +
rita_banan("Racersnigeln", snigelbana2)
 +
</pre>
-
<div class="codebox-divided"><div class="codecolumn1"><div class="title">Unix/Mac OS X</div><pre>&gt;mkdir programkod/lab1<br>&gt;</pre></div><div class="codecolumn2"><div class="title">DOS</div><pre>C:\sommarkurs&gt; mkdir programkod\lab1<br>C:\sommarkurs&gt;</pre></div></div>
+
Vi lägger till den sista funktionen också, den som ska skriva ut
 +
vem som vann. Som indata till funktionen vill vi skicka in längden
 +
på sniglarnas banor samt sniglarnas namn, alltså totalt fyra olika
 +
parametrar. så här blir det:
-
<p>Hade aktuella katalogen varit <span class="code">programkod</span> s&aring; hade f&ouml;ljande kommando skapat katalogen <span class="code">lab1</span>:</p>
+
<pre>
 +
def utse_vinnare(langd1, langd2, namn1, namn2="Racersnigeln"):
 +
''' Skriver ut vinnaren
-
<div class="codebox-divided"><div class="codecolumn1"><div class="title">Unix/Mac OS X</div><pre>&gt;mkdir lab1
+
param langd1: längden på snigel 1's bana
-
&gt;</pre></div><div class="codecolumn2"><div class="title">DOS</div><pre>C:\sommarkurs\programkod&gt;mkdir lab1
+
param langd2: längden på snigel 2's bana
-
C:\sommarkurs\programkod&gt;</pre></div></div>
+
param namn1: namnet på snigel 1
 +
param namn1: namnet på snigel 2
 +
'''
 +
print(langd1, langd2)
 +
print ("\n")
 +
if langd1 >= DISTANS and langd2 >= DISTANS:
 +
print("Det blev oavgjort.")
 +
else:
 +
if langd1 >= DISTANS:
 +
print("Det här loppet tog en oväntad vändning," , namn1, "vann!")
 +
else:
 +
print(namn2, "vann, som vanligt.")
 +
</pre>
-
<p>s&aring; h&auml;r ser filstrukturen ut efter skapandet av katalogen lab1:</p>
+
Den sista parametern har fått ett skönsvärde, alltså ett värde som
 +
används om vi inte skickar in något på den platsen. Då kan man om man
 +
vill utelämna den parametern i anropet, på det här viset:
 +
<pre>
 +
utse_vinnare(snigelbana1, snigelbana2, din_snigels_namn)
 +
</pre>
-
[BILD]
+
Nu sätter vi ihop alltihop och provkör. För att undvika globala variabler så lägger vi in ett huvudprogram:
 +
<pre>
 +
import random
-
==Slutprov 4==
+
def visa_information():
 +
''' Skriver ut informationsrutan
 +
'''
 +
print ("""
 +
---------------------------------------------------
 +
| VEM HAR DEN SNABBASTE SNIGELN? |
 +
| |
 +
| Här får din snigel tävla mot en vältränad |
 +
| racersnigel. Skriv in namnet på din snigel |
 +
| så sätter tävlingen igång! |
 +
---------------------------------------------------""")
 +
 
 +
def las_namn():
 +
''' Läs in namnet på en snigel
 +
 
 +
returns: namnet på snigeln
 +
'''
 +
namn = input("Vad heter din snigel? ")
 +
return namn
 +
 
 +
def tavling():
 +
''' Simulerar en tävling mellan två sniglar
 +
 
 +
returns: sniglarnas slutpositioner
 +
'''
 +
snigelbana1 = 0
 +
snigelbana2 = 0
 +
print("Klara...färdiga...gå! \n")
 +
while snigelbana1 < DISTANS and snigelbana2 < DISTANS:
 +
snigelbana1 += random.randrange(5)
 +
snigelbana2 += random.randrange(5)
 +
return snigelbana1, snigelbana2
 +
 
 +
 +
def rita_banan(snigelnamn, langd):
 +
''' Ritar en snigelbana
 +
 
 +
param snigelnamn: namnet på snigeln
 +
param langd: längden på snigelns bana
 +
'''
 +
print(snigelnamn.rjust(12) + ":", end=" ")
 +
for i in range(1,langd):
 +
print("-", end = " ") #slemspåret
 +
print("@") # snigeln
 +
 
 +
def utse_vinnare(langd1, langd2, namn1, namn2="Racersnigeln"):
 +
''' Skriver ut vinnaren
 +
 
 +
param langd1: längden på snigel 1's bana
 +
param langd2: längden på snigel 2's bana
 +
param namn1: namnet på snigel 1
 +
param namn1: namnet på snigel 2
 +
'''
 +
print(langd1, langd2)
 +
print("\n")
 +
if langd1 >= DISTANS and langd2 >= DISTANS:
 +
print("Det blev oavgjort.")
 +
else:
 +
if langd1 >= DISTANS:
 +
print("Det här loppet tog en oväntad vändning," , namn1, "vann!")
 +
else:
 +
print(namn2, "vann, som vanligt.")
 +
 
 +
def huvudprogram():
 +
''' Simulerar en snigeltävling
 +
'''
 +
visa_information()
 +
din_snigels_namn = las_namn()
 +
snigelbana1, snigelbana2 = tavling()
 +
rita_banan(din_snigels_namn, snigelbana1)
 +
rita_banan("Racersnigeln", snigelbana2)
 +
utse_vinnare(snigelbana1, snigelbana2, din_snigels_namn, "Racersnigeln")
 +
 
 +
DISTANS = 30
 +
huvudprogram()
 +
</pre>
 +
 
 +
==Kommentera en funktion==
 +
Som redan nämnts så är kommentarer mycket viktiga för att man ska kunna förstå och överblicka sina program. Man kommentera både funktioner i sin helhet samt specifika rader, då det ibland kan underlätta ifall kommentaren är där det som den beskriver händer istället för vid funktionsdeklarationen. Använd följande struktur för funktionskommentarer:
 +
<pre>def funktionens_namn(param1, param2):
 +
'''<Kort kommentar/förklaring om vad funktionen gör>
 +
 
 +
param param1: <förklara inparametern>
 +
param param2: <förklara inparametern>
 +
returns: <returvärden>
 +
'''
 +
</pre>
 +
 
 +
Se [[1.1._Att_Kommentera|Att kommentera]] för exempel.
 +
 
 +
==Rekursion==
 +
[[Bild:Droste.jpg|Bild på kakaopaket|thumb]]
 +
 
 +
Nu börjar vi om med ett helt nytt problem. Vi vill skriva ett
 +
program som räknar ut summan av dom första n heltalen, t ex
 +
1+2+3+4=10. Det här är ett så enkelt problem, så det räcker
 +
med en enda funktion. Hur ska vi beräkna summan då? Vi
 +
förenklar problemet lite: Säg att vi ska räkna ut summan av de
 +
fem första heltalen. Då kan vi först räkna ut summan av de fyra
 +
första heltalen och sen lägga till fem. Men summan av de
 +
fyra första heltalen är ju lätt att räkna ut - det är ju
 +
summan av de tre första heltalen plus fyra. Och så vidare...
 +
 
 +
Vi skriver summaberäkningen som en funktion, och låter den
 +
räkna enligt mönstret summa(n) = summa(n-1) + n.
 +
Funktionen har n som parameter, och programmet ser ut så här:
 +
 
 +
<pre>
 +
def summa(n):
 +
''' Beräknar summan 1+2+...+n-1+n
 +
 
 +
param n: positivt heltal
 +
returns: summan av 1+2+...+n-1+n
 +
'''
 +
return summa(n-1) + n
 +
 
 +
print("Välkommen till summaberäkningsprogrammet!")
 +
print("Här beräknas summan 1+2+3+...n")
 +
n = int(input("Vilket heltal ska vara det sista i summan? "))
 +
print("Summan = ", summa(n))
 +
</pre>
 +
 
 +
Provkörde du? Isåfall kan det vara bra att komma ihåg att man kan avbryta
 +
ett program med Ctrl-C. Det som händer här är att funktionen summa
 +
anropar sig själv i all oändlighet. Vad beror det på?
 +
 
 +
Jo, vi har glömt att tala om när beräkningen ska avbrytas. Anropar
 +
vi med n=3, så kommer funktionen att försöka räkna ut
 +
summa(2), summa(1), summa(0), summa(-1) och så vidare.
 +
 
 +
Vi vill ju att det minsta talet i summan ska vara 1, och det måste
 +
vi ange i funktionen. Nytt försök:
 +
 
 +
<pre>
 +
def summa(n):
 +
''' Beräknar summan 1+2+...+n-1+n
 +
 
 +
param n: positivt heltal
 +
returns: summan av 1+2+...+n-1+n
 +
'''
 +
if n > 1:
 +
return summa(n-1) + n
 +
else:
 +
return 1
 +
 
 +
print("Välkommen till summaberäkningsprogrammet!")
 +
n = int(input("Vilket tal ska vara det sista i summan? "))
 +
print("Summan = ", summa(n))
 +
</pre>
 +
 
 +
Det här fungerade väl bra? Att lösa ett problem genom att låta en
 +
funktion anropa sig själv kallas rekursion. Det man behöver är:
 +
* ''Rekursiv tanke:'' som reducerar problemet till ett enklare problem med samma struktur
 +
* ''Basfall:'' det måste finnas ett fall som inte leder till rekursivt anrop
 +
 
 +
 
 +
==Test 4==
 +
 
 +
Dags för test nummer 4. Testet hittar du som vanligt under rubriken Examination
 +
på kursens förstasida. Även detta test rättas automatiskt och du har möjlighet
 +
att göra om testet flera gånger om du inte lyckas på första försöket.
 +
 
 +
 
 +
==Inlämningsuppgift 2==
 +
 
 +
[[4. Inlämningsuppgift 2|Inlämningsuppgift 2: Rondelet]]

Nuvarande version

       Teori          Övningar          Rondelet      


Innehåll

Vad har man funktioner till?

Stora program är besvärligare att konstruera än små. Men man kan göra det enklare för sig genom att dela upp programmet i funktioner. En funktion är som ett litet delprogram i programmet.

Bild:Snigeltavling.jpg

När man anordnar tävlingar med riktiga sniglar tar det mycket lång tid innan deltagarna kommer i mål. Vi ska istället skriva ett litet program som simulerar en tävling mellan två sniglar:

             ---------------------------------------------------
             |          VEM HAR DEN SNABBASTE SNIGELN?         |
             |                                                 |
             |  Här får din snigel tävla mot en vältränad      |
             |  racersnigel. Skriv in namnet på din snigel     |
             |  så sätter tävlingen igång!                     |
             ---------------------------------------------------
Vad heter din snigel? Ebba
Klara...färdiga...gå!

        Ebba: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @
Racersnigeln: - - - - - - - - - - - - - - - - - - - - - - - - - - - @


Det här loppet tog en oväntad vändning, Ebba vann!


Dela upp ett problem i funktioner

För att programmet ska bli enklare att skriva bestämmer vi först vilka funktioner vi ska ha. Det är inte alls självklart hur programmet ska delas upp i funktioner; samma problem kan lösas på många olika sätt! Vi vill se till att varje funktion har ett enda uppdrag.

Här är ett förslag på uppdelning:

  1. Skriv ut informationsrutan och läs in namnet på snigeln
  2. Simulera tävlingen och rita upp banorna
  3. Skriv ut vem som vann

Ettan och tvåan består av flera olika moment. Vi gör det ännu enklare för oss genom att dela upp i ännu mindre delar:

  1. Skriv ut informationsrutan
  2. Läs in namnet på en snigel
  3. Simulera tävlingen
  4. Rita upp en snigels bana
  5. Skriv ut vem som vann

Vi börjar med att skriva in dessa moment som kommentarer i filen snigel.py, för att vi ska hålla reda på vad som är kvar att göra. Då får vi också en kommentar för varje funktion, så att det syns vad som händer var.

Definiera en funktion

Först kommer funktionshuvudet (första raden i funktionen). Exempel:

def visa_information():

Funktionshuvudet består av fyra delar.

  • Ordet def (talar om att en funktion ska definieras här)
  • Funktionens namn, gärna ett verb som beskriver vad funktionen gör
  • Ett par parenteser ()
  • Ett kolon

Under funktionshuvudet lägger man funktionkommentaren:

def visa_information():
    ''' Skriver ut informationsrutan
    '''

Sedan kommer funktionskroppen, som helt enkelt är en följd av indenterade satser som funktionen ska utföra. Så här ser hela funktionen ut:

def visa_information():
    ''' Skriver ut informationsrutan
    '''
    print("""
             ---------------------------------------------------
             |          VEM HAR DEN SNABBASTE SNIGELN?         |
             |                                                 |
             |  Här får din snigel tävla mot en vältränad      |
             |  racersnigel. Skriv in namnet på din snigel     |
             |  så sätter tävlingen igång!                     |
             ---------------------------------------------------""")

(För att kunna skriva en sträng som sträcker sig över flera rader så startar och avslutar vi strängen med tre stycken "-tecken. Vanliga strängar, som börjar och slutar med enkelt citattecken, kan nämligen inte innehålla radbrytningar.)

Lägg till koden ovan i din fil snigel.py och provkör.

Men vad nu - programmet skriver inte ut något alls? Gå vidare till nästa avsnitt för att få reda på varför!

Anropa en funktion

Satserna i funktionskroppen utförs inte om inte funktionen anropas. Anropet ska stå längst ner i programmet, under funktionsdefinitionerna. Den delen av programmet kallar vi huvudprogrammet.

Anropet består bara av funktionens namn följt av ett parentespar. Om du lägger till anropet sist i ditt program ska alltihop se ut så här:

def visa_information():
    ''' Skriver ut informationsrutan
    '''
    print("""
             ---------------------------------------------------
             |          VEM HAR DEN SNABBASTE SNIGELN?         |
             |                                                 |
             |  Här får din snigel tävla mot en vältränad      |
             |  racersnigel. Skriv in namnet på din snigel     |
             |  så sätter tävlingen igång!                     |
             ---------------------------------------------------""")

# Läs in namnet på en snigel
# Simulera tävlingen
# Rita upp en snigels bana
# Skriv ut vem som vann

visa_information()

Spara i filen snigel.py och provkör!

Skicka utdata från en funktion (returvärden)

Ett brev

Då ger vi oss på nästa funktion, som ska läsa in namnet på en snigel. Namnet på snigeln är utdata från funktionen. Den som kör programmet och knappar in namnet tycker förstås att det är indata, men ur funktionens synvinkel är det nåt som ska skickas ut.

För att skicka ut ett värde ur funktionen skriver vi ordet return följt av den variabel vi vill skicka ut värdet på. Funktionen som läser in namnet kommer att se ut så här:

def las_namn():
    ''' Läs in namnet på en snigel

    returns: namnet på snigeln
    '''
    namn = input("Vad heter din snigel? ")
    return namn

När vi ska anropa funktionen måste vi ta hänsyn till att den returnerar ett värde. Därför skriver vi anropet i en tilldelningssats, med en variabel till vänster som får ta emot det returnerade värdet. Variabeln behöver inte ha samma namn som variabeln i return-satsen inuti funktionen.

din_snigels_namn = las_namn()

När den här satsen utförs av Python kommer följande att hända. Först anropas funktionen las_namn, som läser in namnet och returnerar det. Sen kommer det returnerade värdet att lagras i variabel din_snigels_namn.

Vi lägger in den nya i filen snigel.py och provkör.

def visa_information():
    ''' Skriver ut informationsrutan
    '''
    print("""
             ---------------------------------------------------
             |          VEM HAR DEN SNABBASTE SNIGELN?         |
             |                                                 |
             |  Här får din snigel tävla mot en vältränad      |
             |  racersnigel. Skriv in namnet på din snigel     |
             |  så sätter tävlingen igång!                     |
             ---------------------------------------------------""")


def las_namn():
    ''' Läs in namnet på en snigel

    returns: namnet på snigeln
    '''
    namn = input("Vad heter din snigel? ")
    return namn

# Simulera tävlingen
# Rita upp en snigels bana
# Skriv ut vem som vann

visa_information()
din_snigels_namn = las_namn()

Men hur ska vi veta om det fungerade eller inte? Jo, vi lägger in en kontrollutskrift

print("Din snigel heter alltså", din_snigels_namn)

allra sist i programmet. Kontrollutskrifter är ett enkelt sätt att se om programmet fungerar som det ska, och är till stor nytta när man försöker ta reda på varför programmet inte gör som det ska.

Provkör igen. När programmet fungerar kan du ta bort kontrollutskriften!

Det här är viktigt, så vi tar ett exempel till, den här gången med två returvärden. Nästa funktion ska simulera tävlingen, på det här viset:

  • Sniglarna startar bägge på startstrecket, dvs vid 0
  • Vi skriver ut Klara...färdiga...gå
  • Och startar själva tävlingen, som representeras av en while-slinga
  • I varje varv i slingan låter vi sniglarna ta varsitt skutt av slumpmässig längd
  • När någon av sniglarna (eller bägge) passerar mållinjen så avbryter vi
  • Sist returnerar vi bägge sniglarnas slutpositioner
def tavling():
    ''' Simulerar en tävling mellan två sniglar

    returns: sniglarnas slutpositioner
    '''
    snigelbana1 = 0
    snigelbana2 = 0
    print("Klara...färdiga...gå! \n")
    while snigelbana1 < DISTANS and snigelbana2 < DISTANS:
        snigelbana1 += random.randrange(5)
        snigelbana2 += random.randrange(5)
    return snigelbana1, snigelbana2

I return-satsen allra sist kan vi se att två värden returneras, åtskilda med kommatecken. Givetvis måste vi då också ta emot två värden i huvudprogrammet.

Den distans (i centimeter) som sniglarna ska tillryggalägga representeras av konstanten DISTANS. En variabel som ska ha ett konstant värde under hela programmet brukar man skriva med stora bokstäver, VERSALER. Python bryr sig inte om det, men andra människor som läser programmet kan se vad man menar. Vi definierar DISTANS överst i huvudprogrammet, och frågar oss om den kommer att synas inuti funktionen?

Lägg in den nya funktionen i snigel.py, enligt nedan. Glöm inte att lägga in kontrollutskrifter av snigelbana1 och snigelbana2 innan du provkör!

import random

def visa_information():
    ''' Skriver ut informationsrutan
    '''
    print("""
             ---------------------------------------------------
             |          VEM HAR DEN SNABBASTE SNIGELN?         |
             |                                                 |
             |  Här får din snigel tävla mot en vältränad      |
             |  racersnigel. Skriv in namnet på din snigel     |
             |  så sätter tävlingen igång!                     |
             ---------------------------------------------------""")

def las_namn():
    ''' Läs in namnet på en snigel

    returns: namnet på snigeln
    '''
    namn = input("Vad heter din snigel? ")
    return namn

def tavling():
    ''' Simulerar en tävling mellan två sniglar

    returns: sniglarnas slutpositioner
    '''
    snigelbana1 = 0
    snigelbana2 = 0
    print("Klara...färdiga...gå! \n")
    while snigelbana1 < DISTANS and snigelbana2 < DISTANS:
        snigelbana1 += random.randrange(5)
        snigelbana2 += random.randrange(5)
    return snigelbana1, snigelbana2

DISTANS = 30
visa_information()
din_snigels_namn = las_namn()
snigelbana1, snigelbana2 = tavling()

När du fått det här att fungera så ska vi ta en ny titt på funktionen tavling. Ser du att vi gör precis samma sak med variablerna snigelbana1 och snigelbana2? Först sätts variablerna till noll, sedan jämför vi bägge med DISTANS i while-slingans villkor, och inuti while-slingan ökas bägge med ett slumpat värde. Sist returneras bägge värdena. Det här kallas för kodupprepning och det ska man försöka undvika, av följande skäl:

  • Det blir mer kod att skriva, vilket ger fler felkällor
  • Programmet blir svårare att underhålla - när man ska införa ändringar måste man ändra för bägge variablerna
  • Det ser fult ut

Skicka indata till en funktion (parametrar)

I det här avsnittet ska vi sona vårt stilbrott genom att skriva en generell funktion för utskrift av en snigels bana. Funktionen ska gå att använda för vilken snigel som helst.

Utskriften av en snigelbana kan se ut så här:

       Ebba: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @

Vad vill vi kunna variera från en snigel till en annan? Två saker - snigelns namn och banans längd. Säg att vår funktion heter rita_banan. Här följer tre olika exempel på hur en sån funktion skulle kunna anropas:

rita_banan("Ebba", 31)
rita_banan("Racersnigeln", 28)
rita_banan(din_snigels_namn, snigelbana1)

De två värden eller variabler som står innanför parenteserna i anropet kallas för parametrar. När vi definierar funktionen måste vi namnge alla parametrarna. Vi kallar dom för snigelnamn respektive langd. Så här ser funktionen ut.

def rita_banan(snigelnamn, langd):
    ''' Ritar en snigelbana

    param snigelnamn: namnet på snigeln
    param langd: längden på snigelns bana  
    '''
    print(snigelnamn.rjust(12) + ":", end=" ")
    for i in range(1, langd):
        # slemspåret
        print ("-", end=" ")    
    # snigeln           
    print("@")                     

Du har nog inte sett rjust förut? Det är en strängmetod som högerjusterar strängen i givet antal positioner. Namnen kommer då att sluta i samma position (12), så att våra snigelbanor kan starta i samma läge.

De två anropen av funktionen ritaBanan kommer att se ut så här:

rita_banan(din_snigels_namn, snigelbana1)
rita_banan("Racersnigeln", snigelbana2)

Vi lägger till den sista funktionen också, den som ska skriva ut vem som vann. Som indata till funktionen vill vi skicka in längden på sniglarnas banor samt sniglarnas namn, alltså totalt fyra olika parametrar. så här blir det:

def utse_vinnare(langd1, langd2, namn1, namn2="Racersnigeln"):
    ''' Skriver ut vinnaren

    param langd1: längden på snigel 1's bana 
    param langd2: längden på snigel 2's bana 
    param namn1: namnet på snigel 1
    param namn1: namnet på snigel 2
    '''
    print(langd1, langd2)
    print ("\n")
    if langd1 >= DISTANS and langd2 >= DISTANS:
        print("Det blev oavgjort.")
    else:
        if langd1 >= DISTANS:
            print("Det här loppet tog en oväntad vändning," , namn1, "vann!")
        else:
            print(namn2, "vann, som vanligt.")

Den sista parametern har fått ett skönsvärde, alltså ett värde som används om vi inte skickar in något på den platsen. Då kan man om man vill utelämna den parametern i anropet, på det här viset:

utse_vinnare(snigelbana1, snigelbana2, din_snigels_namn)

Nu sätter vi ihop alltihop och provkör. För att undvika globala variabler så lägger vi in ett huvudprogram:

import random

def visa_information():
    ''' Skriver ut informationsrutan
    '''
    print ("""
             ---------------------------------------------------
             |          VEM HAR DEN SNABBASTE SNIGELN?         |
             |                                                 |
             |  Här får din snigel tävla mot en vältränad      |
             |  racersnigel. Skriv in namnet på din snigel     |
             |  så sätter tävlingen igång!                     |
             ---------------------------------------------------""")

def las_namn():
    ''' Läs in namnet på en snigel

    returns: namnet på snigeln
    '''
    namn = input("Vad heter din snigel? ")
    return namn

def tavling():
    ''' Simulerar en tävling mellan två sniglar

    returns: sniglarnas slutpositioner
    '''
    snigelbana1 = 0
    snigelbana2 = 0
    print("Klara...färdiga...gå! \n")
    while snigelbana1 < DISTANS and snigelbana2 < DISTANS:
        snigelbana1 += random.randrange(5)
        snigelbana2 += random.randrange(5)
    return snigelbana1, snigelbana2

      
def rita_banan(snigelnamn, langd):
    ''' Ritar en snigelbana

    param snigelnamn: namnet på snigeln
    param langd: längden på snigelns bana  
    '''
    print(snigelnamn.rjust(12) + ":", end=" ")
    for i in range(1,langd):
        print("-", end = " ")    #slemspåret
    print("@")         # snigeln

def utse_vinnare(langd1, langd2, namn1, namn2="Racersnigeln"):
    ''' Skriver ut vinnaren

    param langd1: längden på snigel 1's bana 
    param langd2: längden på snigel 2's bana 
    param namn1: namnet på snigel 1
    param namn1: namnet på snigel 2
    '''
    print(langd1, langd2)
    print("\n")
    if langd1 >= DISTANS and langd2 >= DISTANS:
        print("Det blev oavgjort.")
    else:
        if langd1 >= DISTANS:
            print("Det här loppet tog en oväntad vändning," , namn1, "vann!")
        else:
            print(namn2, "vann, som vanligt.")

def huvudprogram():
    ''' Simulerar en snigeltävling
    '''
    visa_information()
    din_snigels_namn = las_namn()
    snigelbana1, snigelbana2 = tavling()
    rita_banan(din_snigels_namn, snigelbana1)
    rita_banan("Racersnigeln", snigelbana2)
    utse_vinnare(snigelbana1, snigelbana2, din_snigels_namn, "Racersnigeln")

DISTANS = 30
huvudprogram()

Kommentera en funktion

Som redan nämnts så är kommentarer mycket viktiga för att man ska kunna förstå och överblicka sina program. Man kommentera både funktioner i sin helhet samt specifika rader, då det ibland kan underlätta ifall kommentaren är där det som den beskriver händer istället för vid funktionsdeklarationen. Använd följande struktur för funktionskommentarer:

def funktionens_namn(param1, param2):
'''<Kort kommentar/förklaring om vad funktionen gör>

param param1: <förklara inparametern>
param param2: <förklara inparametern>
returns: <returvärden>
'''

Se Att kommentera för exempel.

Rekursion

Bild på kakaopaket
Bild på kakaopaket

Nu börjar vi om med ett helt nytt problem. Vi vill skriva ett program som räknar ut summan av dom första n heltalen, t ex 1+2+3+4=10. Det här är ett så enkelt problem, så det räcker med en enda funktion. Hur ska vi beräkna summan då? Vi förenklar problemet lite: Säg att vi ska räkna ut summan av de fem första heltalen. Då kan vi först räkna ut summan av de fyra första heltalen och sen lägga till fem. Men summan av de fyra första heltalen är ju lätt att räkna ut - det är ju summan av de tre första heltalen plus fyra. Och så vidare...

Vi skriver summaberäkningen som en funktion, och låter den räkna enligt mönstret summa(n) = summa(n-1) + n. Funktionen har n som parameter, och programmet ser ut så här:

def summa(n):
    ''' Beräknar summan 1+2+...+n-1+n

    param n: positivt heltal
    returns: summan av 1+2+...+n-1+n
    '''
    return summa(n-1) + n

print("Välkommen till summaberäkningsprogrammet!")
print("Här beräknas summan 1+2+3+...n")
n = int(input("Vilket heltal ska vara det sista i summan? "))
print("Summan = ", summa(n))

Provkörde du? Isåfall kan det vara bra att komma ihåg att man kan avbryta ett program med Ctrl-C. Det som händer här är att funktionen summa anropar sig själv i all oändlighet. Vad beror det på?

Jo, vi har glömt att tala om när beräkningen ska avbrytas. Anropar vi med n=3, så kommer funktionen att försöka räkna ut summa(2), summa(1), summa(0), summa(-1) och så vidare.

Vi vill ju att det minsta talet i summan ska vara 1, och det måste vi ange i funktionen. Nytt försök:

def summa(n):
    ''' Beräknar summan 1+2+...+n-1+n

    param n: positivt heltal
    returns: summan av 1+2+...+n-1+n
    '''
    if n > 1:
        return summa(n-1) + n
    else:
        return 1

print("Välkommen till summaberäkningsprogrammet!")
n = int(input("Vilket tal ska vara det sista i summan? "))
print("Summan = ", summa(n))

Det här fungerade väl bra? Att lösa ett problem genom att låta en funktion anropa sig själv kallas rekursion. Det man behöver är:

  • Rekursiv tanke: som reducerar problemet till ett enklare problem med samma struktur
  • Basfall: det måste finnas ett fall som inte leder till rekursivt anrop


Test 4

Dags för test nummer 4. Testet hittar du som vanligt under rubriken Examination på kursens förstasida. Även detta test rättas automatiskt och du har möjlighet att göra om testet flera gånger om du inte lyckas på första försöket.


Inlämningsuppgift 2

Inlämningsuppgift 2: Rondelet