Dagens föreläsningar
•Grafik och grafiska användargränssnitt.
• Databashantering, MySQL.
• Test Driven Development med PyUnit.
• Iteratorer.
• Orientering andra dynamiska språk.
• Liten presentation av Ruby.
Grafiska användargränssnitt
• Varför finns grafiska användargränssnitt?
• Hur är de uppbyggda?
• Skapa grafik i Python.
• Ett antal exempel.
• Händelsehantering.
• Tips inför projekten.
Varför använda grafik (GUI)?
• Öka användbarheten.
• Minska inlärningströskeln.
• Inte alltid bästa lösningen, exempelvis kan
terminalgränssnitt vara lämpligt för
systemadministration.
• Kan vara bra att erbjuda flera olika
gränssnitt.
• Sist men abslout inte minst: Psykologisk
effekt.
Hur är ett GUI uppbyggt?
Grafikbiblioteket Tkinter
• De facto standard, följer med (nästan) alla
standardinstallationer.
• Står för “Tk interface” och kan ses som ett
kopplingsbibliotek mellan Tk GUI toolkit
och Python.
• Lätt att skriva men hopplöst att säga!
• Ett av flera sätt att skapa grafik i Python.
Vad är en widget?
• Widget betyder ordagrant "manick, liten
grej".
• En widget är en komponent som
tillsammans med andra widgets utgör ett
grafiskt användargränssnitt. Vissa widgets
är enkla medan andra är avancerade med
mängder av metoder och attribut.
• Svenska motsvarigheterna har en klang
av "luddighet" varför widget kommer
användas.
Ett första kodexempel
# first_example.py
from Tkinter import *
root = Tk()
my_widget = Label(root, text="Hello world")
my_widget.pack()
root.mainloop()
Genomgång av programmet
# first_example.py
from Tkinter import *
root = Tk()
my_widget = Label(root, text="Hello world")
my_widget.pack()
root.mainloop()
Programmet följer en arbetsordning för att skapa widgets:
•Skapa en huvudbehållare, i detta fall ett Tk-objekt.
•Skapa en widget av den typ man vill ha, här ett Label-objekt.
3.Ange egenskaper för widgeten, tex. färg och form.
•Anropa pack-metoden för att lägga till widgeten i gränssnittet.
5.Repetera steg 2-4 för alla widgets som ska finnas med.
•Anropa mainloop-metoden för huvudbehållaren för att visa den
och alla underliggande widgets på skärmen.
Plattformsoberoende grafik
• Python finns för en mängd plattformar,
exempelvis Unix, Linux, Windows och Mac
OS X.
• Utseendet måste följa plattformarnas
standarder utan att kräva ändringar i koden.
• Kräver en abstrakt beskrivning av ingående
widgets, snarare än exakta grafikdetaljer.
• Fördel: Portabel kod även för grafik.
• Nackdel: Svårt att skapa exakta gränssnitt.
Packa widgets
• Packaren anropas för varje widget med
önskemål om hur widgeten ska se ut och
bete sig.
• Önskemålen innefattar dels ursprunglig
placering och utsträckning i en behållare,
dels hur widgeten ska förändras då
behållaren ändrar storlek.
• Packaren försöker tillgodose alla önskemål.
Om två önskemål krockar tar packaren
hänsyn till packordningen, dvs. i vilken
ordning pack anropats.
Placering
• En widgets placering styrs av argument till
pack-metoden.
• Sida i behållaren anges med side och
placering längs sidorna med anchor.
• Widgetens beteende vid förändringar i
huvudbehållaren anges med expand och
fill.
Exempel på placeringar (PiP
12.2)
# graphics02.py
from Tkinter import *
Button(text="En knapp").pack()
mainloop()
# graphics03.py
from Tkinter import *
Button(text="En
knapp").pack(anchor=NE)
mainloop()
# graphics04.py
from Tkinter import *
Button(text="En knapp").pack(expand=YES,
fill=BOTH)
mainloop()
Flera widgets
# graphics07.py
from Tkinter import *
Button(text="Knapp 1").pack(side=TOP, fill=X)
Button(text="Knapp 2").pack(side=RIGHT, fill=Y)
Button(text="Knapp 3").pack(expand=YES, fill=BOTH)
mainloop()
Flera widgets
# graphics08.py
from Tkinter import *
Button(text="Knapp 2").pack(side=RIGHT, fill=Y)
Button(text="Knapp 1").pack(side=TOP, fill=X)
Button(text="Knapp 3").pack(expand=YES, fill=BOTH)
mainloop()
Vanliga widgets
• Label. Textetikett. (13.1)
• Button. Klickbar knapp. (13.2)
• Radiobutton. Välj ett alternativ av flera. (13.3)
• Checkbutton. Välj valfritt antal alternativ. (13.4)
• Entry. Läs in kortare text. (13.5)
• Frame. Ordna grupper av widgets. (13.6)
Mer avancerade widgets
• Menu. Vanliga menyer. (14.1)
• Menubutton. Knappar som öppnar en meny.
(14.2)
• Canvas. Målarduk för sammansatt grafik. (14.3)
• Text. Textruta för längre texter. (14.4)
• Dialogrutor (egentligen inga widgets). (14.7)
Klasser och arv i Tkinter
• Varje widgettyp i Tkinter är en klass.
• Varje widget i ett GUI är ett objekt.
• Ett GUI har en naturlig hierarkisk struktur.
• Huvudprogrammet kan skapas som
subklasser till Tkinter-klasser, exempelvis
Tk eller Toplevel.
• Grafikkomponenter skapas med fördel som
subklasser till Tkinter, exempelvis Frame.
En förbättrad textruta
# entry01.py
from Tkinter import *
class ExtendedEntry(Entry):
# Konstruktor
def __init__(self, root, **options):
# Anropa superklassens konstruktor
Entry.__init__(self, root, options)
# Koppla fokus till händelsehanterare
self.bind('<FocusIn>', self.selectAll)
def selectAll(self,event):
# Markera all text och sätt markören sist
self.select_range(0,END)
self.icursor(END)
root = Tk()
currText = StringVar() # Kontrollvariabel för texten
currText.set('Klicka här för att ändra texten.')
ExtendedEntry(root, textvariable=currText, width=30).pack()
root.mainloop()
En återanvändbar komponent
# ControlPanel.py
from Tkinter import *
class ControlPanel(Frame):
def __init__(self, root, **options):
# Anropa superklassens konstruktor
Frame.__init__(self, root, options)
# Etikett för titelboxen
Label(self, text='Aktuellt spår:').pack(side=TOP, fill=X)
# Skapa textbox för titel
currTrackTitle = StringVar() # Kontrollvariabel för texten
currTrack = Entry(self, textvariable=currTrackTitle).\
pack(side=TOP, fill=X)
# Skapa och packa knappar
btnPlay = Button(self, text='Play')
btnPlay.pack(side=LEFT, expand=YES, fill=X)
btnRewind = Button(self, text='Rewind')
btnRewind.pack(side=LEFT, expand=YES, fill=X)
btnForward = Button(self, text='Forward')
btnForward.pack(side=LEFT, expand=YES, fill=X)
btnStop = Button(self, text='Stop')
btnStop.pack(side=LEFT, expand=YES, fill=X)
..fortsättning av koden
if __name__ == '__main__':
# Testa ControlPanel-klassen
root = Tk()
cp = ControlPanel(root)
# Packa hela ramen
cp.pack(fill=X)
# Starta väntfas
root.mainloop()
För en applikation som använder denna komponent, se
Programmering i Python 20.5.1
Arv inte alltid rätt lösning
• Att skapa program som subklasser till
Tkinterklasser är inte alltid en bra lösning. I
en objektorienterad design vill man helst
separera logik och presentation (ModelView-Controller, MVC).
•• Anta att vi har kod som hanterar bankkonton
och kunder. Om vi skapar logiken som en
separat modul kan denna kopplas till ett
lokalt GUI, ett webbaserat GUI eller
användas från terminalen.
•• Att använda arv eller inte är ett designval.
Båda alternativen har för- och nackdelar.
Rullbara listor
# listbox01.py
from Tkinter import *
class ScrollListbox(Frame):
def __init__(self, options, parent=None):
Frame.__init__(self, parent)
self.pack(expand=YES, fill=BOTH)
sb = Scrollbar(self) # Skapa rullisten
lb = Listbox(self, relief=SUNKEN) # Skapa listboxen
sb.config(command=lb.yview) # Korslänkning
lb.config(yscrollcommand=sb.set) # - " sb.pack(side=RIGHT, fill=Y)
lb.pack(side=LEFT, expand=YES, fill=BOTH)
for option in options: # Lägg till valen
lb.insert(END, option) # END är listboxens slut
lb.bind('<Double-1>', self.process) # Kopplar dubbelklick
self.listbox = lb
def process(self, event):
print 'Du valde', self.listbox.get(ACTIVE)
options = []
for i in range(1,21):
options.append('Val ' + str(i)) # Lägg till val
root = Tk()
Label(text='Dubbelklicka på ditt val.').pack()
ScrollListbox(options).pack()
root.mainloop()
Standardisera widgets
Det kan vara smidigt att ändra standardutseendet på widgets.
# options.txt
*background: LightGreen
*Button.background: Red
# standardvalues.py
from Tkinter import *
root = Tk()
root.option_readfile ('options.txt')
Label(root, text='Detta är en etikett').pack(side=LEFT)
Button(root, text='Detta är en knapp').pack(side=LEFT)
root.mainloop()
Bilder och bildhantering
• Bildhantering finns implementerat i Python
Imaging Library, PIL. Detta är fritt
nedladdningsbart.
•• Hanterar de flesta vanliga (och ett antal
ovanliga) bildformat, såsom JPEG, TIFF och
PNG.
•• Lägger till text och annan grafik i bilderna.
•• Se PiP 12.7 för detaljer.
Menyer (PiP 14.1)
#menu01.py
from Tkinter import *
def notImplemented():
print 'Menyvalet är inte implementerat ännu.'
def printMenuOpened():
print "Du öppnade en meny."
root = Tk()
# Skapa meny med root som parent
topMenu = Menu(root)
# Skapa en undermeny med topMenu som parent
file = Menu(topMenu, tearoff=0)
file.add_command(label='Ny', command=notImplemented, underline=0)
file.add_command(label='Öppna', command=notImplemented, underline=0)
file.add_command(label='Spara', command=notImplemented, underline=0)
file.add_separator()
file.add_command(label='Avsluta', command=sys.exit, underline=0)
# En undermeny till
tools = Menu(topMenu,postcommand=printMenuOpened)
tools.add_command(label='Kompilera', command=notImplemented, underline=0)
tools.add_command(label='Jämför', command=notImplemented, underline=0)
…fortsättning på menykod.
# Skapa en undermeny till sökalternativet
searchMenu = Menu(tools, tearoff=0)
searchMenu.add_command(label='Källkod',
command = notImplemented, underline=0)
searchMenu.add_command(label='Kommando',
command = notImplemented, underline=0)
searchMenu.add_command(label='Dokumentation',
command = notImplemented, underline=0)
# Skapa menypost med undermeny
tools.add_cascade(label='Sök', menu=searchMenu, underline=0)
# Skapa tvvägslänk mellan root och topMenu
root.config(menu=topMenu)
# Skapa tvåvägslänkar mellan topMenu och undermenyerna
topMenu.add_cascade(label='Fil', menu=file)
topMenu.add_cascade(label='Verktyg', menu=tools)
# Innehåll i fönstret
Label(root, height=10, width=50, text='Välj något i menyerna.').pack()
root.mainloop()
Händelsehantering
• Ett GUI är ointressant i sig, det måste kunna
ta emot inmatning från användaren och
agera utifrån denna.
•• Inmatning kan ske via musen, tangentbordet
eller annan liknande utrustning.
•• Fokuseringen på händelser förändrar
programstrukturen och designen.
•• Passar bra för modularisering och
objektorienterad programmering.
Linjärt eller händelsestyrt program
I ett linjärt program:
•• Utförs saker i en förutbestämd ordning.
•• Sker all inmatning vid bestämda tillfällen.
I ett händelsestyrt program:
•• Finns ofta en central väntslinga (mainloop).
•• Utförs programavsnitt, händelsehanterare,
beroende på vilken händelse som sker.
•• Observera att händelsestyrda program inte
nödvändigtvis måste vara baserade på ett
GUI.
En första händelsehanterare
# events01.py
from Tkinter import *
def onLeftClick(event):
print event.__class__
label.configure(text="Du klickade vänster musknapp.")
def onRightClick(event):
label.configure(text="Du klickade höger musknapp.")
root = Tk()
label = Label(root, text="") # Skapa etikett
label.pack(fill=X)
button = Button(root, text="Klicka med valfri musknapp")
button.bind('<Button-1>', onLeftClick) # Koppla vänsterklick
button.bind('<Button-3>', onRightClick) # Koppla högerklick
button.pack(fill=X)
root.mainloop()
Om händelsehanteraren
• Kan vara en funktion eller metod.
•• Tar ett argument, ett Event-objekt som
innehåller information om händelsen som
ägt rum och den widget som genererade
den.
•• Samma händelsehanterare kan hantera
händelser från flera olika widgets.
•• När händelsehanteraren avslutas återgår
programmet till väntfasen igen.
Att definiera en händelse
• En händelse beskrivs av en sekvens
identifierare tillsammans med en eller flera
modifierare.
•• Identifierare är exempelvis Button-n,
Activate, FocusIn, FocusOut. Se PiP
15.3 för en utförlig lista.
•• Modifierare är exempelvis Alt, Control
och Shift, se PiP 15.3.1
• <Control-ButtonRelease-1> anger att
vänstra musknappen tryckts in och släppts
samtidigt som control-tangenten hållts in.
Fler vanliga händelser
• Activate/Deactivate. Widgeten ändrar
stateattributet.
•• Configure. Widgeten ändrar storlek.
•• Double-n. Musknapp n har dubbelklickats.
•• KeyPress-key. Tangenten key har tryckts in.
•• KeyRelease-key. Tangenten key har släppts.
•• Enter. Muspekaren förs in över widgetens
synliga delar.
•• Leave. Muspekaren lämnar widgetens synliga
delar.
Event-objektet
# events05.py
from Tkinter import *
def onMouseClick(event):
print 'Du klickade med knapp', event.num
root = Tk()
button = Button(text="Klicka på mig")
button.bind('<Any-Button>', onMouseClick)
button.pack(fill=X)
root.mainloop()
• Andra användbara attribut för Event-objektet är:
time, serial, widget, x och y. Se PiP 15.6 för en
utförligare lista.
Ändra egenskaper dynamiskt
• Hittills har alla widgets egenskaper angetts då
widgeten skapas. Naturligtvis går det även att
ändra dessa dynamiskt under programmets
gång.
•• Egenskaperna sätts med metoden configure
eller config.
•• Egenskaper avläses med metoden cget.
•• Egenskaper kan också sättas och avläsas med
hakparenteser liknande ett dictionary:
w[”foreground”] = ”red”
print w[”foreground”]
Exempel på dynamisk grafik
# events06.py
from Tkinter import *
def toggleColor():
if label.cget("background") == "red":
label.config(background="green")
else:
label.config(background="red")
root = Tk()
label = Label(root, text="Text", background="green")
label.pack(expand=YES, fill=BOTH)
Button(root, command=toggleColor, text="Byt
färg").pack(side=BOTTOM,fill=X)
root.mainloop()
Kontrollvariabler
• Används för att smidigt hantera data från
widgets.
•• Kan kopplas till flera widgets som därmed
hålls synkroniserade.
•• Innehållet kan antingen ändras av
användaren via en widget eller av
programmet via kontrollvariablen.
Exempel med kontrollvariabler
# events07.py
from Tkinter import *
root = Tk()
t = StringVar() # Skapa kontrollvariabel
t.set('Detta värde ges till båda objekten') # Sätt startvärde
# Knappens och textrutans text kopplas till samma variabel
Button(root,textvariable=t).pack(fill=X)
Entry(root, textvariable=t).pack(fill=X)
root.mainloop()
Kryssrutor och kontrollvariabler
# checkbutton01.py
from Tkinter import *
def printForm():
if hasLicence.get():
print 'Personen har körkort.'
if hasOwnCar.get():
print 'Personen har egen bil.'
import sys
sys.exit()
root = Tk()
hasLicence = IntVar()
Checkbutton(root,text = 'Körkort', variable =hasLicence).pack(anchor=W)
hasOwnCar = IntVar()
Checkbutton(root,text = 'Egen bil',variable = hasOwnCar).pack(anchor=W)
Button(root,text = 'Spara',command = printForm).pack(anchor=S)
root.mainloop()
Gruppera radioknappar
# radiobutton01.py
from Tkinter import *
def onMealClick():
print mealVar.get()
def onDrinkClick():
print drinkVar.get()
root = Tk()
mealVar = StringVar()
meals = ['Frukost','Lunch','Middag']
for meal in meals:
Radiobutton(root,command = onMealClick,
text = meal,
value = meal,
variable = mealVar).pack(anchor=W)
drinkVar = StringVar()
drinks = ['Mjölk','Vatten','Läsk']
for drink in drinks:
Radiobutton(root,command = onDrinkClick,
text = drink,
value = drink,
variable = drinkVar).pack(anchor=W)
root.mainloop()
Tidsberoende widgets
• För att ange att en händelse ska utföras
efter en viss tid används metoden after.
w.after(time, func, *args)
• Här kommer funktionen/metoden func att
anropas med argumenten *args efter time
millisekunder.
Andra sätt att skapa grafik
• Python Mega Widgets, PMW, tar vid där
Tkinter slutar.
•• Microsoft Foundation Classes, endast
tillgängligt för Windows.
•• wxPython, snabbt och på frammarsch.
•• Jython, Pythonimplementation i Java, ger
tillgång till Javas grafik.
•• GTK+ (GIMP Toolkit). PyGTK.
•• FOX (Free Objects for X).
Plattformsoberoende trots sitt namn. FXPy.
•• Qt. PyQt.
Några tips inför projekten
Alternativ 1
• Skriv ”låtsas”-moduler som returnerar
testdata i samma format som den slutgiltiga
koden.
•• Låt låtsasmodulerna skriva ut den indata de
får från GUI:t innan ni börjar skriva den
riktiga implementationen.
•• Låt låtsasmodulerna returnera felaktiga
värden för att se hur ert GUI hanterar detta.
Några tips inför projekten
Alternativ 2
• Skriv logikkoden först, utan GUI.
•• Tänk igenom vilka värden er logikmodul
behöver och vilka värden som ska
returneras.
•• Skicka in dessa parametrar från ett ”driver”script, som också kan skriva ut resultatet.
•• När logiken fungerar som det ska kan
driverscriptet ersättas med ett GUI.
Testa kod
• För små program är det ofta enkelt att
avgöra om det gör vad det ska.
•• Ju mer komplext och omfattande ett
program blir desto svårare blir det att
överblicka.
•• Ändringar i en del av koden får inte resultera
i att existerande funktionalitet fallerar.
•• Detta kräver någon form av systematisk
testning av kod.
Unit testing
• Unit testing innebär att man isolerar olika beteenden
hos ett objekt och verifierar att dessa fungerar som
förväntat.
• Exempelvis kan man kontrollera att en kod som ska
hantera lösenords giltighet godkänner respektive
underkänner givna exempellösenord.
•• Ofta kopplas ett test till varje objekt i en applikation.
•• Ett bra unit test testar så stor del som möjligt av objektet
samtidigt som varje deltest är så oberoende som
möjligt.
•• Genom att ha oberoende test minskar man den kod
som behöver felsökas då ett fel uppstår.
Test Driven Development
• Test Driven Developmen, TDD, är en metod
för att skapa testkod till applikationer.
•• Då man använder TDD låter man unit
testing vara en styrande del i
utvecklingsprocessen.
•• Tester implementeras parallellt med att ny
funktionalitet läggs till.
•• Därmed kommer den färdiga applikaitonen
ha en testsvit som kontrollerar att varje liten
del fungerar som den ska.
•• Även andra former av testning används men
här fokuserar vi på unit testing.
TDD:s fem steg
1.Lägg till ett test.
2.Kontrollera att testet fallerar.
3.Gör en minimal implementation för att klara
testet.
4.Kontrollera att hela testsviten klaras.
5.Refaktorisera koden.
Exempel TDD
• Anta att vi har en klass som hanterar
taxibilar.
•• Vi har instansvariabler som hanterar körd
sträcka och aktuell taxa.
•• Förutsättningen är att programmet klarar
alla test i nuvarande testsvit vid varje TDDrundas början.
Existerande kod
# -*- coding: utf-8 -*# taxi.py
# -*- coding: utf-8 -*# taxi_test.rb
class Taxi:
def __init__(self, fare, distance):
self.fare = fare
self.distance = distance
import unittest
from taxi import Taxi
$> python taxi_test.py
.
-----------------------------------Ran 1 test in 0.000s
• Vi vill nu lägga till en metod som
returnerar total kostnad för
aktuell resa.
class TaxiTestCase(unittest.TestCase):
def setUp(self):
self.taxi = Taxi(12, 87)
def tearDown(self):
pass
def testCreation(self):
assert self.taxi.fare == 12
assert self.taxi.distance == 87
def suite():
suite = unittest.TestSuite()
suite.addTest(TaxiTestCase("testCreation"))
return suite
if __name__ == "__main__":
unittest.main()
Steg 1: Lägg till ett test
•• Ett test kan läggas till som
en ny assertion i ett
existerande test eller som
ett nytt testfall.
•• Vi väljer att lägga till ett nytt
testfall.
•• Vi utformar vårt test så att
den färdiga koden ska
klara det, väl medvetna om
att existerande kod inte har
denna funktionalitet än.
# -*- coding: utf-8 -*# taxi_test.rb
import unittest
from taxi import Taxi
class TaxiTestCase(unittest.TestCase):
def setUp(self):
self.taxi = Taxi(12, 87)
def tearDown(self):
pass
def testCreation(self):
assert self.taxi.fare == 12
assert self.taxi.distance == 87
def testTotalPrice(self):
assert self.taxi.total_cost() == 12*87
def suite():
suite = unittest.TestSuite()
suite.addTest(TaxiTestCase("testCreation"))
suite.addTest(TaxiTestCase("testTotalPrice"))
return suite
if __name__ == "__main__":
unittest.main()
Steg 2: Kontrollera att testet fallerar
• För att kunna kontrollera att testet misslyckas "på rätt
sätt" måste vi lägga till en tom metod.
$> python taxi_test.py
.F
====================================
FAIL: testTotalPrice
(__main__.TaxiTestCase)
-----------------------------------Traceback (most recent call last):
File "taxi_test.py", line 20, in
testTotalPrice
assert self.taxi.total_cost() ==
12*87
AssertionError
-----------------------------------Ran 2 tests in 0.000s
FAILED (failures=1)
# -*- coding: utf-8 -*# taxi.py
class Taxi:
def __init__(self, fare, distance):
self.fare = fare
self.distance = distance
def total_cost(self):
return 0
Steg 3 och 4: Minimal implementation och
klarat test
• Här görs en så liten
implementation som möjligt
som klarar testet.
•• Därefter kontrolleras om
testsviten klaras.
•• Om inte så görs
förändringar i koden till
sviten klaras.
• Om fel uppstår i
programdelar som inte
berörs av det aktuella testet
indikerar detta oönskade
kopplingar i koden.
# -*- coding: utf-8 -*# taxi.py
class Taxi:
def __init__(self, fare, distance):
self.fare = fare
self.distance = distance
def total_cost(self):
return self.fare * self.distance
$> python taxi_test.py
..
----------------------------------------Ran 2 tests in 0.000s
OK
Båda testen klaras
Steg 5: Refaktorisering
• Refaktorisering innebär att ett objekt ändras
internt utan att dess externa gränssnitt
påverkas.
•• I detta fall behövs inga ändringar men för
större koder kan "uppstädning" behövas.
•• Genom att köra testsviten kan vi garantera
att refaktoriseringen fortfarande har samma
funktion som tidigare.
•• Refaktorisering kan introducera buggar som
inte täcks av testsviten.
Korta kommentarer
• Varning för kopplade test.
•• Undvik för omfattande test. Kan tyda på
"gudsobjekt".
•• Vad är minimal implementation?
•• TDD är en konst som kräver träning!
•• Man tvingas bli konsument av sin egen kod.
•• Även TDD-genererad kod innehåller buggar,
men samma bugg behöver bara hanteras en
gång.
Iteratorer
• En av de vanligaste uppgifterna i ett program är att
repetera kod.
•• I många språk genomförs dessa loopar med hjälp av
numeriska index.
•• En annan lösning är att använda iteratorer.
•• Försök undvika konstruktioner som:
for i in range(len(a))
•• Låt varje objekt vara ansvarigt för sitt innehåll.
•• En iterator säger "Jag kan gå igenom hela min
datamängd ett objekt i taget".
•• En konsument av en iterator säger "Jag vill utföra något
för alla objekt i den här samlingen".
Exempel med beräkningsdomän
• Anta att Mesh är en klass skriven av tredje part som
definierar en 2-dimensionell beräkningsdomän
bestående av ett rutnät med värden i noder.
•• Följande kodfragment skapar ett nät för nuvarande
värden och ett för ändringen. Därefter uppdateras
randen och innandömet med lämpliga FDM-stenciler.
currentMesh = Mesh()
changes = calculateChanges() # Returnerar ett Mesh-objekt
for x in range(len(currentMesh.Nx)):
for y in range(len(currentMesh.Ny)):
if x > 0 and x < currentMesh.Nx – 1 and \
y > 0 and y > currentMesh.Ny - 1:
currentMesh[x][y] += changes[x][y]
else:
currentMesh[x][y] = BC(x,y)
Dags för uppdatering…
Anta nu att klassen Mesh utökas med en eller flera av
följande förändringar:
•• Stöd för N dimensioner.
•• Diskretiseringen byts till triangelformade finita element.
•• Parallellisering införs där nätet delas upp mellan olika
processorer.
•• Ett glest matrisformat används för lagring av värdena
och värden mindre än ett tröskelvärde betraktas som 0.
•• Möjlighet att ha ränder inuti domänen, eller flera icke
sammanhängande domäner.
Konsekvenser
• Samtliga dessa förändringar resulterar i att vår
existerande kod kanske inte fungerar som väntat.
•• Betrakta nu följande kod:
currentMesh = Mesh()
for coords in currentMesh.innerPoints():
currentMesh.calcChange(coords)
for coords in currentMesh.boundary():
currentMesh.calcBoundaryChange(coords)
• Här förutsätts ingenting om Mesh:s interna datastruktur.
Därmed kan uppdateringarna göras utan att existerande
kod behöver ändras.
•• Kanske är for-loopen helt onödig:
currentMesh = Mesh()
currentMesh.calcChange()
En enkel iterator
# -*- coding: utf-8 -*# iterator_01.py
class ColorIterator:
colors = ["Red", "Green", "Blue",
"Yellow", "Black", "Brown"]
def __iter__(self):
self.current_color = -1
return self
def next(self):
self.current_color += 1
if self.current_color == len(self.__class__.colors):
raise StopIteration
return self.__class__.colors[self.current_color]
if __name__ == "__main__":
ci = ColorIterator()
for color in ci:
print color
$> python iterator_01.py
Red
Green
Blue
Yellow
Black
Brown
Implementera med yield
•• Satsen yield lagrar en funktions tillstånd.
•• Nästa gång funktionen anropas kommer exekveringen
att fortsätta efter yield-satsen, med lokala variabler
intakta.
# -*- coding: utf-8 -*# iterator_02.py
def colors(available = ["Red", "Green", "Blue",
"Yellow", "Black", "Brown"]):
for color in available:
yield color
if __name__ == "__main__":
for color in colors():
print color
$> python iterator_02.py
Red
Green
Blue
Yellow
Black
Brown
Fibonacciserie med iterator
• Fibonacciserien är en talserie
som inleds med 0 och 1.
Därefter är varje tal summan av
föregående två tal, 0, 1, 1, 2, 3,
5, 8, 13, 21, 34, 55, 89, 144…
•• Serien dyker upp på många
ställen inom exempelvis
biologin.
•• Serien är oändlig och passar
därför bra för att implementeras
som en iterator.
# -*- coding: utf-8 -*# iterator_03.py
def fib(limit=10):
x, y, count = 0, 1, 0
while count < limit:
yield x
x, y = y, x + y
count += 1
if __name__ == "__main__":
for num in fib(15):
print num,
Skapa en lista från en operator
• Om vi vill spara de
genererade elementen i en
iterator kan vi använda
funktioner från modulen
itertools.
# -*- coding: utf-8 -*# iterator_04.py
def fib( ):
x, y = 0, 1
while True:
yield x
x, y = y, x + y
if __name__ == "__main__":
import itertools
print list(itertools.islice(fib(), 10))
$> python iterator_04.py
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Parallella iteratorer
• För att iterera över två
samlingar parallellt används
funktionen izip, också från
itertools.
•• Modulen itertools innehåller
flera funktioner för iteratorer,
se dokumentationen för
detaljer.
# -*- coding: utf-8 -*# iterator_05.py
import itertools
a = ["a1", "a2", "a3", "a4"]
b = ["b1", "b2", "b3"]
if __name__ == "__main__":
for x, y in itertools.izip(a, b):
print x, y
$>
a1
a2
a3
python iterator_05.py
b1
b2
b3
Andra språk
• Förutom Python finns många andra dynamiska språk
som kan vara lämpliga att känna till.
•• Varje språk har en egen (mer eller mindre väl definierad)
nisch och har fördelar och nackdelar.
•• Python har vi redan stiftat bekantskap med.
•• Perl kommer att presenteras onsdagen 24:e juni.
•• Ruby kommer få en lite närmare introduktion nedan.
•• Här kommer en kort-kort presentation av andra språk
som kan vara av intresse (långt från komplett).
PHP
• Utvecklades ursprungligen för att generera dynamiska
webbplatser.
•• Används nästan uteslutande på webben även om
fristående applikationer är möjliga.
•• Stöder OOP såväl som procedurbaserad
programmering.
• Fördelar:
–
– Enorm användarbas.
–
– Väl anpassat för webben med många bibliotek.
• Nackdelar:
–
– Ej komplett stöd för Unicode.
–
– Inga namnrum.
–
– Typhanteringen kan ställa till problem.
ECMAScript
• Standardiserades i ECMA-262-specifikationen.
•• Används ofta synonymt med de två vanligaste
implementationerna JavaScript och JScript.
•• Används främst i webbläsare.
• Fördelar:
–
– Möjliggör exekverandet av kod hos klienten utan
installation av ny programvara.
• Nackdelar:
–
– Frågetecken kring säkerhet.
–
– Olika dialekter (håller på att standardiseras).
Groovy
• OO-språk designat för Javaplattformen.
•• Lånade delar från Python, Ruby och Smalltalk.
•• Groovy har Javaliknande syntax som kompileras
dynamiskt till bytekod.
• Några skillnader mot standard-Java
–
– Statisk och dynamisk typning
–
– Inbyggt stöd för listor, mappningar, fält och reguljära
uttryck.
Lisp
• En språkfamilj.
•• Skapades ursprungligen av John McCarthy 1958.
•• Används ofta för programmering av artificiell intelligens.
•• Har fått ett uppsving på 2000-talet med open sourcevarianten Common Lisp.
Smalltalk
• Finns i många varianter, oftast åsyftas Smalltalk-80.
•• Rent objektorienterat språk.
•• Allt sker genom att objekt skickar meddelanden till andra
objekt.
•• Smalltalk har inspirerat funktionalitet i många andra
språk, exempelvis Ruby.
Ruby
• Skapades av Yukihiro "Matz" Matsumoto i början an
1990- talet. Språket släpptes officiellt 1995 och har de
senaste fem åren vuxit enormt snabbt.
•• Ruby är designat för att ge ökad produktivitet samtidigt
som det ska inspirera kreativ och vacker programmering.
•• "Principle of least surprise"
•• I grunden objektorienterat. "Allt är objekt" är inte bara en
floskel.
•• Kan även hantera funktionell eller procedurbaserad
programmering.
•• Mest likt Perl och Python men har även byggt vidare på
många idéer från Smalltalk.
Varningens ord
• Att ge en introduktion till ett komplext språk på mycket
kort tid är vanskligt.
•• Rubys fördelar är ofta subtila men i praktiken enormt
viktiga.
•• Kontentan är att Ruby ger kod som är lätt att utveckla
och underhålla.
Likheter mellan Python och Ruby
• Det finns en interaktiv prompt, irb.
•• Det finns en motsvarighet till pydoc, ri.
•• Inga radbrytningstecken behövs.
•• Listor och hashtabeller har samma notation.
•• Har stark, dynamisk typning.
•• Alla variabler är referenser.
•• Felhantering fungerar på samma sätt, men med andra
nyckelord.
•• I stort känns Ruby väldigt välbekant för en
Pythonprogrammerare och vice versa.
Skillnader mellan Python och Ruby
I Ruby…
•• är strängar inte statiska.
•• finns äkta konstanter.
•• styrs variablers räckvidd av namnkonventioner.
•• är reguljära uttryck en inbyggd typ.
•• är parenteser valfria i metodanrop.
•• ersätter mixins (liknande interface i Java) multipelt arv.
•• kan klasser öppnas och modifieras under exekvering.
•• returnerar nästan alla uttryck värden som kan användas för nya
anrop.
•• används iteratorer mer konsekvent.
•• används block för att skapa flexibel och lättanvänd kod.
Öppna klasser
• I Ruby är klassdefinitionerna inte stängda.
•• Detta medför att existerande objekt kan ges nya metoder
efter det att de skapats.
class ExampleClass
def talk()
print "Hejsan\n"
end
end
e = ExampleClass.new()
e.talk
class ExampleClass
def talk_more()
print "Hej igen\n"
end
end
e.talk
e.talk_more
Individuellt beteende inom klassen
• I Ruby kan även enskilda existerande objekt utökas med nya
metoder.
•• Detta innebär att objekt inom samma klass inte nödvändigtvis
har samma uppsättning metoder.
class ExampleClass
def talk()
print "Hejsan\n"
end
end
e = ExampleClass.new()
e.talk
def e.talk_more()
print "Hej igen\n"
end
e.talk
e.talk_more
e2 = ExampleClass.new()
e2.talk_more # => Method missing
Inkrementell klassdefinition
• Vad får detta för konsekvenser för programdesignen i Ruby?
•• Inkrementella klassdefinitioner gör att det blir lättare att
strukturera källkoden.
•• Det går inte att identifiera ett objekts metoder genom att enbart
studera källkoden
•• Introspektion blir en viktig del av programmet.
Callbacks eller krokar
• En dynamisk ingrediens i Ruby är möjligheten att hantera
callbacks för vissa händelser.
•• Callbacks kan ses som händelsehanterare för händelser som
genereras av Rubytolken.
•• Exempel på callbacks är:
– En metod som inte finns anropas.
– En klass blir ärvd.
– En metod läggs till ett objekt.
Exempel på callbacks
• Klassen Cookbook är en samling med egna data.
•• Genom att implementera method_missing kan alla beteenden
som är förknippade med samlingen @recipes skickas vidare.
•• Detta gör att metoder kan anropas direkt för Cookbookinstanser som om de definierats i Cookbook-klassen.
class Cookbook
attr_accessor :title, :author
def initialize
@recipes = []
end
end
def method_missing(m,*args,&block)
@recipes.send(m,*args,&block)
end
Överlagring av hela språket
• Mekanismerna som möjliggör att utöka och
ändra klasser gäller såväl egna som
”inbyggda” klasser.
•• Detta gör att alla standardklasser i Ruby kan
ändras fritt.
•• Eftersom ändringarna kan utföras medan ett
program kör kan även klienters tolkar
överlagras.
Uniform Access Model
• Betrakta en klass Account som ska hålla reda
på ett bankkontos saldo och vilka transaktioner
som gjorts på kontot.
•• Vid designen av klassen bestäms att klassen
ska ha en instansvariabel transactions för
att lagra data om transaktionerna. Datatypen
blir en lista med siffror som anger hur saldot
förändras.
•• Nuvarande saldot räknas ut genom att räkna
samman transaktionerna i metoden balance.
Användning av Account
• Följande kod implementerar och använder klassen i
Python:
class Account:
def __init__(self):
self.transactions = [1000, -180, 50]
def balance(self):
balance = 0
for t in self.transactions:
balance += t
return balance
account = Account()
print account.balance()
Förändring…
• Anta nu att vi tycker att det blir för kostsamt att
räkna ut saldot vid varje anrop till balance.
•• Vi lägger till en instansvariabel för saldot och
kallar den balance.
class Account:
def __init__(self):
self.transactions = [1000, -180, 50]
self.balance = 870
account = Account()
print account.balance
• Vi måste nu ändra alla anrop till metoden
balance till referenser till instansvariabeln
balance.
En bättre designmodell
• Bytet till en instansvariabel kan resultera i stora
mängder ändringar.
•• Många IDE har refaktoriseringsfunktioner. Detta
löser dock inte problemet med extern kod som
behöver ändras.
•• Språk som implementerar Uniform Access
Modell, exempelvis Ruby, har inte detta
problem.
•• I Ruby ser användningen av balance likadan ut
i båda fallen. Om det är en instansvariabel eller
en metod spelar ingen roll.
Åter till getters och setters
• Naturligtvis kan vi undvika framtida förändringar
genom att alltid använda metoder för att
manipulera instansvariabler.
•• Semantiskt sämre. Tappar enkelheten i
syntaxen.
•• Med uniform access får vi möjligheten att
använda virtuella instansvariabler, metoder
som agerar som instansvariabler men som inte
svarar mot någon faktisk instansvariabel.
•• På ett högre plan frikopplar uniform access
intern och extern datastruktur.
Risker
• Dynamiken i Ruby är mycket kraftfull och
möjliggör helt andra designmönster än ett
statiskt språk.
•• Det innebär dock även risker, exempelvis att
göra förändringar i standardklasserna.
•• Kan även ge problem med olika versioner av
applikationer.
Utvecklingsmetodik
• Dynamiska språk passar bra för flera moderna
utvecklingsmetoder.
•• Agile/XP
•• TDD - Test Driven Development
•• DRY - Don’t Repeat Yourself
Ruby on Rails
• Rails är ett ramverk för snabb utveckling av
webbapplikationer.
•• Rails utnyttjar Rubys dynamik för att åstadkomma saker
som vore omöjliga i andra språk.
•• Om man inte är intresserad av webbprogrammering är en
studie av Rails ändå att rekommendera.
• Ramverket innehåller många exempel på hur dynamisk
programmering kan användas i praktiken.