Python

Mureakuha

Loikkaa: valikkoon, hakuun

Python on korkean tason tulkattava olio-ohjelmointikieli. Kieli on vahvasti mutta dynaamisesti tyypitetty. Monipuoliset perustietotyypit yhdessä selkeän ja yksinkertaisen syntaksin kanssa mahdollistavat nopean ohjelmankehityksen.

Sisällysluettelo

Ominaisuuksia

  • tulkattava
  • laaja valikoima perustietotyyppejä, mm.
  • olio-ohjelmointi (mm. luokat ja moniperintä)
  • koodin ryhmittely moduuleihin ja paketteihin
  • poikkeukset
  • list comprehensionit
  • automaattinen roskienkeruu
  • koodilohkot määritellään sisentämällä
  • laaja standardikirjasto (mm. WWW-yhteydet ja säännölliset lausekkeet)
  • asennuspaketti sisältää dokumentaation (mm. tutoriaali) ja interaktiivisen kehitysympäristön
  • laaja tuki eri alustoille (Windows, MacOS, Linux, monet Unixit, OS/2,Series 60, ...)
  • avoimen lähdekoodin alainen, vapaasti käytettävissä myös kaupallisiin tarkoituksiin

Asentaminen

Python todennäköisesti tulee Linux-distron mukana, mutta Windowsiin se tarvitsee asentaa erikseen. Ellei erityisen painavaa syytä ole, kannattaa aina asentaa uusin Pythonin kotisivuilla saatavilla oleva versio.

Mahdolliset Pythoniin asennettavat lisämoduulit (esim. WWW-palvelin) ovat aina versiokohtaisia, joten vanhat versiot eivät toimi uuden Python-version kanssa.

Ensiaskeleet

Asennuspaketin mukana tulee interaktiivinen IDLE-käyttöliittymä, johon voi kirjoittaa välittömästi suoritettavia koodirivejä. Vähänkään pidemmät koodipätkät kannattaa kirjoittaa omaan tiedostoonsa, mutta aloittelijalle on helpointa tutustua ensin interaktiiviseen tilaan.

Asennuksen mukana tulevasta tutoriaalista on hyvä aloittaa, varsinkin jos on ennestään ohjelmointikokemusta. Mikäli Python on käyttäjän ensimmäinen ohjelmointikieli, kielen kotisivuilla on linkkejä sopivaan englanninkieliseen oppimateriaaliin (Python for Non-Programmers).

IDLE 1.1      
>>> 2 * 5
10
>>> 2 ** 200
1606938044258990275541962092341162602522202993782792835301376L
>>> print 'Hello World!'
Hello World!
>>> 'kas' + 'vain'
'kasvain'
>>> 'kas' * 3
'kaskaskas'
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> import random
>>> lottorivi = random.sample(range(1, 40), 7)
>>> lottorivi
[39, 36, 15, 3, 20, 23, 21]
>>> lottorivi.sort()
>>> print 'Voittava rivi: ' + ' '.join(str(x) for x in lottorivi)
Voittava rivi: 3 15 20 21 23 36 39
>>>

Yllä oleva suoritettiin IDLE:n pääikkunassa. Muut oppaan esimerkkikoodit (joissa kehotetta >>> ei ole näkyvissä) on tarkoitus sijoittaa omaan tiedostoonsa ja suorittaa sitä kautta. Se tapahtuu valitsemalla IDLE:n File-valikosta New Window, jolloin avautuvaan ikkunaan koodi voidaan kirjoittaa. Sen suorittaminen tapahtuu painamalla F5.

Kielioppia

Syntaksista

Pythonin syntaksi poikkeaa jonkin verran muista yleisistä kielistä. Koodi on varsin tiivistä verrattuna esim. C-kieleen, mutta se on silti hyvin luettavaa. Ohjelmointia aiemmin harrastanut luultavasti kiinnittää eniten huomiota pakkosisennykseen, eli koodilohkoja ei määritellä esimerkiksi aaltosuluilla vaan ne määräytyvät sisennyksen perusteella. Ratkaisu voi tuntua aluksi erikoiselta, mutta muutenkin tiiviiseen syntaksiin se istuu erittäin hyvin. Kommenttimerkkinä toimii #. Funktion rungon ensimmäisellä rivillä oleva merkkijono tallentuu funktion dokumentaatioksi (käyttö: help(fibonacci) tai print fibonacci.__doc__).

def fibonacci(n):
    """Palauta Fibonaccin lukujonon n:s alkio"""
    a, b, x = 1, 1, 2
    while x < n:
        a, b = b, a + b
        x += 1
    return b

Kaksoispistettä käytetään lohkon alkaessa (class, def, for, while, if jne.) ja sen avulla editori ymmärtää sisentää seuraavasta rivistä alkaen. Sisennykset tallennetaan välilyönteinä.

Pythonin syntaksi on lähes yhtä ilmaisuvoimainen kuin Perlin vastaava. Koodaaja voi keskittyä olennaiseen, koska kieli sisältää hyvät perustyökalut kaikkiin tavallisiin operaatioihin. Alla oleva (toiminnaltaan hyvin rajoitettu) esimerkkifunktio muuntaa tekstin morse-koodiksi.

def morse_code(s):
    morse = { 'o': '---', 's': '...' }
    return ' '.join([morse[c] for c in s.lower() if c in morse.keys()])
 
print morse_code('SOS')  # ... --- ... 
 

Ylläoleva funktio morse_code

  1. määrittelee dictionaryn morse-koodille
  2. muodostaa pienikirjaimisen kopion käyttäjän antamasta merkkijonosta (s.lower())
  3. muodostaa listan, joka koostuu merkkijonon morse-muunnoksesta (['...', '---', '...'])
  4. liittää välilyönneillä yhteen tuon listan alkiot
  5. palauttaa näin syntyneen merkkijonon

Muuttujista

Korkean tason kielenä Python sisältää valmiiksi monia hyödyllisiä perustietotyyppejä. Otettaessa muuttuja käyttöön sen tyyppiä ei tarvitse ilmoittaa, vaan tulkki päättelee tyypin annettavan arvon perusteella. Esim. a = 1 asettaa a:n arvoksi kokonaisluvun yksi. Tässä luvussa esitellään osa Pythonin perustietotyypeistä.

Pythonissa on käytössä dynaaminen tyypitys, eli vaikka muuttuja on aina jotakin tyyppiä, tuota tyyppiä voidaan vaihtaa ohjelman suorituksen aikana. Tyyppimuunnosfunktioista käytetyimmät ovat int(), float() ja str(). Esim. muuttujan a sisältämästä merkkijonosta '3' saadaan kokonaisluku 3 seuraavasti:

>>> a = '3'
>>> a = int(a)

Ennen kattavampaa esittelyä lyhyt silmäys yleisimpiin perustietotyypeihin:

a = 3                 # kokonaisluku
b = 2.7               # liukuluku
c = 3 + 2j            # kompleksiluku
d = 'erkki'           # merkkijono
e = [2, 5, 'r']       # lista
f = (2, 5, 'r')       # monikko eli tuple
g = {'b': 2, 'c': 3}  # dictionary 
 

Kokonaisluku

Kokonaislukuja käytetään tuttuun tapaan. Pythonissa kokonaisluvuilla ei ole ala- tai ylärajaa, vaan niiden pituutta rajoittaa ainoastaan käytettävissä olevan muistin määrä.

x = 10
y = 5 / 2                     # y = 2, ei 2.5
print '2 + 2 = %d' % (2 + 2)  # Tulostaa: 2 + 2 = 4
 
# dynaaminen tyypitys: merkkijonosta kokonaisluvuksi
a = '43'                      # '43'
a = int(a)                    # 43
 
a % 5                         # 3, jakojäännös
5 ** 2                        # 25, potenssiin korotus
int(5.9)                      # 5, muuttaa kokonaisluvuksi leikkaamalla desimaalit 
 

Liukuluku

Määrittely tapahtuu desimaalipisteen avulla.

>>> pituus = 1.74
>>> y = 5.0 / 2  # y on nyt 2.5
>>> print '%.3f' % (10.0 / 7)  # tulostus 3 desimaalin tarkkuudella
1.429
>>> float('5.21')
5.21
>>> float(2)
2.0

Kompleksiluku

Imaginääriyksikön tunnus on j.

>>> 4 - 6j + 3 * (-1 + 4j)
(1+6j)
>>> 5j ** 2
(-25+0j)

Merkkijono

Merkkijono voidaan määritellä kolmella tavalla: heittomerkillä, lainausmerkillä tai kolmella lainausmerkillä ympäröitynä.

nimi = 'Jaakko'
reknro = "ABC-123"
numero = str(123)  # '123'
lainaus = 'Ville: "Helppoa!"'
a = """Rivinvaihtoja
sisältävän
merkkijonon
määrittely"""

Katso alla olevasta Lista-luvusta, miten merkkijono-oliot käyttäytyvät.

Lista

Lista on Pythonissa erittäin hyödyllinen luettelomuotoinen tietorakenne. Se korvaa monesta kielestä tutun taulukko-tyypin, mutta ominaisuuksia on huomattavan paljon (esim. sisäänrakennettu sort()-metodi). Listaan voidaan tallettaa mitä tahansa alkioita, myös toisia listoja.

Alla on esimerkkejä listan käytöstä. Indeksiä käyttämällä saadaan listasta poimittua yksittäisiä alkioita. Negatiivisilla indekseillä laskenta aloitetaan listan lopusta päin eli -1 tarkoittaa listan viimeistä alkiota. Kaksoispisteellä voidaan listan osasta tehdä uusi kopio. Tässä yhteydessä on helpointa ajatella indeksit alkioiden väleihin.

t = []               # aluksi luodaan tyhjä lista
t.append('a')        # t = ['a']
t.extend(['b', 'c']) # t = ['a', 'b', 'c']
t.append(2352)       # t = ['a', 'b', 'c', 2352]
t.pop()              # palauttaa 2352, nyt t = ['a', 'b', 'c']
 
#   0    1    2    3    4    5      pos. indeksit listan osalle
r = ['a', 'b', 'c', 'd', 'e', 'f']
#  -6   -5   -4   -3   -2   -1      neg. indeksit listan osalle
 
r[1]                 # 'b'
r[1:]                # ['b', 'c', 'd', 'e', 'f']
r[:-1]               # ['a', 'b', 'c', 'd', 'e']
r[-2:]               # ['e', 'f']
r[2:-2]              # ['c', 'd']
r[:]                 # helpoin tapa kopioida koko lista 
 

Huom! Lista ja dictionary kuuluvat muokattaviin (mutable) tietotyyppeihin, toisin kuin suurin osa muista perustietotyypeistä. Käytännössä tämä tarkoittaa mm. sitä, että välitettäessä lista funktiolle, joka muokkaa listaa, muutokset välittyvät myös funktion ulkopuolelle. Esimerkit valaisevat asiaa:

def tulosta_jarjestyksessa(s):
    s.sort()  # järjestetään lista
    print s
 
t = [2, 5, 1]
print t                    # tulostaa: [2, 5, 1]
tulosta_jarjestyksessa(t)  # tulostaa: [1, 2, 5]
print t                    # tulostaa: [1, 2, 5] 
 

Listasta ei voi ottaa kopiota tyyliin s = t, joka tekee vain uuden viittauksen samaan olioon. Kuten yllä mainittiin, identtisen kopion saa helpoiten s = t[:] -sijoituksella. Seuraava koodi toimii halutulla tavalla:

def tulosta_jarjestyksessa(s):
    r = s[:]
    r.sort()  # järjestetään lista
    print r
 
t = [2, 5, 1]
print t                    # tulostaa: [2, 5, 1]
tulosta_jarjestyksessa(t)  # tulostaa: [1, 2, 5]
print t                    # tulostaa: [2, 5, 1] eli järjestys säilyi 
 

Miksei sama tapahdu esim. merkkijonoilla? Niissäkin kopioidaan viittaus samaan olioon, mutta koska merkkijono on muokkaamaton (immutable), ei ole olemassa tapaa, jolla viittauksen kohteena olevaa merkkijonoa voisi muokata. Täten merkkijonoja muokkaavat funktiot palauttavat uuden merkkijono-olion, joka on muodostettu vanhan pohjalta. Esimerkki:

a = 'Terve'
b = a
b = b.lower()  # b = 'terve', a = 'Terve'
b = b.upper()  # b = 'TERVE', a = 'Terve'
# 'terve'-merkkijono-olioon ei ole enää viittauksia, joten
# automaattinen roskienkeruu vapauttaa sen viemän muistin.
 
# Seuraava rivi luo uuden merkkijono-olion 'terve', joka on
# olemassa vain print-käskyn suorituksen ajan. a säilyy
# muuttumattomana koko ajan, koska sitä käytettiin vain
# pohjana luotaessa uusi olio.
print a.lower()

Monikko (tuple)

Monikko (engl. tuple) on hieman samanlainen kuin lista, mutta sitä ei voi muokata. Muokkaamattomuudesta johtuen sitä voidaan käyttää dictionaryn avaimena, toisin kuin listaa. Monikko on myös hyvä valinta muuttumattoman datajoukon säilyttämiseen.

suunnat = ((1, 0), (0, 1), (-1, 0), (0, -1))
 
def naapurit(x, y):
    return [(x + sx, y + sy) for sx, sy in suunnat]
 
naapurit(4, 7)  # Palauttaa: [(5, 7), (4, 8), (3, 7), (4, 6)] 
 

Dictionary

Dictionary (ei vakiintunutta suomenkielistä termiä) on kokoelma avain-arvo-pareja. Avaimen täytyy olla muuttumatonta (engl. immutable) tyyppiä, eli se ei saa olla lista tai toinen dictionary. Toteutuksesta johtuen dictionaryn avainten järjestyksestä ei voida olettaa mitään.

postinro = {'Matti': '00100', 'Liisa': '27300', 'Petteri': '99999'}
 
postinro['Liisa']      # Palauttaa: '27300'
postinro.keys()        # Palauttaa: ['Petteri', 'Matti', 'Liisa']
del postinro['Matti']  # postinro = {'Petteri': '99999', 'Liisa': '27300'}
 
def kirjaimia(sana):
    kirjaimet = {}
    for kirjain in sana:
        if kirjaimet.has_key(kirjain):
            kirjaimet[kirjain] += 1
        else:
            kirjaimet[kirjain] = 1
    return kirjaimet
 
kirjaimia('hellalla')  # Palauttaa: {'a': 2, 'h': 1, 'e': 1, 'l': 4}
 
# Tai sama kätevämmin käyttäen joukkoa ja generaattorilauseketta
# (näistä on kerrottu alempana)
sana = 'hellalla'
dict((letter, sana.count(letter)) for letter in set(sana))

Joukko (set)

Joukko kuuluu Pythonin tietotyyppeihin versiosta 2.4 lähtien. Kuten dictionaryssa, myöskään joukon alkioiden järjestys ei ole määrätty eikä duplikaatteja ole. Tietotyypin voima piilee sen tehokkuudessa muodostaa uusia joukkoja toisista matemaattisten operaatioiden avulla.

Joukko voidaan muodostaa mistä tahansa sarjamuotoisesta datasta set-funktion avulla. Seuraavassa joitakin esimerkkejä:

a = set('allas')                    # a = set(['a', 's', 'l'])
b = set(('k', 'a', 'l', 'l', 'e'))  # b = set(['a', 'k', 'e', 'l'])
 
a - b  # joukkojen a ja b erotus: set(['s'])
b - a  # joukkojen b ja a erotus: set(['k', 'e'])
a & b  # leikkaus (intersection): set(['a', 'l'])
a | b  # yhdiste (union):         set(['a', 's', 'e', 'k', 'l']) 
 

Käytännön esimerkkinä pollataan muutosta työntekijälistassa. Työntekijät tallennetaan tiedostoon ;-merkillä toisistaan erotettuna. Funktio päivittää listan nykyisistä työntekijöistä ja tulostaa mahdolliset muutokset aikaisempaan:

def paivita_tt_lista(tt_tiedosto):
    ent = set([tt for tt in file(tt_tiedosto, 'r').read().split(';')])
    nyk = set(tt_lista())  # palauttaa listan nykyisistä, ei näytetä tässä
 
    tulleet = nyk - ent
    menneet = ent - nyk
    if len(tulleet):
        print 'Uudet työntekijät: ' + ', '.join(tulleet)
    if len(menneet):
        print 'Lähteneet työntekijät: ' + ', '.join(menneet)
 
    file(tt_tiedosto, 'w').write(';'.join(nyk))

Joukon duplikaatittomuusominaisuus on hyvin kätevä erilaisissa algoritmeissa. Sitä voi käyttää näppärästi esimerkiksi selvitettäessä onko annetussa listassa duplikaatteja:

def kaikki_erilaisia(lista):
    return len(set(lista)) == len(lista) 
 
print kaikki_erilaisia([1,2,3,4]) # => True
print kaikki_erilaisia([1,1,2,3]) # => False 
 

Funktiot

Pythonissa funktiot määritellään avainsanan def avulla. Määrittelyn ensimmäinen rivi on muotoa

def funktion_nimi(argumenttilista):

jossa argumenttilista sisältää pilkuilla erotetut argumentit toisistaan. Niille voi antaa myös oletusarvoja, esim.

def talo(pinta_ala, kerroksia=1):

Tällöin ylläolevaa funktiota voidaan kutsua joko yhdellä tai kahdella argumentilla.

Joskus on tarpeen tehdä funktioita, joille annettavien argumenttien määrä voi vaihdella. Silloin argumenttilista saadaan talteen tähteä käyttäen:

def keskiarvo(*luvut):
    return float(sum(luvut)) / len(luvut)
 
print keskiarvo(2, 2, 4, 5)  # 3.25 
 

Paluuarvo voi olla mikä tahansa tai se voi myös puuttua kokonaan, jolloin paluuarvoksi jää None.

Vaikka def-lause näyttää hieman erilaiselta, kuin sijoituslause (a = b), ovat Pythonin funktiot kuitenkin olioita - ja def myös eräänlainen sijoituslause - siinä missä Python-maailman datakin. Funktioista voi siis luoda aliaksia vaikka seuraavaan tapaan:

average = keskiarvo
print average(2, 2, 4, 5)  # 3.25 
 

Todellisen voimansa tämä notaatio osoittaa ns. korkeamman asteen funktioiden kanssa - Pythonissa on nimittäin helppo muodostaa funktioita, jotka palauttavat toisen funktion:

def tee_potenssifunktio(eksponentti):
    def potenssiinkorotus(kantaluku):
        # huomaa, kuinka voimme viitata ulomman funktion
        # argumenttiin. Funktiot sidotaan aina siihen
        # leksikaaliseen (siis nimi-) ympäristöön, missä 
        # ne on määritelty; tätä sanotaan klosuuriksi.
 
        return kantaluku ** eksponentti
    
    # palautetaan määritelty funktio
    return potenssiinkorotus
 
# nyt voimme luoda eri potenssiinkorotusfunktioita näppärästi
nelioi = tee_potenssifunktio(2)       # ts, f(x) = x^2
kuutioi = tee_potenssifunktio(3)      # ts, f(x) = x^3
hyperkuutioi = tee_potenssifunktio(4) # ...
 
# seuraava laskee 5^4.
# sisäisesti kutsutaan sellaista potenssiinkorotus-funktiota,
# joka muistaa eksponentti-muuttujan arvoksi 4.
print hyperkuutioi(5) # tulostaa 625 
 

If-lause

Käskyjen ehdollinen suorittaminen on ohjelmoinnin peruspilareita. Pythonissa perustyökaluna on if-elif-else-kokonaisuus. Tässä esimerkkejä:

if a > 0:
    print 'Lukusi on positiivinen'
elif a < 0:
    print 'Lukusi on negatiivinen'
else:
    print 'Nollahan tuo'
 
if a % 2:
    print 'Pariton'
else:
    print 'Parillinen'

For-silmukka

For-silmukan käyttö poikkeaa monien muiden kielten vastaavasta. Pythonissa sillä iteroidaan sarjamuotoista dataa alkio kerrallaan. Merkkijonoa käsitellään kirjain kerrallaan, listaa ja monikkoa alkio kerrallaan jne. Toki perinteinen lukualueen läpikäynti onnistuu myös, esim. range()-funktion avulla.

# Tulostaa kirjaimet a, u, t ja o omille riveilleen:
for c in 'auto':
    print c
 
# Tulostaa luvut 3, 6 ja 9 omille riveilleen:
for x in [1, 2, 3]:
    print 3 * x
 
suom = ['Pohjoinen', 'Etelä']
engl = ['North', 'South']
for i in range(2):
    print '%d: %s - %s' % (i + 1, suom[i], engl[i])
 
# Tulostaa:
# 1: Pohjoinen - North
# 2: Etelä - South 
 

While-silmukka

While toimii yllätyksettömästi: silmukan suoritusta toistetaan niin kauan kuin annettu ehto on voimassa.

def laske_nollaan(n):
    while n >= 0:
        print n
        n -= 1

List comprehension

List comprehension (ei vakiintunutta suomenkielistä termiä) on tiivis ja tehokas tapa muodostaa sarjatyyppisestä muuttujasta (mm. merkkijono, lista tai monikko) uusi lista jonkin operaation avulla. List comprehension on muotoa [... for ... in ...] tai [... for ... in ... if ...], jos halutaan rajoittaa uuteen listaan tulevia alkioita jollain ehdolla.

[c for c in 'auto']             # ['a', 'u', 't', 'o']
[c for c in 'auto' if c != 't'] # ['a', 'u', 'o']
['X' * n for n in range(1, 5)]  # ['X', 'XX', 'XXX', 'XXXX']
 
def numeroiden_summa(s):
    return sum([int(x) for x in str(s) if x in '0123456789'])
 
numeroiden_summa('3 ja 4')      # Palauttaa 7
numeroiden_summa(127)           # Palauttaa 10 
 

Iteraattori

Kuten edellä for-silmukan yhteydessä tuli ilmi, Pythonissa sarjamuotoista dataa voidaan helposti käydä läpi (iteroida) alkio kerrallaan. Tämä tapahtuu nk. iteraattorien avulla.

For-silmukkakin toimii kutsumalla iter-funktiota ja antamalla sille argumenttina sarjamuotoista dataa sisältävän olion. Kyseinen funktio palauttaa iteraattorin. Tämän jälkeen for kutsuu iteraattorin next-metodia silmukan jokaisen suorituskerran alussa niin kauan, kunnes kaikki arvot on käyty läpi.

Iteraattori luodaan "manuaalisesti" seuraavalla tavalla:

>>> s = 'xy'
>>> it = iter(s)
>>> it
<iterator object at 0x00B12930>
>>> it.next()
'x'
>>> it.next()
'y'
>>> it.next()
 
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in -toplevel-
    it.next()
StopIteration

Ylläoleva esimerkki ei ole sinällään hyödyllinen; se vain havainnollistaa iteraattorien toimintaa kielessä. For-silmukan käyttö peittää yksityiskohdat, eli ylläoleva menisi sen avulla: for a in 'xy':, jolloin a kävisi kaikki arvot ('x' ja 'y') läpi. Iteraattorien idea on kuitenkin syytä ymmärtää, sillä itse luodut iteraattorit ovat joskus erittäin käyttökelpoisia. Tästä on esimerkki alempana.

Generaattori

Generaattorilla voidaan luoda iteraattori. Generaattori määritellään samaan tapaan kuin tavallinen funktio, mutta return-lauseen sijasta käytetään yield-lausetta. Generaattoria kutsumalla saadaan paluuarvona iteraattori. Lyhyesti sanottuna generaattori määrittelee tavan, jolla datajoukko käydään läpi. Generaattori voi määritellä itse datajoukon tai saada sen argumenttina.

Yksi yield-lauseen suorituskerta vastaa iteraattorin next-metodin kutsumista. Erona return-lauseeseen on se, että generaattorin sisäisten muuttujien arvot säilyvät niin kauan, kunnes iteraattorin suoritus päättyy. Käytännöllisenä ja yksinkertaisena esimerkkinä toimikoot Fibonaccin sarjan n:n ensimmäisen alkion iterointi:

def fib(n):
    a, b = 0, 1
    while n > 0:
        a, b = b, a + b
        n -= 1
        yield a
 
# Tulostaa luvut 1, 1, 2, 3 ja 5 omille riveilleen:
for f in fib(5):
    print f

Jokainen generaattorin kutsu (esim. fib(5), fib(80)) palauttaa itsenäisen iteraattorin, eivätkä mahdolliset samasta generaattorista luodut ja samaan aikaan olemassa olevat iteraattorit sotke toisiaan. Iteraattorilla saadaan käytyä läpi mahdollisesti suurikin datajoukko ilman, että sitä tarvitsee luoda muistiin kerralla. Ylläoleva tapa tulostaa Fibonaccin sarjan alkioita säästää siis muistia huomattavasti verrattuna funktioon, joka loisi ja palauttaisi listan halutuista Fibonaccin alkioista. Viidellä alkiolla ero on merkityksetön, mutta generaattorin avulla voi käydä läpi vaikka miljoonia alkioita, eikä muistia kulu yhtään enempää. No, miljoonannen alkion kohdalla lukujen pituudet olisivat jo aikamoisia.

Generaattorilauseke

Generaattorilauseke (generator expression) on list comprehensionin syntaksia muistuttava (hakasulkeiden sijasta käytetään kaarisulkeita) keino luoda iteraattori. Generaattorilausekkeita kannattaa suosia list comprehensionin sijasta, ellei ole erityistä tarvetta säilyttää koko listaa muistissa.

Käytetään ylempänä määriteltyä Fibonacci-generaattoria ja tulostetaan tuhannen ensimmäisen alkion joukosta ne, jotka ovat jaollisia seitsemällä. Esimerkin koodin muistinkulutus on hyvin vähäistä, sillä se sisältää vain kaksi iteraattoria (fib ja fib_seven), eikä luo yhtään isoa tietorakennetta:

fib_seven = (x for x in fib(1000) if x % 7 == 0)
for f in fib_seven:
    print f

Generaattorilauseke voidaan sijoittaa myös ilman ylimääräisiä sulkuja funktiokutsun sisälle. Tulostetaan edellisen esimerkin alkioiden summa:

print sum(x for x in fib(1000) if x % 7 == 0)

Käytännön esimerkkinä näytetään tarkistusnumeron liittäminen viitenumeron perään:

def viitteen_tarkistusnro(s):
    return s + str(-sum(int(x)*[7,3,1][i%3] for i, x in enumerate(s[::-1])) % 10)
 
print viitteen_tarkistusnro('1234')   # Tulostaa '12344' 
 

Puretaan tuon toiminta auki:

  1. s[::-1] kääntää merkkijonon '1234' muotoon '4321'
  2. enumerate-funktio iteroi merkkijonoa indeksien kanssa: (0, '4'), (1, '3'), (2, '2') ja (3, '1')
  3. [7, 3, 1][i%3]-osa antaa kertoimeksi vuorotellen numeroita 7, 3, 1, 7, 3, 1, 7, 3, 1,...
  4. sum-funktio laskee summan 7*4 + 3*3 + 1*2 + 7*1 = 46
  5. -46 % 10 = 4, joten palautetaan merkkijono '1234' + '4' eli '12344'

Ehtolauseke

Ehtolauseke (conditional expression) lisättiin Pythoniin versiosta 2.5 alkaen. Sillä voi korvata monirivisen if-else-rakenteen yksirivisellä lausekkeella. Ehtolauseke voi heikentää koodin luettavuutta, joten sen käyttöä ei pidä liioitella.

Lauseke on muotoa x if a else y. Lausekkeen arvoksi tulee x, jos a on tosi, muuten y. Ehtolauseke evaluoidaan laiskasti, eli a:sta riippuen vain x tai y evaluoidaan.

Lausekkeen lopullisesta syntaksista käytiin vilkasta keskustelua python-devissä ja comp.lang.python-alueella, kunnes kielen luoja Guido van Rossum valitsi nykyisen muodon.

Ehtolauseke:

luokitus = 'pariton' if x % 2 else 'parillinen'

Sama toteutettuna perinteisemmällä if-else-rakenteella:

if x % 2:
    luokitus = 'pariton'
else:
    luokitus = 'parillinen'

Syöttö ja tulostus

Näppäimistöltä lukeminen tehdään yleisimmin raw_input-funktiolla. Sen paluuarvo on merkkijono, josta viimeinen rivinvaihto on poistettu.

>>> vuosi = int(raw_input('Mikä vuosi nyt on? '))
Mikä vuosi nyt on? 2005
>>> vuosi
2005

Tulostus tapahtuu print-komennolla. Usein käytetään %-operaattoria sisällyttämään tulostukseen muuttujien sisältöä. Yleisimmin käytetään seuraavia: %s = merkkijono, %d = kokonaisluku ja %f = liukuluku. Tulostusta voi muotoilla monella tavalla, samoin kuin esim. C-kielen printf-funktiossa.

>>> a, b = 6, 4
>>> print '%d / %d = %.3f' % (a, b, float(a) / b)
6 / 4 = 1.500
>>> henk = [['Ari', 1952], ['Outi', 1970]]
>>> for nimi, synt in henk:
... 	print '%s syntyi v. %d' % (nimi, synt)
... 	
Ari syntyi v. 1952
Outi syntyi v. 1970

Tiedostojen käsittely

Tiedostoihin päästään käsiksi file-funktion avulla. Se palauttaa tiedosto-objektin, joka sisältää monia hyödyllisiä metodeja, joista käytetyimmät ovat read, readline, readlines, write, writelines ja close. Seuraava esimerkkikoodi poimii IRC-lokista "python"-tekstin sisältävät rivit ja tallettaa ne erilliseen tekstitiedostoon:

loki = file('irc_log.txt', 'r')  # avataan lukemista varten
tulos = file('python.txt', 'w')  # avataan kirjoitusta varten
 
n = 1
for rivi in loki:
    if 'python' in rivi.lower():
        tulos.write('Rivi %d: %s' % (n, rivi))
    n += 1
 
loki.close()
tulos.close()

Luokat

Olio-ohjelmoinnilla on suuri merkitys varsinkin laajojen ohjelmistojen toteuttamisessa. Python tarjoaa siihen erinomaiset työkalut, mutta tässä artikkelissa raapaistaan vain hieman pintaa.

Alla olevassa esimerkissä määritellään luokka Suorakulmio. Luokalle voidaan määritellä muodostin (konstruktori, engl. constructor), joka on aina nimeltään __init__ ja joka suoritetaan luotaessa luokasta uusi olio, ilmentymä (engl. instance), esimerkissämme sk. Tässä tapauksessa se tarvitsee kaksi argumenttia; suorakulmion leveyden ja korkeuden. Jokainen luokan jäsenfunktio saa aina automaattisesti self-argumentin, joka on viittaus sillä hetkellä käsiteltävään olioon.

class Suorakulmio:
    def __init__(self, leveys, korkeus):
        self.leveys = leveys
        self.korkeus = korkeus
    def ala(self):
        return self.leveys * self.korkeus
    def piirra(self, merkki):
        for i in range(self.korkeus):
            print self.leveys * merkki
 
sk = Suorakulmio(5, 2)
print 'Ala =', sk.ala()
sk.piirra('X')
 
# Tulostus:
# Ala = 10
# XXXXX
# XXXXX 
 

Näytetään vielä miten perintä toimii Pythonissa. Luodaan uusi luokka Nelio periyttämällä se Suorakulmiosta. Ainoana muutoksena on yksi muodostimelle annettava argumentti kahden sijaan. Nelion muodostin toimii tässä tapauksena vain välikätenä eli se kutsuu Suorakulmion muodostinta tekemättä itse mitään. Huomaa olion välitys self-muuttujan avulla.

class Nelio(Suorakulmio):
    def __init__(self, sivu):
        Suorakulmio.__init__(self, sivu, sivu)
 
nel = Nelio(3)
print 'Ala =', nel.ala()
nel.piirra('O')
 
# Tulostus:
# Ala = 9
# OOO
# OOO
# OOO 
 

Poikkeukset

Aloitteleva Python-koodaaja törmää todennäköisesti hyvin pian ensimmäiseen virhetilanteeseen, joka näkyy käsittelemättömänä poikkeuksena. Koodissa ollut huolimattomuusvirhe a = float('3,4') saa tulkin keskeyttämään suorituksen ja tulostamaan virheilmoituksen:

Traceback (most recent call last):
  File "C:\py\esim.py", line 1, in ?
    a = float('3,4')
ValueError: invalid literal for float(): 3,4

Virhetilanteita hallitaan try-except-rakenteella seuraavaan tapaan:

try:
    f = open('versiotiedot.txt')
    versio = int(f.readline().strip())
    print 'Versionumero:', versio
except IOError, (nro, viesti):
    print "I/O-virhe versiotiedostoa luettaessa (%s): %s" % (nro, viesti)
except ValueError:
    print "Versiotiedoston ensimmäinen rivi on virheellinen"
except:
    print "Odottamaton virhe versiotiedostoa luettaessa"

Virhetilanteen mahdollisesti aiheuttava koodi sijoitetaan try-lohkon sisään. Jos sen aikana syntyy poikkeus, sitä yritetään käsitellä seuraavissa except-lohkoissa. except-rivillä voidaan määritellä erikseen, mikä tai mitkä poikkeukset halutaan ottaa kiinni. Pelkkä except ottaa kaikki poikkeukset kiinni, ja sitä käytetään usein viimeisenä varmistuksena.

Säikeet

Säikeitä (engl. thread) käytetään suorittamaan useita rinnakkaisia toimintoja yksittäisen ohjelman sisällä. Esimerkiksi internetistä voi hakea monta sivua samaan aikaan, jos haut käynnistetään eri säikeissä.

Seuraavassa on lyhyt esimerkki säikeistä ja niiden keskinäisestä kommunikoinnista:

# -*- coding: latin1 -*-
import threading
from time import sleep, time
 
def tulosta(s):
    print '%.f s - %s' % (time() - alkuhetki, s)
    
def silmukka(i, event):
    while not event.isSet():
        tulosta('Silmukka nro %d' % i)
        sleep(2)
    tulosta('Silmukka nro %d lopettaa' % i)
 
# Event muille säikeille kommunikointia varten
event = threading.Event()
 
alkuhetki = time()
tulosta('Alku')
th1 = threading.Thread(target=silmukka, args=(1, event))
th1.start()
sleep(1)
th2 = threading.Thread(target=silmukka, args=(2, event))
th2.start()
 
# Annetaan luotujen säikeiden juosta hetki yksinään
sleep(7)
 
# Lopetusviesti säikeille eventin avulla
event.set()
tulosta('Lopetusviesti')
 
# Odotetaan kunnes molemmat luodut säikeet ovat lopettaneet
th1.join()
th2.join()
tulosta('Valmis')

Tulostus:

0 s - Alku
0 s - Silmukka nro 1
1 s - Silmukka nro 2
2 s - Silmukka nro 1
3 s - Silmukka nro 2
4 s - Silmukka nro 1
5 s - Silmukka nro 2
6 s - Silmukka nro 1
7 s - Silmukka nro 2
8 s - Lopetusviesti
8 s - Silmukka nro 1 lopettaa
9 s - Silmukka nro 2 lopettaa
9 s - Valmis

Automaattinen roskienkeruu

Pythonin automaattinen roskienkeruu auttaa ohjelmoijaa muistinkäytön hallinnassa. Python pitää kirjaa yksittäiseen objektiin kohdistuvista viittauksista ja jossain vaiheessa, kun objektiin ei ole enää yhtäkään viittausta (eli siihen ei voi enää päästä koodissa käsiksi mitenkään), objektin viemä muisti vapautetaan. Otetaan esimerkki:

def laske(a, b):
    summa = a + b
    tulos = '%d + %d = %d' % (a, b, summa)
    return tulos
 
lasku_str = laske(2, 5)
print lasku_str  # Tulostaa: 2 + 5 = 7
lasku_str = ''

Ylläolevan esimerkin funktiossa laske luodaan kaksi uutta muuttujaa: summa ja tulos. Funktiosta poistumisen jälkeen summaan ei enää ole viittauksia, joten sen viemä muisti asetetaan vapautettavaksi (itse vapautus tapahtuu sitten, kun Python sen päättää tehdä). Muuttujan tulos suhteen tilanne on kuitenkin erilainen. Koska se - tai oikeammin merkkijono-objektiin kohdistuva viittaus - palautetaan funktion ulkopuolelle (lasku_str), sitä voidaan käyttää myöhemminkin. Vasta kun lasku_str asetetaan viittaamaan tyhjään merkkijonoon esimerkin viimeisellä rivillä, aiemmin viittauksen kohteena olleen merkkijonon muistialue voidaan vapauttaa muuhun käyttöön.

Lisätietoa verkossa

Henkilökohtaiset työkalut