6. Moment06 - Moduler och paket

Moduler, paket och bibliotek i python har alla gemensamt att de hänvisar till saker som måste importeras till din applikation för att kunna användas. En modul i python har du redan tillverkat själv i bankapplikationen i moment05. Modulen du tillverkade hette i tutorialen "functions.py". En modul är helt enkelt en fil som bara innehåller funktioner.

Eftersom moduler är något som vi redan arbetat med och även tillverkat själva är det paket och några olika externa sådana vi kommer titta på i moment06. Ett paket, även kallat bibliotek, är en samling moduler som ökar funktionaliteten i python. Ett paket du använt sedan du första gången använde python är "python standard library", som är ett standardpaket i python som tillåter dig att använda alla de funktioner och datatyper vi använt hittills.

I moment06 kommer du få studera några olika paket och moduler för att få en känsla av den breda funktionalitet som python erbjuder oavsett om du vill tillverka spel, göra vetenskapliga matematiska analyser eller något däremellan.

Info

Till detta moment finns en sida med lösningsförslag och videoklipp.

6.1 Använda paket i PyCharm

När vi arbetar i PyCharm och vill använda ett paket eller en modul som inte ingår i pythons standardbibliotek måste vi genomgå följande steg:

  1. Uppe i statusbaren (på mac), tryck på PyCharm -> Preferences... Klicka för bild
  2. Klicka på Project: dittProjektNamn Klicka för bild
  3. För att söka upp och installera nya paket och moduler, klicka på "+" nere till vänster i rutan. Sök sedan upp paket du vill installera och klicka på "Install package". Klicka för bild

6.2 SciPy

SciPy (scipy.org) är både ett samlingsnamn för en grupp python-paket som tillsammans är användbara i naturvetenskapliga syften och ett specifikt python-paket som heter just SciPy.

6.2.1 NumPy

Ett paket som ingår i SciPy är paketet NumPy. NumPy innehåller bland annat funktionalitet för att smidigare arbeta med listor, som här kallas för array (engelska för matris), funktionalitet för att skapa och arbeta med polynom och en massa annat vars syfte är att ge matematiker och naturvetare verktyg för att arbeta effektivt i python.

Kodexempel: Importera numpy enligt gällande standard

# Följande är det rekommenderade sättet att importera numpy
import numpy as np

6.2.1.1 Polynom

I matematik 2c lär du dig vad ett polynom är. Som en kort repetition så är ett polynom ett algebraiskt uttryck som består av både variabler och konstanter. Polynom kan ha flera dimensioner och dimensionerna i polynom avgörs av hur många olika variabler som används. Varje använd variabel ökar dimensionen med ett.

Polynom repetition [klicka för att visa]

Här finns ett videoklipp från MatteCentrum där två polynom multipliceras.

Kodexempel: Koden för exemplet i videoklippet

import numpy as np

höjd = np.poly1d([2, -3])
bas = np.poly1d([1, 2])
area = bas * höjd

print("Arean kan tecknas:\n", area)

Exempel på några endimensionella polynom:

x² + 3x + 4
2y³ - 2y² + 5y
5x² - 3x + 5012

Med hjälp av poly1d som ingår i numpy-paketet kan vi skapa och arbeta med 1-dimensionella polynom i python.

Kodexempel: Konstruera polynom med poly1d.

import numpy as np

# Konstruerar de tre polynom i exemplet ovan med np.poly1d(lista)
polynom1 = np.poly1d([1, 3, 4])
polynom2 = np.poly1d([2, -2, 5, 0], variable='y')
polynom3 = np.poly1d([5, -3, 5012])

# Som du kan se i utskrifterna skrivs varje polynom ut på två rader,
# där den första raden används för exponenterna på variablerna.
print(polynom1, '\n')
print(polynom2, '\n')
print(polynom3)

Utskrift

   2
1 x + 3 x + 4

   3     2
2 y - 2 y + 5 y

   2
5 x - 3 x + 5012

När vi konstruerat polynom kan vi utföra beräkningar på dem, som att slå ihop polynom med hjälp av valfri räkneoperation och att få fram värden vid olika värden på variabeln.

Kodexempel: Arbeta med polynom.

import numpy as np

polynom1 = np.poly1d([1, 3, 4])
polynom2 = np.poly1d([1, 1, 2, 5, 0], variable='y')
polynom3 = np.poly1d([1, -3, 5012])

# Genomför diverse beräkningar med polynomen
poly_ad_13 = polynom1 + polynom3

# Det spelar ingen roll vilken variabel som används,
# när polynomen adderas behandlas det som samma variabel
poly_ad_12  = polynom1 + polynom2

poly_mult_1konst = polynom1 * 4
poly_mult_13 = polynom1 *  polynom3

print('Polynom 1:\n{}\n\nPolynom 2:\n{}\n\nPolynom 3:\n{}\n'.format(polynom1, polynom2, polynom3))
print('Addition av polynom 1 och 3 ger:\n{}\n'.format(poly_ad_13))
print('Addition av polynom 1 och 2 ger:\n{}\n'.format(poly_ad_12))
print('Multipliceras polynom 1 med en konstant  4 får vi:\n{}\n'.format(poly_mult_1konst))
print('Multiplikation av polynom 1 och 3 ger:\n{}\n'.format(poly_mult_13))

Utskrift

Polynom 1:
   2
1 x + 3 x + 4

Polynom 2:
   4     3     2
1 y + 1 y + 2 y + 5 y

Polynom 3:
   2
1 x - 3 x + 5012

Addition av polynom 1 och 3 ger:
   2
2 x + 5016

Addition av polynom 1 och 2 ger:
   4     3     2
1 x + 1 x + 3 x + 8 x + 4

Multipliceras polynom 1 med en konstant  4 får vi:
   2
4 x + 12 x + 16

Multiplikation av polynom 1 och 3 ger:
   4        2
1 x + 5007 x + 1.502e+04 x + 2.005e+04

Några saker att tänka på vid hantering och särskilt vid utskrifter av polynom:

  • Varje polynom tar upp 2 rader, vilket innebär att vi är tvungna att skriva ut polynom på tomma rader för att det inte ska se konstigt ut.
  • Det spelar ingen roll vilken variabel som används när vi konstruerar polynom, när vi räknar med polynom tolkar numpy det som samma variabel.
  • Numpy använder vetenskaplig notation för stora tal. Till exempel är 2.005e+04 = 2.005 * 104 = 20050

Uppgift: m06u01

Konstruera applikationen fusk_2000 som låter användaren konstruera fyra valfria polynom.

Applikationen ska sedan skriva ut de polynom som användaren har matat in samt detta polynom i kvadrat genom att anropa funktionen print_polynom, som är en funktion du själv konstruerat och som har en lista som inparameter.

Konstanterna användaren anger får bara vara heltal.

Hjälp: Planering [klicka för att visa]

Här är ett ypperligt tillfälle att bryta ner uppgiften i mindre delar. Det kan vara smart att använda sig av pseudokod, som sedan blir kommentarer, för att planera lösningen.

Lämpliga verktyg att använda är;

  • Loopar
  • Listor
  • Funktioner
  • Inmatning
  • Typkonvertering / try-except:

Hjälp: Förslag på utskrifter [klicka för att visa]

Förslag på inmatning

Förslag på inmatning

Utskriften

Förslag på utskrifter

6.2.1.2 Array

Med numpy kan vi också konstruera listor på lite annorlunda sätt än tidigare. I numpy kallas listor för array och i följande kodexempel kan du se några sätt att skapa listor på med lite olika finesser.

Kodexempel: Konstruera listor med numpy.

import numpy as np

# Skapar en array med 6 jämt utspridda värden från 0 till 2
a = np.linspace(0, 2, 6)

# Fungerar på samma sätt som när du skapar en vanlig lista i pythons standardbibliotek
b = np.array([0, 1, 5, 12, -15])

# Skapar en array med heltal från 0 till 10, likt range i standardbiblioteket
c = np.arange(10)

# Skapar en array med 10st 1:or
d = np.ones(10)

# Skapar en array med 10st 0:or
e = np.zeros(10)

# Skapar en array med 10st slumpade tal mellan 0 och 100
f = np.random.randint(0, 101, 10)

print(a)
print(b)
print(c)
print(d)
print(e)
print(f)

Utskrift

[0.  0.4 0.8 1.2 1.6 2. ]
[  0   1   5  12 -15]
[0 1 2 3 4 5 6 7 8 9]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[35 28 27 74 98 55 62 68  6 33]

Uppgift: m06u02

Din kompis ber dig försöka gissa vilket tal hen tänker på, du får veta att det är ett tal mellan 1 och 100. Du börjar med en enkel algoritm och gissar först på 1, sedan på 2, sedan på 3 osv ända tills du kommer till det tal din kompis tänkte på. Detta är ett exempel på en sökalgoritm.

  1. Bygg en applikation som simulerar det som förklarades i texten ovan. Sökalgoritmen ska konstrueras som en funktion i din applikation.
  2. finns det något sätt att optimera sökalgoritmen? Försök komma på ett sätt att skapa en funktion som gör sökandet mer effektivt.
    Tips: Importera time-modulen och undersök hur lång tid de olika algoritmerna tar. Klicka för bild
  3. Studera algoritmen som beskrivs i wikipediaartikeln (den första under rubriken procedure). Är detta den algoritm du hittade i uppgift b? Om ja, bra jobbat! Om nej, försök implementera algoritmen i ditt program och jämför söktiden mellan algoritmen i uppgift a) och uppgift b).

Uppgift: m06u03

Detta är en uppgift som bäst görs i grupp om 2-4 elever. Studera följande kod och resultatet av körningen.

Er uppgift är nu att

  1. Fundera och se till att hela gruppen förstår vad som händer i alla delar i applikationen.
  2. Skriv en pseudokod och tillverka ett aktivitetsdiagram till applikationen.
  3. Som ni ser är körtiden på applikationen lång. Optimera sökalgoritmen som används för att kolla om ett tal finns i listan eller ej.

6.2.2 matplotlib.pyplot

När vi konstruerat ett polynom med hjälp av numpy kan det vara intressant att se hur polynomet ser ut grafiskt, genom att plotta.

Exempel: Grafen till 𝑦=3𝑥3+2𝑥2+𝑥+10

Koden för att producera den här bilden är på endast 8 rader. Med hjälp av biblioteket matplotlib och en modul som heter pyplot blir det med hjälp av python väldigt enkelt att konstruera en mängd olika diagram.

Kodexempel: Importera pyplot enligt gällande standard

from matplotlib import pyplot as plt

För att konstruera ett diagram likt det i exempelbilden behöver vi först konstruera en lista med x-värden, och en lista med y-värden. När vi sedan vill rita ut linjen tar python och studerar punkter på linjen som finns i listorna och anpassar sedan en linje.

Kodexempel: En enkel graf

# Importerar pyplot för att rita grafen
# och numpy för att konstruera polynom och lista med x-värden
import numpy as np
from matplotlib import pyplot as plt

# Konstruerar en lista med x-värden från 0 till 10
x = np.arange(0, 11)

# Konstruerar en lista med 10 slumptal mellan 0 och 10
y = np.random.randint(0, 11, 11)

# konstruerar grafen med plt.plot
plt.plot(x,y)

# Ser till att grafen inte stängs med plt.show om vi inte stänger fönstret
plt.show()

Utskrift

Kodexempel: Grafen till y=2x-3

import numpy as np
from matplotlib import pyplot as plt

# Konstruerar polynomet 2x - 3
y = np.poly1d([2, -3])

# Konstruerar en lista med heltal från 1 till 10 som våra x-värden
x = np.arange(-10, 11)

# poly1d kan ta in x-värden och konstruera en
plt.plot(x,y(x))
plt.show()

Utskrift

Uppgift: m06u04

Konstruera en applikation som ritar ut 10 olika polynom. Här har du stor valfrihet hur applikationen är uppbyggd och får fria tyglar.

6.3 Turtle graphics: En del av pythons standardbibliotek

Turtle graphics (turtle) är en modul som ingår i pythons standardbibliotek och är en modul för att rita ut saker i ett fönster på skärmen. Eftersom turtle ingår i pythons standardbibliotek behöver vi inte installera något utan kan bara importera modulen på samma sätt som t.ex. math. När vi importerat turtle behöver definiera två variabler för klasserna Turtle och Screen och på sista raden i en fil som använder turtle-grafik måste vi skriva turtle.done().

Problem med namngivning

När du leker med Turtle så kan du inte skapa en fil som heter turtle.py för då kommer du få ett error när du skall köra din applikation. Orsaken till detta är att du då försöker importer din egna fil istället för att importera paketet turtle och då finns ju inte alla funktioner du behöver.

Kodexempel: Skapa ett fönster med Turtle

# Importerar turtle modulen
import turtle

# Definierar variablerna fönster och t som
# turtle.Screen() och turtle.Turtle()
fönster = turtle.Screen()
t = turtle.Turtle()

# Saknas detta kommando på sista raden kommer
# fönstret stängas när programmet är klart
turtle.done()

6.3.1 Screen

Klassen Screen kontrollerar ritfönster och genom att anropa den i början av applikationen kan vi ställa in fönsterstorlek och bakgrundsfärg.

Kodexempel: Ställa in ritfönstret

import turtle

fönster = turtle.Screen()

# Sätter bakgrundsfärgen till orange och storleken på fönstret till 800x800 pixlar
fönster.screensize(800, 800)
fönster.bgcolor('orange')

turtle.done()

I fönster.setup() anges först fönstrets bredd (x-led) följt av fönstrets höjd (y-led) och i fönster.bgcolor() anges antingen någon av de fördefinierade färgerna som exempelvis 'orange', 'black', 'pink', 'green' osv., men du kan också ange färger på några andra sätt som förklaras här.

6.3.2 Turtle

Klassen Turtle kan vi tänka på som en penna som kan rita diverse figurer genom att röra sig på skärmen. I uppgift m06u05 får du själv studera några olika funktioner till Turtle-klassen och förklara vad de gör.

På pythons hemsida finns mer information om paketet Turtle för den som vill lära sig mer om de olika funktionerna.

Uppgift: m06u05

Kopiera och kör koden och kommentera sedan vad varje rad gör.

import turtle
t = turtle.Turtle()
t.forward(100)
t.left(45)
t.backward(200)
t.right(90)
t.forward(350)
t.goto(-200, 200)
t.penup()
t.forward(50)
t.pendown()
t.color('red', 'green')
t.begin_fill()
t.circle(100)
t.end_fill()
t.forward(500)
turtle.done()

Hjälp [klicka för att visa]

I Turtles koordinationssystem så ligger origo (x=0, y=0) i mitten på fönstret. För den som jobbat med spelutveckling eller skapande av GUI tidigare så kan det verka märkligt.

Här finns ett kodexempel som använder sig av funktionerna pos(), xcor() och ycor() vilket gör att du kan fråga vilken position din turtle har.

import turtle

def print_turtleinfo(t):
    print("
Information om turtelns position")
    print("Pos():", t.pos())        # Anger position för din turtle (x, y)
    print("XCor():", t.xcor())      # Anger x-position för din turtle
    print("YCor():", t.ycor())      # Anger y-position för din turtle


# Origo i mitten på skärmen
t = turtle.Turtle()
print_turtleinfo(t)
t.goto(50, 50)              # Gå till koordidnat x:50, y:50
print_turtleinfo(t)
t.goto(100, 0)
print_turtleinfo(t)
t.goto(0, 0)
print_turtleinfo(t)
t.left(105)                 # Sväng vänster 105 grader
t.forward(100)              # Gå 100 steg (pixlar) framåt
print_turtleinfo(t)

turtle.done()

I m06u05 ser du att riktningen blir lite konstig i slutet. Vill vi kontrollera utgångsriktningen vår Turtle ska ha kan vi använda metoden setheading() där vi i parentesen anger en riktning i grader (0 - 360).

Uppgift: m06u06

  1. Konstruera en applikation som ritar ut en cirkel, en kvadrat, en rätvinklig triangel och en liksidig triangel. Figuererna får inte överlappa varandra och 2 av figurerna ska vara i fyllda med olika färger.
  2. Utvidga antalet figurer till 4 cirklar, 4 kvadrater, 7 rätvinkliga trianglar och 2 liksidiga trianglar.
    Här kan det vara bra att konstruera egna funktioner för de olika figurerna för att undvika upprepning i din kod.
  3. Konstruera en applikation som genererar en bild som är så lik bilden nedan som möjligt. Det är endast figurerna du ska härma. De svarta punkterna med text är för att visa koordinaterna i några delar av bilden som en hjälp för att kopian ska bli så nära originalet som möjligt.
    bild
    (Koordinaterna syns bättre om du zoomar in bilden)

6.3.3 Eventbaserad programmering

I turtle modulen kan vi låta olika funktioner köras baserat på knapptryckningar på tangentbord eller musklick på skärmen. En sådan händelse kallas för ett event och när vi skapar applikationer som beror på event får vi tänka om lite i strukturen. Eventbaserad programmering kan ses som att vi hela tiden har en loop som inväntar olika event från användaren. Vi ska inte gå in så mycket mer i eventbaserad programmering, men en inblick kan nog vara bra för att hänga med i exemplen när vi börjar styra vår sköldpadda med piltangenterna och muspekaren.

Vill vi styra vår sköldpadda med till exempel piltangenterna behöver vi ha en metod som dels säger åt sköldpaddan att göra något baserat på vilken tangent vi trycker på, men också en metod som säger till applikationen att kolla om användaren tryckt på någon knapp eller ej.

I turtle är metoden för att ritfönstret ska kolla om användaren tryckt på någon knapp eller klickat någonstans på skärmen listen(). När vi i koden implementerat listen måste vi ha någon metod som kopplar ihop ett visst knapptryck med en viss funktion. Metoden som används för detta är onkey() och i den måste vi ange ett funktionsnamn samt vilken knapptryckning som ska starta funktionen.

Tutorials

Här nedanför kommer jag först presentera två kortare kodexemepl på hur vi kan styra händelser i Turtle med tangentbord och musen. Sedan kommer jag lista ett antal tutorials som du kan följa för att bygga olika spel. Spelen visar på olika spellogik och om du senare vill bygga ett eget spel som slutprojekt så kan du behöva använda logik från flera av dessa tutorials. För varje tutorial försöker jag presentera vilken logik som används. Jag har också försökt lägga tutorals i svårighetsgrad med den enklaste först. Ingen bygger på någon annan men flera av dem innehåller samma typ av logik, tex. kollisionshantering.

När du letar efter egna tutorials så kommer du märka att många av dem bygger på objektorientering. Objektorientering kommer först i nästa kurs, så jag har valt att inte länka in sådana tutorials. Vill du följa en tutorial och jobba med objektorientering så är det ok, men eftersom det inte ingår i kursen så kommer det inte vägas in i bedömningen av ett betyg.

Kodexempel: enkelt styrsystem [klicka för att visa]

import turtle

# Setup
fönster = turtle.Screen()
seb = turtle.Turtle()
fönster.bgcolor('lightblue')
fönster.title('Flygande sköldpaddan')
seb.color('red')
seb.shape('turtle')
seb.turtlesize(5)
seb.penup()
seb.speed(5)


# Funktioner
def färdas():
    """Får sköldpaddan att hela tiden färdas framåt"""
    seb.forward(5)
    # Uppdaterar ritfönstret
    fönster.update()
    # Upprepar funktionen 1 gång per milisekund
    fönster.ontimer(färdas, 1)


def vänster():
    """Byter riktningen till vänster"""
    seb.setheading(180)


def höger():
    """Byter riktning till höger"""
    seb.setheading(0)


def upp():
    """Byter riktning till upp"""
    seb.setheading(90)


def ner():
    """Byter riktning till ner"""
    seb.setheading(270)


def avsluta():
    """Avslutar programmet"""
    fönster.bye()


# Ber ritfönster kolla efter specifika knapptryckningar
fönster.onkey(vänster, 'Left')
fönster.onkey(höger, 'Right')
fönster.onkey(upp, 'Up')
fönster.onkey(ner, 'Down')
fönster.onkey(avsluta, 'Escape')
fönster.listen()

# Anropar funktionen färdas
färdas()

# Det obligatoriska kommandot på sista raden i en turtleapplikation
turtle.done()

Kodexempel: Rita en slumpmässig kvadrat då vi trycker på mellanslag [klicka för att visa]

# nödvändiga importer
import random
import turtle

# Setup
fönster = turtle.Screen()
t = turtle.Turtle()
fönster.screensize(800, 800)
t.speed(0)


def slump_kvadrat():
    """Genererar en slumpmässig kvadrat i ritfönstet"""

    # Genererar slumpade x och y koordinater
    # och flyttar sköldpaddan dit
    x = random.randint(-400, 200)
    y = random.randint(-400, 200)
    t.penup()
    t.goto(x, y)
    t.pendown()

    # Genererar en slumpmässig sidstorlek och färger
    sida = random.randint(50, 200)
    r = random.random()
    g = random.random()
    b = random.random()
    t.color((r, g, b))

    # Ritar en kvadrat
    t.begin_fill()
    for i in range(4):
        t.forward(sida)
        t.left(90)
    t.end_fill()


# Anger att när funktionen slump_kvadrat körs när användaren trycker på mellanslag
fönster.onkey(slump_kvadrat, 'space')

# Säger till ritfönstret att lyssna efter event
fönster.listen()

# Det obligatoriska kommandot på sista raden i en turtleapplikation
turtle.done()

Kodexempel: Styra med musen [klicka för att visa]

Här följer två tutorials där du även kan använda musen för att påverka din turtle.

Styra musen med touchpad på en mac

I de tutorials som finns nedan så pratar han om tre musknappar. När du jobbar med touchpad som finns på en mac så används bara två knappar, vänster som aktiveras med onscreenclick(clickleft, 1) och höger som aktiveras med onscreenclick(clickright, 2).

Videotutorial: Kollisionshantering mot vägg [klicka för att visa]

När vi bygger spel så är det viktigt att saker kan kollidera, det gör vi för att både kunna hålla koll på att saker som rör sig finns kvar inne i skärmen men även för att kunna samla poäng eller förlora när vi träffar ett annat objekt.

Här kommer en tutorial i fyra delar där bollar studsar i en avgränsad yta.

Tutorial: A simple game [klicka för att visa]

A simple game

I denna tutorial så byggs ett enkelt spel upp där du kommer lära dig en hel del matnyttiga saker för att kunna bygga ett spel. Fokus i denna tutorial ligger på kollisionshantering, att kunna spela mot flera fiender.

Spelet är kodat i Python 2.7 men jag har kört igenom hela tutorialen och det enda som behöver ändras är sista raden där du skall byta ut delay = raw_input("Press Enter to finish.") mot turtle.done().

Ljud och bilder som används i slutet av tutorial hittar du här.

Tutorial: Space invaders [klicka för att visa]

Space invaders

En tutorial där det klassiska spelet Space Invaders återskapas. Här byggs spelet upp med grafik, ljud, olika typer av kollisioner, poängberäkning mm.

Spelet är kodat i Python 2.7 men jag har kört igenom hela tutorialen och det enda som behöver ändras är sista raden där du skall byta ut delay = raw_input("Press Enter to finish.") mot turtle.done().

Ljud och bilder som används i slutet av tutorial hittar du här.

Uppgift: m06u07

Här vill jag att du själv undersöker eller arbetar vidare med någon uppgift/kodexempel/tutorial som du har gjort här ovan. Exakt vad du vill göra avgör du själv.

  • Bygg vidare på ett av de spelen som du har jobbat med?
  • Bygg ihop delar av två spel till ett nytt?
  • Bygg ihop de två kodexemplen enkelt styrsystem och Rita en slumpmässig kvadrat för att skapa något nytt/annat.

Gör ett kommentarshuvud längst upp där du förklarar vad du lagt till för funktionalitet, varför du lade till den och förslag på ytterligare utveckling av applikationen.

6.4 PyGame

PyGame är den mesta använda spelmotorn för Python där du kan skapa egna spel. För de elever som tycker att det är intressant att jobba med Turtle och där påbörjar att bygga egna spel så är PyGame den naturliga fortsättningen för att göra mer avancerade spel.

I denna kursen kommer vi inte jobba specifikt med PyGame men för den som är intresserad så finns det en uppsjö av olika guider och tutorials för att bygga en mängd olika spel.

Den elev som vill testa PyGame och även göra ett slutprojekt med PyGame får på egen hand utforska detta bibliotek. Lite vaksamhet krävs dock, väldigt mycket av det material som finns bygger på objektorientering vilket är en teknik som ligger utanför denna kursen. Att använda objektorientering inom ramen för kursen är ok, men du kan inte höja ett betyg genom att göra det.

6.5 Tkinter

Nästa bibliotek vi skall kika på heter Tkinter och det är ett inbyggt bibliotek för att bygga GUI-applikationer. GUI är förkortningen för Graphical User Interface och på svenska brukar vi prata om grafiska användargränssnitt.

Grunden i allt arbete med GUI är att skapa fönster och sedan ett antal komponenter som har olika egenskaper, en knapp skall tryckas på, en text skall visas, en textruta skall innehålla något som programmet behöver använda sig av osv. Det är enkelt att hitta listor på alla aktuella komponenter som du behöver använda, men svårigheten är ofta att bygga användarvänliga GUI som utför det som du vill. I ett antal exempel nedan kommer grunderna gås igenom och när ud kan detta så kommer du troligtvis känna sådan trygghet i att du själv kan lägga till de komponenter som just du behöver för att klara av att bygga det GUI som du behöver till din applikation. Ett tips är att först rita ut det GUI som du vill bygga så vet du exakt hur du vill ha det och du vet också om när du är färdig. Att överarbeta ett GUI är lätt gjort.

Kodexempel: Att skapa ett enkelt fönster [klicka för att visa]

from tkinter import *

# Skapa ett fönster som döps till window
window = Tk()

# Sätter storleken på fönstret till 400x200px
window.geometry("400x200")

# Sätter bakgrundsfärg på fönstret
window.configure(background="green")

# Sätter titeln på fönstret
window.title("Hello")

# Skapar en label (text) som skall ligga i "window"
label = Label(window, text="Hello World")

# pack() är funktionen för att lägga till komponenter till fönstret
label.pack()

# mainloop för att applikationen skall köras.
window.mainloop()

Fönster vid körning

Jag har inte satt någon bakgrund på label, därför blir den vit. Testa att byta färg på båda bakgrund och text genom att ge attributen bg="green", fg="white".

Nästa komponent som skall användas är knappar.

Kodexempel: Att skapa ett enkelt fönster med knappar [klicka för att visa]

from tkinter import *

# Skapa ett fönster som döps till window
window = Tk()

# Sätter storleken på fönstret till 400x200px
window.geometry("400x200")

# Sätter bakgrundsfärg på fönstret
window.configure(bg="green")

# Sätter titeln på fönstret
window.title("Hello")

# Skapar en label (text) som skall ligga i "window"
label = Label(window, text="Hello World", bg="green", fg="white")

# pack() är funktionen för att "bygga" komponenter
label.pack()

# Skapar 2 knappar, en för att logga in och och för att kunna registrera
button1 = Button(window, text="Logga in")
button1.pack()
button2 = Button(window, text="Registrera")
button2.pack()

# mainloop för att applikationen skall köras.
window.mainloop()

Fönster vid körning

Här ser vi direkt ett problem med utseendet. När vi lägger till komponenter till vårt window så läggs de under varandra. Här behöver vi hitta ett bättre sätt att placera ut våra komponenter som vi vill ha dem. Detta är ett vanligt problem inom de flesta programmeringsspråk och varje språk har sina speciallösningar för problemet.
Inom Python finns det två tekniker som är relativt enkla och som ofta används. Den ena tekniken kallas frames och där tänker vi oss att vi skapar olika ramar i vår applikation och sedan kan vi placera innehållet i en ram så som vi vill och sedan kan vi placera ramarna inbördes som vi vill.
Den andra tekniken som vi skall kika närmare på är grid. Grid innebär att vi bygger vår applikation med hjälp av rader och kolumner. Det är också möjligt att slå ihop Frames och Grid och bygga applikationer precis som du vill med hjälp av dessa tekniker.

Dags att kolla på hur vi kan använda Grid. Jag vill bygga en inloggningsapplikation.

Kodexempel: Exempel på Grid-layout [klicka för att visa]

from tkinter import *

# Skapa ett fönster som döps till window
window = Tk()

# Sätter storleken på fönstret till 400x200px
window.geometry("400x200")

# Sätter bakgrundsfärg på fönstret
window.configure(background="green")

# Sätter titeln på fönstret
window.title("Inloggningsapplikationen")

# Deklarerar mina komponenter
label1 = Label(window, text="Username", bg="green", fg="white")
label2 = Label(window, text="Password", bg="green", fg="white")
entry1 = Entry(window)      # Entry är en inmatningsruta
entry2 = Entry(window)
button1 = Button(window, text="Logga in")

# Placerar textrutorna på rad 0 och 1, vilket är rad 1 och 2
label1.grid(row=0)
label2.grid(row=1)

# Inmatningsrutorna placeras i kolumn 1, vilket är kolumn 2
entry1.grid(row=0, column=1)
entry2.grid(row=1, column=1)

# Placerar knappen på rad 2, column 2.
button1.grid(row=2, column=1)

# Sätt fokus till entry1
entry1.focus_set()

# mainloop för att applikationen skall köras.
window.mainloop()

Fönster vid körning

Det ser ju betydligt bättre ut. Nu har vi tagit kontrollen på hur vi skall placera ut våra komponenter. Det finns nu ett antal attribut för att kunna skapa luft i våra GUI och då kan vi använda padx och pady för att skapa avstånd mellan celler i x- eller y-led.

label1.grid(row=0, padx=30, pady=15)
label2.grid(row=1, pady=15)

Vi har ju en fin applikation just nu men den kan ju inte göra någonting. Vi behöver koppla en händelse och jag vill, när användaren klickar på knappen, kontrollera om användarnamn och lösenord är korrekt så att användaren kan få logga in.

Kodexempel: Inloggningsapplikation [klicka för att visa]

from tkinter import *

def check_login():
    """
    Funktion som kollar om inmatade värden är korrekta för att
    loggas in eller inte.
    :return: ingenting
    """
    # Hämta värden från inmatningsrutorna
    username = entry1.get()
    password = entry2.get()

    # Kollar om användarnamn OCH lösenord är korrekta.
    if username == "admin" and password == "admin":
        output.config(text="Korrekt inloggning", fg="white")
    else:
        output.config(text="Felaktig inloggning", fg="yellow")

    # Töm inmatningsrutorna från tecken 0 - END
    entry1.delete(0, END)
    entry2.delete(0, END)
    # Sätt fokus på entry1
    entry1.focus_set()


# Skapa ett fönster som döps till window
window = Tk()

# Sätter storleken på fönstret till 400x200px
window.geometry("400x200")

# Sätter bakgrundsfärg på fönstret
window.configure(background="green")

# Sätter titeln på fönstret
window.title("Inloggningsapplikationen")

# Deklarerar mina komponenter
label1 = Label(window, text="Username", bg="green", fg="white")
label2 = Label(window, text="Password", bg="green", fg="white")
entry1 = Entry(window)      # Entry är en inmatningsruta
entry2 = Entry(window, show="*")
# command anropar funktionen jag skapat. OBS: utan parenteser i anropet.
button1 = Button(window, text="Logga in", command=check_login)
output = Label(window, text="Klicka på knappen för att logga in!", bg="green", fg="white")

# Placerar textrutorna på rad 0 och 1, vilket är rad 1 och 2
label1.grid(row=0, padx=30, pady=15)    # padx styr hela kolumnen
label2.grid(row=1, pady=15)

# Inmatningsrutorna placeras i kolumn 1, vilket är kolumn 2
entry1.grid(row=0, column=1)
entry2.grid(row=1, column=1)

# Placerar knappen på rad 2, column 2.
button1.grid(row=2, column=1)

# Placerar utskriften på rad 3, column 1 (2 kolumner bred..)
output.grid(row=3, column=0, columnspan=2, pady=15)

# Sätt fokus till entry1
entry1.focus_set()

# mainloop för att applikationen skall köras.
window.mainloop()

Fönster vid körning

Tutorial: To Do List [klicka för att visa]

ToDo List

En tutorial där en ToDo-app byggs upp steg för steg. Du kommer lära dig att bygga upp en fönsterapplikation och koppla olika händelser till knappar och den vägen kunna styra applikationen.

Applikationen är kodad i Python 2.7 men det enda som behöver ändras är att klassen skrivs med gemener i Python 3.x tkinter istället för Tkinter som det skrivs i Python 2.7.

I film 6 så jobbar han med funktionalitet kring MessageBox. Den kosen som han skriver i Python 2.7 fungerar inte i Python 3.x. import tkMessageBox skall ersättas med from tkinter import messagebox och confirmed = tkMessageBox.askyesno("Please confirm", "Do you really want to delete all?") skall ersättas med confirmed = messagebox.askyesno("Please confirm", "Do you really want to delete all?").

Slutligen ersätts tkMessageBox.showwarning("Warning", "You need to enter a task") med messagebox.showwarning("Warning", "You need to enter a task") för att visa ett felmeddelande när vi försöker lägga till en tom uppgift.

Värt att notera här är att själva händelsen som kopplas till knappen skapas i en funktion för att det skall vara lätt att anropa denna. Alla komponenter i vårt GUI går lika bra att nå ifrån funktionen som ifrån själva programmet.

Uppgift: m06u08

Bygg en enkel miniräknare som kan beräkna addition och subtraktion med var sin knapp. Talen som skall beräknas skall skrivas in i textrutor och svaret skall skrivas ut i en label på valfritt sätt.

Fördjupande uppgift

Denna uppgiten går ju att jobba vidare på om man vill. Exempel på vidareutveckling är;

  • Fler räknesätt
  • Skriva ut alla beräkningar i en listbox eller i en större textruta.
  • Mata in siffror med hjälp av att trycka på knappar som på en vanlig miniräknare.

Länkar

Här finns en sammanställning på enkla komponenter (widgets på engelska) som är användbara när du jobbar med Tkinter. Vet du bara vad komponenten heter så kommer du hitta mängder av kodexempel på nätet.

Här finns en bra bild på samtliga namngivna färger som går att använda i Tkinter.

Youtube: Python GUI with Tkinter (thenewboston) [klicka för att visa]

Här finns en serie om 14 videoklipp från thenewboston som går igenom ett antal widgets genom olika exempel.

Som en förberedelse för ett av slutprojekten så skall jag visa ett exempel på hur vi kan använda olika komponenter för att simulera ett eller flera tärningsslag. Först visar jag hur en knapp kan användas för att slå en tärning och detta värde skrivs ut.

Kodexempel: Tärningsapplikation 1 [klicka för att visa]

# Importer av bibliotek
from tkinter import *
from random import randint

def hit():
    """
    Slår ett tärningsslag och skriver ut tärningen värde
    :return: ingen retur
    """
    # Slumpa ett värde
    dice = randint(1, 6)
    # Skriver ut en text
    dice_text.config(text="Tärningens värde: {}".format(dice))


# Skapa ett fönster som döps till window
window = Tk()

# Sätter storleken på fönstret
window.geometry("400x200")

# Sätter bakgrundsfärg på fönstret
window.configure(background="cornsilk2")

# Sätter titeln på fönstret
window.title("Slå en tärning")

#
# Deklarerar mina komponenter
info = Label(window, text="Tryck på knappen för att slå ett tärningsslag", bg="cornsilk2")
button = Button(window, text="Slå", command=hit, bg="cornsilk2")
dice_text = Label(window, text="Du har inte slagit ännu", bg="cornsilk2")

# Placera ut widgets i min grid
info.grid(row=0, column=0, columnspan=2, pady=15, padx=20)
button.grid(row=1, column=0, pady=15)
dice_text.grid(row=1, column=1, pady=15)

# mainloop för att applikationen skall köras.
window.mainloop()

Fönster vid körning

Vid programstart

Efter att jag har klickat på knappen och slagit en tärning.

Kodexempel: Tärningsapplikation 2 [klicka för att visa]

# Importer av bibliotek
from tkinter import *
from random import randint

def hit():
    """
    Slår ett tärningsslag och skriver ut tärningen värde
    :return: ingen retur
    """
    # Slumpa ett värde
    dice = randint(1, 6)
    # Skriver ut en text
    dice_text.config(text="Tärningens värde: {}".format(dice))
    # Lägger till en "sprite" till Label dice_sprite/dice_img
    dice_sprite.config(file="img//dice{}.png".format(dice))


# Skapa ett fönster som döps till window
window = Tk()

# Sätter storleken på fönstret
window.geometry("400x200")

# Sätter bakgrundsfärg på fönstret
window.configure(background="cornsilk2")

# Sätter titeln på fönstret
window.title("Slå en tärning")

#
# Deklarerar mina komponenter
info = Label(window, text="Tryck på knappen för att slå ett tärningsslag", bg="cornsilk2")
button = Button(window, text="Slå", command=hit, bg="cornsilk2")
dice_text = Label(window, text="Du har inte slagit ännu", bg="cornsilk2")
# Mina bilder ligger i mappen "img", observera // för att ange mapp
dice_sprite = PhotoImage(file="")       # Ingen fil gör att ingen bild ritas ut
dice_img = Label(window, image=dice_sprite, bg="cornsilk2")


# Placera ut widgets i min grid
info.grid(row=0, column=0, columnspan=2, pady=15, padx=20)
button.grid(row=1, column=0, pady=15)
dice_text.grid(row=1, column=1, pady=15)
dice_img.grid(row=2, column=0)

# mainloop för att applikationen skall köras.
window.mainloop()

Fönster vid körning

Vid programstart

Efter att tärning är slagen

Om du vill använda mina tärningar så finns de här, annars kan du skapa egna bilder eller leta upp bilder på nätet som du har rätt att använda.

6.6 appJar

I samband med att jag fick problem att skriva ut tärningsbilder från en lista med olika tärningar så började jag söka efter alternativ till Tkinter. Ett av de möjliga alternativen var appJar som ser väldigt intressant ut. Jämfört med Tkinter så krävs det mindre kod och jag upplever att namngivningen av funktioner är mer logisk. Jag visar tre exempel där jag har byggt GUI med appJar.

Kodexempel: Hello World [klicka för att visa]

# importerar appJar (glöm inte att lägga till paketet appJar)
from appJar import gui

# Skapar gui, sätter titeln och storlek på fönstret
app = gui("Demo GUI", "400x200")

# Sätter bakgrundsfärg
app.setBg("black")
# Sätter textfärg
app.setFg("green")
# Sätter fontstorlek
app.setFont(20)

# Skapar en label som sätter text på en rad
app.addLabel("title", "Hello World!")
# Skapar textytor, namnger dem och ger dem ett innehåll
app.addMessage("text1", "Detta är mitt första lilla program.")
app.addMessage("text2", "Dags att utforska appJar!")

# Sätter vit textfärg på textytan "text1"
app.setMessageFg("text1", "white")
# Sätter bredden till 400px på textytan "text2"
app.setMessageWidth("text2", 400)

# Bygger och kör applikationen
app.go()

Fönster vid körning

Kodexempel: Grid-design och knappar [klicka för att visa]

# importerar appJar (glöm inte att lägga till paketet appJar)
from appJar import gui

# Funktion som skriver ut vilken knapp du har tryckt på
def buttonClick(btn):
    app.setLabel("c1", str(btn))
    # Om knappen "Rensa" är tryckt så rensas label b1-b4 (tyvärr kan jag här inte skicka in en lista)
    if btn == "Rensa":
        app.setLabel("b1", "")
        app.setLabel("b2", "")
        app.setLabel("b3", "")
        app.setLabel("b4", "")

# Skapar gui, sätter titeln och storlek på fönstret
app = gui("Grid Demo", "400x300")

# Används för att låta en widget fylla ut hela ytan.
app.setSticky("news")

# Vad som skall expandera när fönstret ändras i storlek (alternativ: row | column | none)
app.setExpand("both")

# Fonten på texten i applikationen
app.setFont(18)

# En första label som döps till "a1", innehåller texten "rubrik" och
# placeras i rad 0, kolumn 0 och skall gå över 4 kolumner
app.addLabel("a1", "Rubrik", 0, 0, 4)

# Fyra labels som placeras i var sin kolumn
app.addLabel("b1", "b1", 1, 0)
app.addLabel("b2", "b2", 1, 1)
app.addLabel("b3", "b3", 1, 2)
app.addLabel("b4", "b4", 1, 3)
# Tredje raden med en enda label
app.addLabel("c1", "Inget knapptryck", 2, 0, 4)

# Nollställ setSticky innan jag skapar knapparna som jag inte vill skall fylla ut hela ytan
app.setSticky("")

# Lägg till en knapp, buttonClick är funktionen som triggas när jag klickar på knappen
app.addButton("Ok", buttonClick, 3, 0, 4)
# Lägg till flera knappar genom att skicka in en lista med knappnamn, alla knappar aktiverar samma funktion
app.addButtons(["Rensa","Logga ut"], buttonClick, 4, 0, 4)

# Sätter textfärg på specifik label
app.setLabelFg("a1", "green")
app.setLabelFg("c1", "white")

# Sätter bakgrundsfärg på labels
app.setLabelBg("b1", "red")
app.setLabelBg("b2", "white")
app.setLabelBg("b3", "yellow")
app.setLabelBg("b4", "green")

# Anger jag flera labelnamn som en lista, markeras med [], så sätter jag samma värde på flera labels
app.setLabelBg(["a1", "c1"], "black")

# Bygger och kör applikationen
app.go()

Kodexempel: Simulering av tärningsslag [klicka för att visa]

# Importerar de paket som behövs
from appJar import gui
from random import *

# Lista med alla tärningar
dice_list = []

def add_name():
    """
    Funktion som läser av namnet i inmatningsrutan och skriver ut det.
    :return: inget
    """
    # Hämtar data från inmatningsformuläret
    name = app.getEntry("name")
    # Om något är inmatat, skriv ut detta, annars skapa en pop up som meddelar att namn saknas
    if len(name) == 0:
        app.infoBox("Ange namn", "Du har inte angivit något namn!", parent=None)
    else:
        app.setLabel("title", "Welcome {}!".format(name))


def hit():
    """
    Funktion som slår en tärning, lagrar i en lista och skriver ut info
    :return:
    """
    # Slumpa ett tärningsvärde och lägg till i listan
    dice = randint(1,6)
    dice_list.append(dice)

    # Skriv ut info i GUI
    app.setLabel("dice", "Du slog tärning med värdet: {}".format(dice))
    app.setLabel("dice_list", "Alla tärningar: {}\nSumman: {}".format(dice_list, sum(dice_list)))
    # Visa senaste tärningsslaget med en bild
    app.reloadImage("dice_img", "img//dice{}.gif".format(dice))

# Skapar GUI och sätter färg och fontstorlek
app = gui("Dice application", "500x300")
app.setBg("burlywood2")
app.setFg("sienna4")
app.setFont(15)

# Label och Entry för att mata in och skriva ut namn
app.addLabel("title", "Ange ditt namn", 0, 0)
app.addEntry("name", 0, 1)
# Placera markören i inmatningsrutan
app.setFocus("name")
# Lägg till knapp för att hämta namnet, add_name anropar funktion
app.addButton("OK", add_name, 1, 0, colspan=2)

# Skapar knapp och Labels för att ta hand om ett tärningsslag
app.addButton("Slå tärning", hit, 2, 0)
app.addLabel("dice", "Du har inte slagit en tärning", 2, 1)
app.addLabel("dice_list", "", 3, 0, colspan=2)
# Vänsterjustera
app.setLabelAlign("dice_list", "left")

# startLabelFrame skapar en ram runt den utritade tärningen.
app.startLabelFrame("Tärning", 4, 0)
# Ladda widget med en bild från början, det borde vara en "tom" tärning.
app.addImage ("dice_img", "img//dice1.gif")
app.stopLabelFrame ()

# Kör applikationen
app.go()

Om du vill använda mina tärningar så finns de här, annars kan du skapa egna bilder eller leta upp bilder på nätet som du har rätt att använda.

Länkar

  • appJar.info är paketets hemsida med mängder av exempel och dokumentation av alla tillgängliga funktioner. Ta dig lite tid att kika runt så att du lär dig hitta det som du behöver.
  • Introduction to appJar är sex korta youtubeklipp om appJar.