kevytspesifikaatio

Kurssi: 581362 Ohjelmointikielten periaatteet, kevät 2011 (4 op)

Työryhmä

Sisältö

  1. Kielen taustaa ja miniesimerkki
  2. Alkiorakenne
  3. Tunnusten näkyvyysalueet
  4. Kontrollin ja/tai laskennan ohjaus
  5. Perustietotyypit
  6. Laskennan kapselointi
  7. Datan kapselointi
  8. Yhteenveto
  9. Lähteet

Johdanto

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.

1. Kielen taustaa ja miniesimerkki

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.

  1. 1.1.Historia
  2. 1.2.Python perustietoa
  3. 1.3.Käyttökohteet
  4. 1.4.Fibonacci esimerkki

1.1. Historia

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]

1.2. Python perustietoa

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).

1.3. Käyttökohteet

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ä.

1.4. Fibonacci esimerkki

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.

2. Alkiorakenne

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.

  1. 2.1.Tunnukset
  2. 2.2.Varatut sanat, avainsanat
  3. 2.3.Literaalivakiot
  4. 2.4.Erottimet, sisennykset, rivinvaihdot
  5. 2.5.Arvio kieleen valittujen ratkaisujen eduista ja haitoista

2.1. Tunnukset

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

Lataa esimerkki tästä

2.2. Varatut sanat

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


2.3. Literaalivakiot

Python tukee seuraavia literaalivakioita [4]:

Numeerisista literaaleista on hyvä mainita vielä se, että ne eivät sisällä etumerkkiä. Esimerkiksi merkintä -1 koostuu yksipaikkaisesta operaatiosta - ja literaalista 1.

2.4. Erottimet, sisennykset, rivinvaihdot

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")

2.5. Arvio kieleen valittujen ratkaisujen eduista ja haitoista

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.

3. Tunnusten näkyvyysalueet

Tässä luvussa kerromme miten ja missä itse määritellyt rakenteet ovat käytettävissä.

  1. 3.1.Lohkorakenne
  2. 3.2.Sidonta
  3. 3.3,Esimerkki tunnusten näkyvyydestä, lohkorakenteista ja sidonnasta
  4. 3.4.Sisäkkäiset nimiavaruudet ja funktiot
  5. 3.5.Ensimmäisen, toisen ja kolmannen luokan arvo
  6. 3.6.Arvio kieleen valittujen ratkaisujen eduista ja haitoista

3.1. Lohkorakenne

Pythonissa lohkoksi kutsutaan koodinpätkää, joka suoritetaan yhtenä yksikkönä. Seuraavia rakenteita kutsutaan lohkoiksi: [6]

Kuten 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.

3.2. Sidonta

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).

3.3 Esimerkki tunnusten näkyvyydestä, lohkorakenteista ja sidonnasta

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
  

3.4 Sisäkkäiset nimiavaruudet ja funktiot

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

3.5. Ensimmäisen, toisen ja kolmannen luokan arvo

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]

3.6. Arvio kieleen valittujen ratkaisujen eduista ja haitoista

Lohkojen pakotettu sisentäminen on hyvä asia, koodin luettavuus lisääntyy ja se myös pakottaa kaikki kirjoittamaan koodia samalla tavalla eli sisentäen.

4. Kontrollin ja/tai laskennan ohjaus

Tässä luvussa kerromme miten perustoiminnallisuudet ilmaistaan. Esittelemme millaisia mahdollisuuksia Pythonissa on valintaan, iteraatioon ja rekursioon liittyvissä asioissa.

  1. 4.1.Valinta
  2. 4.2.Toisto eli iteraatio
  3. 4.3.Rekursio
  4. 4.4.Arvio kieleen valittujen ratkaisujen eduista ja haitoista

4.1. Valinta

4.1.1. if-lause

if-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].

4.2. Toisto eli iteraatio

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.

4.2.1. for-lause

for-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 12
Seuraavassa 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

4.2.2. while-lause

while-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

4.2.3. continue & break-lauseet

continue-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].

4.3. Rekursio

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)

4.4. Arvio kieleen valittujen ratkaisujen eduista ja haitoista

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.

5. Perustietotyypit

millaisilla arvoilla tai "datalla" pelataan

  1. 5.1.Perustyypit ja arvoalueet
  2. 5.2.Vahva tyypitys ~ heikko tyypitys
  3. 5.3.Staattinen tyypitys ~ dynaaminen tyypitys

5.1 Perustyypit ja arvoalueet

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}

5.2. Vahva tyypitys ~ heikko tyypitys

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ä.

5.3. Staattinen tyypitys ~ dynaaminen tyypitys

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

6. Laskennan kapselointi

miten toiminnallisuuksia abstrahoidaan ja niistä rakennetaan uusia toiminnallisuuksia?

  1. 6.1.Nimetyt aliohjelmat ja/tai alifunktiot
  2. 6.2.Parametrivälitys
  3. 6.3.Suoritusaikaisiin virheisiin varautumisen välineet – poikkeukset
  4. 6.4.Generaattorit
  5. 6.5.Miten kieli tukee rinnakkaista laskentaa
  6. 6.6.Tyypillisiä ohjelma-arkkitehtuureja

6.1. Nimetyt aliohjelmat ja/tai alifunktiot

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]

6.2 Parametrivälitys

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

6.3. Suoritusaikaisiin virheisiin varautumisen välineet – poikkeukset

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ä:

0
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
  
-1
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
  
yks
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'
  
ctrl + c
Anna positiivinen luku:
^CJokin muu meni pieleen
Tämä printataan aina
  

6.4. Generaattorit (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

6.5. Miten kieli tukee rinnakkaista laskentaa

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.

6.6 Tyypillisiä ohjelma-arkkitehtuureja

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.

7. Datan kapselointi

  1. 7.1.Rakenteiset ja kapseloidut tyypit
  2. 7.2.Oliot ja luokat
  3. 7.3.Arvo- ja viitesemantiikka

7.1. Rakenteiset ja kapseloidut tyypit

7.1.1. Moduulit

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

7.1.2. Paketit (Packages) ja alimoduulit (submodules)

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ää.

7.2. Oliot ja luokat

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]

7.2.1. Self

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]

7.2.2. Luokkaoliot

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)

7.2.3. Ilmentymäoliot

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]

7.2.4. Metodioliot

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.

7.2.5. Periytyminen

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.

7.2.6. Moniperiytyminen

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

7.3. Arvo- ja viitesemantiikka

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.

8. Yhteenveto

Python on voimakas dynaaminen ohjelmointikieli, jolla on monia käyttökohteita. Sen tärkeimpiä ominaisuuksia ovat

[19]

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.

Lähteet

  1. http://docs.python.org/release/3.0.1/reference/lexical_analysis.html#keywords
  2. http://docs.python.org/release/3.0.1/reference/lexical_analysis.html#identifiers-and-keywords
  3. http://en.wikipedia.org/wiki/History_of_Python
  4. http://docs.python.org/py3k/reference/lexical_analysis.html#literals
  5. http://docs.python.org/release/2.2.3/whatsnew/node9.html
  6. http://docs.python.org/py3k/reference/executionmodel.html#naming-and-binding
  7. http://python-history.blogspot.com/2009/02/first-class-everything.html
  8. Python v3.2 documentation — 4. More Control Flow Tools
  9. Python v3.2 documentation — 7. Compound statements
  10. Python v3.2 documentation — 27.1. sys — System-specific parameters and functions
  11. Neopythonic — Tail Recursion Elimination
  12. Python v3.2 documentation — 5. Built-in Types
  13. Python v3.2 documentation — 3. Data model
  14. GIL - Global Interpeter Lock
  15. Can't we get rid of the Global Identifier Lock?
  16. http://docs.python.org/release/3.1.3/tutorial/controlflow.html#defining-functions
  17. Python v3.2 documentation » Python Frequently Asked Questions » How do I write a function with output parameters (call by reference)?
  18. Python v3.2 documentation » What’s New in Python » Multiple Inheritance: The Diamond Rule
  19. Python - About
  20. copy — Shallow and deep copy operations
  21. http://docs.python.org/py3k/tutorial/classes.html#tut-private
  22. http://docs.python.org/py3k/tutorial/classes.html#tut-private
  23. http://docs.python.org/py3k/tutorial/classes.html#random-remarks
  24. http://docs.python.org/py3k/tutorial/classes.html#class-definition-syntax
  25. http://www.ibiblio.org/g2swap/byteofpython/read/self.html
  26. http://docs.python.org/py3k/tutorial/classes.html#method-objects
  27. http://docs.python.org/py3k/tutorial/classes.html#class-objects
  28. http://docs.python.org/py3k/tutorial/classes.html#instance-objects
  29. Python v3.2 documentation » The Python Tutorial » Generators
  30. Python scopes and namespaces
  31. Packages
  32. Modules
  33. Wikipedia - Python usage
  34. Type safety
  35. Strong typing
  36. Duck typing
  37. Why is Python a dynamic language and also strongly typed language?

Copyright © 2011 Juhani Åhman, Juha Louhiranta, Matti Nauha, Timo Lehto

import antigravity ;)