Ohjaus 1: Python perusteet ja PythonAnywhere

Näissä tehtävissä tutustutaan Pythonin perusteisiin ja viedään ensimmäinen Flask-sovellus palvelimelle. Tehtävät nojaavat kurssin omaan Python-luentomateriaaliin; kunkin tehtävän kohdalla on linkki siihen osioon, jossa tarvittava asia käsitellään.

Myös tehtävien vanha versio on edelleen saatavilla.

1. Valmistelut

Opettele käyttämään interaktiivista tulkkia ja sen help-komentoa. Se on nopein tapa selvittää, mitä jokin funktio oikeasti tekee.

$ python3
Python 3.12.3 (main, Jan 17 2026, 10:05:12) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> help
Type help() for interactive help, or help(object) for help about object.
>>> import json
>>> help(json.dumps)
>>> dir(json)
>>> exit()

2. Hello World

  1. Luo haluamallasi editorilla (esimerkiksi Visual Studio Code tai Notepad++) tiedosto hello.py.

    Editorin täytyy tallentaa tiedosto UTF-8-muodossa ilman BOM-merkintää ja unix-muotoisin rivinvaihdoin (LF), ei windows-muotoisin (CRLF). VS Code näyttää molemmat oikeassa alanurkassa. Notepad++:ssa rivinvaihdon tyyppi näkyy alalaidassa ja sen voi vaihtaa valinnalla Edit | EOL Conversion | Unix (LF).

  2. Kopioi ohjelmasi pohjaksi seuraava koodi:

    #!/usr/bin/env python3
    
    print("Hello world")

    Ensimmäinen rivi on unix-ympäristön shebang-rivi, joka kertoo, millä tulkilla ohjelma suoritetaan. Muoto /usr/bin/env python3 etsii tulkin polusta, joten sama rivi toimii sekä omalla koneellasi että palvelimella. Windows-ympäristössä riviä ei tarvita, mutta se ei myöskään haittaa.

    Erillistä # -*- coding: utf-8 -*- -riviä ei tarvita: Python 3 olettaa lähdekoodin olevan UTF-8:aa. Riittää, että editorisi tosiaan tallentaa UTF-8:na.

  3. Kokeile ohjelman toimintaa komentoriviltä. Siirry samaan kansioon, jossa hello.py on:

    $ python3 hello.py      # Linux ja macOS
    > py hello.py           # Windows

3. Tietorakenteet

Pythonissa lohkot merkitään sisentämällä. Sama sisennystaso tarkoittaa samaa lohkoa. Varmista, että editorisi korvaa tabulaattorit neljällä välilyönnillä, ja käytä sisennyksenä neljää välilyöntiä. Notepad++:ssa asetus on kohdassa Settings | Preferences | Language.

Lisää ohjelmaasi seuraava tietorakenne. Kyseessä on lista, jonka alkiot ovat sanakirjoja — kts. Sisäkkäiset tietorakenteet.

data = [
    {
        "nimi": "Kalle",
        "ammatti": "Yliopistonopettaja",
        "syntymävuosi": 1980,
        "palkka": 2000,
    },
    {
        "nimi": "Ville",
        "ammatti": "Opiskelija",
        "syntymävuosi": 1995,
        "palkka": None,
    },
    {
        "nimi": "Maija",
        "ammatti": "Professori",
        "syntymävuosi": 1970,
        "palkka": 3000,
    },
]

Toteuta seuraavat asiat:

4. Poikkeusten käsitteleminen

Kts. Poikkeukset. Nappaa aina se poikkeustyyppi, jonka osaat käsitellä:

try:
    palkka = int(henkilo["palkka"])
except (KeyError, TypeError, ValueError):
    palkka = 560

Älä kirjoita paljasta except:-lauseketta. Se nappaa myös kirjoitusvirheet nimissä ja Ctrl+C-keskeytyksen, ja piilottaa tulkin oman virheilmoituksen. Jos joudut nappaamaan laajasti, kirjoita except Exception as virhe:.

Kun haluat nähdä, mikä virhe todella tapahtui, tulosta poikkeus ja tarvittaessa koko pinolistaus. Tämä korvaa vanhoissa esimerkeissä näkyvän sys.exc_info()-kikkailun:

import traceback

try:
    int("foo")
except Exception as virhe:
    print(f"Virhe: {type(virhe).__name__}: {virhe}")
    traceback.print_exc()      # koko pinolistaus rivinumeroineen

Palvelinsovelluksessa sama kirjataan lokiin:

import logging

try:
    kasittele()
except Exception:
    logging.exception("Käsittely epäonnistui")

Omat poikkeusluokat

Omat poikkeukset periytetään luokasta Exception:

# yksinkertaisin mahdollinen
class OmaVirheFoo(Exception):
    pass


# tähän versioon voi liittää viestin ja virhekoodin
class OmaVirhe(Exception):
    def __init__(self, message="oma moka", error_code=0):
        super().__init__(message)
        self.error_code = error_code
        self.message = message

    def __str__(self):
        return f"{self.message} (virhekoodi {self.error_code})"


try:
    raise OmaVirhe("kauhea moka", 666)
except OmaVirhe as virhe:
    print("omavirhe!", virhe)

try:
    raise OmaVirheFoo()
except OmaVirheFoo as virhe:
    print("omavirhefoo!", virhe)

5. Tiedostojen käsitteleminen ja JSON

Kts. Serialisointi ja Tiedostojen käsitteleminen.

6. PythonAnywhere ja WSGI

7. Flask omalla koneella

Kts. Virtuaaliympäristö ja pip. Ohjeita löytyy myös Flask Tutorial in Visual Studio Code -sivulta.

8. PythonAnywhere ja Flask

9. users.jyu.fi, CGI ja Flask

users.jyu.fi-palvelimella Flaskia ei voi ajaa suoraan, vaan se on suoritettava CGI-ohjelmana.

Virtuaaliympäristön asennus palvelimelle

Siirry cgi-bin/ties4080/-kansioon ja luo ympäristö:

$ python3 -m venv venv
$ . venv/bin/activate                  # bash
$ source venv/bin/activate.csh         # csh ja tcsh
(venv) $ pip install --upgrade pip
(venv) $ pip install Flask Flask-WTF
(venv) $ deactivate

Ympäristössä olevia ohjelmia suoritetaan asettamalla shebang-rivi osoittamaan ympäristön omaan tulkkiin. Tässä nimenomaisessa tapauksessa polku kirjoitetaan kokonaan eikä käytetä env-muotoa, koska www-palvelimen on löydettävä juuri tämän virtuaaliympäristön tulkki:

#!/home/oma_tunnus/public_html/cgi-bin/ties4080/venv/bin/python

Huomaa, että polku on eri kuin halava- ja jalava-koneissa: web-koodia suorittava users.jyu.fi näkee kotihakemistosi polussa /home/oma_tunnus/, kun taas kirjautuessasi halavaan tai jalavaan sama hakemisto on /wwwhome/home/oma_tunnus/. Shebang-riville tulee aina users.jyu.fi-muotoinen polku.

Ensimmäinen CGI-ohjelma

Rivinvaihdot (CRLF vs. LF) ja BOM

Seuraava virhe tarkoittaa, että rivinvaihdot ovat DOS-tyyppisiä vaikka pitäisi olla unix-tyyppisiä:

/usr/bin/env: 'python3\r': No such file or directory

Sama vika voi näkyä myös muodossa ./hello.cgi: no such file or directory, ja selaimessa se näkyy aina Internal Server Error -virheenä.

Rivinvaihdot voi tarkistaa file-komennolla. Merkintä with CRLF line terminators kertoo, että ne ovat väärin:

[tunnus@halava ties4080]$ file hello.cgi oma.py
hello.cgi: a /usr/bin/env script, ASCII text executable, with CRLF line terminators
oma.py:    Python script, UTF-8 Unicode text executable, with CRLF line terminators

Toinen tapa on cat -e tiedosto: jos rivien lopussa on pelkkä $, kaikki on kunnossa; ^M$ tarkoittaa vääriä rivinvaihtoja. Korjaaminen onnistuu avaamalla tiedosto nano-editorilla -u-parametrin kera ja tallentamalla se:

$ nano -u hello.cgi

Varmista myös, ettei tiedoston alussa ole BOM-merkintää. Se aiheuttaa virheen:

Exec format error. Binary file not executable.

BOMin näkee esimerkiksi less-komennolla tai komennolla head -c 3 tiedosto | xxd: BOM näkyy tavuina ef bb bf.

Flask CGI-ohjelmana

Mitä tapahtuu, kun sivua pyydetään:

  1. WWW-palvelin suorittaa flask.cgi-tiedoston sen ensimmäisellä rivillä kerrotulla tulkilla eli virtuaaliympäristön Pythonilla.
  2. Sovellus tuo oma.py-tiedoston sisällön (from oma import app as application). Jos siinä on syntaksivirhe, virhe näytetään sivulla.
  3. oma.py:ssä luodaan Flask-sovellus (app = Flask(__name__)).
  4. CGIHandler suorittaa sovelluksen CGI-rajapinnan kautta.
  5. Flask valitsee @app.route("/")-merkinnän perusteella funktion hello_world(), joka asettaa mediatyypin ja palauttaa tekstin.

10. Laskuri

Onnistuuko laskurin rakentaminen? Kokeillaan. Kts. Näkyvyysalueet ja global.

Älä käytä globaaleja muuttujia Flask-sovelluksessa. Globaali muuttuja on aina prosessikohtainen:

Kts. Global variables in Flask.

Kunnollisen laskurin rakentaminen edellyttää joko sessioiden ja evästeiden käyttämistä tai sovelluksen tilan tallentamista HTML-dokumentin rakenteeseen. Näihin palataan myöhemmin.

11. Querystring

Sivun osoitteeseen voidaan lisätä parametreja querystringinä:

https://osoite.example/sivu?parametri=arvo&parametri2=arvo1&parametri2=arvo2

Polun perään lisätään ?-merkki, jonka jälkeen luetellaan avaimia ja arvoja &-merkillä eroteltuina. Sama avain voi esiintyä useita kertoja.

Näitä koodauksia tarvitaan vasta tulevissa tehtävissä. Nyt riittää, että osataan lukea annetut parametrit ohjelman käyttöön.

Parametreihin päästään käsiksi Flaskin Request-oliolla. Samalla tavalla käsitellään myöhemmin myös lomakkeiden lähettämät tiedot.

from flask import request

Request-olion tärkeimmät ominaisuudet:

request.args on MultiDict, joka toimii kuten tavallinen sanakirja, mutta palauttaa get-metodilla vain ensimmäisen arvon, jos avaimelle on annettu useampia. Kaikki arvot saa getlist-metodilla.

nimi = request.args.get("nimi", "Tuntematon")
arvosanat = request.args.getlist("arvosana")
kurssit = request.args.getlist("kurssi")

Tehtävä

Tee ohjelma, joka osaa tulostaa seuraavan querystringin sisällön:

http://127.0.0.1:5000/query?nimi=Malli%20Henkil%C3%B6&arvosana=5&arvosana=3
    &arvosana=2&kurssi=Helppo+kurssi&kurssi=Vaikea+kurssi&kurssi=%C3%84ll%C3%B6+kurssi

Vaihda mediatyypiksi text/plain, niin saat tulosteen näyttämään tältä:

Henkilön nimi : Malli Henkilö
Helppo kurssi 5
Vaikea kurssi 3
Ällö kurssi 2
Keskiarvo : 3.33

Keskiarvo esitetään kahdella desimaalilla. Käytä muotoilua f"{keskiarvo:.2f}", joka näyttää aina kaksi desimaalia, tai round-funktiota, joka pyöristää itse luvun.

Muuttele parametreja ja varmista, että ohjelmasi toimii, vaikka parametreja ei annettaisi lainkaan tai kursseja ja arvosanoja olisi eri määrä:

Saatuasi ohjelman toimimaan omalla koneellasi siirrä se PythonAnywhere-palveluun ja varmista toiminta siellä.

Malliratkaisun lähdekoodi

12. Lisätietoa

Käyttäjien kommentit

Kommentoi Lisää kommentti