kevytspesifikaatioKurssi: 581362 Ohjelmointikielten periaatteet, kevät 2011 (4 op)
Tämä Pythonin kevytspesifikaatio on toteutettu keväällä 2011 osana Helsingin yliopiston tietojenkäsittelytieteen laitoksen Ohjelmointikielten periaatteet -kurssia. Tämän työn tarkoituksena on antaa yleiskatsaus Python-kielen ominaisuuksista. Dokumentaatiomme koskee ensisijaisesti Pythonin 3.x-kehityshaaraa, mutta suurin osa pätee myös vanhempiinkin versioihin.
Tässä luvussa käsittelemme hieman kielen taustaa ja sen syntyä. Käymme lyhyesti läpi sen ominaisuuksia ja mahdollisia käyttökohteita. Lopuksi näytämme Pythonilla toteutetun Fibonaccin lukujonoa koskevan esimerkin.
Pythonin historia alkaa 1980-luvulta. Ensimmäinen versio ohjelmointikielestä kehitettiin 80-luvun loppupuolella Guido van Rossumin toimesta. Python kehitettiin ABC kielen seuraajaksi, joka kykenee mm. poikkeusten käsittelyyn ja yhteistyöhön Amoeba-käyttöjärjestelmän kanssa. Kielen alun perin kehittänyt van Rossum on edelleen aktiivisesti mukana kielen kehittämisessä
Python 2.0, julkaistiin 16. lokakuuta 2000. Toinen versio sisälsi useita uusia ominaisuuksia, mukaan lukien täydellisen roskienkeruumekanismin ja Unicode-tuen. Muutos tapahtui myös kielen kehitysprosessissa. Python-yhteisölle annettiin vapaammat kädet osallistua mukaan kielen kehitykseen.
Joulukuussa 2008 ilmestyi 3.0, jolla haluttiin korjata
havaittuja ongelmia kielessä. Kielestä haluttiin poistaa
duplikaatteja ominaisuuksia poistamalla vanhat tavat tehdä
asioita. 3.x ei ole taaksepäin yhteensopiva versioiden 2.x ja
vanhempien kanssa. Vanhoille versioille kirjoitetut ohjelmat
eivät toimi suoraan versiossa 3.0. Siirtymän helpottamiseksi on
toteutettu käännösohjelma (2to3), joka kääntää
automaattisesti osan koodista kielen uudempaan versioon. Lisäksi
useita kolmosversion keskeisiä ominaisuuksia on toteutettu
rinnakkain kehitettyyn 2.6 versioon, mikä mahdollistaa niiden
hyödyntämisen ilman uudempaan syntaksiin siirtymistä. [3]
Pythonista on tällä hetkellä olemassa kaksi vakaata haaraa. 2.x-haara saa tällä hetkellä pelkkiä virheitä korjaavia päivityksiä. Uudet ominaisuudet kehitetään vain 3.x-haaralle. Suurempi osa kolmansien osapuolien ohjelmistoista käyttää vielä Pythonin 2.x-versiota.
Yleisin Pythonin toteutus on CPython, joka on kirjoitettu C-kielellä. CPython kääntää Python ohjelmat. Se kääntää Python ohjelmat tavukoodiksi, joka suoritetaan virtuaalikoneessa. CPython on saatavilla useille eri käyttöjärjestelmille ja sen mukana tulee laaja peruskirjasto.
Muita Python toteutuksia on esimerkiksi Jython (JVM), IronPython (.NET) ja PyPy (Python).
Yksi suosittu tapa hyödyntää Pythonia on sen käyttäminen skriptikielenä erilaisissa sovelluksissa. Pythonia käyttävät skriptikielenä esimerkiksi 3d-mallinnusohjelmistot Maya ja Blender, kuvankäsittelyohjelma GIMP ja se on toinen valittavissa oleva skriptikieli Google Docsissa
Erilaisten kirjastojen, muun muassa NumPy, SciPy, Matplotlib avulla Pythonia voidaan käyttää tehokkaasti tieteelliseen laskentaan.
Monissa käyttöjärjestelmissä Python kuuluu peruskomponentteihin. Python löytyy muun muassa useimmista Linux distribuutioista, OpenBSD:stä ja Mac OS X:stä.
Seuraavassa esimerkki ohjelmasta, joka lukee yhden syöttöluvun, laskee ensimmäisen Fibonaccin luvun, joka on syöttölukua suurempi ja lopuksi tulostaa kyseisen Fibonaccin luvun.
import sys
def fib(n):
"""Palauttaa ensimmäisen Fibonaccin luvun, joka on syöttölukua suurempi."""
a, b = 0, 1
while b <= n:
a, b = b, a+b
return b
if __name__ == "__main__": # Pääohjelma
s = input("Anna fibonacci syöttöluku: ") # Lue syöte käyttäjältä
# input palauttaa aina stringin, mutta haluamme kokonaisluvun
try:
# Yritä muuttaa syöte kokonaislukutyypiksi
n = int(s)
except ValueError:
# Tyypin muutos epäonnistui, poistu ohjelmasta virheviestillä
sys.exit("Antamasi syöte ei ollut kokonaisluku!")
print(fib(n))
Ensimmäisen rivin import sys lause importtaa eli
tuo fibonacci-moduulin sys-moduulin käytettäväksi. sys-moduulin
exit-metodia käytetään ohjelman loppupuolella päättämään ohjelman
suoritus sopivalla virheviestillä, jos käyttäjän antama syöte ei
ollut kokonaisluku.
Varsinainen Fibonacci luvun laskeminen tapahtuu
fib-funktiossa. fib-funktio asettaa ensiksi muuttujille a ja b
Fibonaccin lukujonon alkuarvot a = 0 ja b =
1. Lukujonon lukuja lasketaan while-silmukassa, kunnes
viimeisin laskettu Fibonaccin luku b = a+b on
syöttölukua suurempi. Lopuksi palautamme halutun luvun.
Koska Python ohjelmissa ei ole varsinaista
"pääohjelmafunktiota", niin se yleensä merkitään näkyviin
if __name__ == "__main__": lauseella. Tämä suorittaa
pääohjelma-alueen vain kun moduuli suoritetaan pääohjelmana eikä
importattuna jostain toisesta moduulista.
Käyttäjän syöte luetaan input-funktiolla. fib-funktiota varten
halutaan kokonaislukusyöte. Syötteen tyyppi on riippuvainen
käyttäjästä, joten se muutetaan kokonaisluvuksi ennen
fib-funktion kutsua int(x)-kutsulla.
Kokonaislukumuutos voi kuitenkin epäonnistua, jolloin
int(x) nostaa ValueError poikkeuksen.
Jotta käyttäjälle saadaan annettua sopiva virheviesti, niin
käsittelemme poikkeuksen try-except-rakenteen avulla. Pääohjelman
lopuksi kutsumme fib-funktiota syötteellä ja tulostamme sen
palautusarvon.
Tässä luvussa käymme läpi millaisista osista ohjelmat rakennetaan. Kerromme muutamista syntaksii liittyvistä seikoista kuten mm. omien tunnusten luontiin liittyvät säännöt, listan varatuista sanoista.
Python 3:ssa tunnukset voidaan nimetä käyttäen merkkejä a-z, A-Z, 0-9 ja _. Tunnus ei kuitenkaan voi alkaa numerolla. Pienet ja iso kirjaimet tulkitaan eri kirjaimiksi. Tunnukset voivat olla rajoittamattoman pituisia. Lisäksi Python 3 tukee suurta joukkoa ASCII-järjestelmän ulkopuolisia unicode-merkkejä. Tarkempi listaus tuetuista merkeistä löytyy osoitteesta: http://www.dcl.hpi.uni-potsdam.de/home/loewis/table-3131.html. Merkit on jaoteltu kahteen osaan: ID_Start ja ID_Continue, excluding ID_Start. Näiden merkkijoukkojen yhdiste sisältää kaikki unicode-merkit joita voi esiintyä python-tunnuksen nimessä, siten että tunnuksen nimi voi alkaa vain ensimmäisen joukon merkillä. [2]
Seuraavassa lyhyt esimerkki joka demonstroi erikoisempien merkkien käyttöä Python 3 ohjelmassa:
#!/usr/bin/python3.1
# -*- coding: UTF-8 -*-
def python_3_tukee_unicodemerkkejä_varsin_laajasti(ភាសាខ្មែរ="khemrin kieli khmeriksi"):
print("Hei vaan ja hellurei!", ភាសាខ្មែរ)
epävalidia_python_koodia='kuitenkin_esim_€_merkki_ei_ole_sallittu = "epic fai"'
try:
exec(epävalidia_python_koodia)
except SyntaxError as se:
print("€-merkki ei ollut sallittu muuttujan nimessä :'(", se)
validia_python_koodia = epävalidia_python_koodia.replace("€", "e")
exec(validia_python_koodia)
print("Hyvin pyyhkii. No problemo, kun €-merkit vaihdettiin e-kirjaimiksi")
if __name__ == "__main__":
python_3_tukee_unicodemerkkejä_varsin_laajasti()
Yllä oleva python ohjelma tulostaa konsoliin tekstin:
Hei vaan ja hellurei! khmerin kieli khmeriksi €-merkki ei ollut sallittu muuttujan nimessä :'( invalid character in identifier (<string>, line 1) Hyvin pyyhkii. No problemo, kun €-merkit vaihdettiin e-kirjaimiksi
Seuraavassa listattuna (Python 3.0.1:n mukainen, tässä on pieniä eroavaisuuksia 2.x-sarjan kanssa) varatut sanat [1] sen tarkemmin niitä esittelemättä.
False class finally is return None continue for lambda try True def from nonlocal while and del global not with as elif if or yield assert else import pass break except in raise
Python tukee seuraavia literaalivakioita [4]:
"hello", 'world',"""Olen
monella
rivillä"""7, 79228162514264337593543950336,
0b100110111, 0xdeadbeef3.14, 10., .001,
1e100, 3.14e-10,
0e03.14j, 10.j, 10j,
.001j, 1e100j,
3.14e-10jNumeerisista literaaleista on hyvä mainita vielä se, että ne
eivät sisällä etumerkkiä. Esimerkiksi merkintä -1
koostuu yksipaikkaisesta operaatiosta - ja
literaalista 1.
Pythonissa käytetään sisennyksiä rajaamaan lohkoja. Sisennystä kasvatetaan tiettyjen lausekkeiden jälkeen ja vastaavasti sisennystä pienennetään lohkon päättymisen merkiksi. Sekä tabulaattorit että välilyönnit kelpaavat sisennysmerkeiksi Pythonissa.
Sisennyksen määrällä ei ole väliä, sisennys voi olla yhden tabulaattorin tai vaikkapa kuuden välilyönnin pituinen (huom! tabulaattorin ja välilyöntien sekaisin käyttäminen ei ole sallittua Pythonissa). Ainoastaan suhteellisella sisennyksen määrällä on väliä sisäkkäisissä lohkoissa.
Lauseet erotellaan toisistaan rivinvaihdoilla. Useita lauseita voidaan kirjoittaa samalle riville käyttämällä puolipistettä. Kontrollirakenteissa totuusarvolausekkeita seuraa aina kaksoispiste ja lausekkeessa ei ole pakko käyttää sulkuja.
Alla olevissa esimerkeissä kaikki kolme if-rakennetta tekevät
saman asian (tulostavat sanat "spam" ja "ham"):
if 1 == 1:
print("spam")
print("ham")
if 1 == 1:
print("spam"); print("ham")
if 1 == 1: print("spam"); print("ham")
Unicode-tuki Python 3:ssa tekee aidosti mahdolliseksi ohjelmoinnin ohjelmoijan omalla äidinkielellä. Tämä on varmasti monessa mielessä hyvä asia ja sen hyödyntäminen todennäköisesti parantaa koodin luettavuutta kehittäjäyhteisössä. Toisaalta tällä alalla ei ole ollenkaan tavatonta, että ohjelmaa kehitetään ympäri maailmaa ja kehittäjien äidinkielet vaihtelevat suuresti. Tällöin on käytettävä jotakin yhteistä kieltä eli siis yleensä englantia ja tämä unicode-tuki ei juurikaan auta. Toinen tilanne jolloin koodia ei varmaankaan haluta tehdä äidinkielellä on, jos tuotetaan avoimen lähdekoodin ratkaisuja. Muutoin saatetaan varsin tehokkaasti eliminoida iso osa käyttäjäyhteisön potentiaalista jatkokehittää tuotetta. Yleisesti ottaen tuote joka on ohjelmoitu vaikkapa suomeksi rajoittaa jatkokehitysmahdollisuuksia vaikka alkuperäisen kehitystyön kannalta kielivalinnalla olisikin ollut positiivisia vaikutuksia. Kustannussyistä jatkokehitys saatettaisiin joissain tilanteissa haluta tuottaa jossakin edullisemmassa maassa, mutta tämä ei nyt olekaan mahdollista ilman huomattavaa käännöstyötä. Kun lisäksi otetaan huomioon, että unicode-tuen sisällyttäminen tunnuksiin monimutkaistaa kielen implementaatioiden toteuttamista, niin olen kyllä henkilökohtaisesti hieman skeptinen koko ominaisuuden järkevyydestä. En kyllä itse ainakaan ikinä ohjelmoisi vakavissani mitään millään muulla kielellä kuin englannilla.
Pythonissa sisennystapa ei ole ohjelmoijan täysin vapaasti valittavissa, vaan ohjelmoinnissa pakotetaan käyttämään tietynlaista sisennystyyliä. Etu tästä on, että ohjelmoija ei voi vahingossa kirjoittaa sekavasti sisennettyä koodia, koska se rikkoo ohjelman suorituksen. Virheiden mahdollisuus pienenee, kun ohjelma tekee, mitä se näyttääkin tekevän, kun virheellinen sisentäminen ei pääse sekoittamaan lukijaa. Python koodin luettavuutta parantaa se, että kaikki joutuvat käyttämään yhtenäistä sisennystapaa.
Tässä luvussa kerromme miten ja missä itse määritellyt rakenteet ovat käytettävissä.
Pythonissa lohkoksi kutsutaan koodinpätkää, joka suoritetaan yhtenä yksikkönä. Seuraavia rakenteita kutsutaan lohkoiksi: [6]
-c komentoriviparametrin
arvona)exec- tai eval-funktioille merkkijonona annettu
koodiKuten on jo aiemmin mainittu Python käyttää sisennyksiä
määrittelemään lohkoja. Sisennys kasvaa tiettyjen ilmaisujen
jälkeen ja puolestaan pienenee lohkon loppumisen merkiksi.
Samalla tasolla lohkossa sijaitsevat lauseet kirjoitetaan
jokainen omalle riville käyttäen samaa sisennystä (tai tietenkin
vaihtoehtoisesti kirjoittamalla kaikki samalle riville käyttäen
;-merkkiä erottimena).
Lohkon määrittely voidaan aloittaa mm. seuraavilla sanoilla:
if, else, elif, for, while, try, except, finally, class,
def, with, pass.
Jos lohkon sisällä määritellään lokaali muuttuja, on tämän
muuttujan näkyvyysalue (scope) rajoitettu siihen lohkoon. Mikäli
tunnus määritellään funktion vartalossa, niin näkyy se myös
funktion vartalon sisäisille lohkoille (elleivät nämä sido
tunnukselle uutta merkitystä). Moduulitason tunnukset ovat
globaaleja. Mikäli lohkon sisältä halutaan (eksplisiittisesti)
viitata lohkon ulkopuolisiin muuttujiin, niin voidaan käyttää
varattuja sanoja nonlocal ja global. Määreellä nonlocal
vihjataan, että halutaan viitata näkyvyysaluehierarkiassa
seuraavaan tasoon jolta tunnus löytyy. Vastaavasti
global-määreellä siirrytään suoraan etsimään tunnusta globaalilta
näkyvyysalueelta.
Pythonissa sidonta tapahtuu osittain dynaamisesti ja osittain staattisesti. Käytännössä siis näkyvyysalueet (scope) määritellään staattisesti, mutta niitä käytetään dynaamisesti. Eli toisin sanoen eri näkyvyysalueilla olevat tunnukset määritellään, mutta niihin ei liitetä arvoa. Ajonaikaisesti voidaan määritellä uusia tunnuksia globaalille tasolle. On kuitenkin huomioitava, että Python-funktioille ei voi määritellä uusia lokaaleja tunnuksia funktion parsimisen jälkeen (eli lokaalit muuttujat määritellään staattisesti). Pythonin dokumentaatioissa [30] myös vihjataan, että staattisuutta tullaan jatkossa lisäämään (ja näin ollen dynaamiseen sidontaan nojautumista kehotetaan välttämään).
Seuraava esimerkki pyrkii demonstroimaan hieman kaikkea tunnusten näkyvyyteen liittyvää. Siinä näytetään miten saadaan aikaan ajonaikainen poikkeus sidonnasta ja toisaalta myös demonstroidaan muuttujien näkyvyyttä.
#!/usr/bin/python3.1
# -*- coding: UTF-8 -*-
muuttuja="globaali"
print("moduulitasolla", muuttuja)
def main():
import sys
#muuttuja on selvästi edelleen globaalien nimien joukossa
if 'muuttuja' in globals(): print("muuttuja on määritelty globaalilla tasolla")
try:
#FAIL! UnboundLocalError: local variable 'muuttuja' referenced before assignmen!
print("fail", muuttuja)
except UnboundLocalError:
name, msg = sys.exc_info()[:2]
#Btw. alemmalla lohkotasolla määritellyt muuttujat näkyvät ylemmälle
print(name.__name__+":", msg)
#Ja syy on seuraavalla koodirivillä. Näin käy sillä kääntäjä merkitsee seuraavan
#lauseen takia nimen muuttuja olemaan tyypiltään lokaali viittaus tässä
#lohkossa. Siten suoritusaikana kun nimeen muuttuja viitataan yllä, sitä
#etsitäänkin vain lokaalien muuttujien joukosta. Koska tämä lause jossa lokaali
#nimi muuttuja sidotaan merkkijonoon "lokaali", on ensimmäisen nimeen tehdyn
#viittauksen jälkeen tässä lohkossa, on muuttuja tässä siis vielä
#sitomatta.
muuttuja = "lokaali"
print("main", muuttuja)
def fun():
global muuttuja
print("funktio", muuttuja)
fun()
lam = lambda: print("lambda", muuttuja)
lam()
#Seuraava if-rakenne saisi kuitenkin aikaan sen, että kaikkialla main-metodissa
#tunnus muuttuja viittaisi saman nimiseen moduulitason globaaliin muuttujaan.
#Tämä ei toki sinänsä liene kovin yllättävää, mutta toimii kuitenkin pienenä
#demonstraatio siitä, että if-rakenne ei ole lohko. Ei ainakaan Pythonissa :)
#if True:
# global muuttuja
# print("globaali", muuttuja)
if __name__ == '__main__':
main()
print("moduulitasolla if-lohkossa", muuttuja)
Skripti tekee seuraavan tulostuksen (lataa esimerkki)
moduulitasolla globaali muuttuja on määritelty globaalilla tasolla UnboundLocalError: local variable 'muuttuja' referenced before assignment main lokaali funktio globaali lambda lokaali moduulitasolla if-rakenteessa globaali
Python tukee sisäkkäisiä nimiavaruuksia versiosta 2.2 lähtien [5]. Sitä ennen nimiavaruuksia oli vain kolme: paikallinen nimiavaruus, moduulitason nimiavaruus ja sisäänrakennettu nimiavaruus. Vaikka funktion määrittely oli mahdollista toisen funktion sisällä, niin sisäkkäinen funktio ei voinut viitata oman nimiavaruutensa ulkopuolisiin nimiin.
def eka(p):
x = "olen ekan x"
print("--- eka alkaa ---")
p() # tokan paikalliset eivät näy tänne.
# funktio p käydään suorittamassa määrittelykohdan ympäristössä
print(x)
print("--- eka päättyy ---")
def toka():
print("--- toka alkaa ---")
x = "olen tokan x"
def fpar():
nonlocal x # muuttuja x viittaa nyt sulkeuman ulkopuoliseen viimeiseksi
# sidottuun tunnukseen eli tokan muuttujaan x
print(x)
x = "tokan x on muutettu!"
eka(fpar) # "nimetyn sulkeuman" välitys funktioparametrina
print(x) # viittaus paikalliseen tunnukseen
print("--- toka päättyy ---")
if __name__ == "__main__": # Pääohjelma
toka()
Ohjelma tulostaa:
--- toka alkaa --- --- eka alkaa --- olen tokan x olen ekan x --- eka päättyy --- tokan x on muutettu! --- toka päättyy ---
Python tukee myös anonyymejä funktioita lambda-lausekkeiden
muodossa. Lambda-lausekkeella voi helposti luoda pieniä
anonyymejä funktioita. Esimerkiksi funktio lambda: a, b:
a*b palauttaa kahden argumenttinsa kertolaskun.
Lambda-lauseke on kuitenkin aina vain yksi lauseke eikä lause,
joten siihen ei voi sisällyttää esimerkiksi toisto- tai
ehtolauseita suoraan. Se ei siis vastaa useimpien muiden kielten
anonyymejä funktioita. Lambda-lausekkeet voivat viitata
nimiavaruutensa ulkopuolisiin tunnuksiin kuten funktiot:
def tee_kertoja(n):
return lambda x: x * n
f = tee_kertoja(5)
print(f(3)) # 15
print(f(5)) # 25
Pythonissa yksi tavoitteista on ollut tehdä kaikista objekteista ensimmäisen luokan arvoja, eli sellaisia, joita voidaan:
Huomion arvoista on, että Pythonissa myös metodit ovat ensimmäisen luokan arvoja. [7]
Lohkojen pakotettu sisentäminen on hyvä asia, koodin luettavuus lisääntyy ja se myös pakottaa kaikki kirjoittamaan koodia samalla tavalla eli sisentäen.
Tässä luvussa kerromme miten perustoiminnallisuudet ilmaistaan. Esittelemme millaisia mahdollisuuksia Pythonissa on valintaan, iteraatioon ja rekursioon liittyvissä asioissa.
if-lauseif-lause koostuu kolmesta eri osasta
if, elif, else.
x = int(input("Please enter an integer: "))
if x < 0:
x = 0
print('Negative changed to zero')
elif x == 0:
print('Zero')
elif x == 1:
print('Single')
else:
print('More')
Edellinen koodi tuottaa esimerkiksi seuraavanlaisen tulosteen:
Please enter an integer: 42 More
elif-osioita voi yhdessä lauseessa olla
mielivaltainen määrä tai ei yhtään. Myös else-osan
käyttö on vapaaehtoista. Python ei tue monista muista kielistä
tuttua switch-case-rakennetta vaan tämä tulee toteuttaa
if-rakenneella [8].
Monista kielistä poiketen toistolausekkeilla on Pythonissa else-lause [8], joka suoritetaan jos for-lauseessa
lista loppuu tai jos while-lause saa
arvon False. else-lohkoa ei suoriteta
jos toistolauseesta poistutaan break-lauseella.
for-lausefor-lausetta käytetään iterointiin. Lause käyttää hyväkseen
annetun objektin iteraattoria, jota se käy järjestyksessä läpi
kunnes viimeinen arvo on saavutettu. Muista kielistä poiketen
tässä rakenteessa ei voi erikseen määritellä iteraatioaskelta tai
pysäytysehtoa.
a = ['cat', 'window', 'defenestrate']
for x in a:
print(x, len(x))
Edellinen koodi tulostaa:
cat 3 window 6 defenestrate 12Seuraavassa
for-lauseessa puretaan listassa
olevat monikot ja käytetään string-luokasta löytyvää
korvausoperaatiota, joka muuttaa {}-merkkien välistä
löytyvä kohdat halutuiksi.
animals = [ ('Cat', 'Meow'),
('Cow', 'Moo'),
('Dog', 'Woof')]
for animal, sound in animals:
print("{0} says {1}!".format(animal, sound))
Edellinen koodi tulostaa:
Cat says Meow! Cow says Moo! Dog says Woof!
Haluttaessa toistaa tietty koodi useampaan kertaan voidaan
käyttää hyväksi esimerkiksi range-funktiota, joka
tuottaa halutun kokoisen kokonaislukuiteraattorin.
for i in range(5):
print(i)
Edellinen koodi tulostaa:
0 1 2 3 4
while-lausewhile-lausetta käytetään suorittamaan lauseessa
olevaa koodia niin pitkään kun jatkamisehto pysyy totena
[9]. Pythonissa ei ole
erillistä loppuehtoista do-while-lausetta. Sama tulos voidaan
kuitenkin saavuttaa muotoilemalla while-lause
oikein (esim. while True: ja sisällä tarkistus
if condition: break)
x = 3
while x < 3:
print("Loopissa")
x += 1
else:
print("Loopin else")
Edellinen koodi tulostaa:
Loopissa Loopissa Loopissa Loopin else
continue & break-lauseetcontinue-lause hyppää toistorakenteessa suoraan
toistorakenteen seuraavalle iteraatiokierrokselle. Tätä voidaan
käyttää siis, kun halutaan ohittaa osa toistorakenteen koodista
juuri tällä suorituskerralla. break-lause lopettaa
toistorakenteen suorittamisen ilman ns. pysäytysehdon täyttymistä
[9].
Rekursiivisissa ohjelmarutiineissa idea on sama kuin matemaattisesti määritellyissä rekursiivisissa funktioissa, ja rekursiivisesti lasketut välitulokset tallennetaan useimmiten pinoon. Viimeisellä rekursiokierroksella pinosta kerätään vastaukset käänteisessä järjestyksessä. Python tukee rekursiota eli sallii funktion kutsua itse itseään.
Python ei tee eroa normaalin rekursion tai häntärekursion välillä [11]. Toisin sanoen se ei siis toteuta ominaisuuksia (tunnetaan mm. nimillä tail call elimination tai tail call optimization), joilla voitaisiin optimoida tilankäyttöä tallettamalla häntärekursiossa kutsuttavan funktion aktivaatiotietue kutsujan aktivaatiotietueen tilalle pinoon eli muuntamalla rekursio vastaamaan tavallista silmukkaa.
Rekursion syvyys on rajoitettu Pythonissa. Tyypillisessä
Pythonin implementaatiossa raja-arvo on 1000. Raja estää ikuista
silmukkaa aiheuttamasta ylivuotoa C:n pinossa ja kaatamasta
Pythonia [10]. sys-moduulista
löytyvät mekanismit, joilla voidaan selvittää
maksimaalinen rekursiosyvyys
(sys.getrecursionlimit()) tai asettaa se
(sys.setrecursionlimit(int)). Rekursiosyvyyden
oletusarvo riippuu implementaatiosta, kun taas suurin mahdollinen
arvo on laitteistoriippuvainen.
Fibonaccin luvun laskeminen rekursiivisesti. Laskee luvun, jonka järjestys on n.
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
Muista ohjelmointikielistä tutut do/while- ja
switch/case-lauseet on jätetty pois ja niitä ei ole
toteutettu perustellusta syystä. Samat toiminnallisuudet
pystytään toteuttamaan jo olemassa olevalla syntaksilla.
Toisaalta esimerkiksi Javasta tuttu
switch-case-break, jossa myös ehdon täyttävän kohdan
jälkeen tulevat kohdat suoritetaan ellei switch-lauseesta
oistuta erikseen break-lauseella,
saattaa olla hiukan työläämpää toteuttaa Pythonilla.
Rekursio toimii Pythonissa, mutta esimerkiksi kielen alkuperäinen kehittäjä ei henkilökohtaisesti ole sitä mieltä, että se olisi jokapäiväinen työkalu vaan pikemminkin teoreettinen lähestymistapa matematiikkaan. Häntärekursiosta on olemassa jonkinasteisia toteutuksia ja virityksiä, mutta ainakaan virallisesti se ei ole Pythonin ominaisuus.
millaisilla arvoilla tai "datalla" pelataan
Pythoniin on sisäänrakennettu kattava kokoelma perustyyppejä. Pythonissa kaikki on olioita, joten varsinaisia primitiivityyppejä ei kuitenkaan ole. Toisaalta ohut oliokääre vaikkapa liukulukujen päällä ei ei käytännössä eroa alla olevasta implementaatiosta mitenkään. Vastaavasti rajoittamattoman suuret kokonaisluvut ovat ääriesimerkki päinvastaisesta tilanteesta. Seuraavassa on listattu useimmin käytetyt tyypit. Tämä luku perustuu CPythonin tyyppejä käsittelevään dokumentaatioon [12][13].
Numeeriset tyypit:
| Tyyppi | Tarkoitus | Arvoalue | Esimerkki |
|---|---|---|---|
None |
NULL-arvot | None | None |
bool |
totuusarvot | False, True | False |
int |
kokonaisluvut | −∞ - ∞ (*) | -45353 |
float |
kaksoistarkkuuden liukuluvut | (**) | 3.141592653589793 |
complex |
kompleksiluvut | (**) | (9+3j) |
| Standardi kirjaston laajennokset numeroille | |||
fractional.Fractional |
rationaaliluvut | ilmeisesti "int/int" (***) | Fraction('14/4')Fraction(14, 4) |
decimal.Decimal |
tarkat desimaaliluvut | käyttäjän määrittelee tarkkuuden | Decimal('1.12456765432345') |
* Periaatteessa arvoalue on rajoittamaton. Käytännössä käytettävissä oleva (virtuaali)muisti määrä arvoalueen.
** Arvoalue riippuu alla olevasta toteutuksesta/laitteistosta.
*** Fractional on abstraktin luokan
numbers.Rational aliluokka ja ilmeisesti toteutettu
kahdella kokonaisluvulla (ja siten tarjoaa "rajattoman"
tarkkuuden).
Jonotyypit:
| Tyyppi | Tarkoitus | Esimerkki |
|---|---|---|
str |
Unicode-merkkijonot (****) | 'xyzzy', "Hello, 世界" |
bytes |
immutaabelit tavumerkkijonot | b'xyzzy' |
bytearray |
tavumerkkijonot | b'xyzzy' |
list |
listat | [1, 2, 3] |
tuple |
monikot | (1, 2, 3) |
**** Unicode-merkkijonot koostuvat 16- tai 32-bittisisitä
unicode-merkeistä. Koko riippuu siitä käytetäänkö
implementaatiossa/ympäristössä UCS-2 vai UCS-4. Suurimman tuetun
unicode-merkin arvon voi kysyä näin:
sys.maxunicode.
Joukkotyypit:
| Tyyppi | Tarkoitus | Esimerkki |
|---|---|---|
set |
joukot | set([32, 26, 12, 54]) |
frozenset |
immutaabelit joukot | set([32, 26, 12, 54]) |
Kuvaustyypit:
| Tyyppi | Tarkoitus | Esimerkki |
|---|---|---|
dict |
avain: arvo -parit | {"one": 1, "two": 2} |
Python voidaan luokitella vahvasti tyypitetyksi kieliksi [34].
Sen vahvoihin tyyppiturvallisuusominaisuuksiin kuuluu muun muassa,
että implisiittisiä tyyppimuunnoksia ei käytetä (lähes
ollenkaan?) ja olioilla on aina tyyppi, eikä sitä voi muuttaa
(muuttujilla ei ole tyyppiä) [35]. Vertaa,
Javassa "spam" + 1 tuottaa merkkijonon "spam1".
Pythonissa ei numeroa suostuta implisiittisesti konvertoimaan vaan tuloksena on
poikkeus. Vastaavasti C:ssä voidaan esimerkiksi osoittimet
konvertoida eksplisiittisesti ja siten käytännössä tulkita mikä
tahansa olio miksi tahansa olioksi.
Objektien tyyppiä ei tarkisteta käännösaikana, vaan ajon aikana jotkut operaatiot voivat epäonnistua (jolloin syntyy poikkeus), mikä osoittaa, että objekti ei ole sopivaa tyyppiä.
Python on dynaamisesti tyypitetty kieli [37]. Vaikka olioiden tyyppi onkin aina tiedossa, niin Pythonilla ohjelmoitaessa tämä yritetään jättää mahdollisimman pitkälti huomiotta. Muuttujalla ei ole ennalta määrättyä tyyppiä [37]. Funktioiden parametrien tyyppiä ei normaalisti tarkisteta vaan noudatetaan niin sanottua duck typing -paradigmaa, jonka ajatuksen voi kiteyttää aforismilla: "Jos se vaakkuu kuin ankka ja kävelee kuin ankka, niin sen täytyy olla ankka." [36]. Olion (Pythonissa kaikki ovat olioita) tyyppi määritellään tutkimalla sen metodeja ja attribuutteja. Tyypillisesti käytetään EAFP (Easier to Ask Forgiveness than Permission) -tyyliä. Kun jotakin metodia yritetään käyttää, ei olion tyyppiä tarkisteta etukäteen, vaan käytetään mielummin poikkeuksia. Lyhyesti ideana on siis se, että ei välitetä siitä, mistä olio on periytynyt, vaan siitä, pystyykö se suorittamaan halutun tehtävän. Seuraavassa esimerkki duck typing -paradigman käytöstä:
class Duck:
def quack(self):
print("Quaaaaaack!")
def duck_walk(self):
print("Walks.")
class Person:
def quack(self):
print("The person quacks like a duck.")
def duck_walk(self):
print("The person walks like a duck.")
def name(self):
print("John Smith")
def in_the_forest(duck):
duck.quack()
duck.duck_walk()
def game():
donald = Duck()
john = Person()
in_the_forest(donald)
in_the_forest(john)
game()
Muuttujan tyypin voi tarvittaessa tarkistaa
isinstance-funktiolla. isinstance
tarkistaa onko sille parametrina annettu muuttuja parametrina
annetun tyypin tai luokan instanssi. Seuraavassa esimerkkejä sen
käytöstä Python-konsolissa:
>>> isinstance(42, str)
False
>>> isinstance('stringi', int)
False
>>> isinstance(42, int)
True
>>> isinstance('stringi', str)
True
miten toiminnallisuuksia abstrahoidaan ja niistä rakennetaan uusia toiminnallisuuksia?
Funktio määritellään avainsanalla def, jota tulee seurata funktion nimi ja muodolliset parametrit. Funktio on siis muotoa def [nimi] ([parametrit]), ja normaaliin Python tapaan lohkon merkiksi tulee kaksoispiste. Funktion rungon muodostavat lausekkeet alkavat seuraavalta riviltä ja niiden tulee olla sisennettyinä.
Pythonissa on sallittu sisäkkäiset funktiomäärittelyt. Funktion f1 sisällä määritelty funktio f2 pääsee käsiksi f1:n muuttujiin. Jos f2 määrittelee päällekkäisiä nimiä f1:n kanssa, ympäröivän funktion nimet peittyvät. Näkyvyyksistä on kerrottu tarkemmin luvussa 3
Funktion ensimmäinen lause voi olla string-literaali, jolloin se toimii funktion dokumentaatiomerkkijonona eli docstringinä.
def funktio(viesti):
"""Funktion docstring"""
print(viesti)
Pythonissa kaikki funktiot palauttavat arvon, myös sellaisissa
tapauksissa, kun funktiolla ei ole return lausetta. Ilman
return-lausetta funktiot palauttavat arvon None. [16]
Pythonissa parametrinvälitys on toteutettu olioviitteillä,
jotka välitetään arvoparametreinä. Joidenkin olioiden arvo voi
muuttua. Oliota, jonka arvo voi muuttua, kutsutaan
mutaabeliksi olioksi, vastaavasti muuttumatonta oliota
kutsutaan immutaabeliksi olioksi. Olion muuttumattomuus
määräytyy sen tyypin mukaan. Esimerkiksi numeeriset tyypit,
stringit ja monikot ovat muuttumattomia, mutta dict- ja
list-rakenteet ovat muuttuvia.
Seuraavassa esimerkki parametrinvälityksestä:
def muuta1(y):
print('muuta1 sai', y)
y = [1, 2, 3]
print('muuta1 muutti y arvoksi', y)
def muuta2(y):
print('muuta2 sai', y)
y.append(3)
print('muuta2 muutti y arvoksi', y)
def muuta3(y):
print('muuta3 sai', y)
y = "egg and bacon"
print('muuta3 muutti y arvoksi', y)
# pääohjelma
x = [1, 2]
print('ennen muuta1 x arvo on', x)
muuta1(x)
print('muuta1 jälkeen x arvo on', x)
x = [1, 2]
print('ennen muuta2 x arvo on', x)
muuta2(x)
print('muuta2 jälkeen x arvo on', x)
x = "spam spam spam"
print('ennen muuta3 x arvo on', x)
muuta3(x)
print('muuta3 jälkeen x arvo on', x)
# ohjelman tulostus
'''
ennen muuta1 x arvo on [1, 2]
muuta1 sai [1, 2]
muuta1 muutti y arvoksi [1, 2, 3]
muuta1 jälkeen x arvo on [1, 2]
ennen muuta2 x arvo on [1, 2]
muuta2 sai [1, 2]
muuta2 muutti y arvoksi [1, 2, 3]
muuta2 jälkeen x arvo on [1, 2, 3]
ennen muuta3 x arvo on spam spam spam
muuta3 sai spam spam spam
muuta3 muutti y arvoksi egg and bacon
muuta3 jälkeen x arvo on spam spam spam
'''
Funktio muuta1 yrittää muuttaa saamansa listaparametrin
arvoksi [1, 2, 3] sijoittamalla siihen uuden
listainstanssin, mutta tämä ei toimi vaikka listarakenne onkin
mutaabeli, koska y = [1, 2, 3] korvaa vain
parametrimuuttujan paikallisen kopion arvon.
muuta2-funktio muuttaa onnistuneesti listaparametrin
arvoksi [1, 2, 3] käyttämällä listaolion
append-metodia.
String-tyyppi on Pythonissa muuttumaton, joten muuta3-funktion tyylinen stringin uudelleensijoitus parametriin ei myöskään toimi.
Python tukee tavallisen parametrien lisäksi vaihtuvan
mittaisia parametrilistoja ja nimettyjä parametrejä.
Parametrilista otetaan käyttöön erikoissyntaksilla
*args, jossa args on parametrit
sisältävä listamuuttuja. Vastaavasti nimetyt parametrit otetaan
käyttöön **kwargs syntaksilla, jossa
kwargs sisältää nimetyt parametrit
dict-avain-arvorakenteessa. Seuraavassa esimerkki
niiden käytöstä:
def tulosta_kaikki(a, b, *c, **d):
print('tavalliset parametrit: ', a, b)
print('listaparametrit: ')
for arg in c:
print(arg)
print('nimiparametrit: ')
for kwarg in d.items():
print(kwarg)
# pääohjelma
c = [1, 2, 3, 4]
d = {'pi': 3.14, 'e': 2.72}
tulosta_kaikki('eka', 'toka', *c, **d)
# sama kuin:
# tulosta_kaikki('eka', 'toka', c[0], c[1], c[2], c[3], pi=3.14, e=2.72)
# ohjelman tulostus
'''
tavalliset parametrit: eka toka
listaparametrit:
1
2
3
4
nimiparametrit:
('pi', 3.14)
('e', 2.72)
'''
Pythonissa voidaan käyttää myös oletusparametreja [16], jolloin funktiota voidaan kutsua pienemmällä määrällä parametreja kuin mitä on mainittu sen määrittelyssä.
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
while True:
ok = input(prompt)
if ok in ('y', 'ye', 'yes'):
return True
if ok in ('n', 'no', 'nop', 'nope'):
return False
retries = retries - 1
if retries < 0:
raise IOError('refusenik user')
print(complaint)
# Edellä mainittua koodia olisi mahdollista kutsua esimerkiksi seuraavilla tavoilla:
#
# ask_ok('Do you really want to quit?')
# ask_ok('OK to overwrite the file?', 2)
# ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
Oletusarvot evaluoidaan määrittelykohdassaan omalla näkyvyysalueellaan.
i = 5
def f(arg=i):
print(arg)
i = 16
f() # Tulostaa arvon 5
Oletusarvot sidotaan vain kerran. Tällä on väliä varsinkin jos oletusarvona käytetään mutaabelia oliota kuten esim. listaa tai sanakirjaa.
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
# Koodi tulostaa:
#
# [1]
# [1, 2]
# [1, 2, 3]
Funktioita voidaan myös kutsua käyttämällä nimettyjä
parametreja [16] esim.
parametrin_nimi = arvo, jolloin arvot voidaan
järjestellä kutsujan haluamaan järjestykseen.
def foo(bar,fuu):
print("BAR: {}".format(bar))
print("FUU: {}".format(fuu))
foo("Eka", "Toka")
foo(fuu = "Toka", bar = "Eka")
foo("Eka", fuu = "Toka")
# Kaikki tulostavat saman eli:
#
# BAR: Eka
# FUU: Toka
Python 3:n poikkeuksien käsittely toimii niin tavanomaisesti, että ainoastaan Python 2 -ohjelmoija voi pitää sitä jotenkin odottamattomana. Python 2:sta stacktrace tallennettiin globaaliin muuttujaan ja tästä seurasi, että mikäli poikkeus syntyi ja poikkeuksen käsittelyn aikana syntyi toinen poikkeus, niin ensimmäisen poikkeuksen stacktrace katosi, jollei ohjelmoija ollut itse laittanut sitä jonnekin talteen. Näin kävi myös jos poikkeuskäsittelijässä paketoi käsitellyn poikkeuksen toiseen poikkeukseen. Tämä Python 2:den ominaisuus ei periytynyt Python 3:een. Siispä poikkeukset käyttäytyvät Python 3:ssa hyvin Javamaiseen tapaan. Suurimmat erot muihin kieliin lienevät syntaktisia tai toteutuksen yksityiskohtia.
#!/usr/bin/python3.1
# -*- coding: UTF-8 -*-
class NegativeNumberError(Exception):
pass #Meille riittää tässä yksinkertaisen tavallisen poikkeuksen toteutus, joten jätetään tämä määrittely tyhjäksi
def main():
try:
print("Anna positiivinen luku:")
jakaja = int(input())
if jakaja < 0:
raise NegativeNumberError("Negatiivinen numero ei ole tässä sallittu")
1/jakaja
except ZeroDivisionError as e: #Tässä käsitellään vain ZeroDivisionErrorit ja poikkeus-olio välitetään käsittelevään lohkoon as-avainsanan avulla muuttujassa e
#Seuraavassa muutamia tapoja päästä poikkeuksen tietoihin käsiksi
import sys, traceback
print(e.__class__.__name__)
print(e)
print(e.__cause__)
print(e.__context__)
print(e.__traceback__)
print(sys.exc_info())
traceback.print_tb(e.__traceback__)
except (ValueError, NegativeNumberError): #Käsittelijä voidaan määrittää käsittelemään useamman tyyppisiä poikkeuksia.
print("ValueError tai NegativeNumberError")
raise
except ZeroDivisionError: #Tätä käsittelijää ei suoriteta koskaan, sillä käsittelijöistä valitaan aina vain yksi ja se on ensimmäinen
#ylhäältä alas katsottuna, joka kelpaa poikkeuksen käsittelyyn.
print("Tänne ei päädytä koskaan")
except: #Oletus "catch"-haara. Tänne päästään ainakin ajamalla ohjelma ja jos syötteen annon sijasta painetaan ctrl + c tai ctrl + d
print("Jokin muu meni pieleen")
#except Exception: #Tämä ei menisi kääntäjästä läpi, sillä oletuskäsittelijän (pelkkä except:) jälkeen ei saa määritellä enää käsittelijöitä.
# print("Tänne ei ole laillista")
finally: #Tämän haaran koodi kaikissa tapauksissa.
print("Tämä printataan aina")
if __name__ == "__main__":
main()
Lataa esimerkki tästä.
Ohjelman tulosteet eri syötteillä:
Anna positiivinen luku:
0
ZeroDivisionError
int division or modulo by zero
None
None
<traceback object at 0x920ff2c>
(<class 'ZeroDivisionError'>, ZeroDivisionError('int division or modulo by zero',), <traceback object at 0x920ff2c>)
File "./esimerkit/poikkeukset.py", line 13, in main
1/jakaja
Tämä printataan aina
Anna positiivinen luku:
-1
ValueError tai NegativeNumberError
Tämä printataan aina
Traceback (most recent call last):
File "./esimerkit/poikkeukset.py", line 38, in <module>
main()
File "./esimerkit/poikkeukset.py", line 12, in main
raise NegativeNumberError("Negatiivinen numero ei ole tässä sallittu")
__main__.NegativeNumberError: Negatiivinen numero ei ole tässä sallittu
Anna positiivinen luku:
yks
ValueError tai NegativeNumberError
Tämä printataan aina
Traceback (most recent call last):
File "./esimerkit/poikkeukset.py", line 38, in <module>
main()
File "./esimerkit/poikkeukset.py", line 10, in main
jakaja = int(input())
ValueError: invalid literal for int() with base 10: 'yks'
Anna positiivinen luku: ^CJokin muu meni pieleen Tämä printataan aina
yield)Generaattorit ovat yksinkertainen työkalu iteraattorien
luomiseen [29], joita voi tietenkin luoda muillakin keinoilla. Ne luodaan samaan
tapaan kuin tavalliset funktiot, mutta niissä käytetään return-lauseen
sijasta yield-lausetta. Kutsuttaessa
generaattorin next()-funktiota, se jatkaa suoritusta siitä mihin
se edellisellä kerralla jäi ja palauttaa arvon kohdatessaan jälleen yield-lauseen.
Generaattoreille luodaan automaattisesti iteraattoreista tyypillisesti löytyvät metodit __iter__ ja __next__. Suorituksen tila ja paikallisten muuttujien arvot jäävät muistiin. Generaattorien suorituksen loppuessa nostetaan automaattisesti StopIteration poikkeus, joka kertoo kutsujalle iteroinnin loppumisesta.
Seuraavaksi esimerkki fibonaccin lukujen laskemisesta generaattorin avulla:
def fib(n):
"""Palauttaa Fibonaccin lukuja, kunnes saavutetaan luku, joka on suurempi kuin syöttöluku."""
yield 0
a, b = 0, 1
while b <= n:
a, b = b, a+b
yield a
for f in fib(20):
print(f)
Edellinen koodi tulostaa:
0 1 1 2 3 5 8 13
Python ei erityisemmin tue rinnakkaisuutta. Pythonissa (ainakin CPython implementaatiossa) on käytössä GIL eli Global Interpreter Lock. GIL:in tarkoitus on yksinkertaisesti estää Pythonin bytecoden rinnakkainen suorittaminen. Tämä tarkoittaa sitä, että riippumatta siitä montako ydintä/prosessoria on käytettävissä ja moneenko säikeeseen Python-ohjelman suoritus on jaettu, samanaikaisesti voi kuitenkin yleensä suorittaa vain yksi säie. Ratkaisuun on päädytty siksi, että CPython implementaatiosta on näin saatu paljon tehokkaampi ja myös yksinkertaisempi. Aikaisemmin CPythonista kokeiltiin versiota, joka jossa GIL oli poistettu, mutta sen katsottiin heikentävän aivan liikaa Pythonin tehokkuutta yksiytimisillä järjestelmillä (ainakin 30% tehottomampi). Toistaiseksi tähän ei ole odotettavissa mitään muutosta. Tämä ei toki tarkoita, etteikö Pythonilla voitaisi saavuttaa aitoa rinnakkaisuutta. Rinnakkaisuuteen on vain käytettävä prosesseja tai C/C++ laajennoksia joissa GIL vapautetaan. Myös esimerkiksi IO-operaatioiden yhteydessä GIL vapautetaan, joten tiedostoja voi lukea eri säikeissäkin aidosti rinnakkain. Mielenkiintoinen kysymys on kuinka pitkälle nykyinen kanta pitää, kun yksiytimiset prosessorit tulevat yhä harvinaisemmiksi ja lopulta katoavat kokonaan historiaan. [14][15]
Seuraavassa vielä pieni demonstraatio siitä mitä tämä tarkoittaa.
#!/usr/bin/python3.1
# -*- coding: UTF-8 -*-
from multiprocessing import Pool
from multiprocessing.pool import ThreadPool
import multiprocessing
import time
tyypit = ["Bob Sagget", "Cloris Leachman", "Norm MacDonald", "Greg Ricardo", "Gilbert Gottfried"]
tulokset = {}
def tee_jotain(name):
for i in range(1,4):
#Mikäli tässä olisi vain nukuttu sen sijaan, että tuhlataan hieman prosessoriaikaa, niin tulos
#olisikin ollut se, että säikeet olisivat olleet nopeimpia. Ja niinhän sen täytyy olla, sillä
#tokihan moni säie voi nukkua samaan aikaan ja lisäksi säikeet ovat (jaettu muisti) paljon
#prosesseja kevyempiä.
for j in range(1, 1000000):
if j > 0:
continue;
break
print(i, name)
def moniprosessitesti():
allas = Pool(5)
allas.map(tee_jotain, tyypit, 1)
def yksiprosessitesti():
for juippi in tyypit:
tee_jotain(juippi)
def säietesti():
säieallas = ThreadPool(5)
säieallas.map(tee_jotain, tyypit, 1)
def main():
print("Konessa on", multiprocessing.cpu_count(), "prosessoria.")
for testi in [moniprosessitesti, yksiprosessitesti, säietesti]:
print("\n", testi.__name__)
aloitus = time.time()
testi()
tulokset[testi.__name__] = time.time() - aloitus
print("tulokset:")
for avain in tulokset:
print(avain, tulokset[avain])
if __name__ == "__main__":
main()
Voit ladata esimerkin
tästä.
Testin tuloksena omalla Linux-kannettavallani konsoliin ilmestyy jotakin tällaista:
Konessa on 2 prosessoria. moniprosessitesti 1 Cloris Leachman 2 Cloris Leachman 1 Gilbert Gottfried 1 Bob Sagget 1 Norm MacDonald 1 Greg Ricardo 3 Cloris Leachman 2 Bob Sagget 2 Norm MacDonald 2 Greg Ricardo 2 Gilbert Gottfried 3 Norm MacDonald 3 Gilbert Gottfried 3 Greg Ricardo 3 Bob Sagget yksiprosessitesti 1 Bob Sagget 2 Bob Sagget 3 Bob Sagget 1 Cloris Leachman 2 Cloris Leachman 3 Cloris Leachman 1 Norm MacDonald 2 Norm MacDonald 3 Norm MacDonald 1 Greg Ricardo 2 Greg Ricardo 3 Greg Ricardo 1 Gilbert Gottfried 2 Gilbert Gottfried 3 Gilbert Gottfried säietesti 1 Greg Ricardo 1 Gilbert Gottfried 1 Bob Sagget 1 Cloris Leachman 1 Norm MacDonald 2 Greg Ricardo 2 Bob Sagget 2 Cloris Leachman 2 Gilbert Gottfried 2 Norm MacDonald 3 Bob Sagget 3 Cloris Leachman 3 Greg Ricardo 3 Gilbert Gottfried 3 Norm MacDonald tulokset: yksiprosessitesti 3.42161202431 säietesti 3.89715909958 moniprosessitesti 2.04168510437
Tuloksista nähdään selvästi, että säikeet toimivat kaikista hitaimmin. Pelkästään kahdella ytimellä varustettu kannettavani toimi prosesseilla lähes 50% nopeammin verrattuna säikeisiin. Säikeet toimivat jopa hitaammin kuin yksiprosessitesti. Säiealtaan allokointi on toki ylimääräistä työtä, mutta kyseessä ei voi olla niin raskas operaatio, että säietesti voisi mitenkään päästä lähes puoli sekuntia nopeampaan aikaan ja edes samalle viivalle yksiprosessitestin tuloksen kanssa. Kysymys on vain siitä, että säikeet eivät tuo mitään aitoa rinnakkaisuutta laskentaan vaan ainoastaan lisäävät synkronointitarvetta. GIL:in ansiosta synkronointi on toki yksinkertaista, mutta prosessoriaikaa pitää silti tasapuolisesti jakaa säikeiden välillä ja suoritusvuoroja vaihdella. Kuten kommenteistakin ilmenneen, niin mikäli olisimme tulostusten välillä nukkuneet silmukassa pyörimisen sijasta olisi säietesti noussutkin nopeimmaksi. Tämä johtuu tietenkin siitä, että säikeet ovat kevyempiä kuin prosessit ja voivat toki kaikki nukkua samaan aikaan siinä missä prosessitkin.
Pythonia käytetään hyvin moninaisiin tarkoituksiin [33]. Siksipä Pythonin yhteydessä on vaikea puhua jostakin tietystä vallitsevasta arkkitehtonisesta piirteestä, jota yleensä noudatetaan. Pythonille ominaisena voidaan kuitenkin pitää esimerkiksi duck typing-paradigman käyttöä ja EAFP-tyyliä, joita jo käsiteltiinkin luvussa 5.3. Staattinen tyypitys ~ dynaaminen tyypitys. Tyypillisin syy miksi Python valitaan, johonkin ohjelmointiprojektiin lienee sen dynaamisuus ja laajennettavuus (paljon valmmiita moduuleja). Täten ei liene ihme, että tyypilliset arkktehtuurit ovat ennemminkin ohjeistusta erittäin dynaamiseen, nopeaan (tuottaa) ja laajennettavaan ohjelmointiin.
Tässä kappaleessa on pyritty tiivistämään oleellisimmat asiat
Pythonin dokumentaation moduuli-osiosta [32].
Pythonissa lähdekooditiedosto muodostaa moduulin. Moduulin
nimi on tiedostonimi ilman tiedostopäätettä .py.
Ajettavan moduulin nimeä voi moduulissa kysyä
__name__-metodilla. Moduulin tarkoitus on koota
yhteen määrittelyitä (funktioita, luokkia, muuttujia). Moduuli
voidaan sitten jostakin toisaalta (esimerkiksi toisesta
moduulista) pyytää käytettäväksi. Moduuli voidaan pyytää käyttöön
avainsanan import avulla. Tämä voidaan tehdä
esimerkiksi näin:
import moduuli #kutsutaan moduuli.moduulissa_määritelty_funktio()
Kuten esimerkistä nähdään, niin moduulissa määriteltyjä asioita ei tuoda suoraan osaksi sen hetkistä symbolitaulua, vaan ainoastaan moduulin nimi tuodaan sinne ja sen sisältöön viitataan sitä kautta. Toisenlaista käytöstä saadaan aikaan, jos yksinkertaisesti määritellään nimi myös sellaisenaan symbolitauluun. Eli vaikkapa näin:
import moduuli #määritellään nimi moduulissa_määritelty_funktio viittaamaan #moduulin moduuli sisällä määriteltyyn funktioon moduulissa_määritelty_funktio moduulissa_määritelty_funktio = moduuli.moduulissa_määritelty_funktio #ja nyt voimme viitata funktioon suoraan ilman moduulia moduulissa_määritelty_funktio()
Yllä esitettyä tapaa elegantimpi tapa tuoda nimiä lokaaliin
nimiavaruuteen on käyttää import-lauseen
erityismuotoa. Voidaan sanoa from module import
moduulissa_määritelty_funktio. Tämä ei toki ole aivan sama
kuin yllä, sillä tässä tapauksessa vain ja ainoastaan tämä yksi
funktio liitetään moduulista osaksi tätä moduulia ja siten siis
mitkään muut moduulin nimet eivät ole edes moduulin nimen kautta
viitattavissa tässä nimiavaruudessa.
Jos kuitenkin kaikki moduulin nimet halutaan tuoda toisen
moduulin lokaaliin nimiavaruuteen sellaisenaan, niin tämäkin on
mahdollista. Tällöin käytetään edellisen import-lauseen
variantin erikoismuotoa. Eli sanomme: from moduuli
import *.
Python ei ilmeisesti tue piirreluokkia (eng. trait). Kuitenkin näyttäisi siltä, että Pythonillakin tätä tekniikkaa voidaan jossain määrin matkia moduulien avulla. Esimerkiksi luokan funktiot voidaan määritellä erillisessä moduulissa ja sitten sisällyttää luokan määrittelyssä ne toisesta moduulista osaksi lokaalia nimiavaruutta. Seuraavassa yksinkertainen esimerkki:
#!/usr/bin/python3.1
#-*- coding: UTF-8 -*-
# moduuli1.py
class Luokka:
from moduuli2 import tee_jotain
def tee_jotain_muuta(self):
print("Moduulissa 1 määritelty")
def main():
l = Luokka()
l.tee_jotain()
l.tee_jotain_muuta()
if __name__ == "__main__":
main()
#------------------------------
#!/usr/bin/python3.1
# -*- coding: UTF-8 -*-
# moduuli2.py
def tee_jotain(self):
print("Moduulissa 2 määritelty")
Suorittamalla moduuli1.py-moduulin skriptinä konsolissa, tulostuu konsoliin:
Moduulissa 2 määritelty Moduulissa 1 määritelty
Pythonin dokumentaatiossa [31] kerrotaan paketeista muun muassa
seuraavanlaisia asioita. Monista moduuleista muodostuvan kokonaisuuden voi koota
paketiksi. Paketit ovat erityistä tiedostorakennetta noudattavia
hakemistoja. Pakein nimi on sen juurihakemiston nimi.
Juurikansion tulee sisältää __init__.py-tiedosto sen merkiksi,
että kyseessä on paketti. Samoin myös kaikkien alikansioiden
(joiden halutaan olevan osa pakettia) tulee sisältää tämä
tiedosto. __init__.py voi kuitenkin olla tyhjä. Esimerkki paketin
hakemistorakenteesta:
paketin_juuri/ paketin_juuri/__init__.py paketin_juuri/alipaketti/__init__.py paketin_juuri/alipaketti/alimoduuli.py
Nyt alimoduuliin voidaan viitata käyttäen pistenotaatiota vaikkapa näin:
import paketin_juuri.alipaketti.alimoduuli
Mikäli paketeista halutaan tyypillisesti ottaa kerrallaan
käyttöön kokonainen alipaketti tai koko paketti itsessään, olisi
luontevaa käyttää tehdä se jotenkin näin:
from paketin_juuri.alipaketti import *
Ja näin se tehdäänkin. Pakettien etsiminen ja kaikkien tuominen
voisi olla potentiaalisesti paitsi raskas ja hidas operaatio myös
aiheuttaa yllättäviä ja vaarallisia sivuvaikutuksia. Ongelma on
ratkaistu siten, että mikäli paketin ylläpitäjä haluaa
mahdollistaa tällaisen massasisällyttämisen (mass include), niin
hänen tulee määritellä kunkin tason __inti__.py-tiedostossa
muuttujan __all__ arvoksi lista niistä
alimoduuleista, jotka tulisi tällöin sisällyttää.
Pythonissa luokat ovat olioita, kuten kaikki muutkin
tietotyypit. Luokat ovat myös dynaamisia: ne luodaan
ajoaikaisesti, ja niitä voidaan muokata luomisen jälkeenkin.
Luokkien jäsenmuuttujat ovat normaalisti julkisia [21], eikä Pythonissa ole mahdollista piilottaa
dataa,
[http://docs.python.org/py3k/tutorial/classes.html#random-remarks]
vaan kaikki perustuu ohjelmointikäytäntöihin. Privaattien
muuttujien luomiseen on rajallinen tuki: kirjoittamalla muuttujan
nimen eteen kaksi alaviivaa saadaan muuttuja käyttäytymään, ikään
kuin se olisi privaatti ( esim: __spam). [22]
Jos data-attribuutilla ja metodilla on sama nimi, metodin nimi peittyy. Nimiongelmien välttämiseksi suositellaan käyttämään jonkinlaista nimeämiskäytäntöä, esimerkiksi nimeämällä metodit niin, että nimi alkaa isolla kirjaimella. [23]
Luokkamäärittely yksinkertaisimmillaan:
class ClassName:
<lause-1>
.
.
.
<lause-N>
Luokkamäärittelyihin pätee sama kuin funktioidenkin
määrittelyihin: ne tulee suorittaa, ennen kuin niitä voidaan
käyttää ohjelmassa. (Jos luokkamäärittely on laitettu
if-lauseen sisään, sitä ei voida käyttää ennen kuin
if-lause on suoritettu) Kun luokkamäärittelystä
poistutaan normaalisti, syntyy luokkaolio. [24]
Pythonissa self vastaa Javan
this-käskyä. Sillä viitataan luokan nykyiseen
ilmentymään. Se tulee antaa jokaisessa metodimäärittelyssä
metodin ensimmäiseksi parametriksi, vaikka kutsuttaessa metodia
self-parametrille ei anneta arvoa (Python hoitaa
itse tämän). self ei ole varattu sana, joten sen
tilalla voitaisiin käyttää muutakin, mutta koodin luettavuuden
vuoksi sen käyttöä suositellaan.
class Luokka:
def metodi(self, parametri1, parametri2):
...
Self:in toiminta voi tuntua hieman kummalliselta, joten
selvennykseksi pieni esimerkki: Jos on määritelty luokka
Luokka ja siitä on olemassa ilmentymä
olio. Kun kutsutaan tuon ilmentymän metodia
olio.metodi(parametri1, parametri2) kutsu muutetaan
automaattisesti muotoon Luokka.metodi(olio, parametri1,
parametri2). [25] [26]
Luokkaoliot tukevat kahdenlaisia operaatioita: attribuuttiviittauksia ja ilmentymien luomista. [27]
Attribuuttiviittauksessa syntaksi on olio.nimi.
Esimerkkinä olevan luokkamäärittelyn tapauksessa
Luokka.i palauttaisi kokonaisluvun ja
Luokka.metodi palauttaisi funktio-olion
(Luokka.metodi(Luokka) palauttaisi merkkijonon
"spam").
class Luokka:
i = 1
def metodi(self):
return 'spam'
Ilmentymien luominen toimii kuin luokkaolio olisi parametriton funktio, joka palauttaa luokan uuden ilmentymän.
x = Luokka()
Luokkaan voidaan määritellä erikoismetodi
__init__(), jota kutsutaan automaattisesti, kun olio
on luotu. __init__():n avulla ilmentymä voidaan
alustaa johonkin haluttuun tilaan.
class Luvut:
def __init__(self, luku1, luku2):
self.r = luku1
self.i = luku2
x = Luvut(3, 4)
x.r, x.i #tulostaa: (3, 4)
Ilmentymäoliot eivät ymmärrä muuta kuin attribuuttiviittauksia. Attribuuttinimiä on kahdenlaisia: data-attribuutteja ja metodeita.
Data attribuutit ovat "ilmentymämuuttujia". Ne luodaan kuten paikalliset muuttujatkin, eli silloin kun niihin asetetaan jonkin arvo. [28]
Yksinkertainen metodikutsu:
x.metodi() #Luokka-esimerkissä palauttaisi "spam"
Metodia ei ole kuitenkaan pakko aina kutsua suoraan.
x.metodi on metodiolio, joka voidaan tallentaa
muuttujaan, jota kutsutaan myöhemmin. [26]
metodimuuttuja = x.metodi
while True:
print(metodimuuttuja())
#Tulostaa "loputtomassa" loopissa metodin palauttamaa merkkijonoa
Ajatellaan edelleen, että x on luokan
Luokka ilmentymä. Tällöin x.metodi ei
kuitenkaan ole sama asia kuin Luokka.metodi, sillä
x.metodi on metodiolio ja
Luokka.metodi on funktio-olio.
Pythonissa luokkaperiytyminen on toteutettu seuraavanlaisella syntaksilla:
class Luokka(Yliluokka):
<lause-1>
.
.
.
<lause-N>
Luokka Luokka perii yliluokan
Yliluokka jäsenet ja sen yliluokan jäsenet kunnes
saavutaan object kantaluokkaan. Peritty luokka voi
korvata yliluokan metodeita yksinkertaisesti määrittelemällä ne
uudelleen. Yliluokan metodeihin voi viitata
Yliluokka.metodi(self, parametrit) tyylisellä
syntaksilla tai Java-tyylisesti
super()-funktiolla.
Pythonissa on mahdollista periyttää luokka monesta luokasta. Yleinen syntaksi tähän on sama kuin yhden luokan periytymisessä paitsi että periytetyt luokat erotellaan pilkuilla.
Moniperiytymisen niin kutsuttu timanttiongelma on se,
että periytettäessä luokka D luokista B ja C, joilla on sama
isäluokka A, on epäselvää kumman luokan, B vai C, metodeilla
korvataan isäluokan aliluokissa, B ja C, korvatut jäsenet
[18]. Pythonissa tämä ongelma on
ratkaistu periyttämällä kaikki luokat yhteisestä
object luokasta ja muodostamalla periytymiselle
tietty periytymisjärjestyslista. Luokkien
periytymisjärjestyslista muodostetaan hiukan yksinkertaistaen
läpikäymällä luokat syvyyshaulla vasemmalta oikealle, jonka
jälkeen listasta poistetaan jo kerran läpikäydyt luokat.
Seuraavassa esimerkki moniperiytymisestä. Esimerkissä on
edellä kuvatun kaltainen moniperintätilanne luokista A, B, C ja
D. Luokka D periytetään luokista B ja C. Periytymishakujärjestys
on tässä tapauksessa lista [D, B, A, C, A], joka
supistuu muotoon [D, B, C, A], kun kaksoiskappaleet
poistetaan niin että vain viimeinen viittaus luokkaan jää
jäljelle listaan.
# Kuvio luokkamallista
#
# A
# / \
# B C
# \ /
# D
class D(B, C):
<lause-1>
.
.
.
<lause-N>
Lähteenä: http://docs.python.org/tutorial/modules.html
Muuttujat Pythonissa sisältävät pelkästään viittauksen olioon [17]. Viittauksen kohteena olevat oliot sisältävät myös tyyppitietonsa eli minkä luokan olion ilmentymä on kyseessä. Esimerkiksi funktiokutsussa annettu parametri on siis vain viittaus johonkin olioon, joka kuten aiemmin on jo mainittu, on joko immutaabeli tai ei. Kutsuttu funktio ei voi muuttaa kutsujansa olioviitettä, eli esimerkiksi seuraavanlainen funktio ei pystyisi muuttamaan alkuperäistä stringiä ollenkaan, vaan sen tulisi palauttaa viite uuteen string-olioon.
def muuta_str(s):
s = "Uusi stringi" # funktion paikallinen muuttuja viittaamaan uuteen olioon
a = "asdf"
muuta_str(a)
Muuttujaan sijoittaminen (esimerkiksi a = b)
asettaa muuttujan osoittamaan, tyypistä riippumatta, johonkin
olioon. Mutaabeleja olioita voidaan muuttaa muutosoperaatioilla
kutsutussa metodissa.
def add_bananas(l):
l.append("bananas")
a = ["apples", "tomatoes"]
add_bananas(a) # a = ['apples', 'tomatoes', 'bananas']
Muuttujan arvon sijoittaminen eli arvon kopiointi suoritetaan
copy-pakkauksen copy- tai
deepcopy-funktioilla [20].
copy-funktio luo pinnallisen kopion saamastaan
oliosta ja deepcopy on syväkopion. Pinnallinen kopio
on uusi olio, jonka sisäisiin olioihin on asetettu viitteet
vanhan olion viitteisiin. Deepcopy eli syväkopio luo
rekursiivisesti saamansa olion kaikista viitteistä kopiot.
Python on voimakas dynaaminen ohjelmointikieli, jolla on monia käyttökohteita. Sen tärkeimpiä ominaisuuksia ovat
Pythonia käytetään usein skriptikielenä sen helppokäyttöisyyden ja joustavuuden takia. Esimerkiksi monet Linux-distribuutiot käyttävät Pythonia asennuksessaan tai paketointijärjestelmän toteutuksessa. Python toimitetaankin nykyään jo usein Linux-distribuution mukana.
Python-tulkki sulautetaan usein ohjelmien sisään skriptausrajapinnaksi. Esimerkiksi 3D-mallinnusohjelma Blender, rasteripiirto-ohjelma GIMP ja jotkin tietokonepelit hyödyntävät Pythonia näin. Web-ohjelmointia varten on kehitetty monia ohjelmistokehyksiä kuten Django, Pylons, TurboGears, web2py, Flask ja Zope. Niitä voidaan käyttää web-palvelinten kuten Apachen kanssa WSGI:n kautta, joka on erikseen Pythonin ja palvelinten välistä vuorovaikutusta varten kehitetty rajapinta. Tieteellistä laskentaa varten on olemassa NumPy, SciPy ja monia muita kirjastoja.
Pythonin dynaamisen tyypitysjärjestelmän kääntöpuoli on että mahdolliset virheet koodissa huomataan vasta suoritusaikana, syntaksivirheitä lukuun ottamatta, eikä käännösaikana kuten monissa muissa kielissä.
Python on nopea kieli tulkattavaksi kieleksi, mutta joskus suoritusnopeuskriittinen koodi täytyy toteuttaa C/C++-laajennoksilla, jotta se olisi kelvollista. Tämä on usein hankalampaa kuin tavallisen Python-koodin kirjoittaminen.
Rinnakkaislaskentaa vaivaa Pythonissa GIL, joka estää säikeiden suorittamisen samanaikaisesti monella ytimellä. GILin poistaminen Pythonista on yhä avoin kysymys, johon ei näytä tulevan ratkaisua lähitulevaisuudessa.
Copyright © 2011 Juhani Åhman, Juha Louhiranta, Matti Nauha, Timo Lehto
import antigravity ;)