├── 01a_Ensimmäinen_ohjelma.md ├── 01b_Versionhallinnan_käyttöönotto.md ├── 02_Muuttujat_ja_vuorovaikutteiset_ohjelmat.md ├── 03_Valintarakenne.md ├── 04_While-toistorakenne.md ├── 05_Listarakenne_ja_for-toistorakenne.md ├── 06_Funktio.md ├── 07_Monikko,_joukko_ja_sanakirja.md ├── 08_Relaatiotietokannan_käyttö.md ├── 09_Olio-ohjelmoinnin_lähtökohdat.md ├── 10_Assosiaatio.md ├── 11_Periytyminen.md ├── 12_Ulkoisen_rajapinnan_käyttö.md ├── 13_Taustapalvelun_ja_rajapinnan_rakentaminen.md ├── English ├── 01a_First_Program.md ├── 01b_Deployment_of_Version_Control.md ├── 02_Variables_and_Interactive_Programs.md ├── 03_Conditional_Structures.md ├── 04_While_Loops.md ├── 05_List_Structures_and_Iterative_Loops.md ├── 06_Functions.md ├── 07_Tuple,_Set,_and_Dictionary.md ├── 08_Using_Relational_Databases.md ├── 09_Fundamentals_of_Object-Oriented_Programming.md ├── 10_Association.md ├── 11_Inheritance.md ├── 12_Using_External_Interfaces.md ├── 13_Setting_Up_a_Backend_Service_With_an_Interface.md ├── Exercises.md ├── Project │ ├── 01_Preliminary_Project_Assignment.md │ ├── 02_Final_Project_Assignment.md │ └── img │ │ ├── 640px-Hot_Air_Balloon_Launch_(Unsplash).jpg │ │ └── Pipistrel_WATTsUP_airplane.jpg ├── README.md ├── Software_Testing.pptx └── img │ ├── address.png │ ├── client-server.png │ ├── dictionary.png │ ├── first_program.png │ ├── flask_json.png │ ├── flask_response.png │ ├── list.png │ ├── new_project.png │ ├── new_repo.png │ ├── new_repo2.png │ ├── path_envvar.png │ ├── project_tree.png │ ├── result_set.png │ ├── schema.png │ └── stop_button.png ├── Projektityö ├── 01_Esiprojektin_toimeksianto.md ├── 02_Projektityön_toimeksianto.md ├── Kestävän kehityksen näkökulma projektissa.pdf ├── Määrittelydokumentin_pohja.docx └── img │ ├── 640px-Hot_Air_Balloon_Launch_(Unsplash).jpg │ └── Pipistrel_WATTsUP_airplane.jpg ├── README.md ├── Tehtävät.md ├── Testaus_v2.pptx └── img ├── add-collaborator.png ├── asiakas-palvelin.png ├── copy-task-link.png ├── ekaohjelma.png ├── flask_json.png ├── flaskvastaus.png ├── lista.png ├── osoite.png ├── path_envvar.png ├── projektipuu.png ├── sanakirja.png ├── skeema.png ├── stop-nappi1.png ├── tulosjoukko.png ├── uusi_repo.png ├── uusi_repo2.png └── uusiprojekti.png /01a_Ensimmäinen_ohjelma.md: -------------------------------------------------------------------------------- 1 | # Ensimmäinen ohjelma 2 | 3 | Tervetuloa ohjelmoimaan Python-kieltä Metropolia Ammattikorkeakoulussa! 4 | 5 | ... ja sama Pythoniksi: 6 | ``` 7 | print("Tervetuloa opiskelemaan Python-kieltä!") 8 | ``` 9 | Python on maailman yleisimpiä ohjelmointikieliä. Kun opiskelet Pythonia, voit: 10 | - oppia ohjelmoimaan helposti ja hauskasti 11 | - koodata laadukkailla ja ergonomisilla kehitystyökaluilla 12 | - luoda näyttävää grafiikkaa visualisointikirjastojen avulla 13 | - soveltaa tekoälyä kattavien koneoppimiskirjastojen ansiosta 14 | 15 | Ensimmäisenä opiskeluvuonna saat vankat Python-ohjelmoinnin perustaidot. Syvennät osaamistasi myöhemmissä opinnoissa, 16 | ja opit käyttämään Python-kieltä työkaluna ohjelmointi- ja ohjelmistotuotantoprojekteissa. 17 | 18 | Tässä ensimmäisessä moduulissa asennat Python-kehitystyökalut ja opit kirjoittamaan ja ajamaan ensimmäisen Python-ohjelmasi. 19 | 20 | ## Python-tulkin asennus 21 | 22 | Tällä opintojaksolla käytetään Python-ohjelmointikieltä ja JetBrains PyCharm -kehitintä. 23 | 24 | Python-tulkki on ohjelma, joka lause 25 | kerrallaan tulkkaa Python-kieliset lauseet tietokoneen suorittimen ymmärtämään muotoon eli konekielelle. Sen asentaminen 26 | on välttämätöntä, jotta voit ohjelmoida Pythonilla. 27 | 28 | Lataa Python-tulkki käyttöösi seuraavasti: 29 | 30 | 1. Siirry selaimella sivulle https://www.python.org/downloads/. 31 | 2. Valitse **Downloads / All releases** 32 | 3. Selaa sivua alemmaksi kohtaan **Looking for a specific release?**. Napsauta **Download** jonkin versionumeron 33 | 3.7.X - 3.9.X kohdalla, missä X voi olla mikä tahansa numero. 34 | 4. Lataa Python napsauttamalla sivun alaosassa linkkiä **Windows installer (64-bit)**. 35 | 5. Etene ohjatun toiminnon 36 | esittämällä tavalla, ja asenna Python asennusohjelman ehdottamaan oletussijaintiin. 37 | 38 | **Tärkeää:** Ohjattu asennustoiminto tarjoaa mahdollisuuden lisätä Python-tulkki Windows-käyttöjärjestelmän 39 | `PATH`-ympäristömuuttujaan. Kyseinen ympäristömuuttuja sisältää luettelon kansioista, joista 40 | käyttöjärjestelmä etsii suoritettavia ohjelmia automaattisesti. Lisäys kannattaa tehdä: sen 41 | ansiosta voit antaa komentoikkunassa komennon `python` mistä tahansa kansiosta, ja Python-tulkki 42 | löytyy automaattisesti. (Tästä on hyötyä tulevilla Laitteisto 1 ja 2 -opintojaksoilla, joilla myös työskennellään Pythonilla.) 43 | 44 | Seuraava kuva näyttää valintaruudun, josta lisäys tehdään: 45 | 46 | ![PATH-ympäristömuuttujan päivittäminen](img/path_envvar.png) 47 | 48 | 49 | >Tällä kurssilla käytetään myös MariaDB-tietokantaa. Sen vaatima MySQL Connector/Python -ajuri ei tätä kirjoitettaessa (elokuu 2022) 50 | vielä tue uusimpia Python 3.10 -versioita. Tästä syystä kannattaa valita hieman vanhempi versio (esimerkiksi 3.9), jolle 51 | tuki on jo olemassa. Jos lataat nyt uusimman Python-version, voit myöhemmin joutua asentamaan aiemman version rinnalle. 52 | 53 | ## Kehittimen asennus 54 | 55 | Seuraavaksi asennetaan PyCharm-kehitin eli IDE. IDE on lyhenne englannin kielen sanoista 56 | *Integrated Development Environment*. Se tarkoittaa ammattikäyttöön 57 | soveltuvaa ohjelmistoa, jonka avulla voit kirjoittaa, ajaa ja testata ohjelmia. 58 | 59 | PyCharm-kehittimen voit ladata seuraavasti: 60 | 1. Siirry osoitteeseen https://www.jetbrains.com/ 61 | 2. Valitse **Developer tools / PyCharm** ja paina **Download**. 62 | 3. Valitse ladattavaksi Professional-versio. 63 | 4. Saat käyttöön oikeuttavan lisenssin, kun rekisteröit ohjelmiston Metropolian opiskelijana. Asennusohjelma opastaa sinua tässä. Käyttöönotto edellyttää JetBrains-tilin luomista ja opiskelijalisenssin hankkimista. Voit opiskelijana hankkia maksutta vuoden kerrallaan voimassa olevan lisenssin napsauttamalla JetBrains-sivuston yläreunassa olevaa ostoskärrykuvaketta ja valitsemalla **Special offers / For students and teachers**. Syötä lomakkeen tiedot käyttämällä Metropolian sähköpostiosoitetta ja viimeistele aktivointi sähköpostiin saamasi ohjeen mukaan. Kun lisenssi on vuoden päästä vanhenemassa, saat sähköpostiin automaattisesti ohjeen sen uusimisesta. 64 | 65 | Asennuksen jälkeen voit käynnistää PyCharm-ohjelman napsauttamalla sen kuvaketta. 66 | 67 | Kun käynnistät kehittimen ensimmäistä kertaa, PyCharm saattaa tarjota esittelykierrosta tai tervetuloprojektia. 68 | Voit kieltäytyä niistä. 69 | 70 | ## Projektin ja Python-tiedoston luonti 71 | 72 | Nyt olet valmis kirjoittamaan ensimmäisen ohjelman. Ensimmäiseksi on perustettava projekti. Projektia voi ajatella eräänlaisena 73 | salkkuna, johon kerätään samaan aihepiiriin liittyviä ohjelmia. Esimerkiksi ensimmäisiä ohjelmointikokeiluja 74 | varten voit perustaa uuden projektin nimeltä ´kokeilut´. Nimi kirjoitetaan projektin tiedostopolun perään: 75 | 76 | ![Uuden projektin luonti](img/uusiprojekti.png) 77 | 78 | Uusi projekti perustetaan oletusarvoisesti virtuaaliympäristöön (venv). Tämä helpottaa ohjelmien käyttämien 79 | pakkausten ja niiden versioiden hallintaa. 80 | 81 | Kun painat **Create**, kehitin kysyy, avataanko projekti olemassaolevassa vai uudessa ikkunassa. Voit valita kumman vain vaihtoehdon. 82 | 83 | Näytön vasempaan reunaan ilmestyy projektipuu, joka näyttää projektiin kuuluvat tiedostot: 84 | 85 | ![Uuden projektin luonti](img/projektipuu.png) 86 | 87 | Jokainen ohjelma kirjoitetaan tiedostoon projektin kansiohierarkian sisälle. Voit tehdä ensimmäistä 88 | ohjelmaa varten uuden tiedoston napsauttamalla projektin nimeä projektipuussa hiiren kakkospainikkeella. 89 | Valitse sitten **New / Python file** ja kirjoita avautuvaan valintaikkunaan tiedoston nimi, esimerkiksi 90 | `heimaailma`. 91 | Projektipuuhun ilmestyy kuvassa näkyvä tiedosto nimeltä `heimaailma.py`. Python-ohjelma kirjoitetaan tiedostoihin, joiden päättenä on ´.py´. 92 | 93 | 94 | ## Ohjelman kirjoitus, tallennus ja ajo 95 | 96 | Ohjelma, eli Python-lähdekoodi, kirjoitetaan editorikenttään: 97 | 98 | ![Ensimmäinen ohjelma](img/ekaohjelma.png) 99 | 100 | Voit suorittaa eli ajaa ohjelman napsauttamalla hiiren kakkospainiketta editorikentässä ja valitsemalla **Run 'hello'**. 101 | 102 | Tuloste ilmestyy alareunan konsoli-ikkunaan: 103 | 104 | ```python 105 | Hei, maailma! 106 | ``` 107 | 108 | Jos ohjelmassa on virheitä, ei hätää! Saat virheilmoituksen, joka auttaa virheen paikantamisessa. Sen jälkeen voit 109 | korjata ohjelmaa niin monta kertaa kuin on tarpeen ja suorittaa sen aina uudelleen. 110 | 111 | Virheiden teko kuuluu ohjelmointiin. On arvioitu, että 80 prosenttia ammattimaisen ohjelmoijan työajasta kuuluu 112 | virheiden jäljitykseen ja niiden korjaamiseen. Myös oppiminen tapahtuu virheitä tekemällä. Kun selvität virheen 113 | syyn ja korjaat sen, olet oppinut hieman paremmaksi ohjelmoijaksi. 114 | 115 | -------------------------------------------------------------------------------- /01b_Versionhallinnan_käyttöönotto.md: -------------------------------------------------------------------------------- 1 | # Versionhallinnan käyttöönotto 2 | 3 | Ohjelmistot rakentuvat vaihe vaiheelta ja pala palalta. Ohjelmistoja tehdään usein tiimityönä, ja tiimin jäsenten on päästävä käsiksi 4 | samaan ohjelmakoodiin. Ohjelmakoodista syntyy kehitystyön aikana useita versioita, ja joskus on tarve palata aiempaan versioon. On myös 5 | tärkeää varmistaa, että kerran kirjoitettu ohjelmakoodi ei vahingossa tuhoudu eikä katoa. 6 | 7 | Tätä varten otetaan käyttöön versionhallinta. 8 | 9 | ## Git ja GitHub 10 | 11 | Opintojaksolla (ja ammattimaisessa ohjelmistokehityksessä usein) käytetään Git-nimistä hajautettua versionhallintaa. 12 | Hajautettu versionhallinta tarkoittaa sitä, että ohjelmakoodien yhteisestä tallennuspaikasta - eli repositoriosta - 13 | luodaan paikallisia kopioita versionhallinnan käyttäjien omille tietokoneille. 14 | Ajantasaiset tiedot haetaan repositoriosta työskentelyn alkaessa (tästä käytetään termiä pull), ja tehdyt 15 | muutokset päivitetään repositorioon (tätä kutsutaan termillä push). 16 | 17 | GitHub on kaikkien saatavilla oleva verkkosivusto, johon Git-projektien ohjelmakoodit voidaan tallentaa. 18 | Se on myös maailman suurin tähän tarkoitukseen kehitetty sivusto, ja käytännössä jokainen 19 | ohjelmointialan ammattilainen käyttää tavalla tai toisella GitHubia. 20 | 21 | Jotta voit käyttää GitHubia, rekisteröidy sen käyttäjäksi osoitteessa https://github.com/. 22 | 23 | Kun olet luonut itsellesi käyttäjätilin GitHubiin, voit luoda sinne repositorioita eli tallennuspaikkoja 24 | projekteille. 25 | 26 | Voit luoda omille Python-koodeillesi repositorion kahdella tavalla: joko PyCharm-kehittimen kautta tai suoraan GitHubista. 27 | 28 | ## Repositorion luominen PyCharm-kehittimen kautta (suositeltava tapa) 29 | 30 | Repositorion luominen olemassaolevan PyCharmilla tehdyn projektin kautta on suoriteltavaa, koska tällöin virtuaaliympäristön määritykset 31 | voidaan tehdä helposti projektia luotaessa. Yhteys versionhallintaan määritetään näin: 32 | 33 | 1. Valitse GitHub-tili, jota käytät. Paina **Ctrl/Alt S** tai **CMD/,**. Valitse **Version Control / Github /Add**, ja syötä 34 | GitHub-kirjautumistietosi. 35 | 2. Valitse PyCharm-kehittimessä 36 | **VCS / Share project on GitHub**. Rastita **Private**, jotta saat yksityisen repositorion. 37 | 3. Valitse repositorioon aluksi tulevat tiedostot. Voit lisätä tiedostoja myöhemmin. 38 | 4. Kun yhteys GitHub-repositorioon on muodostettu, 39 | ilmestyy PyCharm-kehittimen valikkoriville uusi Git-valikko. Lisää haluamiasi tiedostoja toiminnolla **Git/Add**. PyCharm kysyy aina uutta tiedostoa luotaessa, liitetäänkö se repositorioon. 40 | 41 | ## Repositorion luominen GitHubissa (vaihtoehtoinen tapa) 42 | 43 | Vaihtoehtoisesti voit luoda repositorion GitHub-sivustolla: 44 | 45 | 1. Rekisteröidy GitHubin käyttäjäksi osoitteessa https://github.com/. 46 | 2. Kirjauduttuasi sisään paina **Repositories**-otsikon vieressä olevaa **New**-painiketta. 47 | 3. Tee itsellesi oma yksityinen repositorio alla olevan kuvan mukaisesti. 48 | 49 | ![Uuden repositorion luonti](img/uusi_repo2.png) 50 | 51 | Tämän jälkeen PyCharm-kehittimelle annetaan pääsy GitHubiin ja täsmennetään käytettävä repositorio: 52 | 53 | 1. Valitse GitHub-tili, jota käytät. Paina **Ctrl/Alt S** tai **CMD/,**. Valitse **Version Control / Github /Add**, ja syötä 54 | GitHub-kirjautumistietosi. 55 | 56 | 2. Ota repositorio käyttöön Python-projektissasi. valitse PyCharm-kehittimessä **VCS / Get from 57 | Version Control**, ja hae GitHubissa luomasi Git-repositorio napsauttamalla **Clone**. PyCharm luo uuden 58 | projektin, joka käyttää GitHub-reposiota. 59 | 60 | Tässäkin tapauksessa PyCharm-kehittimen valikkoriville ilmestyy uusi Git-valikko. 61 | 62 | ## Repositorion käyttö 63 | 64 | Tarkastellaan tässä vaiheessa GitHubin käyttöä yhden kehittäjän näkökulmasta. Tällöin voimme olettaa, että eri kehittäjät 65 | eivät käytä samoja tiedostoja, ja tästä aiheutuvia samanaikaisen muokkauksen ongelmia ei esiinny. Oletamme myös, että 66 | meille ei synny tarvetta jakaa kehitysprojektia eri kehityshaaroihin. GitHubin edistyneeseen 67 | käyttöön kehitystiimin yhteistyöalustana 68 | kannattaa perehtyä vasta myöhemmin projektityön alkaessa. 69 | 70 | Työskentelyssä kannattaa ottaa tavaksi seuraavat käytännöt: 71 | 72 | - Kun aloitat työskentelyn, anna komento **Git / Pull**. Komento hakee mahdolliset muuttuneet tiedot GitHub-palvelussa olevasta repositoriosta. 73 | - Kun olet saanut uusia tuotoksia aikaan, sitouta muutokset komennon **Git / Commit** avulla. Muutosten pohjalta syntyy uusi tallennuspiste, johon on mahdollista myöhemmin palata, jos tarvetta ilmenee. Sitouta muutokset aina viimeistään siinä vaiheessa, kun olet lopettamassa työskentelyä. **Git / Add** toiminnolla voi valita tiedostoja mukaan seuraavaan tallennuspisteeseen (commit). 74 | - Kun lopetat työskentelyn, anna komento **Git / Push**. Komento kopioi paikallisella koneessa olevat sitoutetut muutokset GitHub-palvelussa olevaan repositorioon. 75 | 76 | Voit tutkia kehityshaaraa tallennuspisteineen GitHub-palvelussa. 77 | 78 | Aina kun luot uuden tiedoston, PyCharm kysyy, otetaanko se mukaan versionhallintaan. Kaikki lähdekoodit, kuvat ja muut 79 | arvokkaat tiedostot on syytä tallentaa GitHubiin. Toisaalta konfiguraatiotiedostot ja muu ajoympäristöön liittyvä 80 | silppu kannattaa jättää versionhallinnan ulkopuolelle. Myöskään salasanoja sisältäviä tiedostoja ei tietoturvasyistä 81 | tule tallentaa versionhallintaan. 82 | 83 | ## Tehtävien palautus GitHubin avulla 84 | 85 | Palauta Omassa oleviin tehtäviin aina suoraan kyseisen moduulin tehtävien ratkaisuun osoittava linkki. Se onnistuu helpoiten niin, että navigoit selaimella GitHubissa oikeaan kansioon ja kopioit linkin suoraan selaimen osoiteriviltä. Nimeä python-tiedostosi ja kansiosi niin, että niistä voi päätellä suoraan, minkä tehtävien ratkaisut ne sisältävät. 86 | 87 | ![Linkin kopioiminen](img/copy-task-link.png) 88 | 89 | Jotta opettajat näkevät tiedostosi GitHubissa, sinun pitää lisätä opettajien käyttäjätunnukset GitHub-projektiisi käyttäjiksi. Tämä tapahtuu _Settings_-välilehdeltä kohdasta _Collaborators_. Opettajien tunnukset kerrotaan Oma-työtilan kautta. Vaihtoehtoisesti voit määritellä projektisi näkyvyyden julkiseksi (public). 90 | 91 | ![Käyttäjien lisääminen](img/add-collaborator.png) 92 | -------------------------------------------------------------------------------- /05_Listarakenne_ja_for-toistorakenne.md: -------------------------------------------------------------------------------- 1 | # Listarakenne ja läpikäyvä toistorakenne (for) 2 | 3 | Tässä moduulissa opit käyttämään listoja, jotka ovat Python-kielen tärkein tietorakenne. Lista tarkoittaa järjestettyä 4 | joukkoa alkioita. Listarakenteen avulla voit tallentaa useita arvoja yhteen listamuuttujaan, ja voit käydä 5 | nuo arvot läpi tarkoitukseen kehitettyä for-toistorakennetta käyttäen. 6 | 7 | ## Lista ja alkiot 8 | 9 | Ohjelmoinnissa tietorakenteella tarkoitetaan ratkaisua, jossa yhteen muuttujaan on tallennettuna yhden arvon 10 | sijasta kokonainen joukko arvoja. Tietorakenteita on erilaisia: lista, sanakirja, puu, pino ja verkko. Jokaisella tietorakenteella 11 | on oma käyttöalueensa. 12 | 13 | Lista on tietorakenne, jossa alkiot ovat ikään kuin jonossa, määrätyssä järjestyksessä. Listaan voidaan liittää 14 | alkioita haluttuun kohtaan, ja listasta voidaan poistaa alkioita. Listan alkiot voidaan myös käydä läpi luetellen 15 | ne. Listarakenteessa uusi alkio lisätään usein listan loppuun, ja alkio poistetaan listan alusta. Muutkin ratkaisut 16 | ovat mahdollisia. 17 | 18 | Tarkastellaan ohjelmaa, joka luo listan ja asettaa sen arvoksi viiden henkilön nimet, eli viisi merkkijonoa: 19 | 20 | ```python 21 | nimet = ["Viivi", "Ahmed", "Pekka", "Olga", "Mary"] 22 | ``` 23 | Listamuuttujan luovassa sijoituslauseessa listamuuttujan nimi on yhtäsuuruusmerkin vasemmalla puolella. 24 | Oikealle puolelle kirjoitetaan hakasulkeet. Listaan 25 | liitettävät jäsenet luetellaan hakasulkeiden sisällä pilkulla erotettuina. 26 | 27 | Tässä tapauksessa tuloksena on viisialkioinen lista, jonka alkiot ovat merkkijonoja. Listaan viitataan 28 | listamuuttujalla, joka on nimeltään `nimet`. Seuraava kuva havainnollistaa listan rakennetta: 29 | 30 | ![Listamuuttuja ja listan alkiot](img/lista.png) 31 | 32 | Tarkastellaan sitten tapoja viitata listan alkioihin. Seuraavassa ohjelmassa tulostaa luodun listan alkioita ja osia: 33 | 34 | ```python 35 | nimet = ["Viivi", "Ahmed", "Pekka", "Olga", "Mary"] 36 | 37 | print(nimet[3]) 38 | print(nimet[1]) 39 | print(nimet[-2]) 40 | print(nimet[1:3]) 41 | print(nimet[2:]) 42 | print(nimet) 43 | ``` 44 | 45 | Tuloste on seuraava: 46 | 47 | ```monospace 48 | Olga 49 | Ahmed 50 | Olga 51 | ['Ahmed', 'Pekka'] 52 | ['Pekka', 'Olga', 'Mary'] 53 | ['Viivi', 'Ahmed', 'Pekka', 'Olga', 'Mary'] 54 | ``` 55 | 56 | Ensimmäinen tulostuslause tulostaa listan alkion, jonka indeksi on kolme. Python-listan alkioihin voidaan aina viitata 57 | indeksin avulla. Indeksi on listan alkion järjestysnumero. Numerointi alkaa aina nollasta, joten indeksillä 3 viitataan listan 58 | neljänteen alkioon, joka on merkkijono "Olga". 59 | 60 | Toisessa tulostuslauseessa tulostetaan indeksiä 1 vastaava alkio. Se on listan toinen merkkijono, eli "Ahmed". 61 | 62 | Kolmannessa tulostuslauseessa on annettu negatiivinen indeksi. Tällöin listan alkioiden laskenta aloitetaan listan lopusta 63 | lukien: merkintä -1 vastaa listan viimeistä alkiota, -2 toiseksi viimeistä ja niin edelleen. Tässä tapauksessa tulostuu 64 | toiseksi viimeinen alkio eli merkkijono "Olga". 65 | 66 | Neljäs tulostuslause tulostaa listan osan. Hakasulkeissa annettu indeksiväli `1:3` tarkoittaa, että tuloksena 67 | olevaan uuteen listaan otetaan alkiot indeksistä 1 alkaen (alkupiste mukaan lukien) ja indeksiin 3 päättyen (päätepiste pois lukien). 68 | Välin alkupiste otetaan siis aina mukaan tuloksena olevaan uuteen listaan, mutta loppupiste ei. Näin uuteen listaan 69 | päätyvät alkiot 1 ja 2 eli "Ahmed" ja "Pekka". 70 | 71 | Viidennessä tulostuslauseessa indeksivälin loppupiste on jätetty antamatta. Tällöin tulostetaan alkiot alkupisteestä 72 | alkaen (ja se mukaan lukien) aina listan loppuun asti. Tuloksena on indeksejä 2, 3 ja 4 vastaavien alkioiden 73 | "Pekka", "Olga" ja "Mary" muodostama lista. 74 | 75 | Viimeinen esimerkki tulostaa listan kokonaisuudessaan. 76 | 77 | Listan pituus saadaan tarvittaessa Python-kielen sisäänrakennetulla `len`-funktiolla: 78 | 79 | ```python 80 | print (len(nimet)) 81 | ``` 82 | 83 | Tuloksena on listan pituus, joka on siis yhtä suurempi kuin viimeisen alkion indeksi: 84 | ```monospace 85 | 5 86 | ``` 87 | 88 | ## Viittaus listan ulkopuolelle 89 | 90 | Kun listan alkioon viitataan indeksin avulla, on mahdollista ohjelmoida kielletty viittaus, joka osoittaa 91 | sellaiseen alkioon, jota ei listassa ole. Tällainen virhe on ohjelmoitaessa varsin yleinen, ja se on hyvä 92 | opetella heti tässä vaiheessa tunnistamaan. 93 | 94 | Seuraavassa listassa on viisi alkiota. Tarkoitus on viitata listan viidenteen alkioon, mutta - koska indeksointi 95 | alkaa nollasta - ohjelmaan kirjoitettu indeksi 5 viittaa kuudenteen alkioon. Tuloksena on virhetilanne: 96 | 97 | ```python 98 | nimet = ["Viivi", "Ahmed", "Pekka", "Olga", "Mary"] 99 | # Virheellinen viittaus 100 | print (nimet[5]) 101 | ``` 102 | 103 | Syntyy ajonaikainen poikkeus, ja konsolissa nähdään selostus virheestä: 104 | ```monospace 105 | Traceback (most recent call last): 106 | File "C:/Users/olliv/PycharmProjects/Python_Ohjelmistoteema/Esimerkit/listaesimerkki.py", line 4, in 107 | print (nimet[5]) 108 | IndexError: list index out of range 109 | 110 | Process finished with exit code 1 111 | ``` 112 | 113 | Virheilmoitus on tälläkin kertaa ohjelmoijan ystävä: se näyttää virheellisen lauseen sekä virheen syyn: listan indeksi oli 114 | sallitun arvoalueen ulkopuolella. 115 | 116 | ## Listaoperaatiot 117 | 118 | Edeltävissä esimerkeissä listan alkiot lueteltiin listaa luotaessa, ja lista pysyi sen jälkeen muuttumattomana 119 | koko ohjelman suorituksen ajan. Listaa käytetään usein kuitenkin dynaamisesti: siihen lisätään ja siitä poistetaan 120 | alkioita ohjelman suorituksen aikana. 121 | 122 | Seuraava ohjelma luo aluksi tyhjän listan. Sen jälkeen ohjelma pyytää käyttäjältä nimiä siihen asti, että 123 | käyttäjä syöttää tyhjän merkkijonon pelkästään painamalla Enter-näppäintä. Lopuksi ohjelma tulostaa listan kokonaisuudessaan: 124 | 125 | ```python 126 | nimet = [] 127 | 128 | nimi = input("Anna ensimmäinen nimi tai lopeta painamalla Enter: ") 129 | while nimi!="": 130 | nimet.append(nimi) 131 | nimi = input("Anna seuraava nimi tai lopeta painamalla Enter: ") 132 | 133 | print(nimet) 134 | ``` 135 | 136 | Alla on esimerkki ohjelman toiminnasta: 137 | ```monospace 138 | Anna ensimmäinen nimi tai lopeta painamalla Enter: Mikko 139 | Anna seuraava nimi tai lopeta painamalla Enter: Kerttu 140 | Anna seuraava nimi tai lopeta painamalla Enter: John 141 | Anna seuraava nimi tai lopeta painamalla Enter: Miriam 142 | Anna seuraava nimi tai lopeta painamalla Enter: 143 | ['Mikko', 'Kerttu', 'John', Miriam'] 144 | ``` 145 | 146 | Esimerkissä listan loppuun saatiin lisättyä uusia alkioita yksi kerrallaan `append`-listaoperaatiolla. 147 | Python-kielessä on joukko valmiita listaoperaatioita, joiden avulla listaan voidaan lisätä ja poistaa alkioita. 148 | Operaatioiden avulla voidaan myös järjestellä listan alkioita erilaisin tavoin. 149 | 150 | Tavallisimmat listaoperaatiot on koottu alla olevaan taulukkoon: 151 | 152 | | Operaatio | Tarkoitus | Esimerkki | 153 | |-----------|---------------------------------------------------------------------------------------------|-------------------------------------------------------------------| 154 | | append | lisää alkion listan loppuun | nimet.append("Matti") | 155 | | remove | poistaa alkion ensimmäisen ilmentymän listasta | nimet.remove("Pekka") | 156 | | insert | lisää alkion haluttuun kohtaan, ennen alkiota, jonka indeksi vastaa ensimmäistä argumenttia | nimet.insert(4, "Teppo") | 157 | | extend | liittää toisen listan ensimmäiseen listaan | toisetNimet = ["Allu","Ninni"]
nimet.extend(toisetNimet) | 158 | | index | palauttaa alkion ensimmäisen sijainnin indeksin | monesko = nimet.index("Olga") | 159 | | in | testaa, esiintyykö alkio listassa | if "Matti" in nimet:
    "Matti löytyi" | 160 | | sort | lajittelee listan alkiot aakkos- tai suuruusjärjestykseen | luvut.sort() | 161 | 162 | ## Listan läpikäynti for-toistorakenteen avulla 163 | 164 | Edellä kirjoittamamme ohjelma kysyi käyttäjiltä nimiä ja tulosti sen jälkeen listamuuttujan kerralla ja kokonaisuudessaan. 165 | 166 | Tarkastellaan seuraavaksi, miten voimme käydä listan läpi alkio alkiolta. Laajennetaan ohjelmaa siten, 167 | että ohjelma tervehtii jokaista listaan lisättyä henkilöä erikseen. Ohjelma kirjoitetaan seuraavasti: 168 | 169 | ```python 170 | nimet = [] 171 | 172 | etunimi = input("Anna ensimmäinen nimi tai lopeta painamalla Enter: ") 173 | while etunimi ! = "": 174 | nimet.append(etunimi) 175 | etunimi = input("Anna seuraava nimi tai lopeta painamalla Enter: ") 176 | 177 | for nimi in nimet: 178 | print (f"Moi, {nimi}!") 179 | ``` 180 | 181 | Ohjelma toimii näin: 182 | ```monospace 183 | Anna ensimmäinen nimi tai lopeta painamalla Enter: Stefan 184 | Anna seuraava nimi tai lopeta painamalla Enter: Ville 185 | Anna seuraava nimi tai lopeta painamalla Enter: Aamu 186 | Anna seuraava nimi tai lopeta painamalla Enter: 187 | Moi, Stefan! 188 | Moi, Ville! 189 | Moi, Aamu! 190 | ``` 191 | 192 | Listan läpikäynti toteutettiin for-toistorakenteen avulla: 193 | ```python 194 | for n in nimet: 195 | print (f"Moi, {n}!") 196 | ``` 197 | 198 | Toistorakenne on omiaan listan läpikäyntiin. Kierrosmuuttuja `n` saa vuoron perään arvokseen kunkin listan alkion. Toistoa 199 | jatketaan niin kauan kuin listassa riittää alkioita. 200 | 201 | Tällaista listan läpikäyntiä alkio kerrallaan kutsutaan listan iteroimiseksi. 202 | 203 | ## Range-funktio 204 | 205 | `For`-toistorakenteella on myös muita käyttömahdollisuuksia kuin edellä esitetty listan läpikäynti. 206 | Rakenteen avulla voidaan helposti luoda kierrosmuuttuja, joka saa vuoron perään halutut arvot, joko yhden välein tai muutoin 207 | askeltaen. 208 | 209 | - range(1,4) määrittää arvot 1, 2, 3 210 | - range(5,0,-1) määrittää arvot 5, 4, 3, 2, 1 211 | - range(10,21,2) määrittää arvot 10, 12, 14, 16, 18, 20 212 | 213 | `Range`-funktion ensimmäinen argumentti on välin alkupiste, toinen argumentti on välin loppupiste ja kolmas, valinnainen argumentti on 214 | askeleen suuruus. Jos askeleen suuruus jätetään määrittämättä, on askel yksi. Jos askeleeksi annetaan nolla, saadaan virheilmoitus. 215 | 216 | Jos `range`-funktiolle annetaan vain yksi argumentti, tulkitaan se välin loppupisteeksi. Alkupiste on tällöin nolla 217 | ja askel yksi. 218 | 219 | Range-funktio tuottaa Pythonin range- eli arvoväli-tietorakenteen, joka määrittää halutut arvot. Arvoväli voidaan 220 | käydä läpi samaan tapaan kuin listarakenne. 221 | 222 | Esimerkiksi seuraava ohjelma tulostaa kolmella jaolliset luvut väliltä 3 ja 30. Huomaa, että `range`-funktion 223 | kutsussa oleva alkupiste kuuluu mukaan arvoväliin, mutta loppupiste ei. Tästä syystä loppupisteeksi on asetettava jokin luku, joka 224 | on hieman suurempi kuin 30. 225 | 226 | ```python 227 | for luku in range(3,31,3): 228 | print (luku) 229 | ``` 230 | 231 | Ohjelma tulostaa: 232 | 233 | ```monospace 234 | 3 235 | 6 236 | 9 237 | 12 238 | 15 239 | 18 240 | 21 241 | 24 242 | 27 243 | 30 244 | ``` 245 | 246 | `Range`-funktion avulla on näppärää korvata kierrosmuuttujaan perustuva toistorakenne. 247 | Seuraava ohjelma tulostaa kuusi kertaa merkkijonon "Moi!". 248 | 249 | ```python 250 | for luku in range(6): 251 | print ("Moi!") 252 | ``` 253 | -------------------------------------------------------------------------------- /07_Monikko,_joukko_ja_sanakirja.md: -------------------------------------------------------------------------------- 1 | # Monikko, joukko ja sanakirja 2 | 3 | Aiemmin käsiteltiin Python-kielen listarakennetta, joka on kielen tarjoamista tietorakenteista yleisimmin käytetty. 4 | Python-kieli tarjoaa myös kolme muuta sisäänrakennettua tietorakennetta, joista kullakin on oma käyttöalueensa. 5 | 6 | Tässä moduulissa opit käyttämään näitä kolmea Pythonin tietorakennetta: monikkoa, joukkoa ja sanakirjaa. 7 | 8 | ## Monikko 9 | 10 | Monikko (*tuple*) muistuttaa Pythonin listarakennetta siinä mielessä, että siinä voidaan esittää järjestetty 11 | jono alkioita. Toisin kuin lista, monikko on kuitenkin muuttumaton: siihen ei voi lisätä alkioita eikä siitä 12 | voi poistaa alkioita monikon luonnin jälkeen. 13 | 14 | Monikkoa on tarkoituksenmukaista käyttää tilanteissa, joissa alkioiden jono on luonteeltaan staattinen: tiedetään, 15 | että muutoksille ei ole tarvetta ohjelman suorituksen aikana. Monikon käytön edut liittyvät muistinhallintaan: 16 | monikolle voidaan varata sitä luotaessa kiinteä muistialue, ja yksittäisen alkion osoite keskusmuistissa voidaan 17 | laskea suoraan tietorakenteen alkuosoitteen ja indeksin avulla. Listan tapauksessa tämä ei ole mahdollista, vaan 18 | haettaessa alkiota indeksin perusteella ajoympäristö joutuu yleensä iteroimaan alkiot lävitse. Ero näkyy siten, 19 | että alkion haku indeksin perusteella on monikon tapauksessa nopeampaa. Käytännössä ero on huomaamaton silloin, 20 | kun tietorakenne on pieni. 21 | 22 | Katsotaan esimerkkiä monikon käytöstä. Seuraava ohjelma luo monikon viikonpäivien nimistä. Ohjelma kysyy käyttäjältä 23 | viikonpäivän järjestysnumeron ja tulostaa sitä vastaavan viikonpäivän nimen. Huomaa, että monikon indeksointi 24 | alkaa nollasta samoin kuin listan: 25 | 26 | ```python 27 | viikonpäivät = ("maanantai", "tiistai", "keskiviikko", "torstai", "perjantai", "lauantai", "sunnuntai") 28 | järjestysnumero = int(input("Anna viikonpäivän järjestysnumero (1-7): ")) 29 | viikonpäivä = viikonpäivät[järjestysnumero-1] 30 | print (f"{järjestysnumero}. viikonpäivä on {viikonpäivä}.") 31 | ``` 32 | 33 | Ohjelma toimii ajettaessa näin: 34 | 35 | ```monospace 36 | Anna viikonpäivän järjestysnumero (1-7): 3 37 | 3. viikonpäivä on keskiviikko. 38 | ``` 39 | 40 | Edellisessä esimerkissä monikon lueteltujen alkioiden ympärillä on kaarisulkeet. 41 | Kaarisulkeiden käyttö ei yleensä ole välttämätöntä, vaan monikko voidaan myös kirjoittaa ilman 42 | niitä. Seuraavan ohjelma luo ja tulostaa monikon, joka sisältää kolme merkkijonoa: 43 | 44 | ```python 45 | hedelmät = "Appelsiini", "Banaani", "Omena" 46 | print(hedelmät) 47 | ``` 48 | 49 | Ohjelma tulostaa: 50 | ```monospace 51 | ('Appelsiini', 'Banaani', 'Omena') 52 | ``` 53 | 54 | Kaarisulkeiden käyttö voi tulla pakolliseksi tilanteessa, jossa monikko on toisen tietorakenteen 55 | sisällä. Esimerkiksi sijoituslauseessa 56 | `arvot = 1, (2, 3), 4` kaarisulkeet ilmaisevat, että `arvot`-muuttujaan sijoitettavan monikon toinen alkio 57 | on itsessään monikko. 58 | 59 | Vaikka kaarisulkeiden käyttö monikkojen yhteydessä ei yleensä ole välttämätöntä, monet 60 | Python-kehittäjät pitävät sitä hyvänä ohjelmointikäytäntönä, joka parantaa koodin luettavuutta. 61 | 62 | ### Monikon arvojen purku muuttujiin 63 | 64 | Monikon sisältämät arvot voidaan purkaa muuttujiin seuraavan esimerkin osoittamalla tavalla: 65 | ```python 66 | hedelmät = ("Appelsiini", "Banaani", "Omena") 67 | (eka, toka, kolmas) = hedelmät 68 | print (f"Hedelmiä ovat {eka}, {toka} ja {kolmas}.") 69 | ``` 70 | 71 | Tämä ohjelma tuottaa seuraavan tulosteen: 72 | ```monospace 73 | Hedelmiä ovat Appelsiini, Banaani ja Omena. 74 | ``` 75 | 76 | ### Monikko funktion paluuarvona 77 | 78 | Aiemmin käsittelimme funktioita, joilla tarkoitetaan tarvittaessa kutsuttavia aliohjelmia. 79 | Monikkojen avulla voidaan helposti kiertää sitä rajoitetta, että funktiolla voi olla vain 80 | yksi paluuarvo: Jos arvoja halutaan palauttaa kaksi, rakennetaan niistä monikko ja palautetaan 81 | se paluuarvona. Teknisesti paluuarvoja on edelleen yksi, mutta se on tyypiltään monikko, joka 82 | voi sisältää useamman kuin yhden arvon. 83 | 84 | Seuraava esimerkki havainnollistaa monikon käyttöä paluuarvona. Ohjelmassa tehdään nopanheittofunktio 85 | Monopoli-lautapeliä varten. Monopoli-pelissä heitetään aina kahta noppaa. 86 | 87 | ```python 88 | import random 89 | 90 | def heitä(): 91 | eka, toka = random.randint(1,6), random.randint(1,6) 92 | return eka, toka 93 | 94 | noppa1, noppa2 = heitä() 95 | print(f"Nopista tuli {noppa1} ja {noppa2}.") 96 | ``` 97 | 98 | Tulosteesta nähdään, että yhdellä funktiokutsulla saadaan kahden heiton tulos: 99 | ```monospace 100 | Nopista tuli 5 ja 6. 101 | ``` 102 | 103 | ## Joukko 104 | 105 | Joukko (*set*) on järjestämätön tietorakenne, eli sen alkiot voivat olla missä tahansa järjestyksessä. Koska joukon alkioille 106 | ei ole määritelty järjestystä, ei alkioihin voi myöskään viitata indeksillä. Toisin kuin listassa tai monikossa, sama 107 | alkio voi esiintyä joukossa vain kertaalleen, eli joukossa ei voi olla kahta samansisältöistä alkiota. 108 | 109 | Tarkastellaan seuraavaa esimerkkiä: 110 | ```python 111 | pelit = {"Monopoli", "Shakki", "Cluedo"} 112 | print(pelit) 113 | 114 | pelit.add("Dominion") 115 | print(pelit) 116 | 117 | pelit.remove("Shakki") 118 | print(pelit) 119 | 120 | pelit.add("Cluedo") 121 | print(pelit) 122 | 123 | for p in pelit: 124 | print(p) 125 | ``` 126 | 127 | Ohjelman tuottaa seuraavan tulosteen: 128 | ```monospace 129 | {'Shakki', 'Cluedo', 'Monopoli'} 130 | {'Shakki', 'Dominion', 'Cluedo', 'Monopoli'} 131 | {'Dominion', 'Cluedo', 'Monopoli'} 132 | {'Dominion', 'Cluedo', 'Monopoli'} 133 | Dominion 134 | Cluedo 135 | Monopoli 136 | ``` 137 | 138 | Aluksi luodaan joukko, jossa on kolme peliä: Monopoli, Shakki ja Cluedo. Tämän jälkeen pelit sisältävä joukko tulostetaan. 139 | Havaitaan, että pelit ovat tulosteessa eri järjestyksessä kuin missä ne joukkoa luotaessa kirjoitettiin. 140 | Tulostuslause konkretisoi sen, että joukon alkioiden järjestys ei ole määritelty: ohjelmoijan on varauduttava 141 | siihen, että joukkoa tulostettaessa alkiot voivat näkyä missä järjestyksessä tahansa. Esitysjärjestys 142 | määräytyy ajoympäristön sisäisen tallennusratkaisun mukaisesti, ja sen on 143 | sallittua vaihdella jopa saman ohjelman eri suorituskerroilla. 144 | 145 | Seuraavaksi kolmealkioiseen pelien joukkoon liitetään neljäs jäsen, Dominion. Joukkoon liittäminen tehdään `add`-operaation 146 | avulla. Tulostuslause paljastaa jälleen, että alkioiden 147 | näkyvä järjestys voi olla mikä tahansa. 148 | 149 | Tämän jälkeen joukosta poistetaan yksi alkio, Shakki. Tähän käytetään `remove`-operaatiota. 150 | 151 | Jäljellä on Dominion, Cluedo ja Monopoli. Seuraavaksi yritetään lisätä joukkoon uusi alkio, joka on sama 152 | kuin joukossa ennestään oleva alkio: Cluedo. Lisäämiseen käytetty `add`-metodi ei tuota virheilmoitusta, 153 | mutta tulosteesta nähdään, että samaa peliä ei lisätty toista kertaa. Tämä on joukon keskeinen ominaisuus: 154 | sama alkio voi esiintyä vain kertaalleen. 155 | 156 | Lopuksi joukon alkiot iteroidaan eli käydään läpi yksi kerrallaan. Tämä tapahtuu samanlaisella `for/in`-rakenteella 157 | kuin listojen tapauksessa. Tuloksena oleva järjestys voi jälleen olla ohjelmoijan näkökulmasta mikä tahansa. 158 | 159 | Edellisessä esimerkissä kaikki joukon alkiot olivat merkkijonoja, joten ne olivat keskenään saman tyyppisiä. 160 | Mainittakoon, että samantyyppisyyden vaatimusta ei ole. On siis sallittua luoda joukko, jonka yksi alkio on 161 | kokonaisluku, toinen alkio on merkkijono ja kolmas vaikkapa lista. 162 | 163 | Tarkastellaan lopuksi tilannetta, jossa luodaan tyhjä joukko. 164 | Tyhjä joukko luodaan edellä esitetystä poiketen `set`-funktion avulla. Tyhjän joukon 165 | luominen pelkkien tyhjien aaltosulkeiden kautta ei siis onnistu. 166 | Seuraava ohjelma luo ensin tyhjän joukon, lisää siihen sen jälkeen yhden alkion ja tulostaa 167 | lopuksi joukon `print`-lauseella: 168 | ```python 169 | nimet = set() 170 | nimet.add("Viivi") 171 | print(nimet) 172 | ``` 173 | Tuloste on seuraavanlainen: 174 | ```monospace 175 | {'Viivi'} 176 | ``` 177 | 178 | ## Sanakirja 179 | 180 | Sanakirja (*dictionary*) on Pythonin käytetyimpiä tietorakenteita. 181 | 182 | Sanakirjaan voidaan tallentaa avain-arvopareja. Avain on ikään kuin kahva, josta vetämällä 183 | oikea sanakirjan tietue löytyy, jotta sen arvon päästään käsiksi. 184 | 185 | Sanakirja-tietorakenteesta käytetään toisinaan nimityksiä assosiatiivinen taulukko ja hajautusrakenne. 186 | 187 | Seuraava kuva havainnollistaa sanakirjarakennetta: 188 | 189 | ![Sanakirjarakenne](img/sanakirja.png) 190 | 191 | Sanakirja nimeltä `numerot` sisältää viiden henkilön puhelinnumerot. Henkilön nimi toimii avaimena: kun tiedät nimen, 192 | saat arvon - eli puhelinnumeron - selville. 193 | 194 | Laaditaan vastaava rakenne Python-kielellä. Luodaan aluksi kolmen henkilön tiedot sisältävä sanakirja. 195 | Sitten lisätään kaksi henkilöä erikseen sanakirjan luonnin jälkeen ja tulostetaan sanakirja, 196 | jossa on tässä vaiheessa viiden henkilön nimet ja puhelinnumerot. 197 | Lopuksi kysytään käyttäjältä henkilön nimi ja tulostetaan saatua nimeä vastaava puhelinnumero, jos annettu 198 | nimi löytyy sanakirjan avainten joukosta: 199 | 200 | ```python 201 | numerot = {"Viivi":"050-1234567", 202 | "Ahmed":"040-1112223", 203 | "Pekka":"050-7654321"} 204 | 205 | numerot["Olga"] = "050-1011012" 206 | numerot["Mary"] = "0401-2132139" 207 | 208 | print (numerot) 209 | 210 | nimi = input("Anna nimi: ") 211 | if nimi in numerot: 212 | print (f"Henkilön {nimi} puhelinnumero on {numerot[nimi]}.") 213 | ``` 214 | 215 | Ohjelma tuottaa seuraavan tulosteen: 216 | ```monospace 217 | {'Viivi': '050-1234567', 'Ahmed': '040-1112223', 'Pekka': '050-7654321', 'Olga': '050-1011012', 'Mary': '0401-2132139'} 218 | Anna nimi: Olga 219 | Henkilön Olga puhelinnumero on 050-1011012. 220 | ``` 221 | 222 | Kun sanakirja alustetaan arvot luettelemalla, annetaan kukin avain-arvopari seuraavasti: `avain : arvo`. Peräkkäiset 223 | avain-arvoparit erotellaan toisistaan pilkulla. 224 | 225 | Kun olemassa olevaan sanakirjaan lisätään arvo, käytetään notaatiota `sanakirja[avain] = arvo`, missä `sanakirja` on 226 | sanakirjaan viittaavan muuttujan nimi. Vastaavasti sanakirjassa olevan alkion arvo saadaan haettua 227 | kirjoittamalla `sanakirja[avain]`. 228 | 229 | Kun sanakirja läpikäydään `for/in`-rakennetta käyttäen, saa kierrosmuuttuja arvokseen vuoron perään kunkin sanakirjassa 230 | esiintyvän avaimen. 231 | 232 | Entä ovatko sanakirjan alkiot - eli avain-arvoparit - järjestettyjä? Tämän osalta tilanne riippuu Pythonin versiosta: 233 | versiosta 3.7 lukien alkiot ovat järjestettyjä, eli ajoympäristö takaa, että sanakirjan iterointijärjestys on 234 | sama kuin järjestys, jossa alkiot sanakirjaan syötettiin. Vanhemmissa Python-versioissa tätä ei taata. 235 | 236 | -------------------------------------------------------------------------------- /08_Relaatiotietokannan_käyttö.md: -------------------------------------------------------------------------------- 1 | # Relaatiotietokannan käyttö 2 | 3 | Tässä moduulissa opit käyttämään relaatiotietokantaa Python-ohjelmasta. 4 | 5 | Sitä varten oletetetaan, että olet jo tutustunut relaatiotietokannan peruskäsitteisiin (taulukot, kentät, 6 | tietueet, perus- ja viiteavaimet, tietotyypit), ja osaat ilmaista tietokantahakuja ja datan muutosoperaatioita 7 | SQL-kielellä. Osaat myös suunnitella pienen tietokannan rakenteen ja perustaa tietokannan tietokantapalvelimelle. 8 | 9 | Tässä moduulissa käytetään MariaDB-tietokantaa. Muita tiedonhallintaohjelmistoja käytettäessä prosessi on samankaltainen. 10 | 11 | Ohjelmisto 1 -opintojaksolla olet perehtynyt relaatiotietokannan laadintaan ja käytön periaatteisiin opintojakson tietokantaosuudessa. 12 | Siinä tutustuttiin myös MariaDB-tietokantaohjelmiston asentamiseen. 13 | 14 | # Tietokanta-ajuri 15 | 16 | Relaatiotietokannan käyttö itse ohjelmoidusta ohjelmasta edellyttää tietokanta-ajurin asentamista. 17 | 18 | Tällä opintojaksolla ohjelmoimme Python-kielellä ja käytämme MariaDB-tiedonhallintaohjelmistoa. Tarvittava 19 | tietokanta-ajuri on MariaDB:n ja oman Python-ohjelman välissä oleva ohjelma, 20 | joka mahdollistaa ohjelmien välisen keskustelun. 21 | 22 | Tietokanta-ajuria tarvitaan jo tietokantayhteyden muodostamiseen. Kun yhteys on muodostettu, ajurin ansiosta 23 | ohjelmasta voidaan lähettää SQL-lauseita 24 | (esimerkiksi `SELECT`-lauseita) tietokantapalvelimelle. Ajuri myös muuntaa kyselyn vastauksena saatavat tulosjoukot 25 | Pythonin tietorakenteiden mukaisiksi. 26 | 27 | Tietokanta-ajuri riippuu sekä tiedonhallintaohjelmistosta että valitusta ohjelmointikielestä. Nyt tarvitsemme siis 28 | MariaDB-ohjelmistoa tukevan ajurin Python-kielelle. Koska MariaDB on yhteensopiva MySQL-tiedonhallintaohjelmiston kanssa, voimme 29 | asentaa MySQL-ajurin Python-kielelle. 30 | 31 | Voit asentaa MySQL:n Python-ajurin jommallakummalla alla esitetyistä tavoista. Valitse asennustavoista vain toinen. 32 | - Valitse PyCharmissa **View/Tools Windows/Python Packages**. Kirjoita hakukenttään 33 | hakusanaksi **connector** ja etsi luettelosta vaihtoehto **mysql-connector-python**. Napsauta kyseistä vaihtoehtoa ja paina **Install**. 34 | - Vaihtoehtoisesti voit asentaa ajurin verkkosivun https://dev.mysql.com/downloads/connector/python/ ohjeen mukaan. 35 | 36 | Tässä materiaalissa oletetaan, että käytämme MySQL-ajuria käyttöönoton helppouden ja käytön ongelmattomuuden vuoksi 37 | sekä MySQL-ajurin pitkän historian takia (ensimmäinen MariaDB-ajuri julkaistiin vasta vuonna 2020). 38 | Niinpä vaihtoehto 1 on tämän opintojakson näkökulmasta suositeltava. Jos haluat kuitenkin asentaa MariaDB:n Connector/Python-ajuri, voit tehdä sen verkkosivun https://mariadb.com/docs/clients/mariadb-connectors/connector-python/install/ 39 | ohjeen mukaan. 40 | 41 | Kun olet asentanut MySQL:n tietokanta-ajurin, voit testata sen toiminnan kirjoittamalla yhdestä `import`-lauseesta koostuvan ohjelman: 42 | ```python 43 | import mysql.connector 44 | ``` 45 | 46 | Huomaa, että jos käytät MariaDB-ajuria, kirjaston hakeva `import`-lause 47 | poikkeaa materiaalissa esitetystä, ja ajurin toiminnassa voi olla vähäisiä eroja MySQL-ajurin toimintaan verrattuna. 48 | 49 | Jos ajuri on asentunut oikein, mitään ei tapahdu. Jos asennuksessa on ollut ongelma, saat virheilmoituksen. Korjaa asennus 50 | tarvittaessa. 51 | 52 | Asennuksen yhteydessä saatat saada virheilmoituksen, jonka mukaan tietokoneesta puuttuu Microsoftin Visual C++ -kirjasto. Jos näin kävisi, 53 | asenna puuttuva kirjasto Microsoftin sivuilta https://www.microsoft.com/en-us/download/default.aspx hyödyntäen sivuston hakutoimintoa. 54 | Virheilmoitus kertoo yksityiskohdat hakua varten. 55 | 56 | 57 | 58 | ## Tietokantayhteyden muodostaminen 59 | 60 | Tarkastellaan esimerkkitietokantaa nimeltä `ihmiset`. Tietokanta sisältää `Työntekijä`-nimisen taulukon, 61 | jonka rakenne ja sisältö ilmenevät seuraavasta näytteestä: 62 | 63 | ![Työntekijä-taulukon datanäyte](img/skeema.png) 64 | 65 | Taulukon perusavaimena on `Numero`-kenttä. Esimerkin yksinkertaisuuden vuoksi tietokannassa on vain yksi taulukko. 66 | 67 | Täydennämme ohjelmaa siten, että se ottaa tietokantayhteyden MariaDB-palvelimeen: 68 | 69 | ```python 70 | import mysql.connector 71 | 72 | yhteys = mysql.connector.connect( 73 | host='127.0.0.1', 74 | port= 3306, 75 | database='ihmiset', 76 | user='dbuser', 77 | password='sAL_a3ana', 78 | autocommit=True 79 | ) 80 | ``` 81 | 82 | Yhteys muodostetaan tietokanta-ajurin `connect`-metodin avulla. Katsotaan metodin parametreja tarkemmin: 83 | - `host` määrittää tietokoneen, johon yhteys otetaan. Kun yhteys otetaan samassa koneessa olevaan palvelimeen, 84 | jossa Python-ohjelmaa ajetaan, kirjoitetaan osoitteeksi `127.0.0.1` tai vaihtoehtoisesti `localhost`. 85 | - `port` määrittää tietoliikenneportin, jota tietokantapalvelin kuuntelee. MariaDB:n käyttämä portti on 3306, jos 86 | et ole erikseen muuttanut sitä. 87 | - `database` määrittää tietokannan nimen. 88 | - `user` määrittää käyttäjätunnuksen, jolla tietokantaa käytetään. Python-sovellusta varten kannattaa luoda oma käyttäjätunnus, 89 | jolla on tarvittavat oikeudet datan lukemiseen ja muokkaamiseen. Muita oikeuksia tuolle käyttäjätunnukselle ei yleensä tule antaa. 90 | - `password` määrittää käyttäjätunnukseen liittyvän salasanan. Huomaa, että Internetin kautta käytettäviä Python-ohjelmia ajetaan 91 | taustapalvelimella, jolloin loppukäyttäjällä ei ole pääsyä salasanan sisältävään Python-koodiin. 92 | - `autocommit` kertoo, sitoutetaanko jokainen SQL-operaatio välittömästi omana transaktionaan. Tähän 93 | kannattaa normaalisti asettaa arvoksi `True`, jolloin yksittäisten päivityslauseiden (esim. `UPDATE`) 94 | sitouttamisesta `COMMIT`-lausein ei tarvitse erikseen huolehtia. 95 | 96 | Jos ohjelma ei vieläkään tulosta mitään näkyvää, kun ajat sen, asiat ovat hyvin: ajuri on asennettu ja tietokantaan 97 | saadaan onnistuneesti yhteys. 98 | 99 | ## Hakukysely ja tulosjoukon käsittely 100 | 101 | Kirjoitetaan nyt tietokantaa käyttävä ohjelma, joka kysyy käyttäjältä sukunimen, 102 | hakee sitä vastaavien työntekijöiden tiedot tietokannasta ja esittelee kunkin työntekijän: 103 | 104 | ```python 105 | import mysql.connector 106 | 107 | def hae_työntekijät_sukunimellä(sukunimi): 108 | sql = f"SELECT Numero, Sukunimi, Etunimi, Palkka FROM Työntekijä where Sukunimi='{sukunimi}'" 109 | print(sql) 110 | kursori = yhteys.cursor() 111 | kursori.execute(sql) 112 | tulos = kursori.fetchall() 113 | if kursori.rowcount >0 : 114 | for rivi in tulos: 115 | print(f"Päivää! Olen {rivi[2]} {rivi[1]}. Palkkani on {rivi[3]} euroa kuussa.") 116 | return 117 | 118 | # Pääohjelma 119 | yhteys = mysql.connector.connect( 120 | host='127.0.0.1', 121 | port= 3306, 122 | database='ihmiset', 123 | user='dbuser', 124 | password='sAL_a3ana', 125 | autocommit=True 126 | ) 127 | 128 | sukunimi = input("Anna sukunimi: ") 129 | hae_työntekijät_sukunimellä(sukunimi) 130 | 131 | ``` 132 | 133 | Ohjelmaa ajettaessa saadaan seuraava tuloste: 134 | ```monospace 135 | Anna sukunimi: Rojola 136 | SELECT Numero, Sukunimi, Etunimi, Palkka FROM Työntekijä WHERE Sukunimi='Rojola' 137 | Päivää! Olen Mimmi Rojola. Palkkani on 5008 euroa kuussa. 138 | Päivää! Olen Topi Rojola. Palkkani on 4280 euroa kuussa. 139 | Päivää! Olen Satu Rojola. Palkkani on 2158 euroa kuussa. 140 | ``` 141 | 142 | Tietokantahaku on ohjelmoitu `haeTyöntekijätSukunimellä`-funktion sisälle. 143 | 144 | Aluksi kyselyn tuottava SQL-lause rakennetaan merkkijonomuuttujaan, jonka nimeksi ohjelmakoodissa on annettu `sql`. 145 | Ohjelmaa kirjoitettaessa SQL-lauseen toimivuus kannattaa ensin testata tietokantaeditorissa (esimerkiksi HeidiSQL), 146 | ja vasta kun kysely toimii, on aika upottaa se Python-ohjelmakoodiin. Tässä tapauksessa kyselyyn on "liimattava" 147 | sukunimen arvo, joka saadaan parametrimuuttujasta. 148 | 149 | Kun `sql`-muuttuja on rakennettu valmiiksi, se kannattaa tulostaa konsolille `print`-lauseella. Kysely menee 150 | harvoin oikein ensi yrittämällä, ja virheenjäljitys on helpompaa, kun kysely tulostetaan nähtäville. Kun kysely on todettu 151 | toimivaksi, voi tulostuslauseen poistaa tai lisätä rivin alkuun kommenttimerkin (`#`). 152 | 153 | Kun kysely on valmis, pyydetään tietokantayhteysoliolta kursoriolio. Kursorin avulla SQL-lause voidaan välittää 154 | tietokantapalvelimelle ja tarkastella tulosjoukkoa. Esimerkkikoodissa kursori pyydetään seuraavasti: 155 | 156 | ```python 157 | kursori = yhteys.cursor() 158 | ``` 159 | 160 | Tämän jälkeen kursoria pyydetään suorittamaan merkkijonomuuttujassa oleva SQL-lause: 161 | 162 | ```python 163 | kursori.execute(sql) 164 | ``` 165 | 166 | Sen jälkeen tulosjoukko on pyydetään palvelimelta: 167 | 168 | ```python 169 | tulos = kursori.fetchall() 170 | ``` 171 | 172 | Metodikutsu hakee tulosjoukon kokonaisuudessaan. Jos tulosjoukko olisi erittäin suuri, olisi mahdollista hakea sen 173 | tietueita pienissä määrin `fetchmany` ja `fetchone`-metodeilla. Tähän on harvoin tarvetta. 174 | 175 | `Tulos`-muuttujaan tallennettu tulosjoukko on rakenteeltaan lista, jonka alkiot ovat monikoita. Ulomman rakenteen (listan) kukin 176 | alkio vastaa yhtä tulosjoukon riviä. Jokainen rivi esitetään monikkona, ja sen alkioina ovat kenttien arvot 177 | siinä järjestyksessä kuin ne `SELECT`-lauseessa määriteltiin. 178 | 179 | Esimerkin tapauksessa tulosjoukkoa voi visualisoida seuraavasti: 180 | 181 | ![Tulosjoukko kaaviona](img/tulosjoukko.png) 182 | 183 | Tulosjoukkoa voi tämän jälkeen käsitellä normaaliin listojen tapaan. Esimerkissä jokaista riviä vastaavasta 184 | työntekijästä luodaan Työntekijä-olio. Luodut oliot kootaan `työntekijät`-nimiseen oliolistaan, 185 | joka palautetaan metodin paluuarvona. 186 | 187 | ## Dataa muokkaava kysely 188 | 189 | Dataa muokkaavien operaatioiden - eli `UPDATE`-, `INSERT`- ja `DELETE`-lauseiden - suorittaminen on suoraviivaisempaa 190 | kuin hakukyselyiden suoritus. Tämä johtuu siitä, että tulosjoukkoa ei tarvitse käsitellä. Näiden operaatioiden 191 | osalta tietokantapalvelin palauttaa ainoastaan tiedon siitä, moneenko tietueeseen tehty muutosoperaatio 192 | on kohdistunut. 193 | 194 | Tarkastellaan esimerkkinä työntekijän palkan muuttamista. Huomaa, että tässä esimerkissä päivitetään suoraan 195 | tietokannassa olevan palkan arvoa. Yksittäiselle työntekijälle tehtävä muutos voitaisiin vaihtoehtoisesti 196 | toteuttaa siten, että työntekijän tiedot haettaisiin tietokannasta, muutos tehtäisiin Python-kielen tasolla 197 | olion ominaisuuteen ja lopuksi olion muuttunut tila päivitettäisiin tietokantaan. Alla olevassa esimerkissä 198 | näin ei siis tehdä. 199 | 200 | Kirjoitetaan tietokannassa olevan palkan päivittämiseksi toinen globaali funktio, 201 | joka rakentaa ja suorittaa sitä vastaavan `UPDATE`-lauseen: 202 | 203 | ```python 204 | def päivitä_palkkaa(numero, uusi_palkka): 205 | sql = f"UPDATE Työntekijä SET Palkka={uusi_palkka} WHERE Numero={numero}" 206 | print(sql) 207 | kursori = yhteys.cursor() 208 | kursori.execute(sql) 209 | if kursori.rowcount==1: 210 | print("Palkka päivitetty") 211 | ``` 212 | 213 | Lisätään pääohjelman loppuun syötteen luku- ja funktion kutsulauseet: 214 | ```python 215 | numero = int(input("Anna numero: ")) 216 | uusi_palkka = float(input("Anna uusi palkka: ")) 217 | päivitä_palkkaa(numero, uusi_palkka) 218 | ``` 219 | 220 | Juuri kirjoitettu funktio vahvistaa tietokantaan tehdyn muutoksen: 221 | ```monospace 222 | Anna numero: 2 223 | Anna uusi palkka: 3456 224 | UPDATE Työntekijä SET Palkka=3456.0 WHERE Numero=2 225 | Palkka päivitetty 226 | ``` 227 | 228 | Onnistuneen päivityksen voi varmistaa tietokantaeditorin avulla suoraan tietokannasta. 229 | 230 | -------------------------------------------------------------------------------- /09_Olio-ohjelmoinnin_lähtökohdat.md: -------------------------------------------------------------------------------- 1 | # Luokka, olio, alustaja 2 | 3 | Tässä moduulissa opit olio-ohjelmoinnin lähtökohdat. Opit kirjoittamaan luokkia, jotka määrittävät yhteiset ominaisuudet 4 | ja operaatiot luokan ilmentymille eli olioille. Opit olioiden luonnin, alustamisen ja käytön periaatteet. 5 | 6 | ## Luokat ja oliot 7 | 8 | Olio-ohjelmoinnissa luokalla tarkoitetaan yleiskäsitettä, joka määrittää yleiset ja yhteiset piirteet, joita 9 | sen jäsenillä on. 10 | 11 | Esimerkiksi **koira** on tällainen yleiskäsite. Jokaisella koiralla on joukko ominaisuuksia 12 | kuten nimi ja syntymävuosi. Lisäksi koiralla on toimintoja (eli Python-termein metodeja) kuten haukkuminen. 13 | 14 | Voimme nyt kirjoittaa pienimmän mahdollisen Koira-luokan seuraavasti: 15 | ```python 16 | class Koira: 17 | pass 18 | ``` 19 | 20 | Edellä `pass`-lause on tyhjä lause, joka ei tee mitään. Se tarvitaan paikkamerkiksi, sillä luokan määrityksen rungossa on oltava 21 | ainakin yksi lause. 22 | 23 | Tämä luokan määritys vain kertoo, että on olemassa Koira-luokka. Se ei toistaiseksi ota kantaa koirien ominaisuuksiin 24 | eikä niiden metodeihin. 25 | 26 | Voimme käyttää Koira-luokkaa siten, että luomme tuosta luokasta olion. Olio tarkoittaa luokan ajonaikaista ilmentymää 27 | eli realisaatiota. Luomme näin Koira-olion, joka on nimeltään Rekku ja jonka syntymävuosi on 2022: 28 | 29 | 30 | 31 | ```python 32 | class Koira: 33 | pass 34 | 35 | koira = Koira() 36 | koira.nimi = "Rekku" 37 | koira.syntymävuosi = 2022 38 | 39 | print (f"{koira.nimi:s} on syntynyt vuonna {koira.syntymävuosi:d}." ) 40 | ``` 41 | 42 | Pääohjelman ensimmäinen lause luo Koira-olion, johon viitataan muuttujalla `koira`. Luodulle koiralle annetaan 43 | nimeksi Rekku ja syntymävuodeksi 2022. Nämä ovat luodun olion ominaisuuksia, ja ne ovat oliokohtaisia. Voisimme siis luoda 44 | monta koiraa, joista jokaisella olisi oma yksilöllinen nimensä ja syntymävuotensa. Jollekin koirista voisimme 45 | lisäksi määrittää rodun ja jollekin lempinimen. Olioiden ominaisuudet voivat siis poiketa toisistaan. 46 | 47 | Kuten esimerkistä näkyy, olion ominaisuuteen viitataan kirjoittamalla ensin olion nimi, sitten piste ja lopuksi 48 | ominaisuuden nimi. Esimerkki tällaisesta viittauksesta on `koira.nimi`. 49 | 50 | Esimerkkiohjelman viimeinen lause tulostaa pääohjelman luoman koiraolion nimen ja syntymävuoden: 51 | ```monospace 52 | Rekku on syntynyt vuonna 2022. 53 | ``` 54 | 55 | Huomaa Python-kielen vakiintunut kirjoitustapa luokkien nimille: ne kirjoitetaan isoin alkukirjaimin. Jos luokan nimi koostuu 56 | useammasta sanasta, sanat kirjoitetaan yhteen ilman alaviivamerkkiä siten, että kukin sana aloitetaan isolla 57 | alkukirjaimella. Tästä kirjoitustyylistä käytetään nimitystä *CamelCase*. Esimerkki tällaisesta luokan nimestä 58 | on `NäytönSuorakulmio`. 59 | 60 | 61 | ## Alustaja eli konstruktori 62 | 63 | Edellä Koira-olio luotiin siten, että ensin tehtiin olio ilman ominaisuuksia, ja sen jälkeen oliolle määritettiin 64 | ominaisuudet yksi kerrallaan. Tämä on ohjelmoijalle melko työläs tapa luoda olioita. 65 | 66 | Olioiden luonnin helpottamiseksi luokan sisälle kirjoitetaan usein alustaja eli konstruktori, joka automaattisesti 67 | asettaa halutut arvot luotavan olion ominaisuuksiksi. Seuraavan esimerkin luokassa on nimen ja syntymävuoden asettava 68 | alustaja. Pääohjelma käyttää olion luonnissa juuri laadittua alustajaa: 69 | 70 | ```python 71 | class Koira: 72 | def __init__(self, nimi, syntymävuosi): 73 | self.nimi = nimi 74 | self.syntymävuosi = syntymävuosi 75 | 76 | koira = Koira("Rekku", 2022) 77 | 78 | print (f"{koira.nimi:s} on syntynyt vuonna {koira.syntymävuosi:d}." ) 79 | ``` 80 | 81 | Python-kielen alustaja määritetään luokan sisällä kirjoittamalla funktio, jonka nimenä 82 | on `__init__`. Funktion ensimmäiseksi parametriksi määritetään aina `self`. Tämän jälkeen määritellään 83 | muut parametrit, jotka alustajalle halutaan antaa. Tässä tapauksessa ne ovat nimi ja syntymävuosi. Näin kirjoitettu 84 | ja nimetty funktio tulkitaan ohjelmaa ajettaessa automaattisesti alustajaksi, ja se suoritetaan aina, kun uusi 85 | olio luodaan. Alustajan loppuun ei kirjoiteta return-lausetta. 86 | 87 | Alustajan sisällä on kaksi sijoituslausetta, joilla annetaan arvot luotavan olion ominaisuuksille. 88 | Uuden olion ominaisuuksiin viitataan kirjoittamalla varattu sana `self`, minkä jälkeen tulee piste ja halutun 89 | ominaisuuden nimi. Uuden olion ominaisuuden arvoksi annetaan tyypillisesti alustajan parametrina saatu arvo. Esimerkiksi 90 | lause `self.nimi = nimi` antaa uuden olion nimi-ominaisuuden arvoksi nimi-parametrimuuttujan arvon. 91 | 92 | Huomaa, että oliota luotaessa alustajan ensimmäinen parametri `self` ohitetaan kokonaan. Ei siis kirjoiteta: 93 | ```python 94 | # Virheellinen luontilause 95 | koira = Koira(self, "Rekku", 2022) 96 | ``` 97 | vaan kirjoitetaan: 98 | ```python 99 | koira = Koira("Rekku", 2022) 100 | ``` 101 | 102 | 103 | ## Metodit 104 | 105 | Edellä opittiin määrittämään oliolle ominaisuuksia. Tämän lisäksi olioille halutaan usein määrittää toimintoja, joita 106 | kutsutaan metodeiksi. Kirjoitetaan alla Koira-luokkaan hauku-metodi, jota voidaan kutsua Koira-luokan ilmentymille eli 107 | olioille. Esimerkin ohjelma luo kaksi Koira-oliota ja käskee niitä haukkumaan kyseiselle oliolle 108 | tyypilliseen tapaan: 109 | 110 | ```python 111 | class Koira: 112 | def __init__(self, nimi, syntymävuosi, haukahdus="Vuh-vuh"): 113 | self.nimi = nimi 114 | self.syntymävuosi = syntymävuosi 115 | self.haukahdus = haukahdus 116 | 117 | def hauku(self, kerrat): 118 | for i in range(kerrat): 119 | print(self.haukahdus) 120 | return 121 | 122 | 123 | koira1 = Koira("Muro", 2018) 124 | koira2 = Koira("Rekku", 2022, "Viu viu viu") 125 | 126 | koira1.hauku(2) 127 | koira2.hauku(5) 128 | ``` 129 | 130 | Alustajan parametreja on nyt kolme. Viimeiselle parametrille (`haukahdus`) on annettu oletusarvo, joka asetetaan 131 | silloin, kun parametria ei oliota luetaessa anneta. Esimerkissä Muro-koira saa siis oletushaukahduksen. 132 | 133 | Luokan sisään kirjoitettiin `hauku`-niminen metodi, jota voidaan kutsua mille tahansa olemassa olevalle Koira-luokan 134 | ilmentymälle. Metodin ensimmäiseksi parametriksi asetetaan aina `self`. Tämän jälkeen luetellaan muut parametrit, joiden 135 | arvo annetaan metodia kutsuttaessa. 136 | 137 | Metodin kutsu tehdään siten, että kirjoitetaan olion nimi, sen jälkeen piste ja lopuksi metodin nimi kaarisulkeineen 138 | ja mahdollisine parametreineen. Esimerkiksi kutsu `koira1.hauku(2)` kutsuu koira1-olion hauku-metodia. Metodikutsun parametrina 139 | välitetään haukahdusten määrä (2). Metodin sisällä voidaan viitata olion omiin ominaisuuksiin siten, 140 | että kirjoitetaan `self`, jota seuraa piste ja ominaisuuden nimi. Esimerkiksi ilmaisu `self.haukahdus` viittaa kulloisenkin 141 | olion `haukahdus`-ominaisuuden arvoon. 142 | 143 | ## Luokkamuuttuja eli staattinen muuttuja 144 | 145 | Aiemmassa esimerkissä koiran ominaisuuksina olivat nimi, syntymävuosi ja haukahdus. Olion ominaisuudet ovat tietenkin oliokohtaisia, eli yhdellä koiralla voi olla eri nimi kuin toisella koiralla. 146 | 147 | Toisinaan on tarve tallentaa jokin koko luokkaa koskeva tieto, joka ei liity mihinkään yksittäiseen luokan olioon. 148 | Esimerkiksi `Koira`-luokan tapauksessa tällainen ominaisuus voisi olla tieto siitä, montako koiraa (eli `Koira`-luokan ilmentymää) kaiken kaikkiaan on luotu. Tällainen tieto voidaan tallentaa luokkamuuttujaan eli staattiseen muuttujaan. 149 | 150 | Seuraavassa esimerkissä on laadittu `tehty`-niminen luokkamuuttuja lukumäärän tallentamiseksi. Huomaa muuttujan sijoittaminen alustajan ulkopuolelle. Luokkamuuttujan määrityslauseeseen ei lisätä `self.`-etuliitettä. (Esimerkistä on jätetty pois aiempi haukkumistoiminnon toteutus.) 151 | 152 | ```python 153 | class Koira: 154 | 155 | tehty = 0 156 | 157 | def __init__(self, nimi, syntymävuosi, haukahdus="Vuh-vuh"): 158 | self.nimi = nimi 159 | self.syntymävuosi = syntymävuosi 160 | self.haukahdus = haukahdus 161 | Koira.tehty = Koira.tehty + 1 162 | 163 | koira1 = Koira("Muro", 2018) 164 | koira2 = Koira("Rekku", 2022, "Viu viu viu") 165 | print (f"Koiria on nyt {Koira.tehty}.") 166 | ``` 167 | 168 | Luokkamuuttujan arvoon viitataan kirjoittamalla sekä luokan että luokkamuuttujan nimi, esimerkissä siis `Koira.tehty`. 169 | 170 | Ohjelma tuottaa seuraavan tulosteen: 171 | ```monospace 172 | Koiria on nyt 2. 173 | ``` 174 | -------------------------------------------------------------------------------- /10_Assosiaatio.md: -------------------------------------------------------------------------------- 1 | # Assosiaatio 2 | 3 | Tässä moduulissa opit kirjoittamaan ohjelmia, joissa oliot ovat vuorovaikutuksessa keskenään. 4 | 5 | Olio-ohjelmoinnissa ohjelma koostetaan luokista. Niistä luodaan ajon aikana ilmentymiä eli olioita. 6 | Oliot voivat olla vuorovaikutuksessa keskenään: olio voi käsitellä toisia olioita ja kutsua niiden metodeja. 7 | 8 | Tätä olioiden välistä tuntemissuhdetta kutsutaan assosiaatioksi. Assosiaatiosuhteet ohjelmoimalla saavutetaan 9 | olio-ohjelmoinnin voima: ohjelma pilkkoutuu pieniksi, helposti ymmärrettäviksi palasiksi, ja ohjelmoija 10 | voi kirjoittaa koodia vähän kerrallaan yhteen asiaan keskittyen. Kun olioiden väliset assosiaatiot 11 | on hyvin suunniteltu, suurikin ohjelmakokonaisuus rakentuu näistä pienistä osista ikään kuin varkain. 12 | 13 | ## Rakenteen suunnittelu 14 | 15 | Aiemmassa moduulissa kirjoitimme `Koira`-luokan, jossa määritetään koiran ominaisuudet (nimi, syntymävuosi ja yksilöllinen 16 | haukahdus). Lisäksi luokkaan on kirjoitettu yksi metodi: `hauku`. `Koira`-luokka on seuraavanlainen: 17 | 18 | ```python 19 | class Koira: 20 | def __init__(self, nimi, syntymävuosi, haukahdus="Vuh-vuh"): 21 | self.nimi = nimi 22 | self.syntymävuosi = syntymävuosi 23 | self.haukahdus = haukahdus 24 | 25 | def hauku(self, kerrat): 26 | for i in range(kerrat): 27 | print(self.nimi + " haukkuu: " + self.haukahdus) 28 | return 29 | ``` 30 | 31 | Laajennetaan esimerkkiä siten, että perustamme koirahoitolan. Määritellään koirahoitola seuraavasti: 32 | koira voidaan viedä hoitoon koirahoitolaan ja hakea 33 | sieltä pois. Välillä hoitolan työntekijä tekee hoitolassa kierroksen; silloin hän tervehtii kaikkia koiria, ja jokainen koira haukahtaa. 34 | 35 | Aloitetaan pohtimalla sitä, mitä koirahoitolan toteuttamiseksi vaaditaan. 36 | 37 | Ensinnäkin koirahoitola kannattaa toteuttaa omana luokkanaan. Hoitolan toiminnallisuus ei liity mitenkään yksittäiseen 38 | koiraan, eikä sitä tule kirjoittaa Koira-luokan sisään. Kirjoitamme siis ohjelmaan toisen luokan nimeltä Hoitola. 39 | 40 | Sitten pohdimme, mitä ominaisuuksia koirahoitolalla on. Huomaamme, että hoitolan täytyy 41 | olla tietoinen siitä, mitä koiria siellä kulloinkin on hoidossa. Sen voimme toteuttaa listan avulla: liitetään 42 | hoitolan ominaisuudeksi lista, jonka alkiot ovat koiria. 43 | 44 | Entä onko hoitolalla toimintoja, jotka on syytä kirjoittaa metodeiksi? 45 | Äsken tehdystä hoitolan määrittelystä tunnistetaan, että koirahoitolalle kannattaa laatia kolme 46 | metodia: 47 | 1. koiran kirjaaminen sisään hoitolaan 48 | 2. koiran kirjaaminen ulos hoitolasta 49 | 3. kierroksen tekeminen hoitolassa. 50 | 51 | Nyt ohjelma on määritelty ja suunniteltu, ja pääsemme toteuttamaan sen. 52 | 53 | ## Kahdesta luokasta koostuva ohjelma 54 | 55 | Esimerkkiohjelmassamme on kaksi luokkaa: `Koira` ja `Hoitola`. Python-kielessä samaan lähdekooditiedostoon voidaan 56 | kirjoittaa monta luokaa, ja näin usein tehdäänkin. Luokat voisivat sijaita myös eri tiedostoissa. Jos tähän ratkaisuun päädytään, 57 | on toiseen tiedostoon viittaaminen mahdollista vain, jos ohjelman alkuun liitetään toisen tiedoston (eli moduulin) 58 | esittelevä `import`-lause. 59 | 60 | Pienissä ohjelmissa on näppärää kirjoittaa luokat samaan tiedostoon, ja niin teemme nytkin. Luomme koirahoitola.py-nimisen 61 | tiedoston, ja ohjelmoimme sinne vaadittavan toiminnallisuuden: 62 | 63 | ```python 64 | class Koira: 65 | def __init__(self, nimi, syntymävuosi, haukahdus="Vuh-vuh"): 66 | self.nimi = nimi 67 | self.syntymävuosi = syntymävuosi 68 | self.haukahdus = haukahdus 69 | 70 | def hauku(self, kerrat): 71 | for i in range(kerrat): 72 | print(self.nimi + " haukkuu: " + self.haukahdus) 73 | return 74 | 75 | class Hoitola: 76 | def __init__(self): 77 | self.koirat = [] 78 | 79 | def koira_sisään(self, koira): 80 | self.koirat.append(koira) 81 | print(koira.nimi + " kirjattu sisään") 82 | return 83 | 84 | def koira_ulos(self, koira): 85 | self.koirat.remove(koira) 86 | print(koira.nimi + " kirjattu ulos") 87 | return 88 | 89 | def tervehdi_koiria(self): 90 | for koira in self.koirat: 91 | koira.hauku(1) 92 | 93 | # Pääohjelma 94 | 95 | koira1 = Koira("Muro", 2018) 96 | koira2 = Koira("Rekku", 2022, "Viu viu viu") 97 | 98 | hoitola = Hoitola() 99 | 100 | hoitola.koira_sisään(koira1) 101 | hoitola.koira_sisään(koira2) 102 | hoitola.tervehdi_koiria() 103 | 104 | hoitola.koira_ulos(koira1) 105 | hoitola.tervehdi_koiria() 106 | ``` 107 | 108 | Esimerkkiohjelma koostuu kolmesta osasta: 109 | 1. `Koira`-luokasta 110 | 2. `Hoitola`-luokasta 111 | 3. pääohjelmasta. 112 | 113 | Ohjelman suoritus alkaa pääohjelman alusta. Aluksi luodaan kaksi koiraa, Muro ja Rekku. Sitten luodaan uusi 114 | hoitola: 115 | ```python 116 | hoitola = Hoitola() 117 | ``` 118 | 119 | Tässä vaiheessa suoritus siirtyy Hoitola-luokan alustajaan, jossa luotavan hoitolan ominaisuudeksi 120 | lisätään koirat-niminen tyhjä lista. Vasta luodussa hoitolassa ei vielä ole yhtään koiraa, mutta siinä on nyt 121 | olemassa lista, johon koirat voidaan aikanaan lisätä. 122 | 123 | Tämän jälkeen ensimmäinen koira (Muro) kirjataan sisään hoitolaan: 124 | ```python 125 | hoitola.koira_sisään(koira1) 126 | ``` 127 | Kyseessä on hoitolan tarjoama metodi: sisäänkirjaus on selkeästi hoitolan toiminto, ja se on sen vuoksi ohjelmoitu 128 | `Hoitola`-luokkaan. Sisäänkirjauksen yhteydessä on tietenkin kerrottava, mitä koiraa ollaan kirjaamassa. Tätä varten 129 | `Koira`-olio (tai oikeastaan viittaus siihen) annetaan metodikutsun argumenttina. Metodikutsun seurauksena suoritus siirtyy 130 | `koira_sisään`-metodiin, jossa parametrina saatu koira lisätään hoitolan koiralistaan. 131 | 132 | Samaan tapaan hoitolaan lisätään toinen koira, Rekku. 133 | 134 | Sitten hoitajan on aika tehdä hoitolassa kierros ja tervehtiä kaikkia koiria. Tätä varten kutsutaan vastaavaa `Hoitola`-luokkaan 135 | kirjoitettua metodia: 136 | ```python 137 | hoitola.tervehdi_koiria() 138 | ``` 139 | Tämä metodi toteutettiin parametrittomana. Tervehtiminen kohdistuu kaikkiin hoitolassa kulloinkin oleviin koiriin, ja 140 | hoitola itse tietää, mitä koiria siellä kulloinkin on hoidossa. Metodi käy läpi koirien listan ja käskee 141 | kutakin koiraa haukahtamaan yhden kerran. 142 | 143 | Lopuksi esimerkkiohjelmassa kirjataan ulos yksi koira, Muro. Tätä varten kutsutaan `Hoitola`-luokkaan kirjoitettua vastaavaa 144 | metodia, joka poistaa annetun alkion listasta. Tämän jälkeen koiria tervehditään jälleen, mutta tervehdykseen on 145 | vastaamassa enää Rekku. 146 | 147 | Ohjelman toiminta ilmenee sen tuottamasta tulosteesta: 148 | ```python 149 | Muro kirjattu sisään 150 | Rekku kirjattu sisään 151 | Muro haukkuu: Vuh-vuh 152 | Rekku haukkuu: Viu viu viu 153 | Muro kirjattu ulos 154 | Rekku haukkuu: Viu viu viu 155 | ``` 156 | 157 | Näin kirjoitimme ohjelman, jossa on ilmentymiä (eli olioita) kahdesta eri luokasta. Sanomme, että `Hoitola`- 158 | ja `Koira`-luokkien välillä on pysyvä assosiaatiosuhde: `Hoitola`-oliolla on instanssimuuttuja, joka sisältää viittaukset 159 | `Koira`-olioihin. 160 | 161 | Assosiaatiosuhde on tässä yksisuuntainen: `Hoitola`-olio tietää, mitä koiria kulloinkin on hoidossa. `Koira`-olio sen sijaan 162 | ei tiedä mitään hoitolasta, jossa se mahdollisesti on. Assosiaatiosuhde voidaan toteuttaa yksi- tai kaksisuuntaisena. 163 | Kaksisuuntainen assosiaatiosuhde kannattaa ottaa käyttöön vain silloin, kun sille on hyvät perusteet. Tällöin 164 | ohjelmoijalle tulee ylimääräistä kuormaa siitä, että eri suuntiin olevien olioviittausten on oltava sisällöiltään 165 | synkronoidut. 166 | 167 | 168 | ## Tilapäinen assosiaatiosuhde 169 | 170 | Edellä todettiin, että esimerkin `Hoitola`- ja `Koira`-luokkien välillä oli pysyvä assosiaatiosuhde: hoitolan koirat 171 | on tallennettu hoitolan ominaisuutena olevaan koiralistaan. 172 | 173 | `Hoitola`- ja `Koira`-luokkien välillä on myös toisenlainen riippuvuus: `Hoitola`-luokka tarjoaa kaksi metodia, joiden 174 | parametrina annetaan viittaus `Koira`-olioon. Assosiaatiosuhde voi olla voimassa vain metodikutsun ajan silloin, 175 | kun toisen luokan ilmentymä kerrotaan metodin parametrina. Kun metodin kutsu päättyy, katoaisi metodin suorituksen 176 | aikainen assosiaatiosuhdekin, ellei tietoa suhteesta ole tallennettu ominaisuudeksi, kuten esimerkissämme on tehty. 177 | 178 | Tarkastellaan nyt esimerkkiä tilanteesta, jossa assosiaatiosuhde on puhtaasti tilapäinen: auton ja maalaamon välistä 179 | suhdetta. Esimerkissä luodaan sininen auto ja annetaan se maalaamolle maalattavaksi punaiseksi: 180 | 181 | ```python 182 | class Auto: 183 | def __init__(self, rekisteritunnus, väri): 184 | self.rekisteritunnus = rekisteritunnus 185 | self.väri = väri 186 | 187 | class Maalaamo: 188 | def maalaa(self, auto, väri): 189 | auto.väri = väri 190 | 191 | maalaamo = Maalaamo() 192 | auto = Auto("ABC-123", "sininen") 193 | print("Auto on " + auto.väri) 194 | maalaamo.maalaa(auto, "punainen") 195 | print("Auto on nyt " + auto.väri) 196 | ``` 197 | 198 | Ohjelma tulostaa auton värin ennen ja jälkeen maalauksen: 199 | 200 | ```monospace 201 | Auto on sininen 202 | Auto on nyt punainen 203 | ``` 204 | 205 | Tässä esimerkissä maalaamo tuntee maalattavan auton vain `maalaa`-metodin suorituksen ajan, sillä viittaus `Auto`-olioon 206 | on saatu metodikutsun parametrina. Kun metodin suoritus päättyy, parametrimuuttujan arvoon ei enää pääse käsiksi. 207 | Myöskään auto ei tiedä maalaamosta mitään. Maalaamon ja auton assosiaatiosuhde on tässä esimerkissä tilapäinen. 208 | -------------------------------------------------------------------------------- /11_Periytyminen.md: -------------------------------------------------------------------------------- 1 | # Periytyminen 2 | 3 | Periytyminen on olio-ohjelmoinnin mekanismi, jossa luokkien välille muodostetaan hierarkia: yksittäisestä 4 | luokasta, jotka kutsutaan yliluokaksi, johdetaan sitä tarkentavia ja täsmentäviä aliluokkia. 5 | 6 | Tässä moduulissa opit käyttämään periytymissuhdetta kirjoittaessasi oliopohjaisia Python-olioita. 7 | 8 | ## Yliluokka ja aliluokka 9 | 10 | Tarkastellaan seuraavaa tilannetta, jossa ohjelma käsittelee Työntekijä-olioita: 11 | 12 | ```python 13 | class Työntekijä: 14 | 15 | työntekijöiden_lukumäärä = 0 16 | 17 | def __init__(self, etunimi, sukunimi): 18 | Työntekijä.työntekijöiden_lukumäärä = Työntekijä.työntekijöiden_lukumäärä + 1 19 | self.työntekijänumero = Työntekijä.työntekijöiden_lukumäärä 20 | self.etunimi = etunimi 21 | self.sukunimi = sukunimi 22 | 23 | def tulosta_tiedot(self): 24 | print(f"{self.työntekijänumero}: {self.etunimi} {self.sukunimi}") 25 | 26 | työntekijät = [] 27 | työntekijät.append(Työntekijä("Viivi", "Virta")) 28 | työntekijät.append(Työntekijä("Ahmed", "Habib")) 29 | 30 | for t in työntekijät: 31 | t.tulosta_tiedot() 32 | ``` 33 | 34 | Ohjelma luo kaksi työntekijä: Viivin ja Ahmedin, lisää heidät työntekijälistaan ja tulostaa listan sisällön: 35 | 36 | ``` monospace 37 | 1: Viivi Virta 38 | 2: Ahmed Habib 39 | ``` 40 | 41 | Jokaisella työntekijällä on kolme ominaisuutta: työntekijänumero, etunimi ja sukunimi. Työntekijänumero annetaan 42 | jokaiselle työntekijälle automaattisesti työntekijöiden lukumäärän perusteella. Työntekijöiden lukumäärä on 43 | tässä luokkamuuttuja: sen arvoa ei ole määritelty erikseen jokaiselle Työntekijä-luokan oliolle, vaan 44 | arvo on määritelty kertaalleen Työntekijä-luokalle. Huomaa, että luokkamuuttuja määritellään alustajan ulkopuolella, 45 | ja siihen viitattaessa käytetään `self`-sana korvataan luokan nimellä. 46 | 47 | 48 | Oletetaan, että kohtaamme uuden kehitystarpeen: osa työntekijöistä on tuntipalkkaisia työntekijöitä 49 | ja osa kuukausipalkkaisia. Miten palkkatieto tulisi lisätä Työntekijä-luokan ominaisuuksien luetteloon? 50 | 51 | Yksi ratkaisu olisi lisätä kaksi eri ominaisuutta: tuntipalkka ja kuukausipalkka. Ratkaisu olisi 52 | kuitenkin epätarkka, ja sovellusta käytettäessä joutuisimme aina tarkastamaan kenttien arvojen perusteella, 53 | kumpi työntekijän alatyyppi on kyseessä. Lisäksi mikään ei teknisesti estäisi meitä määrittämästä 54 | samalle työntekijälle sekä tunti- että kuukausipalkkaa. 55 | 56 | Otetaan ratkaisuksi Python-kielen periytymismekanismi. 57 | Kirjoitetaan Työntekijä-luokalle kaksi tarkentavaa aliluokkaa: 58 | Tuntipalkkainen ja Kuukausipalkkainen. Kun luomme uuden olion, voimme tehdä siitä esimerkiksi Tuntipalkkainen-aliluokan 59 | ilmentymän. Tällöin sillä on kaikki Työntekijä-yliluokasta perityt ominaisuudet ja metodit (kuten etunimi-ominaisuus 60 | ja työskentele-metodi), mutta niiden lisäksi käytössä on vain tuntipalkkaisille työntekijöille aliluokassa määritetty 61 | tuntipalkka-ominaisuus. 62 | 63 | Laajennettu ohjelma näyttää tältä: 64 | 65 | ```python 66 | class Työntekijä: 67 | 68 | työntekijöiden_lukumäärä = 0 69 | 70 | def __init__(self, etunimi, sukunimi): 71 | Työntekijä.työntekijöiden_lukumäärä = Työntekijä.työntekijöiden_lukumäärä + 1 72 | self.työntekijänumero = Työntekijä.työntekijöiden_lukumäärä 73 | self.etunimi = etunimi 74 | self.sukunimi = sukunimi 75 | 76 | def tulosta_tiedot(self): 77 | print(f"{self.työntekijänumero}: {self.etunimi} {self.sukunimi}") 78 | 79 | class Tuntipalkkainen(Työntekijä): 80 | 81 | def __init__(self, etunimi, sukunimi, tuntipalkka): 82 | self.tuntipalkka = tuntipalkka 83 | super().__init__(etunimi, sukunimi) 84 | 85 | def tulosta_tiedot(self): 86 | super().tulosta_tiedot() 87 | print(f" Tuntipalkka: {self.tuntipalkka}") 88 | 89 | class Kuukausipalkkainen(Työntekijä): 90 | 91 | def __init__(self, etunimi, sukunimi, kuukausipalkka): 92 | self.kuukausipalkka = kuukausipalkka 93 | super().__init__(etunimi, sukunimi) 94 | 95 | def tulosta_tiedot(self): 96 | super().tulosta_tiedot() 97 | print(f" Kuukausipalkka: {self.kuukausipalkka}") 98 | 99 | 100 | työntekijät = [] 101 | työntekijät.append(Tuntipalkkainen("Viivi", "Virta", 12.35)) 102 | työntekijät.append(Kuukausipalkkainen("Ahmed", "Habib", 2750)) 103 | työntekijät.append(Työntekijä("Pekka", "Puro")) 104 | työntekijät.append(Tuntipalkkainen("Olga", "Glebova", 14.92)) 105 | 106 | for t in työntekijät: 107 | t.tulosta_tiedot() 108 | 109 | ``` 110 | 111 | Esimerkissä luodaan kaksi tuntipalkkaista työntekijää, yksi kuukausipalkkainen työntekijä sekä yksi työntekijä (Pekka), jonka 112 | osalta ei oteta kantaa, minkälainen hänen työsuhteensa on. 113 | 114 | Toistorakenne tuottaa seuraavan tulosteen: 115 | ```monospace 116 | 1: Viivi Virta 117 | Tuntipalkka: 12.35 118 | 2: Ahmed Habib 119 | Kuukausipalkka: 2750 120 | 3: Pekka Puro 121 | 4: Olga Glebova 122 | Tuntipalkka: 14.92 123 | ``` 124 | 125 | 126 | Yliluokka-aliluokkasuhde ilmaistaan Pythonissa siten, että aliluokan määrittävään `class`-lauseeseen 127 | lisätään sulkeisiin yliluokan nimi. Siis lauseen alku `class Tuntipalkkainen(Työntekijä)` määrää, 128 | että Tuntipalkkainen-luokasta tulee Työntekijä-luokan aliluokka. 129 | 130 | Aliluokalle kirjoitetaan tarvittaessa oma alustaja. Kun aliluokan ilmentymä luodaan, vain aliluokkaan 131 | kirjoitettu alustaja suoritetaan. Käytännössä usein halutaan toimia kuten esimerkissämme: ohjelmassa kutsutaan 132 | aliluokan alustajaa, joka puolestaan kutsuu yliluokan alustajaa. Tässä tapauksessa aliluokan alustaja 133 | antaa arvon tuntipalkalle, kun taas etunimi ja sukunimi on määritetty yliluokassa. Niiden arvot välitetään 134 | yliluokalle kutsumalla yliluokan alustajaa eli `__init__`-metodia. Olion yliluokkaan päästään käsiksi 135 | super()-funktion avulla: lause `super().__init__(etunimi, sukunimi)` kutsuu siis yliluokan alustajaa, 136 | joka saa parametreinaan etu- ja sukunimen. 137 | 138 | Yliluokassa määritetyt ominaisuudet näkyvät automaattisesti aliluokkaan. Voimme siis luoda `Tuntipalkkainen`-luokan ilmentymän 139 | `t` ja milloin tahansa hakea hänen etunimensä ilmaisulla `t.etunimi`. 140 | 141 | ## Metodien ylikirjoittaminen 142 | 143 | Kun tarkastelemme edellä olevaa esimerkkiä, havaitsemme, että `Työntekijä`-yliluokkaan kirjoitettu `tulosta_tiedot`-metodi 144 | tulostaa henkilön etu- ja sukunimen. Metodi toimii hyvin silloin, kun henkilö on luotu `Työntekijä`-luokan 145 | ilmentymäksi ottamatta kantaa siihen, onko hän tunti- vai kuukausipalkkainen. Toisaalta esimerkiksi tuntipalkkaisten 146 | työntekijöiden tietojen tulostamiseen metodi on liian suppea: se tulostaa nimitiedot mutta ei pääse käsiksi 147 | aliluokassa määritettyyn tuntipalkkaan. 148 | 149 | Ongelma ratkeaa ylikirjoittamalla `tulosta_tiedot`-metodi. Ylikirjoittaminen tarkoittaa sitä, että aliluokkaan 150 | luodaan toinen toteutus yliluokassa olevasta metodista. Aliluokassa oleva, ylikirjoitettu metodi menee edelle 151 | yliluokassa määritellystä metodista. Kun siis kirjoitamme `t.tulosta_tiedot()` oliolle, joka on 152 | `Tuntipalkkainen`-luokan ilmentymä, kutsutaan automaattisesti `Tuntipalkkainen`-luokassa olevaa versiota metodista. 153 | Jos sama metodikutsu kirjoitetaan `Työntekijä`-luokan ilmentymäksi luodulle oliolle, kutsutaan yliluokassa 154 | olevaa versiota. 155 | 156 | Ylikirjoittamisen ansiosta saamme ohjelmaan joustavuutta: työntekijät voivat olla keskenään erilaisia, ja niiden 157 | tietorakenteet voivat poiketa toisistaan. Tästä huolimatta kaikkien työntekijöiden tiedot saadaan pääohjelmassa 158 | tulostettua yksinkertaisella toistorakenteella: 159 | 160 | ``` 161 | for t in työntekijät: 162 | t.tulosta_tiedot() 163 | ``` 164 | 165 | Kutsuttavan metodin muunnelmat ja toteutustekniset yksityiskohdat on piilotettu sinne minne ne kuuluvat: toteuttaviin luokkiin. 166 | Pääohjelmaan ne eivät säteile. 167 | 168 | ## Moniperintä 169 | 170 | Toisinaan on tilanteita, joissa sama luokka halutaan määrittää kahden tai jopa useamman luokan aliluokaksi. 171 | Tätä piirrettä kutsutaan moniperinnäksi. Python-kielessä moniperintä on sallittua toisin kuin joissain 172 | muissa olio-ohjelmointikielissä. 173 | 174 | Seuraava esimerkki kuvaa moniperintää. Määritetään kaksi luokkaa: `Kulkuneuvo` ja `Urheiluväline`. Kolmas luokka `Polkupyörä` voidaan 175 | asettaa molempien mainittujen luokkien aliluokaksi. 176 | 177 | ```python 178 | class Kulkuneuvo: 179 | def __init__(self, nopeus): 180 | self.nopeus = nopeus 181 | 182 | class Urheiluväline: 183 | def __init__(self, paino): 184 | self.paino = paino 185 | 186 | class Polkupyörä(Kulkuneuvo, Urheiluväline): 187 | def __init__(self, nopeus, paino, vaihteet): 188 | Kulkuneuvo.__init__(self, nopeus) 189 | Urheiluväline.__init__(self, paino) 190 | 191 | self.vaihteet = vaihteet 192 | 193 | pp = Polkupyörä(45, 18.7, 3) 194 | print (pp.vaihteet) 195 | print (pp.nopeus) 196 | print (pp.paino) 197 | ``` 198 | 199 | Luomme Polkupyörä-olion, josta tulostamme vaihteiden lukumäärän, nopeuden ja painon. Vaihteiden lukumäärä on määritetty 200 | `Polkupyörä`-luokassa. Nopeus periytyy `Kulkuneuvo`-luokasta, ja paino periytyy `Urheiluväline`-luokasta. Ohjelma tuottaa seuraavan tulosteen: 201 | ```monospace 202 | 3 203 | 45 204 | 18.7 205 | ``` 206 | 207 | 208 | Tässä tapauksessa emme voi `Polkupyörä`-luokan alustajasta viitata molempien yliluokkien alustajin 209 | `super`-funktiolla tähän tapaan: 210 | 211 | ```python 212 | # Toimimattomat alustajien kutsut 213 | super().__init__(nopeus) 214 | super().__init__(paino) 215 | ``` 216 | Yliluokka, johon `super`-funktio viittaa, määräytyy Pythonin metodien etsintäjärjestyksen perusteella. Tässä tapauksessa 217 | kumpikin lause kutsuisi `Kulkuneuvo`-luokassa olevaa alustajaa, ja ohjelma toimisi väärin. 218 | 219 | Voimme kutsua yliluokkien alustajia vaihtoehtoisella notaatiolla, jossa yliluokka mainitaan nimeltä: 220 | 221 | ```python 222 | Kulkuneuvo.__init__(self, nopeus) 223 | Urheiluväline.__init__(self, paino) 224 | ``` 225 | -------------------------------------------------------------------------------- /13_Taustapalvelun_ja_rajapinnan_rakentaminen.md: -------------------------------------------------------------------------------- 1 | # Taustapalvelun ja rajapinnan rakentaminen 2 | 3 | Tässä moduulissa opit toteuttamaan Python-kielisen taustapalvelun (*backend*). Tällöin voit rakentaa 4 | web-palvelun siten, että web-sovelluksen HTML-/CSS-/JavaScript-käyttöliittymä kommunikoi 5 | Python-kielisen taustapalvelun tarjoamien HTTP-päätepisteiden (*endpoint*) kanssa. 6 | 7 | Taustapalvelun käyttäjän ei välttämättä tarvitse olla selain. Tässä opittavalla lähestymistavalla toteutettavaa 8 | taustapalveua voi käyttää HTTP-yhteyskäytännön ansiosta ohjelmallisesti mistä tahansa palvelusta, millä tahansa ohjelmointikielellä. 9 | 10 | ## Flask-kirjaston asennus 11 | 12 | Python-ohjelmasta rakennetaan taustapalvelu Flask-kirjaston avulla. Flask tarjoaa mahdollisuuden 13 | päätepisteiden ohjelmointiin. Ulkoinen ohjelma (esimerkiksi verkkoselain) voi käyttää noita päätepisteitä 14 | ja sitä kautta suorittaa taustapalveluun ohjelmoitua toiminnallisuutta. 15 | 16 | Tarkastellaan esimerkkiä, jossa teemme taustapalvelun, joka laskee kaksi saamaansa lukua yhteen. Tällaiseen 17 | laskentaan ei toki taustapalvelua tarvittaisi, mutta tarkoitus onkin havainnollistaa taustapalvelun toteuttamiseen 18 | tarvittavia tekniikoita yksinkertaisen esimerkin kautta. 19 | 20 | Aloitetaan Flask-kirjaston asentamisesta. Asennus on helppo tehdä suoraan PyCharm-kehittimestä: 21 | 22 | 1. Valitse **View / Tool Windows / Python Packages**. 23 | 2. Kirjoita hakukenttään **Flask**. Valitse avautuvasta listasta **Flask**-vaihtoehto ja napsauta **Install**. 24 | 25 | Kirjasto on tämän jälkeen heti käytössä. 26 | 27 | ## Päätepisteen kirjoitus 28 | 29 | Kun Flask on asennettu, voimme kirjoittaa ensimmäisen version ohjelmasta tiedostoon `summapalvelu.py`: 30 | 31 | ```python 32 | from flask import Flask, request 33 | 34 | app = Flask(__name__) 35 | @app.route('/summa') 36 | def summa(): 37 | args = request.args 38 | luku1 = float(args.get("luku1")) 39 | luku2 = float(args.get("luku2")) 40 | summa = luku1+luku2 41 | return str(summa) 42 | 43 | if __name__ == '__main__': 44 | app.run(use_reloader=True, host='127.0.0.1', port=3000) 45 | 46 | ``` 47 | 48 | Aloitetaan ohjelmaan perehtyminen sen viimeisestä rivistä. Sillä oleva `app.run`-metodin kutsu käynnistää taustapalvelun. 49 | Palvelu avataan IP-osoitteessa 127.0.0.1, joka on omaa tietokonetta vastaava erikoisosoite. Tämä tarkoittaa, että 50 | yhteys tuohon IP-osoitteeseen voidaan ottaa vain samasta tietokoneesta, jossa ohjelmaa ajetaan. Porttinumero 3000 kertoo, 51 | että taustapalvelin odottaa mainittuja saman koneen yhteydenottoja tietoliikenneportin 3000 kautta. 52 | 53 | Ohjelman rivi `@app.route('summa')` määrittää niin kutsutun päätepisteen. Se kertoo, että seuraavan rivin funktio 54 | nimeltä `summa` ajetaan silloin, 55 | kun taustapalvelun käyttäjä lähettää pyynnön, jossa IP-osoiteosan jälkeen esiintyy merkkijono `/summa`. 56 | Funktiota voidaan siis kutsua esimerkiksi selaimesta kirjoittamalla verkko-osoitteeksi `http://127.0.0.1:3000/summa`. 57 | Teknisesti selain lähettää tällöin HTTP-yhteyskäytännön mukaisen GET-pyynnön, johon Flaskin avulla toteutettu 58 | taustapalvelu vastaa. 59 | 60 | Edellä kuvattu kutsu ei aivan riitä summan laskemiseen, sillä kutsun yhteydessä on myös määritettävä summan 61 | yhteenlaskettavat. Ne voidaan välittää GET-pyynnön parametreina, jotka käsitellään `request`-kirjaston `args.get`-metodin 62 | avulla. 63 | 64 | Näin taustapalvelua voidaan kutsua kirjoittamalla selaimeen vaikkapa 65 | osoite `http://127.0.0.1:3000/summa?luku1=13&luku2=28`. 66 | Ensimmäinen parametri, eli liukuluvuksi muunnettu parametri "13" päätyy `luku1`-muuttujan arvoksi. Vastaavasti 67 | toinen parametri, merkkijono "28" muutetaan liukuluvuksi ja annetaan muuttujan `luku2` arvoksi. 68 | Summa lasketaan, ja se muunnetaan merkkijonoksi sekä palautetaan funktion paluuarvona. 69 | 70 | Kun taustapalvelua kutsutaan selaimesta, vastauksena oleva luku nähdään selainikkunassa (kuvan porttinumero poikkeaa esimerkissä käytetystä): 71 | 72 | ![Taustapalvelun palauttama vastaus selainikkunassa](img/flaskvastaus.png) 73 | 74 | Taustapalvelu toimii nyt teknisesti, mutta tulos ei vielä ole sellaisessa muodossa, että sen ohjelmallinen 75 | käsittely olisi helppoa. 76 | 77 | ## JSON-vastauksen tuottaminen 78 | 79 | Kun taustapalvelu palauttaa vastauksen selaimelle, halutaan vastaus antaa usein JSON-muodossa. JSON (*JavaScript 80 | Object Notation*) on esitysmuoto, joka mukailee JavaScript-kielen oliorakennetta. Rakenne on onneksi intuitiivinen 81 | myös Python-kielen olioihin tottuneelle kehittäjälle. 82 | 83 | Muokataan esimerkin `summa`-funktiota siten, että se ei enää palauta merkkijonoa vaan tuottaa vastauksen JSON-muodossa. 84 | Muodon tuottamiseen onnistuu suoraan Pythonin sanakirjarakenteesta: 85 | 86 | ```python 87 | from flask import Flask, request 88 | 89 | app = Flask(__name__) 90 | @app.route('/summa') 91 | def summa(): 92 | args = request.args 93 | luku1 = float(args.get("luku1")) 94 | luku2 = float(args.get("luku2")) 95 | summa = luku1+luku2 96 | 97 | vastaus = { 98 | "luku1" : luku1, 99 | "luku2" : luku2, 100 | "summa" : summa 101 | } 102 | 103 | return vastaus 104 | 105 | if __name__ == '__main__': 106 | app.run(use_reloader=True, host='127.0.0.1', port=3000) 107 | ``` 108 | 109 | Nyt ohjelma tuottaa JSON-vastauksen, jonka ohjelmallinen käsittely on helppoa vaikkapa selaimessa ajettavan 110 | JavaScript-kielen avulla (kuvan porttinumero poikkeaa esimerkissä käytetystä): 111 | 112 | ![JSON-vastaus selainikkunassa](img/flask_json.png) 113 | 114 | Edellä kuvatun yksinkertaisen taustapalvelun idean pohjalta on mahdollista 115 | rakentaa monipuolinen taustapalvelu, jossa on tarpeellinen määrä päätepisteitä. 116 | 117 | ## Pyynnön jäsentäminen 118 | 119 | Edellä kuvatuissa esimerkeissä päätepisteeseen liittyvät parametriarvot annettiin HTTP-pyynnön parametreina, jotka eroteltiin 120 | domain- ja maaosasta kysymysmerkillä (`?`). Tämä on perinteinen tapa välittää parametreja HTTP-pyynnön yhteydessä. 121 | 122 | Vaihtoehtoinen tapa on kuvata pyynnön kohteena 123 | oleva resurssi osana varsinaista verkko-osoitetta. Seuraava yksinkertainen esimerkki tuottaa "kaikupalvelun", 124 | joka palauttaa annetun merkkijonon JSON-rakenteessa kaiutettuna eli kahdennettuna. Esimerkissä merkkijonoa ei anneta 125 | parametrina vaan ikään kuin osana verkko-osoitetta. Flask tarjoaa suoraviivaisen tavan verkko-osoitteen osien 126 | käsittelyyn: 127 | 128 | ```python 129 | from flask import Flask 130 | 131 | app = Flask(__name__) 132 | @app.route('/kaiku/') 133 | def kaiku(teksti): 134 | vastaus = { 135 | "kaiku" : teksti + " " + teksti 136 | } 137 | return vastaus 138 | 139 | if __name__ == '__main__': 140 | app.run(use_reloader=True, host='127.0.0.1', port=3000) 141 | ``` 142 | 143 | Selaimessa palvelu näyttäytyy näin: 144 | 145 | ![Kaikupalvelu selainikkunassa](img/osoite.png) 146 | 147 | Taustapalvelun ohjelmoija voi täysin päättää, miten verkko-osoitteena domain-osan ja maatunnuksen jälkeinen osa käsitellään. 148 | Erityisesti REST-arkkitehtuurityylissä suositaan lähestymistapaa, jossa käsiteltävä resurssi kuvataan 149 | viimeksi esitellyllä tavalla, osana verkko-osoitetta. 150 | 151 | ## Virhetilanteiden käsittely 152 | 153 | Tarkastellaan aiempaa summan laskentaesimerkkiä, jota on muunnettu siten, että 154 | molemmat yhteenlaskettavat annetaan osana osoitepolkua. Kelvollinen pyyntö voidaan siis 155 | antaa esimerkiksi seuraavasti: `http://127.0.0.1:3000/summa/42/117`. 156 | 157 | Esimerkissä oletettiin, että käyttäjän lähettämä pyyntö on virheetön. 158 | 159 | Jos virheenkäsittelyä ei erikseen ohjelmoida, ovat ainakin seuraavat virhetilanteet 160 | mahdollisia: 161 | 1. Käyttäjä yrittää kutsua virheellistä päätepistettä: `http://127.0.0.1:3000/sumka/42/117` 162 | 2. Käsittely ohjautuu oikeaan päätepisteeseen, mutta vastauksen tuottaminen epäonnistuu virheellisen 163 | yhteenlaskettavan vuoksi: `http://127.0.0.1:3000/summa/4t23/117` 164 | 165 | Ensimmäisessä tapauksessa Flask-taustapalvelu palauttaa automaattisesti virhekoodin 404 (Not found). 166 | Jälkimmäisessä tapauksessa palautuu statuskoodi 500 (Internal server error). Pyynnön 167 | lähettäjä voi toki käsitellä nuo virheilmoitukset ohjelmallisesti. Taustapalvelun 168 | ohjelmoijina meidän on kuitenkin mahdollisuus käsitellä virhetilanteet samalla kun ne 169 | syntyvät ja tarjota palvelun käyttäjälle (asiakasohjelmalle) seikkaperäisempää tietoa 170 | virheen syystä. 171 | 172 | Seuraava ohjelma käsittelee virhetilanteet edellä esitettyä yksityiskohtaisemmin: 173 | 1. Kutsu virheelliseen päätepisteeseen tuottaa statuskoodin 404 ja JSON-vastauksen `{"status": 404, "teksti": "Virheellinen päätepiste"}`. 174 | 2. Jos parametrin muunnos liukuluvuksi ei onnistu, tulostuu `{"status": 400, "teksti": "Virheellinen yhteenlaskettava"}`. 175 | Taustapalvelu palauttaa nyt kuvaavamman HTTP-statuskoodin 400 (Bad Request) eikä oletusarvoista koodia 500 (Internal server error). 176 | 177 | Ohjelma lisää statuskoodin myös onnistuneesta operaatiosta rakennettavan vastauksen runkoon. 178 | Rungossa oleva statuskoodi on vain lisätiedoksi. Varsinainen HTTP-yhteyskäytännön 179 | mukainen statuskoodi annetaan Response-olion statuscode-parametrina. (Response-olio on luotava 180 | silloin, kun vastauksena palautetaan muutakin kuin paljas sanakirjarakenteesta tuotettu JSON 181 | ja oletusstatuskoodi 200. 182 | Response-oliota luotaessa määritetään ns. mime-tyyppi. Se antaa lisätietona selaimelle 183 | tiedon siitä, miten vastaus on tarkoitus tulkita.) 184 | 185 | Laajennettu ohjelma on seuraavanlainen: 186 | ```python 187 | from flask import Flask, Response 188 | import json 189 | 190 | app = Flask(__name__) 191 | @app.route('/summa//') 192 | def summa(luku1, luku2): 193 | try: 194 | luku1 = float(luku1) 195 | luku2 = float(luku2) 196 | summa = luku1+luku2 197 | 198 | tilakoodi = 200 199 | vastaus = { 200 | "status": tilakoodi, 201 | "luku1": luku1, 202 | "luku2": luku2, 203 | "summa": summa 204 | } 205 | 206 | except ValueError: 207 | tilakoodi = 400 208 | vastaus = { 209 | "status": tilakoodi, 210 | "teksti": "Virheellinen yhteenlaskettava" 211 | } 212 | 213 | jsonvast = json.dumps(vastaus) 214 | return Response(response=jsonvast, status=tilakoodi, mimetype="application/json") 215 | 216 | @app.errorhandler(404) 217 | def page_not_found(virhekoodi): 218 | vastaus = { 219 | "status" : "404", 220 | "teksti" : "Virheellinen päätepiste" 221 | } 222 | jsonvast = json.dumps(vastaus) 223 | return Response(response=jsonvast, status=404, mimetype="application/json") 224 | 225 | if __name__ == '__main__': 226 | app.run(use_reloader=True, host='127.0.0.1', port=3000) 227 | ``` 228 | -------------------------------------------------------------------------------- /English/01a_First_Program.md: -------------------------------------------------------------------------------- 1 | # First program 2 | 3 | Welcome to study the Python programming language in Metropolia University of Applied Sciences! 4 | 5 | ... and the same in Python: 6 | ``` 7 | print("Welcome! Let's start learning Python programming!") 8 | ``` 9 | 10 | Python is one of the most popular programming languages in the world. When studying Python, you can: 11 | - learn to program in a fun and easy way 12 | - program with high-quality, user-friendly development tools 13 | - create impressive graphics with visualization libraries 14 | - apply artificial intelligence into your programs with extensive machine learning libraries 15 | 16 | On your first year in Metropolia, you will acquire strong basic skills in Python programming. In future studies you will 17 | deepen your knowledge in the Python language and learn to use Python efficiently in software development projects. 18 | 19 | In this first module, you will install the Python development tools and learn to write and run your first Python program. 20 | 21 | ## Installation of the Python Interpreter 22 | 23 | First, we install the Python interpreter. The Python interpreter is a program that translates the Python language 24 | statements in a program one at a time into instructions 25 | accepted by the computer's central processing unit (CPU). 26 | 27 | The installation of the Python interpreter proceeds as follows: 28 | 29 | 1. Navigate to https://www.python.org/downloads/ with your browser. 30 | 2. Select **Downloads / All releases**. 31 | 3. Scroll to the section titled **Looking for a specific release?**. 32 | Click Download for the version of your choice (any version between 3.7.X and 3.9.X should do). The minor revision number 33 | X can be any number. 34 | 4. Download the Python interpreter by clicking the link *Windows installer (64-bit)* at the bottom of the page. 35 | The wizard will guide you in the installation process. It is recommended to use the default folder for the installation. 36 | 37 | **Important:** The guided installation process provides the opportunity to add 38 | the Python interpreter to the `PATH` environment variable in the Windows operating 39 | system. This environment variable contains a list of directories that the operating 40 | system automatically searches for executable programs. Adding Python to the `PATH` 41 | is recommended because it allows you to use the `python` command from any directory 42 | in the command prompt, and the Python interpreter will be found automatically. 43 | (This is beneficial for the upcoming Hardware 1 and 2 courses, where Python is also used.) 44 | 45 | The following image shows the checkbox from which the addition is made: 46 | 47 | ![Updating the PATH environmental variable](img/path_envvar.png) 48 | 49 | > On this course, we also use MariaDB databases. At the time of writing (August 2022), the MySQL Connection/Python database 50 | driver will not support the newest 51 | Python 3.10 versions. For this reason, it is recommended that you install a slightly earlier Python version (e.g. version 3.9.) 52 | that has guaranteed support. If you choose to install the newest Python version now, you may later need to install 53 | another, earlier version to coexist in your computer. 54 | 55 | ## Installation of the IDE 56 | 57 | Next, we install the development environment (IDE), which is short for *Integrated Development Environment*. 58 | IDEs are professional software tools used for writing, executing, and testing program code. 59 | 60 | On this course we are using the JetBrains PyCharm IDE. Please follow these instructions to install PyCharm: 61 | 1. Navigate to https://www.jetbrains.com/ 62 | 2. Select **Developer tools / PyCharm** ja click **Download**. 63 | 3. Select the Professional version to download. 64 | 4. To use PyCharm, you must create a JetBrains account with your Metropolia email to acquire a free student license. The student license is active for one year at a time. The installation tool helps you register your account. To acquire the software license, click the shopping cart icon at the top of the JetBrains website and select **Special offers / For students and teachers**. **Remember to use your Metropolia email**, and complete the registration by following the instructions in the activation email. When your license is about to expire after the first year, you will automatically receive instructions to your email on how to renew your license. 65 | 66 | Once your installation is finished, you can open PyCharm by clicking at the PyCharm icon on your computer. 67 | 68 | ## Creating a new project and a Python source file 69 | 70 | Before you can start writing programs, you must create a new project. A project is a bit like a suitcase where you can store 71 | programs focused on the same topic. For example, you can create a new project called 'Learning Python' for your first Python programs. 72 | The name of the project is written at the end of the file path. 73 | 74 | ![Creating a new project](img/new_project.png) 75 | 76 | By default, a new project is created under a virtual environment (venv). Virtual environments help the management and version control of software packages required by programs. 77 | 78 | When you select **Create**, the IDE prompts you whether you want to open the new project in a new window. You can select either option. 79 | 80 | A project tree opens on the left side of the screen, showing you all the files that belong to the project. 81 | 82 | ![Project tree](img/project_tree.png) 83 | 84 | Each program is written in a file under the project's directory. For the first program, you can create a new file by right-clicking the project name under the project tree. 85 | Then select **New / Python file** and write the name for the new file in the popup window. You can name the first file `hello`. A new file with the name `hello.py` is now 86 | seen is the project tree. File extension `.py` is characteristic of Python program files. 87 | 88 | ## Writing, saving and running a program 89 | 90 | A program, or Python source code, is written in the editor field: 91 | 92 | ![First program](img/first_program.png) 93 | 94 | You can execute, or run, a program by right-clicking the editor field and selecting **Run 'hello'**. 95 | 96 | Output will be shown in the console window at the bottom of the screen. 97 | 98 | ```python 99 | Hello, world! 100 | ``` 101 | 102 | If there are errors in your program, don't worry! You will get an error message that helps you find the source for the error. 103 | You can edit the program as many times as you need and run it over and over again. 104 | 105 | Making errors is part of programming. It has been estimated that 80% of the work time of a professional programmer is used 106 | for troubleshooting and fixing errors. Making errors also helps you learn. Every time you run into an error, find it, and fix it, 107 | you have become a bit better at programming. 108 | -------------------------------------------------------------------------------- /English/01b_Deployment_of_Version_Control.md: -------------------------------------------------------------------------------- 1 | # Introduction to version control 2 | 3 | Programs are built in an iterative manner, piece by piece. Often software applications are developed in teams and all team members must 4 | have access to the same program code. New versions of the program code are developed throughout the phases of the development project and 5 | sometimes there is a need to downgrade to an earlier version of the program. It's also important to make sure that program code that 6 | has previously been written won't disappear or get deleted by accident. 7 | 8 | These are the reasons for using version control. 9 | 10 | ## Git and GitHub 11 | 12 | A distributed version control tool called Git is used on this course (and often in professional software development as well) to manage 13 | version control. Distributed version control means that there is a shared storage (a repository) for program code which is used to create 14 | local copies of the program code on the user's own computers. Up-to-date information is fetched from the shared, remote repository (referred to as 'pull') and new local 15 | changes are loaded into the remote repository (referred to as 'push'). 16 | 17 | GitHub is a freely available website where you can store the source code of your Git projects. It is also the largest open code repository and practically all 18 | professional programmers use GitHub in one way or the other. 19 | 20 | To use GitHub, you must register a user account at https://github.com/. 21 | 22 | Once you have signed up, you can create your own remote repositories for your projects on GitHub. 23 | 24 | There are two ways you can create a repository for your own Python programs: either directly on GitHub or by using PyCharm. Let's look at the first method. 25 | 26 | ## Creating a repository on GitHub 27 | 28 | Let's start by creating your own GitHub repository for a project: 29 | 30 | 1. Sign up to GitHub at https://github.com/. 31 | 2. Once you are logged in, click the **New** button next to **Repositories**. 32 | 3. Create yourself a private repository by using the settings in the image below. 33 | 34 | ![Creating a new repository](img/new_repo2.png) 35 | 36 | Next you will give PyCharm access to your repository. 37 | 38 | 1. Select the GitHub account you are using. Press **Ctrl/Alt S**. Select **Version Control / GitHub /Add** and 39 | enter your GitHub credentials. 40 | 41 | 2. Connect the repository to your Python project. In PyCharm, select **VCS / Get from Version Control** and 42 | select **Clone** to retrieve the Git repository you created on GitHub. PyCharm will create a new project that 43 | uses the GitHub repository. 44 | 45 | If the GitHub repository does not yet exist, you can skip the second step and select **VCS / Create Git Repository** instead. 46 | In this case the repository will be created based on the PyCharm project and you can choose which files to store. 47 | 48 | Once you have connected your project to a GitHub repository, a new Git tab will appear on PyCharm menu bar. 49 | 50 | ## Using the repository 51 | 52 | Now we will look at how GitHub can be used if there is just one developer. With a single developer there is no concern of 53 | files to be modified by several developers simultaneously. We will also assume that there will not be a need to separate 54 | the development project into several branches. You will learn more advanced use of Git as a co-operative platform at the 55 | beginning of the course project. 56 | 57 | 58 | - Use the **Git / Pull** command every time you start working. The command will fetch all possible updates from the remote 59 | repository on GitHub. 60 | - Every time you have finished some work, commit your changes using **Git / Commit**. The changes will create a new checkpoint 61 | that you can return to if needed. At minimum, commit your changes every time you are planning to stop working. 62 | - When you stop working, use the **Git / Push** command. The command will copy all your locally committed changes to the remote 63 | repository on GitHub. 64 | 65 | You can explore the development branch and checkpoints on GitHub. 66 | 67 | Every time you create a new file, PyCharm will ask if you want the file to be included in version control. All source code, 68 | images and other valuable files should be stored on GitHub. On the other hand, configuration files and IDE-related data 69 | should be left out from version control. Also, any files containing passwords should not be saved to version control due to 70 | security reasons. -------------------------------------------------------------------------------- /English/02_Variables_and_Interactive_Programs.md: -------------------------------------------------------------------------------- 1 | # Variables and interactive programs 2 | 3 | In this module you will learn to write interactive Python programs. 4 | 5 | An interactive program communicates with the use: it reads and processes input and generates output accordingly. 6 | 7 | An example of an interactive program would be a program that asks the user to enter two numbers, calculates their sum 8 | and shows the sum to the user. In this case the input is read from the user (for example numbers 2 and 3), the input 9 | is processed by calculating the sum and the output is shown to the user (sum of the numbers is 5). 10 | 11 | ## Printing 12 | 13 | Let's start with Python's printing function called print. The following program prints out the text "Hello, world!": 14 | 15 | ```python 16 | print('Hello, world!') 17 | ``` 18 | 19 | The printing is handled with a Python built-in function called print. The argument to the function is called inside 20 | brackets. In this case the message to print is a string literal "Hello, world!". A string literal is a string that 21 | is written directly into the program code. A string literal is written inside apostrophes ' ' or quotation marks " ". 22 | The same program could also be written as shown here: 23 | 24 | ```python 25 | print("Hello, world!") 26 | ``` 27 | 28 | What if you need to print out a message that includes apostrophes or quotation marks? The solution is to write the 29 | symbol inside a string literal using the other alternative symbol to enclose the string literal: 30 | 31 | ```python 32 | print('"Hello", said Joe') 33 | ``` 34 | 35 | When a program has multiple print statements, a line break is printed after each of them automatically: 36 | 37 | ```python 38 | print("Good") 39 | print("morning") 40 | ``` 41 | 42 | Output: 43 | ```monospace 44 | Good 45 | morning 46 | ``` 47 | 48 | The above program shows one of the basic structures of programming: sequences. By default, statements are executed 49 | in the order they have been written in the program code. Other basic structures are selections and loops. They will 50 | be introduced later. 51 | 52 | You can also print a message with line breaks with a single line of code: It is possible to write a line break symbol 53 | \n inside a string literal. You can get the same output as before by writing: 54 | 55 | ```python 56 | print("Good\nmorning") 57 | ``` 58 | 59 | ## Inputs, variables, and assignment statements 60 | 61 | You have now learned how to create simple programs that generate the same output on each run. However, usually 62 | it is required that a program reads inputs from the users and uses the input to execute tasks. 63 | 64 | Let's write a program the asks the user for their name and then greets the user with their name. 65 | This can be done as follows: 66 | 67 | ```python 68 | user = input('Enter your name: ') 69 | print("Nice to meet you, " + user + "!") 70 | ``` 71 | 72 | The user input is read using the built-in input function. The function receives the text to be printed on the screen 73 | as an argument. The text should tell the user what information they are expected to enter. 74 | 75 | The built-in input function waits for input from the user's keyboard. The user ends the input with the Enter key. 76 | When the input has been give, the value of the input function is the string entered by the user. 77 | 78 | The string must be saved into a *variable* so that it can be used later in the program. Here we are using a variable called 79 | *user*. User input is saved into the memory of the computer and can be fetched from memory using the name of the variable. 80 | The name of a variable is a sort of a handle or name tag that can be used to retrieve the value from memory. 81 | 82 | A variable can be given a value using an assignment statement. The assignment statement uses an equals symbol (=). 83 | The name of the variable is on the left side and the expression that determines the value to be assigned to the variable 84 | is written on the right side. 85 | 86 | Let's look at the printing statement more closely: 87 | 88 | If we only wanted to print out the name the user entered as input, we could replace the bottom row with the following: 89 | 90 | ```python 91 | print(user) 92 | ``` 93 | 94 | Notice that *user* is the name of the variable. As it is not a string literal, the name is not surrounded by quotes. 95 | 96 | However, we want the program to output a whole greeting message, not just the name. 97 | The string to output can be composed of several substrings by joining them together with a plus sign (+). 98 | the lower row of the original program creates the output with three parts: 99 | 100 | 1. String literal "Nice to meet, " 101 | 2. The value of the *user* variable 102 | 3. String literal "!" 103 | 104 | The program works as follows: 105 | ```monospace 106 | Enter your name: Joanne 107 | Nice to meet you, Joanne! 108 | ``` 109 | 110 | ## Variable type 111 | 112 | Above we assigned the string entered by the user to a variable. 113 | 114 | In Python, variables and their type do not have to be declared in advance. The type of a variable 115 | is determined automatically in the assignment statement. Variable type is the type of data a variable 116 | refers to: is the value for example a string or a number? 117 | 118 | Python has six basic types of variables: 119 | - string 120 | - number, that can either be integer, long, float or complex 121 | - boolean, that is either True or False 122 | - list 123 | - tuple 124 | - dictionary 125 | 126 | Moreover, the type of a variable can be a reference to an object. A string type variable was discussed above. 127 | Lists, tuples, dictionaries and object referencing will be introduced later on the course. Next we will look 128 | at number data types. 129 | 130 | The number data type in Python has four sub-types: integer (e.g. 4), long (e.g. 12756413000), float (e.g. 7.28 or 4.0) and 131 | complex (e.g. 3-2i). Next we will create four variables. The first one will be assigned with an integer, the second with a 132 | long integer, the third with a floating point number and the fourth with a complex number. Then we will print out the values 133 | of all four variables and the values of both the real and imaginary parts of the complex number: 134 | 135 | ```python 136 | first = -9 137 | second = 12_456_123_180 138 | third = 4.973 139 | fourth = -4 + 2j 140 | 141 | print(first) 142 | print(second) 143 | print(third) 144 | print(fourth) 145 | print(fourth.real) 146 | print(fourth.imag) 147 | ``` 148 | 149 | When entering an integer or long, the digits can be grouped with an underscore as was done with the *second* variable 150 | above. However, this is not mandatory. 151 | 152 | The difference between an integer and long is that the value range of long is wider. It can be used to store integers 153 | that are very large or small. A regular integer can store values between -2147483648 and 2147483647 including the end 154 | points. A long requires more space in memory than a regular integer. 155 | 156 | Notice that the imaginary part of a complex number in Python is marked with symbol j and not i as usually in mathematics. 157 | 158 | The example program produces the following output: 159 | ```monospace 160 | -9 161 | 12456123180 162 | 4.973 163 | (-4+2j) 164 | -4.0 165 | 2.0 166 | ``` 167 | 168 | ## Mathematical operations and type conversion 169 | 170 | Variables and constants can be used in mathematical operations. The order of the operations can be defined 171 | with brackets if needed. 172 | 173 | The arithmetic operations are addition (`+`), subtraction (`-`), multiplication (`*`) and division (`/`). In addition, there is the modulo operator (`%`) for the remainder, as well as the floor division operator (`//`) and the exponential operator (`**`). 174 | 175 | The program below asks for a temperature in Fahrenheit and converts it to Celsius. The conversion is done by 176 | subtracting 32 from the Fahrenheit degrees and multiplying the difference with a constant 5/9. 177 | 178 | 179 | ```python 180 | fahrenheit_str = input("Enter a temperature in Fahrenheit: ") 181 | fahrenheit = float(fahrenheit_str) 182 | celsius = (fahrenheit-32)*5/9 183 | print("The temperature in Celsius: " + str(celsius)) 184 | ``` 185 | 186 | The program works as follows: 187 | ```monospace 188 | Enter a temperature in Fahrenheit: 102 189 | The temperature in Celsius: 38.888888888888886 190 | ``` 191 | 192 | Notice that the value returned by the input function is always interpreted as a string even if 193 | it contains numbers only. A string can be converted into a float with the *float* funcion or into an integer 194 | with the *int* function. 195 | 196 | Furthermore, a number can be converted into a string with the *str* function. In the example program the conversion 197 | must be done to add the calculated Celsius degrees to the output string. Both parts of the print must be strings. 198 | 199 | ## Output formatting 200 | 201 | Sometimes it is required to modify the format of the output: how many decimals of a float are shown or for example 202 | how many character spaces are reserved for a string. 203 | 204 | This can be done by using a formatted string literal, where the string to be printed includes format codes. 205 | 206 | Let's look at this through an example. We will modify the output of the last example program so that the Celsius 207 | degrees are always shown with two decimals. 208 | 209 | ```python 210 | print(f"The temperature in Celsius: {celsius:6.2f}") 211 | ``` 212 | 213 | Notice that the argument of the print function call now starts with letter `f` that shows that the string to print 214 | includes formatting. Without the letter f the string literal would be printed out as it is in the program code, 215 | including the curly brackets. 216 | 217 | The string and the format code are enclosed in curly brackets. The expression to be formatted in the example is the 218 | float value stored in the `celsius` variable. 219 | 220 | In this case the format code is `6.2f`. The letter `f` determines that the expression is printed out as a floating 221 | point number. The `6.2` notation defines the output to be printed out in a field that is 6 characters long and with 222 | the accuracy of two decimals. 223 | 224 | The following list shows examples of format codes: 225 | - .5f : a float with the accuracy of 5 decimals 226 | - 10.2f : a float with two decimals into a field of 10 characters wide 227 | - <20s : a string printed into a field of 20 characters wide, justified to the left 228 | - 8d : an integer into a field of 8 characters wide 229 | 230 | Writing format codes is optional. However, using formatted string literals make combining numeric and string type 231 | prints as `str` functions and other types of conversion functions are not necessarily needed. Above we could simply 232 | print out the Celsius degrees without format code: 233 | 234 | ```python 235 | print(f"The temperature in Celsius: {celsius}") 236 | ``` 237 | 238 | The same formatted string literal can include multiple expressions to format and their possible format codes. The 239 | following program ouputs the value of two natural constants: pi and Euler's number (e) so that the name of each 240 | constant is printed in a field of 12 characters and their corresponding values are printed in a field of 10 241 | characters using 5 decimals: 242 | 243 | ```python 244 | print(f"{'Pi':12s}:{math.pi:10.5f}") 245 | print(f"{'e':12s}:{math.e:10.5f}") 246 | ``` 247 | 248 | Above, the natural constants pi and e where printed out using Python's math library. They were referred to with 249 | expressions `math.pi` and `math.e`. 250 | 251 | Lastly, Python offers multiple ways of formatting outputs. Formatted string literals introduces here are quite a 252 | new way of formatting that has been available since Python version 3.6. It is enough to learn one good way of 253 | formatting output, but you might see alternative methods in online learning materials and when looking at 254 | program code made by others. These alternative methods are: 255 | 1. using the `str.format()` function 256 | 2. using format specifiers and a list of expressions (% operator notation) 257 | 3. using template strings 258 | 259 | The alternative methods listed above are not introduced here. You can find more information in the Python 3 documentation: 260 | [https://docs.python.org/3/tutorial/inputoutput.html] 261 | -------------------------------------------------------------------------------- /English/05_List_Structures_and_Iterative_Loops.md: -------------------------------------------------------------------------------- 1 | # List structures and iterative loop structures (for) 2 | 3 | In this module you will learn how to use lists, the most important data structures of Python language. A list is 4 | an ordered group of items. With a list, you can store multiple values into a single list variable and loop through 5 | the values using a for loop structure that has been developed for this purpose. 6 | 7 | ## List and list items 8 | 9 | In programming a data structure is a structure where not just one but a whole group of values is stored into a single variable. 10 | There are different types of data structures: list, dictionary, tree, stack and graph. Each data structure has their own 11 | use range. 12 | 13 | A list is a data structure where items are in a sort of and ordered queue. Items can be added to a specific position 14 | of the list or removed. The items in a list can be iterated through. Most commonly new items are added to the end of a 15 | list and items are removed from the beginning. Other practices are also possible. 16 | 17 | Let's look at a program that creates a lists and sets its values to the names of five persons, or five strings: 18 | 19 | ```python 20 | names = ["Viivi", "Ahmed", "Pekka", "Olga", "Mary"] 21 | ``` 22 | 23 | In the assignment statement where the list is created the name of the list is on the left side of the equal to sign. 24 | A pair of square brackets is written on the right side. The list members are listed inside the square brackets separated 25 | by commas. 26 | 27 | In this case the result is a list with five string-type items. A list can be referenced with a list variable called `names`. 28 | The following image illustrates the structure of a list: 29 | 30 | ![List variable and list items](img/list.png) 31 | 32 | Let's look at ways to reference list items. The following program prints out items and parts of a created list: 33 | 34 | ```python 35 | names = ["Viivi", "Ahmed", "Pekka", "Olga", "Mary"] 36 | 37 | print(names[3]) 38 | print(names[1]) 39 | print(names[-2]) 40 | print(names[1:3]) 41 | print(names[2:]) 42 | print(names) 43 | ``` 44 | 45 | The output is as follows: 46 | 47 | ```monospace 48 | Olga 49 | Ahmed 50 | Olga 51 | ['Ahmed', 'Pekka'] 52 | ['Pekka', 'Olga', 'Mary'] 53 | ['Viivi', 'Ahmed', 'Pekka', 'Olga', 'Mary'] 54 | ``` 55 | 56 | The first printing statement prints out the list item by index three. The items in a Python list can always 57 | be referenced using their index. Index is the order number of a list item. Numbering always starts from zero, 58 | so the item with index 3 is the fourth item in the list, in this case "Olga". 59 | 60 | The second print is for the item at index 1. It it the second string in the list: "Ahmed". 61 | 62 | The third printing statement uses a negative index. Negative index counts items from the end of the list in the reverse 63 | order: notation -1 is the last item, -2 the second to last and so on. In this case "Olga" is printed as it is the 64 | second to last item in the list. 65 | 66 | The fourth printing statement prints out a portion of the list. The square brackets enclose an index range `1:3` which 67 | means that the resulting new list has the items beginning from index 1 (including the starting point) up to index 3 68 | (excluding the end point). The start index in the range is always included in the new list while the endpoint is not. 69 | Therefore, the new list has items 1 and 2 or "Ahmed" and "Pekka". 70 | 71 | In the fifth print the endpoint has not been given. In this case items from the starting point up to the end of the 72 | list are printed, including the last item. The resulting indices are 2, 3 and 4 and the corresponding strings "Pekka", 73 | "Olga" and "Mary" are in the new list. 74 | 75 | The last example statement prints out the whole list. 76 | 77 | The length of a list can be retrieved using the Python built-in `len` function. 78 | 79 | ```python 80 | print(len(names)) 81 | ``` 82 | 83 | This outputs the length of the list, which is one larger than the index of the last item. 84 | ```monospace 85 | 5 86 | ``` 87 | 88 | ## Index out of range 89 | 90 | When referencing a list item by its index, it is possible to program an illegal reference that points 91 | to an item that does not exist in the list. This type of an error is quite common in programming and 92 | it is good to learn how to recognize right from the start. 93 | 94 | The following list has five items. The idea is to reference the fifth item in the list, but - as the indexing 95 | starts from zero - the index 5 in the program actually refers to the sixth item. This results in an error: 96 | 97 | ```python 98 | names = ["Viivi", "Ahmed", "Pekka", "Olga", "Mary"] 99 | # Illegal reference 100 | print(names[5]) 101 | ``` 102 | 103 | A runtime exception occurs and a description of the error is printed on the console: 104 | ```monospace 105 | Traceback (most recent call last): 106 | File "C:/Users/olliv/PycharmProjects/Python_Ohjelmistoteema/Esimerkit/listaesimerkki.py", line 4, in 107 | print(names[5]) 108 | IndexError: list index out of range 109 | 110 | Process finished with exit code 1 111 | ``` 112 | 113 | Once more the error message gives valuable information to the programmer: it show both the faulty statement as well as the 114 | reason for the error: list index out of range. 115 | 116 | ## List operations 117 | 118 | In the examples above, the list items were assigned to the list when the list was created after which the list remained 119 | unchanged throughout the execution of the program. However, lists are often used dynamically by adding more items or removing 120 | items while the program executes. 121 | 122 | The following program first creates and empty list. Then the program asks the user to enter names until the user inputs 123 | an empty string by just pressing Enter. Finally, the program prints out the entire list: 124 | 125 | ```python 126 | names = [] 127 | 128 | name = input("Enter the first name or quit by pressing Enter: ") 129 | while name!="": 130 | names.append(name) 131 | name = input("Enter the next name or quit by pressing Enter: ") 132 | 133 | print(names) 134 | ``` 135 | 136 | An example on how the program works below: 137 | ```monospace 138 | Enter the first name or quit by pressing Enter: Mikko 139 | Enter the next name or quit by pressing Enter: Kerttu 140 | Enter the next name or quit by pressing Enter: John 141 | Enter the next name or quit by pressing Enter: Miriam 142 | Enter the next name or quit by pressing Enter: 143 | ['Mikko', 'Kerttu', 'John', Miriam'] 144 | ``` 145 | 146 | In the example, new items were added at the end of the list one by one using the `append` list operation. In Python 147 | there are several ready made list operation for adding or removing items. The list items can also be sorted in 148 | different ways using then operations. 149 | 150 | The most common list operations are listed in the table below: 151 | 152 | | Operation | Meaning | Example | 153 | |-----------|---------------------------------------------------------------------------------------------|-------------------------------------------------------------------| 154 | | append | adds an item to the end of the list | names.append("Matti") | 155 | | remove | removes the first occurance of an item in the list | names.remove("Pekka") | 156 | | insert | inserts an item into a defined position in the list, index specified in the first argument | names.insert(4, "Teppo") | 157 | | extend | adds the items in the second list to the first list | otherNames = ["Allu","Ninni"]
names.extend(otherNames) | 158 | | index | returns the index of the first occurence of the specified item | what_index = names.index("Olga") | 159 | | in | checks if an item exists in the list | if "Matti" in names:
    "Matti found" | 160 | | sort | sorts the list items in alphabetical or numerical order | numbers.sort() | 161 | 162 | ## Going through a list using a for loop 163 | 164 | The program above asked the user for names and then printed the entire list of names. 165 | 166 | Next we will look at how to go through the list items one by one. Let's extend the program so that 167 | it separately greets each person added to the list. The program is written as follows: 168 | 169 | ```python 170 | names = [] 171 | 172 | name = input("Enter the first name or quit by pressing Enter: ") 173 | while name!="": 174 | names.append(name) 175 | name = input("Enter the next name or quit by pressing Enter: ") 176 | 177 | for n in names: 178 | print(f"Hello, {n}!") 179 | ``` 180 | 181 | The program works as follows: 182 | ```monospace 183 | Enter the first name or quit by pressing Enter: Stefan 184 | Enter the next name or quit by pressing Enter: Ville 185 | Enter the next name or quit by pressing Enter: Aamu 186 | Enter the next name or quit by pressing Enter: 187 | Moi, Stefan! 188 | Moi, Ville! 189 | Moi, Aamu! 190 | ``` 191 | 192 | Accessing the list items one by one was implemented using a `for` loop: 193 | ```python 194 | for n in names: 195 | print(f"Hello, {n}!") 196 | ``` 197 | 198 | These repetitive strucutres are great for looping through a list. The loop variable is assigned the value of each list 199 | item one by one. The operation continues as long as there are items left. 200 | 201 | The operation of going through each item in a list is called iteration. 202 | 203 | ## Range function 204 | 205 | Besides iterating through a list, `for` loops have other uses as well. The structure can be used to easily create 206 | a loop variable that is assigned to any required values as a sequence with one or more steps between the numbers. 207 | 208 | - range(1,4) defines values 1, 2, 3 209 | - range(5,0,-1) defines values 5, 4, 3, 2, 1 210 | - range(10,21,2) defines values 10, 12, 14, 16, 18, 20 211 | 212 | The first argument of the `range` fucntion is the starting point, the second argument the end point and the third, optional 213 | argument defines the step size between numbers. If the step size is not defined, the default step is 1. If the step is defined 214 | as 0, an error message is printed. 215 | 216 | If the `range` function is only given one argument, it is interpreted as the endpoint of the range. In this case the starting 217 | point is zero and step is one. 218 | 219 | The `range` function generates a Python range with specific values. A range can be looped through the same way as a list 220 | structure. 221 | 222 | For example, the following program outputs all numbers divisible by 3 between numbers 3 and 30. Notice that the start point 223 | in the function call is included in the range while the endpoint is excluded. For this reason the endpoint must be set to 224 | a number a bit larger than 30. 225 | 226 | ```python 227 | for number in range(3,31,3): 228 | print(number) 229 | ``` 230 | 231 | Program output: 232 | 233 | ```monospace 234 | 3 235 | 6 236 | 9 237 | 12 238 | 15 239 | 18 240 | 21 241 | 24 242 | 27 243 | 30 244 | ``` 245 | 246 | Using the `range` function is a handy way of replacing a loop that uses a loop variable. The following 247 | program prints string "Hello!" six times: 248 | 249 | ```python 250 | for number in range(6): 251 | print("Hello!") 252 | ``` 253 | -------------------------------------------------------------------------------- /English/07_Tuple,_Set,_and_Dictionary.md: -------------------------------------------------------------------------------- 1 | # Tuple, set, and dictionary 2 | 3 | Earlier we discussed the Python list structure that is the most common data structure of Python. 4 | Python language also offers three more builtin data structures that each have their own areas of use. 5 | 6 | In this module you will learn to use these three Python data structures: tuple, set and dictionary. 7 | 8 | ## Tuple 9 | 10 | Similar to a Python list, a tuple is used to present an ordered list of items. On the contrary, 11 | a tuple is immutable: items cannot be added or removed after the tuple has been created. 12 | 13 | Tuples are useful in situations where the queue of items is static: it is known that the items will not be 14 | changed during the execution of the program. The advantages of using tuples over lists relate to memory management: 15 | when a tuple is created, it can be reserved a fixed location in memory, and the address of a single item can 16 | be calculated directly based on the beginning address and index. With lists this is not possible: when searching 17 | for an item based on index, the running environment usually has to iterate through all the items in the list. 18 | This causes a speed difference: finding an item based on its index is faster in tuples. In practice, the difference 19 | is unnoticeable when the data structure is small. 20 | 21 | Let's look at an example of using a tuple. The following program creates a tuple of the names of the days of the week. 22 | The program asks the user the number of the day and prints out the corresponding name. Notice that the indexing of a 23 | tuple starts from zero the same way as with lists: 24 | 25 | ```python 26 | days_of_the_week = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") 27 | day_number = int(input("Enter the day number (1-7): ")) 28 | day = days_of_the_week[day_number-1] 29 | print(f"Day number {day_number} is {day}.") 30 | ``` 31 | 32 | When executed, the program works as follows: 33 | 34 | ```monospace 35 | Enter the day number (1-7): 3 36 | Day number 3 is Wednesday. 37 | ``` 38 | 39 | In the previous example, the elements inside the tuple are written between parentheses. 40 | In many cases, these parentheses can be omitted. The following 41 | program creates a tuple of three strings, and outputs its contents: 42 | 43 | ```python 44 | fruits = "Orange", "Banana", "Apple" 45 | print(fruits) 46 | ``` 47 | 48 | The output is: 49 | ```monospace 50 | ('Orange', 'Banana', 'Apple') 51 | ``` 52 | 53 | If data structures are nested, the use of parentheses may become necessary to avoid 54 | ambiguity. For example, 55 | in statement `values = 1, (2, 3), 4` the parentheses indicate that numbers 2 and 3 constitute 56 | a tuple that is, in turn, inside a tuple. 57 | 58 | Even though the use of parentheses around a tuple is not mandatory in most cases, many Python 59 | developers consider it a good practice that improves code readability. 60 | 61 | ### Tuple unpacking 62 | 63 | The elements inside a tuple can be unpacked into variables in the following way: 64 | ```python 65 | fruits = "Orange", "Banana", "Apple" 66 | (first, second, third) = fruits 67 | print(f"The fruits are: {first}, {second} and {third}.") 68 | ``` 69 | 70 | This program makes the following output: 71 | ```monospace 72 | The fruits are: Orange, Banana and Apple. 73 | ``` 74 | 75 | ### Tuples as return values 76 | 77 | Earlier on the course we learned about functions that are callable subroutines. 78 | Tuples provide an easy way to circumvent the limitation that a function can have only one return 79 | value. If the developer wants to return, for example, two values, he/she can create a 80 | tuple that holds both values and return that tuple as the return value. From a technical 81 | point of view, there's still one return value, but it is a tuple that can in turn contain 82 | more than one value. 83 | 84 | The following example illustrates the use of a tuple as a return value. The program contains 85 | a die-casting function for the Monopoly board game. In Monopoly, one always casts two dice at a time. 86 | 87 | ```python 88 | import random 89 | 90 | def cast(): 91 | first, second = random.randint(1,6), random.randint(1,6) 92 | return first, second 93 | 94 | die1, die2 = cast() 95 | print(f"The dice show {die1} and {die2}.") 96 | ``` 97 | 98 | The output reveals that we get a result for both casts with a single function call: 99 | ```monospace 100 | The dice show 2 and 4. 101 | ``` 102 | 103 | 104 | ## Set 105 | 106 | Set is an unordered data structure, meaning that the items can occur in any order. As the items of a set are not 107 | ordered, they cannot be referenced with an index. On the contrary to lists and tuples, the same item can occur 108 | in a set only once, meaning that a set cannot have two identical items. 109 | 110 | Let's look at the following example: 111 | 112 | ```python 113 | games = {"Monopoly", "Chess", "Cluedo"} 114 | print(games) 115 | 116 | games.add("Dominion") 117 | print(games) 118 | 119 | games.remove("Chess") 120 | print(games) 121 | 122 | games.add("Cluedo") 123 | print(games) 124 | 125 | for g in games: 126 | print(g) 127 | ``` 128 | 129 | The program produces the following output: 130 | ```monospace 131 | {'Chess', 'Monopoly', 'Cluedo'} 132 | {'Chess', 'Monopoly', 'Dominion', 'Cluedo'} 133 | {'Monopoly', 'Dominion', 'Cluedo'} 134 | {'Monopoly', 'Dominion', 'Cluedo'} 135 | Cluedo 136 | Dominion 137 | Monopoly 138 | ``` 139 | 140 | First, we create a set with three games: Monopoly, Chess and Cluedo. Then the set containing the games is printed out. 141 | Notice that the games in the output are in different order than when the set was created. The printing statement 142 | concretizes the fact that the order of the items is not defined: the programmer must prepare for the fact that the 143 | items can be printed out in any order. The order of presentation is determined by the internal storage solution and 144 | may differ even when the same program is run again. 145 | 146 | Next a fourth item, Dominion, is added to the three-item set of games. New items can be added with the `add` operation. 147 | The print statement shows again that the order of the items is random. 148 | 149 | Then one of the items, Chess, is removed from the set. This is done using the `remove` operation. 150 | 151 | The items left are Dominion, Cluedo and Monopoly. Next, we are trying to add a new item which the same as one 152 | of the existing items in the set: Cluedo. The `add` method does not raise an error but from the output we can 153 | see that the same game has not been added again. This is an essential characteristic of a set: the same item 154 | can be included only once. 155 | 156 | Lastly, the items in the set are iterated through one by one. This is done with a similar `for/in` structure 157 | that was used with lists. The resulting order can again be any possible order from the programmer's point of view. 158 | 159 | In the previous example all of the items in the set were strings, so they were all of the same type. 160 | However, there is no requirement for the items to be the same type. As an example, it is possible to create a set 161 | where one of the items is an integer, the second one a string and the third one a list. 162 | 163 | Finally, let's see how an empty set is constructed. 164 | An empty set is created and initialized with the `set` function. 165 | Thus, it is not possible to construct an empty set by simply adding 'empty' braces to 166 | the source code in this manner: `names = {}`. 167 | The following program correctly creates an empty set, adds one element into it, and outputs 168 | the set's contents with a `print` statement: 169 | 170 | ```python 171 | names = set() 172 | names.add("Mary") 173 | print(names) 174 | ``` 175 | The program produces the following output: 176 | ```monospace 177 | {'Mary'} 178 | ``` 179 | 180 | 181 | ## Dictionary 182 | 183 | Dictionary is one of the most commonly used Python data structures. 184 | 185 | A dictionary can store key-value pairs. A key is a sort of a handle that can be pulled to find the 186 | correct record in the dictionary to access its value. 187 | 188 | Sometimes dictionary is called an associative table or a hash structure. 189 | 190 | The following image represents the structure of a dictionary: 191 | 192 | ![The dictionary structure](img/dictionary.png) 193 | 194 | A dictionary called `numbers` contains the phone numbers of five persons. The name of a person is used as the key: 195 | when you know the name, you retrieve the value - in this case, phone number. 196 | 197 | Let's create a similar structure using Python. First, we will create a dictionary with the information of three persons. 198 | After the dictionary is created, we will add two more persons and print out the dictionary, now including the names 199 | and numbers of five persons. Lastly, we will ask the user for a person's name and print out the corresponding number if 200 | the provided name is included in the keys of the dictionary. 201 | 202 | ```python 203 | numbers = {"Viivi":"050-1234567", 204 | "Ahmed":"040-1112223", 205 | "Pekka":"050-7654321"} 206 | 207 | numbers["Olga"] = "050-1011012" 208 | numbers["Mary"] = "0401-2132139" 209 | 210 | print(numbers) 211 | 212 | name = input("Enter name: ") 213 | if name in numbers: 214 | print(f"{name}'s phone number is {numbers[name]}.") 215 | ``` 216 | 217 | The program produces the following output: 218 | ```monospace 219 | {'Viivi': '050-1234567', 'Ahmed': '040-1112223', 'Pekka': '050-7654321', 'Olga': '050-1011012', 'Mary': '0401-2132139'} 220 | Enter name: Viivi 221 | Viivi's phone number is 050-1234567. 222 | ``` 223 | 224 | When a dictionary is initialized by listing the values, each key-value pair is written as follows: `key : value`. Subsequent 225 | key-value pairs are separated with commas. 226 | 227 | A new value can be added to an existing dictionary using the `dictionary[key] = value` notation where `dictionary` is 228 | the name of the variable that refers to the dictionary. Respectively, the value of an item in the dictionary can be 229 | retrieved with `dictionary[key]`. 230 | 231 | When iterating through a dictionary using the `for/in` structure, on each loop the value of the loop variable to refers 232 | to the correponding key. 233 | 234 | But are the items in a dictionary, or the key-value pairs - ordered? This depends on the Python version: starting from 235 | version 3.7 the items are ordered, meaning that the running environment makes sure that the order of iteration is always the 236 | same as the order in which the items were added to the dictionary. This is not guaranteed in older Python versions. 237 | -------------------------------------------------------------------------------- /English/08_Using_Relational_Databases.md: -------------------------------------------------------------------------------- 1 | # Using relational databases 2 | 3 | In this module you will learn how to use a relational database in a Python program. 4 | 5 | Before you start, you should already have mastered the basic concepts of relational databases (tables, fields, records, 6 | primary and foreign keys, datatypes). You should also be able to express database queries and data transformation 7 | operations using the SQL language. Furthermore, you need to be able to design a small database structure and set up 8 | a database on a database server. 9 | 10 | This module uses the MariaDB database. However, the process is similar when using other databases. 11 | 12 | # Database drivers 13 | 14 | To use a relational database with your own program requires you to install a database driver. 15 | 16 | On this course we are using Python programming language and MariaDB database manager. The required database 17 | driver is a software between MariaDB and your own Python program that enables communication between the 18 | two programs. 19 | 20 | The database driver is needed already when establishing a connection to the database. Once the connection has been 21 | established, the driver allows for SQL statements (such as `SELECT` statements) to be sent to the database server. 22 | Furthermore, the driver converts the result sets to corresponding Python data structures. 23 | 24 | The correct database driver depends on both the chosen database manager software and the programming language 25 | used. Thus, we need a MariaDB-compatible driver for Python language. As MariaDB is compatible with MySQL database manager, we can 26 | install a MySQL driver for Python. 27 | 28 | You can proceed in one of the two ways presented below. Choose one option. 29 | 1. In PyCharm, click **View / Tools Windows / Python Packages**. Type **connector** as a search term, 30 | and select the **mysql-connector-python** option. Press **Install**. 31 | 2. Alternatively, you can install the driver by following the instructions on this website: https://dev.mysql.com/downloads/connector/python/. 32 | 33 | The material here assumes that we are using a MySQL driver due to easy installation and ease of use as well as the long 34 | history of MySQL drivers (the first MariaDB driver was not published until 2020). Based on this, following option 1 is recommended. 35 | If you prefer installing the MariaDB Connector/Python driver instead, you can do so by 36 | following the instructions on this website: https://mariadb.com/docs/clients/mariadb-connectors/connector-python/install/. 37 | 38 | 39 | Once you have installed the MySQL driver, you can test that it works by writing a program with this single `import` statement: 40 | 41 | ```python 42 | import mysql.connector 43 | ``` 44 | 45 | Notice that if you are using the MariaDB driver, the `import` statement that imports the library is different than the one 46 | presented here. There may also be slight differences in how the driver works compared to the MySQL driver. 47 | 48 | If the driver was successfully installed, nothing happens. If there were problems in the installation, you get an error message. 49 | Repair the installation if needed. 50 | 51 | During the installation, you may see an error message saying that a library called 52 | **Microsoft Visual C++ runtime** is missing. Should this happen, go to the Microsoft's download page at 53 | https://www.microsoft.com/en-us/download/default.aspx. Locate the missing library 54 | with the Search functionality, and install it. You can use the library details in the error message 55 | as search terms. 56 | 57 | ## Establishing connection to the database 58 | 59 | Let's look at an example database called `people`. The database contains a table called `Employee`. The following image 60 | shows the structure and contents of the table: 61 | 62 | ![Data example from the Employee table](img/schema.png) 63 | 64 | The primary key of the table is the `Number` field. For simplicity, the database in the example only has one table. 65 | 66 | We will extend the program so that it can establish a database connection to the MariaDB server: 67 | 68 | ```python 69 | import mysql.connector 70 | 71 | connection = mysql.connector.connect( 72 | host='127.0.0.1', 73 | port= 3306, 74 | database='people', 75 | user='dbuser', 76 | password='pAs5w_0rD', 77 | autocommit=True 78 | ) 79 | ``` 80 | 81 | The connection is established by using the `connect` method of the database driver. Let's take a closer look at 82 | the parameters of the method: 83 | - `host` defines the computer the connection is made to. When the server runs on the same computer where the Python program 84 | is run, the address is `127.0.0.1` or alternatively `localhost`. 85 | - `port` determines the port number the server is listening to. The default port number of MariaDB is 3306. 86 | - `database` is the name of the database. 87 | - `user` is the user account that is used to access the database. For a Python program you should create a new user account that 88 | has the required permissions for reading and modifying the data. Usually other permissions should not be given. 89 | - `password` defines the password tied to the user account. Notice that Internet-based Python programs are run on a background server 90 | and the end user does not have access to the Python code that includes the password. 91 | - `autocommit` determines if each SQL operation is committed immediately as a single transaction. Normally, this should be set 92 | as `True` so that separate update statements (such as `UPDATE`) do not have to be committed separately using a `COMMIT` statement. 93 | 94 | If the program still does not produce any visible output when you run it, everything is working as it should: the driver has been 95 | installed and the database connection has been established successfully. 96 | 97 | ## Search inquiry and processing result sets 98 | 99 | Next, we will write a program that uses a database. The program asks the user for a person's last name, searches matching employee 100 | records from the database and presents each employee: 101 | 102 | ```python 103 | import mysql.connector 104 | 105 | def get_employees_by_last_name(last_name): 106 | sql = f"SELECT Number, Last_name, First_name, Salary FROM Employee WHERE Last_name='{last_name}'" 107 | print(sql) 108 | cursor = connection.cursor() 109 | cursor.execute(sql) 110 | result = cursor.fetchall() 111 | if cursor.rowcount >0 : 112 | for row in result: 113 | print(f"Hello! I'm {row[2]} {row[1]}. My salary is {row[3]} euros per month.") 114 | return 115 | 116 | # Main program 117 | connection = mysql.connector.connect( 118 | host='127.0.0.1', 119 | port= 3306, 120 | database='people', 121 | user='dbuser', 122 | password='pAs5w_0rD', 123 | autocommit=True 124 | ) 125 | 126 | last_name = input("Enter last name: ") 127 | get_employees_by_last_name(last_name) 128 | 129 | ``` 130 | 131 | Running the program produces the follwing output: 132 | ```monospace 133 | Enter last name: Fernsby 134 | SELECT SELECT Number, Last_name, First_name, Salary FROM Employee WHERE Last_name='Fernsby' 135 | Hello! I'm Margaret Fernsby. My salary is 5008 euros per month. 136 | Hello! I'm Haven Fernsby. My salary is 4280 euros per month. 137 | Hello! I'm Ash Fernsby. My salary is 2158 euros per month. 138 | ``` 139 | 140 | The database query has been programmed inside the `getEmployeesByLastName` function. 141 | 142 | The SQL statement for the query is first written into a string variable that has been names `sql` in the program 143 | code. When writing a program, it is recommended to check the operation of an SQL query in a database editor 144 | (such as HeidiSQL). Only when the query works, it should be embedded into Python code. In this case the query 145 | is given the value of the last name from the parameter variable. 146 | 147 | Once the `sql` variable is ready, it should be output to the console using a `print` statement. A query rarely 148 | succeeds on the first try and troubleshooting is easier when the query is printed out to be visible. Once the 149 | query is deemed working, the printing statement can be removed or commented out by adding a comment character (`#`) 150 | at the beginning of the line. 151 | 152 | Once the query is finished, a cursor object is requested from the database connection object. The cursor is used 153 | to forward the SQL statement to the database server and look at the result set. In the example code the cursor is 154 | requested as follows: 155 | 156 | ```python 157 | cursor = connection.cursor() 158 | ``` 159 | 160 | Later the cursor is asked to execute the SQL statement in the string variable: 161 | 162 | ```python 163 | cursor.execute(sql) 164 | ``` 165 | 166 | Then the result set is requested from the server: 167 | 168 | ```python 169 | response = cursor.fetchall() 170 | ``` 171 | 172 | The method call fetches the entire result set. In case the result set is exceptionally large, it is be possible to 173 | fetch the records in smaller parts using the `fetchmany` and `fetchone` methods. This is rarely necessary. 174 | 175 | The result set stored in the `result` variable is a list structure where the elements are tuples. Each item 176 | in the outer structure (list) corresponds to one row in the result set. Each row is presented as a tuple where the 177 | items are the field values in the order they were written in the `SELECT` statement. 178 | 179 | The result set of the example can be visualized as follows: 180 | 181 | ![Result set as a diagram](img/result_set.png) 182 | 183 | After this the result set can be processed the same way as lists. In the example each row that corresponds 184 | to an employee is made into an employee object. The created objects are gathered in the `employees` object 185 | list that is returned as the return value of the method. 186 | 187 | ## Data modification queries 188 | 189 | Executing `UPDATE`, `INSERT` and `DELETE` statements that modify data is more straightforward than completing 190 | search queries. This is because there is no need to process a result set. With these operations the database 191 | server only returns the information of how many records were modified. 192 | 193 | Let's take a look at changing an employee's salary as an example. Notice that in this example the salary value 194 | in the database is updated directly. For a single employee, the change could alternatively be done so that the 195 | employee record would first be fetched from the database, the change would be done on the Python level to the 196 | object and finally the updated state of the object would be updated to the database. However, this is not done 197 | in the example below. 198 | 199 | Let's write another global function for updating the salary information. The function builds and executes the 200 | corresponding `UPDATE` statement: 201 | 202 | ```python 203 | def update_salary(number, new_salary): 204 | sql = f"UPDATE Employee SET Salary={new_salary} WHERE Number={number}" 205 | print(sql) 206 | cursor = connection.cursor() 207 | cursor.execute(sql) 208 | if cursor.rowcount==1: 209 | print("Salary updated") 210 | ``` 211 | 212 | The main program is extended by adding a statement for reading input and a call to the function: 213 | ```python 214 | number = int(input("Enter number: ")) 215 | new_salary = float(input("Enter new salary: ")) 216 | update_salary(number, new_salary) 217 | ``` 218 | 219 | The function we added confirms that the change was made to the database: 220 | ```monospace 221 | Enter number: 2 222 | Enter new salary: 3456 223 | UPDATE Employee SET Salary=3456.0 WHERE Number=2 224 | Salary updated 225 | ``` 226 | 227 | A successful update can be confirmed directly from the database by using a database editor. 228 | -------------------------------------------------------------------------------- /English/09_Fundamentals_of_Object-Oriented_Programming.md: -------------------------------------------------------------------------------- 1 | # Class, object, initializer 2 | 3 | In this module you will learn the fundamentals of object-oriented programming. You will learn how to write classes 4 | that determine common properties and operations to instances, or objects, of the class. You will learn the 5 | principles of creating, initializing and using objects. 6 | 7 | ## Classes and objects 8 | 9 | In object-oriented programming, a class is a general concept that determines common and shared properties 10 | for the members of the class. 11 | 12 | For example, **dog** can be such a general concept. Each dog has a group of properties such as name and 13 | birth year. Dogs also have activities (or in Python, methods) such as barking. 14 | 15 | Now we can write the simplest possible Dog class as follows: 16 | 17 | ```python 18 | class Dog: 19 | pass 20 | ``` 21 | 22 | The `pass` statement above is an empty statement that does not do anything. It is needed as a placeholder as the body of 23 | a class definition must contain at least one statement. 24 | 25 | This class definition only tells that there is a class called Dog. So far it does not specify any properties or methods 26 | of dogs. 27 | 28 | We can use the Dog class to create a Dog object. Objects are runtime instances or realizations of a class. Here is how 29 | we create a Dog object called Bubbles that was born in 2022: 30 | 31 | ```python 32 | class Dog: 33 | pass 34 | 35 | dog = Dog() 36 | dog.name = "Bubbles" 37 | dog.birth_year = 2022 38 | 39 | print(f"{dog.name:s} was born in {dog.birth_year:d}." ) 40 | ``` 41 | 42 | The first statement in the main program creates a Dog object that is referenced by the variable `dog`. The dog is given 43 | the name Bubbles and birth year of 2022. These are properties of the object we created and specific to that exact object. 44 | We could create multiple dogs each with their own name and year of birth. We could also specify a breed for some of the dogs 45 | and a nickname for others. Therefore, the properties of objects can vary from each other. 46 | 47 | As we see in the example, the properties of an object can be referenced by first writing the name of the object, then 48 | a period and lastly the name of the property. An example of such reference would be `dog.name`. 49 | 50 | The last statement of the example program outputs the name and birth year of the dog object created in the main program: 51 | ```monospace 52 | Bubbles was born in 2022. 53 | ``` 54 | 55 | Notice that class names in Python are written write uppercase initials. If the name of a class consists of multiple words, 56 | the words are written together without underscores so that each word starts with an uppercase letter. This naming style 57 | is called *CamelCase*. An example of this type of a name would be `ScreenRectangle`. 58 | 59 | ## Initializer 60 | 61 | In the example above the Dog object was created so that first an object was created with no properties and then the 62 | properties were assigned one by one. This way of creating objects is quite tiresome for the programmer. 63 | 64 | To make creating object easier, an initializer, or constructor, is written inside the class. The constructor automatically 65 | sets the required values to the properties of the new object. The following example shows a class constructor that sets 66 | the values of name and birth year automatically. The constructor is used in the main program to create an object. 67 | 68 | ```python 69 | class Dog: 70 | def __init__(self, name, birth_year): 71 | self.name = name 72 | self.birth_year = birth_year 73 | 74 | dog = Dog("Bubbles", 2022) 75 | 76 | print(f"{dog.name:s} was born in {dog.birth_year:d}." ) 77 | ``` 78 | 79 | A Python initializer is defined inside a class by writing a function with the name `__init__`. The first parameter of the 80 | function is always `self`. After this other parameters to the initializer are given. In this case they are name and birth year. 81 | A function defined this way is interpreted automatically as the initializer when the program is run and it is executed every 82 | time a new object is created. There is no return statement at the end of an initializer. 83 | 84 | Inside the initializer in the example there are two assignment statements were values are given to the properties of the new 85 | object. The properties of the new object are referenced by the reserved word `self` which is followed by a period and the name 86 | of the property. Typically, the parameters of the initializer are used to assign values to the properties of the new object. 87 | For example, the statement `self.name = name` assigs the value of the name parameter to the value of the name property. 88 | 89 | Notice that when a new object is created, the first parameter of the initializer, `self`, is not written. So, not this way: 90 | ```python 91 | # Incorrect instantiation statement 92 | dog = Dog(self, "Nuggets", 2022) 93 | ``` 94 | but this way instead: 95 | ```python 96 | dog = Dog("Nuggets", 2022) 97 | ``` 98 | 99 | ## Methods 100 | 101 | You have already learnt how object properties are defined. Usually you would want to also specify actions, or methods, to 102 | your objects. Let's write a bark method to our Dog class. The method can be called to instances, or objects, of the class. 103 | The program in the following example creates two Dog objects and makes the two dogs bark in their specific barking sound: 104 | 105 | ```python 106 | class Dog: 107 | def __init__(self, name, birth_year, sound="Woof woof"): 108 | self.name = name 109 | self.birth_year = birth_year 110 | self.sound = sound 111 | 112 | def bark(self, times): 113 | for i in range(times): 114 | print(self.sound) 115 | return 116 | 117 | 118 | dog1 = Dog("Rascal", 2018) 119 | dog2 = Dog("Boi", 2022, "Yip yip yip") 120 | 121 | dog1.bark(2) 122 | dog2.bark(5) 123 | ``` 124 | 125 | Now the initializer has three parameters. The last parameter (`sound`) has been given a default value that is assigned 126 | if the parameter is not given when a dog object is created. In this example, Rascal gets the default barking sound. 127 | 128 | The `bark` method the was written inside the class can be called to any existing instance of the Dog class. The first parameter 129 | of a method is always `self`. This is followed by other parameters that are given values when the method is called. 130 | 131 | A method is called by writing the name of an object followed by a period and the name of the method followed by parentheses 132 | and possible parameters. For example the statement `dog1.bark(2)` calls the bark method for the dog1 object. The times to bark 133 | is passed as a parameter in the method call (2). Inside a method the properties of and object can be referred to by writing `self` 134 | followed by a period and then the name of the property. For example, the expression `self.sound` refers to the value of the 135 | `sound` property specific to each object. 136 | 137 | ## Class methods or static methods 138 | 139 | In the previous example the properties of a dog were name, birth year and barking sound. The properties are of course specific to each object, 140 | meaning that different dogs can have different names. 141 | 142 | Sometimes there is a need to store some information that applies to the entire class instead of a single object. In the `Dog` class in the example 143 | this type of a property could for example be the total amount of dogs instantiated from the class. This type of information can be stored in a 144 | class variable, or static variable. 145 | 146 | In the following example a class variable called `created` has been defined to store the amount of dogs. Notice that the variable is defined outside the 147 | initializer. The definition statement of a class variable does not include the `self.` prefix. (The prior barking feature has been left out of this example). 148 | 149 | ```python 150 | class Dog: 151 | created = 0 152 | 153 | def __init__(self, name, birth_year, sound="Woof woof"): 154 | self.name = name 155 | self.birth_year = birth_year 156 | self.sound = sound 157 | Dog.created = Dog.created + 1 158 | 159 | dog1 = Dog("Rascal", 2018) 160 | dog2 = Dog("Boi", 2022, "Yip yip yip") 161 | print(f"{Dog.created} dogs have been created so far.") 162 | ``` 163 | 164 | The value of a class variable is referenced by writing both the name of the class and the class variable, in this example `Dog.created`. 165 | 166 | The program produces the following output: 167 | 168 | ```monospace 169 | 2 dogs have been created so far. 170 | ``` 171 | -------------------------------------------------------------------------------- /English/10_Association.md: -------------------------------------------------------------------------------- 1 | # Association 2 | 3 | In this module you will learn to write programs where objects can interact with each other. 4 | 5 | In object-oriented programming a program is composed of classes. The classes are used to create 6 | instances, or objects, during runtime. The objects can interact with each other: an object can 7 | process other objects and call their methods. 8 | 9 | This relationship between objects is called association. The power of object-oriented programming is achieved 10 | by programming these associative relationships: the program breaks up into small, easily understandable pieces 11 | and the programmer can write code in small portions, focusing in one feature at a time. When the associations 12 | between objects are designed well, even a large program is easy to build with these small parts. 13 | 14 | ## Designing the structure 15 | 16 | In the last module we wrote a `Dog` class that defines the properties of a dog (name, birth year and distinctive barking 17 | sound). Furthermore, the class has a single method: `bark`. The `Dog` class looks like this: 18 | 19 | ```python 20 | class Dog: 21 | def __init__(self, name, birth_year, sound="Woof woof"): 22 | self.name = name 23 | self.birth_year = birth_year 24 | self.sound = sound 25 | 26 | def bark(self, times): 27 | for i in range(times): 28 | print(self.name + " barks: " + self.sound) 29 | return 30 | ``` 31 | 32 | Let's extend the example by adding a dog hotel. A dog hotel is defined as follows: a dog can be taken to a dog hotel and 33 | later be picked up from the hotel. Occasionally, a member of the dog hotel staff takes a round around the hotel: they greet 34 | all the dogs and each dog barks back. 35 | 36 | First, we will need to think about what we need to implement the dog hotel. 37 | 38 | First of all, the dog hotel should be implemented as a separate class. The functionality of the dog hotel has nothing to 39 | do with a single dog, so it should not be written inside the Dog class. Therefore, we will add a second class called Hotel 40 | to our program. 41 | 42 | Next, we need to think about what properties relate to a dog hotel. We notice that a dog hotel must know which dogs are in 43 | its care at any given time. This can be done by using a list: let's add a list of dogs as a property of the Hotel class. 44 | 45 | What about activities of a dog hotel that should be written into methods? From the definition earlier we 46 | can identify three methods we should write for a dog hotel: 47 | 1. Checking a dog in to the dog hotel. 48 | 2. Checking a dog out from the dog hotel. 49 | 3. Doing a round in the dog hotel. 50 | 51 | Now we have defined and designed the program and we can go ahead to implement it. 52 | 53 | ## A program with two classes 54 | 55 | There are two classes in our example program: `Dog` and `Hotel`. In Python, it is common to write multiple classes into 56 | a single source file. The classes could also be in separate files. If the classes are in placed in 57 | separate files, referencing another class is only possible if an `import` statement for the other file (or module) is added 58 | at the beginning of the program. 59 | 60 | It is handy to write classes of a small program into one file, and this is what we will do now. We will create a file called 61 | doghotel.py where we will program all the required functionality: 62 | 63 | ```python 64 | class Dog: 65 | def __init__(self, name, birth_year, sound="Woof woof"): 66 | self.name = name 67 | self.birth_year = birth_year 68 | self.sound = sound 69 | 70 | def bark(self, times): 71 | for i in range(times): 72 | print(self.name + " barks: " + self.sound) 73 | return 74 | 75 | class Hotel: 76 | def __init__(self): 77 | self.dogs = [] 78 | 79 | def dog_checkin(self, dog): 80 | self.dogs.append(dog) 81 | print(dog.name + " checked in") 82 | return 83 | 84 | def dog_checkout(self, dog): 85 | self.dogs.remove(dog) 86 | print(dog.name + " checked out") 87 | return 88 | 89 | def greet_dogs(self): 90 | for dog in self.dogs: 91 | dog.bark(1) 92 | 93 | # Main program 94 | 95 | dog1 = Dog("Rascal", 2018) 96 | dog2 = Dog("Boi", 2022, "Yip yip yip") 97 | 98 | hotel = Hotel() 99 | 100 | hotel.dog_checkin(dog1) 101 | hotel.dog_checkin(dog2) 102 | hotel.greet_dogs() 103 | 104 | hotel.dog_checkout(dog1) 105 | hotel.greet_dogs() 106 | ``` 107 | 108 | The example program consists of three parts: 109 | 1. the `Dog` class 110 | 2. the `Hotel` class 111 | 3. the main program. 112 | 113 | The execution of the program starts at the beginning of the main program. First two dogs, Rascal and Boi, are created. 114 | Then a new hotel is created: 115 | 116 | ```python 117 | hotel = Hotel() 118 | ``` 119 | 120 | Now the execution moves to the initializer of the Hotel class where an empty dogs list is added as a property of 121 | the hotel. The newly created hotel does not have any guests yet, but it has an empty list for storing dogs later. 122 | 123 | Next, the first dog (Rascal) is checked in to the hotel: 124 | 125 | ```python 126 | hotel.dog_checkin(dog1) 127 | ``` 128 | 129 | This is a method provided by the hotel: the check-in is clearly an activity of the hotel, which is why it has been 130 | programmed into the `Hotel` class. It is necessary to know which dog is going to be checked in, so the corresponding 131 | Dog object (or actually a reference to the object) is passed as an argument in the method call. When the method is called, 132 | the execution moves to the `dog_checkin` method where the dog received as a parameter is added to the dog list of the hotel. 133 | 134 | The second dog Boi is checked in to the hotel the same way. 135 | 136 | Now it is time for the caretaker to do a round in the hotel and greet all the dogs. To do this the corresponding method written 137 | in the `Hotel` class is called: 138 | 139 | ```python 140 | hotel.greet_dogs() 141 | ``` 142 | 143 | This method was implemented without parameters. The greeting is targeted to all dogs that are currently in the hotel and 144 | the hotel itself knows which dogs its taking care of at any given time. The method iterates through the list of dogs and 145 | tells each dog to bark once. 146 | 147 | Finally, one dog, Boi, is checked out from the hotel. For this, the corresponding method that removes a dog from the list 148 | in the `Hotel` class is called. Then the dogs are greeted again, but this time only Rascal is there to answer. 149 | 150 | The operation of the program can be seen from its output: 151 | 152 | ```python 153 | Rascal checked in 154 | Boi checked in 155 | Rascal barks: Woof woof 156 | Boi barks: Yip yip yip 157 | Boi checked out 158 | Rascal barks: Woof woof 159 | ``` 160 | 161 | Now we have written a program that has instances (or objects) from two different classes. We can say that there is a 162 | permanent association between the `Hotel` class and the `Dog` class: A `Hotel` object has an instance variable that 163 | holds the references to `Dog` objects. 164 | 165 | Here the associative relationship is unidirectional: A `Hotel` object knows which dogs are currently staying in the hotel. 166 | On the other hand, a `Dog` object has no knowledge of the hotel it might currently be staying in. An associative relationship 167 | can be implemented either unidirectionally or bidirectionally. A bidirectional association should only be used when absolutely 168 | needed. Bidirectional association brings an extra burden to the programmer because the contents of the object references to 169 | different directions must be in sync. 170 | 171 | ## Temporary association 172 | 173 | As was mentioned above, the `Hotel` and `Dog` classes in the example shared a static association: the dogs in the hotel 174 | were stored as a list into the property of the hotel. 175 | 176 | The `Hotel` and `Dog` classes also share another type of a dependency: The `Hotel` class provides to methods that 177 | have a reference to a `Dog` object as a parameter. An associative relationship can also be valid only during a method call 178 | when an instance of the other class is listed as a parameter of a method. When the method call finishes, the associative 179 | relationship used during the method call would vanish if the information of the relationship wasn't stored as a property like 180 | we did in our example. 181 | 182 | Let's look at an example of a situation where an associative relationship is purely temporary: the relationship between a 183 | car and a car paint shop. In this example we will create a blue car and pass it to a paint shop to be painted red: 184 | 185 | 186 | ```python 187 | class Car: 188 | def __init__(self, plate_number, colour): 189 | self.plate_number = plate_number 190 | self.colour = colour 191 | 192 | class PaintShop: 193 | def paint(self, car, colour): 194 | car.colour = colour 195 | 196 | paint_shop = PaintShop() 197 | car = Car("ABC-123", "blue") 198 | print("The car is " + car.colour) 199 | paint_shop.paint(car, "red") 200 | print("The car is now " + car.colour) 201 | ``` 202 | 203 | The program prints out the colour of the car before and after the paint job: 204 | 205 | ```monospace 206 | The car is blue 207 | The car is now red 208 | ``` 209 | 210 | In this example the paint shop knows the car it needs to paint only for time of the execution of the `paint` method as 211 | the reference to the `Car` object was received as a parameter of the method call. Once the execution of method is finished, 212 | the value of the parameter variable can no longer be accessed. The car has no knowledge of the paint shop either. In this 213 | example the associative relationship between the paint shop and the car is only temporary. 214 | -------------------------------------------------------------------------------- /English/11_Inheritance.md: -------------------------------------------------------------------------------- 1 | # Inheritance 2 | 3 | Inheritance is a mechanism in object-oriented programming where classes have a defined hierarchy: 4 | one class called base class can have more detailed subclasses (derived classes). 5 | 6 | In this module you will learn to use inheritance when writing object-based Python programs. 7 | 8 | ## Base class and subclass 9 | 10 | Let's look at the following situation where a program processes Employee objects: 11 | 12 | ```python 13 | class Employee: 14 | 15 | total_employees = 0 16 | 17 | def __init__(self, first_name, last_name): 18 | Employee.total_employees = Employee.total_employees + 1 19 | self.employee_number = Employee.total_employees 20 | self.first_name = first_name 21 | self.last_name = last_name 22 | 23 | def print_information(self): 24 | print(f"{self.employee_number}: {self.first_name} {self.last_name}") 25 | 26 | employees = [] 27 | employees.append(Employee("Viivi", "Virta")) 28 | employees.append(Employee("Ahmed", "Habib")) 29 | 30 | for e in employees: 31 | e.print_information() 32 | ``` 33 | 34 | The program creates two employees: Viivi and Ahmed, adds them to the employee list and prints out the contents 35 | of the list: 36 | 37 | ``` monospace 38 | 1: Viivi Virta 39 | 2: Ahmed Habib 40 | ``` 41 | 42 | Each employee has three properties: employee number, first name and last name. Each employee gets an employee number 43 | automatically and it is based on the total amount of employees. The total amount of employees here is a 44 | class variable: its value is not defined separately for each instance of the Employee class but instead only once 45 | for the entire Employee class. Notice that a class variable is defined outside the initializer and when referenced 46 | the `self` expression is replaced by the name of the class. 47 | 48 | Let's assume that we face a need for improvement: some employees are hourly employees while others have a monthly salary. 49 | How should the salary information be added to the properties list in the Employee class? 50 | 51 | One solution would be to add two separate properties: hourly pay and monthly pay. The solution would, however, be imprecise 52 | and when using the software, we would always have to check the field values to see which employee type is in question. 53 | Moreover, nothing would technically prevent us from defining both and hourly as well as a monthly pay for the same employee. 54 | 55 | Let's solve this by using the inheritance mechanism of Python. 56 | Let's write two subclasses for the Employee class to clarify things: HourlyPaid and MonthlyPaid. When we create a new object, 57 | we could make it for example an instance of the HourlyPaid subclass. Then it would have all the properties and methods inherited 58 | from the Employee base class (such as first name and the method work) but also the hourly pay property only defined for hourly 59 | paid staff inside the subclass. 60 | 61 | The extended program looks as follows: 62 | 63 | ```python 64 | class Employee: 65 | 66 | total_employees = 0 67 | 68 | def __init__(self, first_name, last_name): 69 | Employee.total_employees = Employee.total_employees + 1 70 | self.employee_number = Employee.total_employees 71 | self.first_name = first_name 72 | self.last_name = last_name 73 | 74 | def print_information(self): 75 | print(f"{self.employee_number}: {self.first_name} {self.last_name}") 76 | 77 | class HourlyPaid(Employee): 78 | 79 | def __init__(self, first_name, last_name, hourly_pay): 80 | self.hourly_pay = hourly_pay 81 | super().__init__(first_name, last_name) 82 | 83 | def print_information(self): 84 | super().print_information() 85 | print(f"Hourly pay: {self.hourly_pay}") 86 | 87 | class MonthlyPaid(Employee): 88 | 89 | def __init__(self, first_name, last_name, monthly_pay): 90 | self.monthly_pay = monthly_pay 91 | super().__init__(first_name, last_name) 92 | 93 | def print_information(self): 94 | super().print_information() 95 | print(f"Monthly pay: {self.monthly_pay}") 96 | 97 | 98 | employees = [] 99 | employees.append(HourlyPaid("Viivi", "Virta", 12.35)) 100 | employees.append(MonthlyPaid("Ahmed", "Habib", 2750)) 101 | employees.append(Employee("Pekka", "Puro")) 102 | employees.append(HourlyPaid("Olga", "Glebova", 14.92)) 103 | 104 | for e in employees: 105 | e.print_information() 106 | 107 | ``` 108 | 109 | The example has four employees: two with hourly pay, one with monthly pay and one employee (Pekka) without a defined 110 | contract type. 111 | 112 | The program provides the following output: 113 | ```monospace 114 | 1: Viivi Virta 115 | Hourly pay: 12.35 116 | 2: Ahmed Habib 117 | Monthly pay: 2750 118 | 3: Pekka Puro 119 | 4: Olga Glebova 120 | Hourly pay: 14.92 121 | ``` 122 | 123 | A base class - subclass relationship in Python is expressed by adding the name of the base class enclosed in brackets 124 | to the `class` statement that defines the subclass. Thus, the beginning of statement the `class HourlyPaid(Employee)` 125 | determines that the HourlyPaid class becomes a subclass of the Employee class. 126 | 127 | If needed, a subclass can have its own initializer. When an instance of the subclass is created, only the initializer 128 | of the subclass is executed. In practice, usually this is done the same way as in out example: The program calls the 129 | initializer of the subclass which then calls the initializer of the base class. In this case the initializer of the 130 | subclass assigns the value for hourly pay whereas the first and last name are defined in the base class. Their values 131 | are passed to the base class by calling the base class initializer, or `__init__` method. The base class of an object 132 | can be accessed with the super() function: statement `super().__init__(first_name, last_name)` calls the initializer 133 | of the base class that receives the first and last name as parameters. 134 | 135 | Properties defined in the base class are automatically visible in the subclass. Thus, we can create an instance of the 136 | `HourlyPaid` class and fetch their name anytime with the expression `e.first_name`. 137 | 138 | ## Overriding methods 139 | 140 | When we look at the example above, we notice that the `Employee` base class has a `print_information` method that 141 | prints out the first and last name of the employee. The method works well when a person has been created as an 142 | instance of the `Employee` class regardless of their contract type. On the other hand, for printing the information 143 | of hourly paid employees for example the method is too concise: it prints out the name information but cannot access 144 | the hourly pay defined in the subclass. 145 | 146 | The problem can be solved by overriding the `print_information` method. Overriding means that another implementation 147 | of a method that exists in the base class is written into the subclass. The overridden method in the subclass overrides 148 | the method defined in the base class. Therefore, when we write `e.print_information()` for an object of the `HourlyPaid` 149 | class, it automatically calls the version of the method defined in the `HourlyPaid` class. If the same method call 150 | is written for an object from the `Employee` class, the version of the same method in the base class is called. 151 | 152 | Overriding a method makes our program more flexible: we can have diverse types of employees with different 153 | data structures. Nevertheless, the information of all employees can be printed out in the main program with a simple 154 | loop structure: 155 | 156 | ``` 157 | for e in employees: 158 | e.print_information() 159 | ``` 160 | 161 | Variants of the called method and technical details of the implementation are hidden where they belong: the implementing classes. 162 | They are not visible to the main program. 163 | 164 | ## Multiple inheritance 165 | 166 | Sometimes there are situations where a single class needs to be defined as a subclass of two or even more base classes. 167 | This feature is called multiple inheritance. In contrary to some other programming languages, Python allows multiple inheritance. 168 | 169 | The following example illustrates multiple inheritance. We define two classes: `Vehicle` and `SportsItem`. A third class `Bicycle` 170 | can be made a subclass of both of the classes: 171 | 172 | ```python 173 | class Vehicle: 174 | def __init__(self, speed): 175 | self.speed = speed 176 | 177 | class SportsItem: 178 | def __init__(self, weight): 179 | self.weight = weight 180 | 181 | class Bicycle(Vehicle, SportsItem): 182 | def __init__(self, speed, weight, gears): 183 | Vehicle.__init__(self, speed) 184 | SportsItem.__init__(self, weight) 185 | 186 | self.gears = gears 187 | 188 | b = Bicycle(45, 18.7, 3) 189 | print(b.gears) 190 | print(b.speed) 191 | print(b.weight) 192 | ``` 193 | 194 | We create a Bicycle object and print out the number of gears, speed and weight. The number of gears is defined in the `Bicycle` class. 195 | The class inherits speed ifrom the `Vehicle` class and weight from the `SportsItem` class. The program produces the following output: 196 | 197 | ```monospace 198 | 3 199 | 45 200 | 18.7 201 | ``` 202 | 203 | In this case we cannot reference the initializers of both base classes from the initializer of the `Bicycle` class using `super`: 204 | 205 | ```python 206 | # Incorrect initializer calls 207 | super.__init__(speed) 208 | super.__init__(weight) 209 | ``` 210 | 211 | The base class that the `super` function refers to is determined by the Python Method Resolution Order (MRO). In this case both 212 | statements would call the initializer in the `Vehicle` class and the program would not work correctly. 213 | 214 | We can call the initializers of both base classes using an alternative notation where the base class is specified by its name: 215 | 216 | ```python 217 | Vehicle.__init__(self, speed) 218 | SportsItem.__init__(self, weight) 219 | ``` 220 | -------------------------------------------------------------------------------- /English/13_Setting_Up_a_Backend_Service_With_an_Interface.md: -------------------------------------------------------------------------------- 1 | # Setting up a backend service with an interface 2 | 3 | In this module you will learn to implement a backend in Python. This way you can build 4 | a web service so that the HTML, CSS or JavaScript user interface (UI) communicates with the 5 | HTTP endpoints provided by the backend written in Python. 6 | 7 | The user of a backend does not necessarily have to be a browser. With the approach presented here, 8 | the backend service can be used programmatically from any service with any programming language thanks to 9 | the HTTP connection protocol. 10 | 11 | ## Flask library installation 12 | 13 | A Python program is made into a backend service using the Flask library. Flask enables the programming 14 | of endpoints. An external program (such as a web browser) can use those endpoints to execute operations 15 | programmed into the backend service. 16 | 17 | Let's look at an example where we create a backend service that receives two numbers and adds them together. 18 | This kind of an operation would not of course need a backend service. However, this simple example is used 19 | just to demonstrate the required technology. 20 | 21 | We will start by installing the Flask library. The installation can be done directly from the PyCharm IDE: 22 | 23 | 1. Select **View / Tool Windows / Python Packages**. 24 | 2. Type **Flask** into the search field. Select **Flask** from the list that opens and click **Install** 25 | 26 | The library is now ready to use. 27 | 28 | ## Programming endpoints 29 | 30 | Once Flask has been installed, we can write the first version of our program into a file named `sum_service.py`: 31 | 32 | ```python 33 | from flask import Flask, request 34 | 35 | app = Flask(__name__) 36 | @app.route('/sum') 37 | def calculate_sum(): 38 | args = request.args 39 | number1 = float(args.get("number1")) 40 | number2 = float(args.get("number2")) 41 | total_sum = number1+number2 42 | return str(total_sum) 43 | 44 | if __name__ == '__main__': 45 | app.run(use_reloader=True, host='127.0.0.1', port=5000) 46 | 47 | ``` 48 | 49 | Let's see how the program works by starting from the last line. The call to the `app.run` method launches the backend service. 50 | The service is opened in IP address 127.0.0.1 which is a so-called loopback (or localhost) address that points to the IP address 51 | of your own computer. This means that the connection to that IP address can only be established from the same computer where the 52 | program is running. Port number 5000 tells that the backend server listens to port 5000 for communication from the same 53 | computer. 54 | 55 | Line `@app.route('sum')` defines a so called endpoint. It means that the function `calculate_sum` on the next line is executed when 56 | a user of the backend sends a request to the IP address followed by the string `/sum`. This means that the function can be 57 | called from the browser by typing `http://127.0.0.1:5000/sum` as the web address. Technically, the browser then sends an 58 | HTTP protocol GET request that the backend service built with Flask responds to. 59 | 60 | The request portrayed above is not yet enough to calculate the sum, as also the numbers for calculating the sum must be defined 61 | in the request. The numbers can be passed as parameters of the GET request and then be processed using the `args.get` method of the 62 | `request` library. 63 | 64 | This way the backend service could be called by writing for example the address `http://127.0.0.1:5000/sum?number1=13&number2=28` to 65 | a browser. The first parameter that has been converted to a float "13" is assigned as the value of the `number1` variable. Respectively, 66 | the second parameter, string "28", is converted to a float and assigned to the `number2` variable. The sum is calculated, converted into 67 | a string and then returned as the return value of the function. 68 | 69 | When the backend service is called from a browser the resulting number is seen on the browser window: 70 | 71 | ![Backend service response in a browser window](img/flask_response.png) 72 | 73 | At this point the backend service technically works, but the format of the result is not optimal to be processed programmatically. 74 | 75 | ## Generating a JSON response 76 | 77 | When a backend sends a response to a browser, the best format for the response is usually JSON. JSON (*JavaScript 78 | Object Notation*) is a presentation format that complies with the structure of JavaScript objects. Luckily, the structure is also 79 | intuitive for developers accustomed to objects in Python language. 80 | 81 | Let's modify the `sum` function in the example so that it no longer returns a string but instead produces a response in JSON format. 82 | This will be done automatically from the Python dictionary structure: 83 | 84 | ```python 85 | from flask import Flask, request 86 | 87 | app = Flask(__name__) 88 | @app.route('/sum') 89 | def calculate_sum(): 90 | args = request.args 91 | number1 = float(args.get("number1")) 92 | number2 = float(args.get("number2")) 93 | total_sum = number1+number2 94 | 95 | response = { 96 | "number1" : number1, 97 | "number2" : number2, 98 | "total_sum" : total_sum 99 | } 100 | 101 | return response 102 | 103 | if __name__ == '__main__': 104 | app.run(use_reloader=True, host='127.0.0.1', port=5000) 105 | ``` 106 | 107 | Now the program produces a JSON response which is easy to process for example by running a JavaScript code on a browser: 108 | 109 | ![JSON response in a browser window](img/flask_json.png) 110 | 111 | The simple backend service presented here can be used to build a more versatile backend service with the required amount of 112 | endpoints. 113 | 114 | ## Parsing the request 115 | 116 | In the previous examples, the parameter values were provided as HTTP request parameters, separated from the domain and country parts 117 | with a question mark (`?`). This is a traditional way to send parameters in HTTP requests. 118 | 119 | An alternative way is to specify the resource targeted by the request in the body of the web address. 120 | The following simple example implements an "echo service" that echoes, or doubles, the string provided 121 | by the client. In the example, the string is not given as a parameter but as a part of the actual web address. 122 | Flask provides an easy approach for handling parts of the web address: 123 | 124 | ```python 125 | from flask import Flask 126 | 127 | app = Flask(__name__) 128 | @app.route('/echo/') 129 | def echo(text): 130 | response = { 131 | "echo" : text + " " + text 132 | } 133 | return response 134 | 135 | if __name__ == '__main__': 136 | app.run(use_reloader=True, host='127.0.0.1', port=3000) 137 | ``` 138 | 139 | The service looks like this when viewed via a web browser: 140 | 141 | ![Echo service in web browser](img/address.png) 142 | 143 | The developer of the backend service can freely choose how the handling of the web address part after the domain and the country code is done. 144 | Particularly, the REST architecture style encourages the latter approach where the targeted resource is given 145 | as part of the actual web address instead of providing it as a parameter value. 146 | 147 | ## Error handling 148 | 149 | Let's return to the earlier example on calculating the sum of two numbers. We assume that the program 150 | has been amended so that the two numbers are provided as part of the body of the web address. Thus, 151 | a valid request looks like this: `http://127.0.0.1:3000/sum/42/117`. 152 | 153 | In the earlier example, we assumed that the request is always error-free. 154 | 155 | However, at least the following errors are possible and should be dealt with: 156 | 1. The user tries to call an erroneous endpoint: `http://127.0.0.1:3000/dum/42/117` 157 | 2. A correct endpoint is called, but the sum cannot be computed because of an invalid number as input: 158 | `http://127.0.0.1:3000/sum/4t23/117` 159 | 160 | 161 | In the first case, the Flask backend service automatically returns the error code 404 (Not found). 162 | In the latter case, the status code 500 (Internal server error) is returned. The originator 163 | of the request can handle the error situations programmatically. However, as the authors 164 | of the backend service, we have the option to handle the error situations as they emerge, 165 | producing the request sender more detailed information about the potential cause 166 | of the error. 167 | 168 | The following program handles the error situations in a more elegant fashion: 169 | 1. A request to an invalid endpoint produces the status code 404 with a JSON response: 170 | `{"status": 404, "message": "Invalid endpoint"}`. 171 | 2. Should the conversion of a parameter to float type fail, the following JSON is sent: 172 | `{"status": 400, "teksti": "Invalid number as addend"}`. The backend service now returns the more suitable HTTP status code 173 | 400 (Bad Request) instead of the default code 500 (Internal server error). 174 | 175 | Also, the program adds the status code to the body of the JSON response. The code in the body is sent just as 176 | additional information for the client. The 'real' HTTP status code is provided as the statuscode parameter 177 | of the Response object. (The Response object must be created whenever we want to send something else than the JSON auto-converted 178 | from the dictionary accompanied with the default error code 200 (OK). 179 | Unfortunately, we cannot take advantage of the dictionary-to-JSON auto-conversion in this case, but 180 | we must use the `json.dumps` method instead.). 181 | 182 | As the Response object is created, we need to specify the so-called MIME type. A MIME type tells the client 183 | how the content should be interpreted. In this case, the MIME type is set to `"application/json"`. 184 | 185 | The expanded program is as follows: 186 | ```python 187 | import json 188 | 189 | from flask import Flask, Response 190 | 191 | app = Flask(__name__) 192 | @app.route('/sum//') 193 | def calculate_sum(number1, number2): 194 | try: 195 | number1 = float(number1) 196 | number2 = float(number2) 197 | total_sum = number1+number2 198 | response = { 199 | "number1" : number1, 200 | "number2" : number2, 201 | "total_sum" : total_sum, 202 | "status" : 200 203 | } 204 | return response 205 | 206 | except ValueError: 207 | response = { 208 | "message": "Invalid number as addend", 209 | "status": 400 210 | } 211 | json_response = json.dumps(response) 212 | http_response = Response(response=json_response, status=400, mimetype="application/json") 213 | return http_response 214 | 215 | @app.errorhandler(404) 216 | def page_not_found(error_code): 217 | response = { 218 | "message": "Invalid endpoint", 219 | "status": 404 220 | } 221 | json_response = json.dumps(response) 222 | http_response = Response(response=json_response, status=404, mimetype="application/json") 223 | return http_response 224 | 225 | if __name__ == '__main__': 226 | app.run(use_reloader=True, host='127.0.0.1', port=5000) 227 | ``` 228 | -------------------------------------------------------------------------------- /English/Project/01_Preliminary_Project_Assignment.md: -------------------------------------------------------------------------------- 1 | # Preliminary project assignment 2 | 3 | Your goal is to build a **functional prototype of a flight game**. 4 | 5 | ![Electric Airplane](img/Pipistrel_WATTsUP_airplane.jpg) 6 | 7 | An image of the Pipistrel WATTsUP electric airplane: Ymmo, CC BY-SA 4.0 , via Wikimedia Commons 8 | 9 | The prototype flight is a game where the player can travel either globally or in a predefined area. The game 10 | uses the locations of real airports. 11 | 12 | The project consists of four phases: 13 | 14 | 1. Defining the requirements for the game in the preliminary project. (During Software 1) 15 | 2. Implementation and testing of the preliminary project. (During Software 1) 16 | 3. A more detailed list of requirements for the final project work. (During Software 2) 17 | 4. Implementation and testing of the final project work. (During Software 2) 18 | 19 | The first two phases are completed during the Software 1 study module. The goal of the preliminary project is to build 20 | a functional prototype of the game that utilizes Python and a relational database. 21 | 22 | The last two phases are completed over the Software 2 study module. The functional prototype built during the previous 23 | study module is used as a basis of the final project. The prototype is extended and modified by adding a web-based user interface (UI) 24 | that uses a map service. Furthermore, the game is programmed using an external data source, such an online weather service. 25 | 26 | The requirement specification phase of the preliminary project aims at finding a shared vision of what type of an application the team will 27 | start building in the preliminary project. You will produce a written requirement specification document that describes all the functionality 28 | of the application, meaning what can be done using the upcoming software. Furthermore, the specification lists requirements that can not be 29 | summarized into plain actions. These are called quality requirements and can for example relate to performance, response times or usability. 30 | 31 | ## Requirement specification document 32 | 33 | The most important virtue of the requirement specification document is its concreteness. The operation of the software is described 34 | in such precision that there is as little room as possible left for interpretation. No features are typically left to be decided 35 | during the implementation phase. 36 | 37 | As said, the functionality and quality requirements are listed in the requirement specification. This is done from the user's perspective: 38 | the document should answer the question "What can the user do with the software?". The technical aspects of the implementation are not 39 | discussed. 40 | 41 | The requirement specification document must contain at least the following chapters: 42 | 1. Introduction 43 | 2. Vision 44 | 3. Functional requirements 45 | 4. Quality requirements 46 | 47 | Chapter 1 (Introduction) discusses what the purpose of the document is and who it is targeted to. Also the structure of the document 48 | can be presented in the Introduction. 49 | 50 | Chapter 2 (Vision) describes the general idea of the the flight game. What is the purpose of the game and how does it work? 51 | The vision can be presented also as a figure that is supported by a written description. Here you must explain the main idea of the game 52 | in your own words: how does the game proceed and what stages must the player go through? 53 | 54 | Chapter 3 (Functional requirements) discusses everything the user can do with the game. The functional requirements are typically presented 55 | as user stories with a role (who), action (what), and benefit (why). An example of a user story would be "As a player I can choose the next 56 | airport from the cities showing on the map, so that my electric airplane will move there.". The example user story contains a role (player), 57 | an action (selecting the next city) and a benefit the user can gain by completing the action (moving to the new location). There are enough 58 | user stories when they together describe all functionality of the game. For the flight game this probably means dozens of user stories. 59 | Each user story must be unambiguous and concrete. It must be possible to look back at the user stories later to determine whether each 60 | planned functionality has been implemented in the software. 61 | 62 | Chapter 4 (Quality requirements) defines other requirements besides the functional requirements the flight game has. Examples of these 63 | could be performance requirements ("Fetching airport information from the database must take a maximum of two seconds.") or usability 64 | requirements ("The user must get instant feedback from all actions they perform"). 65 | 66 | A requirements specification document can be evaluated with the following questions: 67 | 68 | - Does the vision provide a general view of the game and the idea behind the game? 69 | - Has the functionality of the game been presented in a comprehensive, unambiguous, concrete and feasible way? Does the document give an accurate 70 | impression of how the game works? 71 | - Does the game contain novel ideas? 72 | - Have the necessary quality requirements been presented in a concrete way? 73 | - Does the game fulfill the set mandatory conditions for the game? 74 | - Can the document be considered a high-quality, technical document in terms of language and appearance? 75 | 76 | 77 | 78 | ## Mandatory conditions for the game 79 | 80 | A set of mandatory conditions are used as a basis for the requirement specification. The purpose of the mandatory conditions is that you will 81 | reach the defined learning objectives of the course when working on the game project. 82 | 83 | Therefore, you can design and implement any type of a game you want as long as it fulfills the boundary conditions listed below: 84 | 85 | 1. The user plays the game interactively using their keyboard. 86 | 2. The game utilizes a relational database that is based on the airport database that has been used on this course. The schema of the 87 | database can be freely modified and extended as needed. 88 | 3. The game has a concrete goal, and it produces a good game experience. 89 | 4. The game takes sustainable development into account. 90 | 5. The game follows a good gaming etiquette and courtesy and is also suitable for young users (12+). 91 | 92 | ## Determining the Grade 93 | 94 | The evaluation of the project follows the following guidelines: 95 | 96 | - **Grade 1**: The work reflects an effort to meet the mandatory conditions 1-5. There are deficiencies and weaknesses in the technical implementation, or the work is modest in scope and ambition. 97 | - **Grade 3**: The game fulfills the mandatory conditions 1-5. There are no significant shortcomings in the technical implementation. From the perspective of its scope and complexity, the work is of a good standard. 98 | - **Grade 5**: The game fulfills the mandatory conditions 1-5. The work is of high quality in terms of technical implementation and the structural quality of the source code. The skilled and versatile use of learned technologies is emphasized in the work. 99 | 100 | The individual grade derived from the project grade is also influenced by continuous evidence, self-assessment, and peer assessment. 101 | -------------------------------------------------------------------------------- /English/Project/02_Final_Project_Assignment.md: -------------------------------------------------------------------------------- 1 | # Final project assignment 2 | 3 | The goal of the final project work is to modify and complete the functional prototype you created during the preliminary 4 | project so that the outcome is **a web-based flight game**. 5 | 6 | ![Hot-air Balloon](img/640px-Hot_Air_Balloon_Launch_(Unsplash).jpg) 7 | 8 | Kuumailmapallon kuva: Songeunyoung songeunyoung, CC0, via Wikimedia Commons 9 | 10 | 11 | ## Requirements specification 12 | 13 | The requirement specification document describes what the final, extended flight game can be used for. 14 | You may use the contents and parts of the requirement specification of the preliminary project in your new requirement 15 | specification document. 16 | 17 | The requirements document must include at least the following chapters: 18 | 1. Introduction 19 | 2. Current State 20 | 3. Vision 21 | 4. Functional Requirements 22 | 5. Quality Requirements 23 | 24 | Chapter 1 (Introduction) discusses the purpose and target audience of the document. Also the structure of the document can be 25 | described in the Introduction. 26 | 27 | Chapter 2 (Current State) explains the current state of the project work: What can your prototype of the flight be used for? 28 | 29 | Chapter 3 (Vision) describes the general idea of the new, extended flight game. How does the user play the game and what 30 | happens in the game? The vision can again be presented as an image with a supporting text description. 31 | 32 | Chapter 4 (Functional Requirements) describes what the user can do with the extended game. Write more user stories the same way 33 | as in the requirement specification of the preliminary project. 34 | 35 | Chapter 5 (Quality requirements) specifies what requirements the game has other than the functional requirements. These could for example be 36 | requirements relating to response time or usability. 37 | 38 | A requirements specification document can be evaluated with the following questions: 39 | 40 | - Does the vision provide a general view of the game and the idea behind the game? 41 | - Has the functionality of the game been presented in a comprehensive, unambiguous, concrete and feasible way? Does the document give an accurate 42 | impression of how the game works? 43 | - Does the game contain novel ideas? 44 | - Have the necessary quality requirements been presented in a concrete way? 45 | - Does the game fulfill the set mandatory conditions for the game? 46 | - Can the document be considered a high-quality, technical document in terms of language and appearance? 47 | 48 | 49 | ## Mandatory conditions for the game 50 | 51 | The finished game must follow the mandatory conditions listed below. You are again free to design and implement any type 52 | of a game you want as long as it fulfills these conditions: 53 | 54 | 1. The user playes the game in an interactive way on a browser. 55 | 2. The user interface (UI) is implemented using HTML language and CSS style sheets. JavaScript is used for implementing the necessary 56 | browser functionality. 57 | 3. The operation logic of the game is implemented as a Python-based backend service that provides an API for the browser. 58 | 4. The communication between the backend service and the browser is implemented using API requests and JSON responses. 59 | 5. The game's backend service utilizes a relational database that is based on the airport database used on this course. The schema of 60 | the database can be modified and extended freely as needed. 61 | 6. The game has a concrete goal and it offers a good gaming experience. 62 | 7. The game takes sustainable development factors into account. 63 | 8. The game follows a good gaming etiquette and courtesy and is also suitable for young users (12+). 64 | 65 | The following criteria are not mandatory but they will be regarded as merits: 66 | 1. Satellite or map data is shown graphically on the browser. 67 | 2. The program utilizes the object-oriented features of Python. 68 | 3. The game's backend service communicates with external data sources. 69 | 70 | ## Determining the Grade 71 | 72 | - **Grade 1**: The effort to meet the mandatory conditions 1-8 is evident. There are deficiencies and weaknesses in the technical implementation, or the work is modest in scope and ambition. 73 | - **Grade 3**: The game fulfills the mandatory conditions 1-8. There are no significant shortcomings in the technical implementation. From the perspective of its scope and complexity, the work is of a good standard. Any shortcomings in the work can be compensated by the fulfillment of optional conditions 1-3. 74 | - **Grade 5**: The game fulfills the mandatory conditions 1-8. The work is of high quality in terms of technical implementation and the structural quality of the source code. The skilled and versatile use of learned technologies is emphasized in the work. If there are minor deficiencies in the work, they have been compensated by implementing optional conditions 1-3. 75 | 76 | The individual grade derived from the project grade is also influenced by continuous evidence, self-assessment, and peer assessment. 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /English/Project/img/640px-Hot_Air_Balloon_Launch_(Unsplash).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/Project/img/640px-Hot_Air_Balloon_Launch_(Unsplash).jpg -------------------------------------------------------------------------------- /English/Project/img/Pipistrel_WATTsUP_airplane.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/Project/img/Pipistrel_WATTsUP_airplane.jpg -------------------------------------------------------------------------------- /English/README.md: -------------------------------------------------------------------------------- 1 | # Python Language 2 | 3 | ## Part 1: Basics of Python Programming 4 | 1. [First Program](01a_First_Program.md) and [Deployment of Version Control (GitHub)](01b_Deployment_of_Version_Control.md) 5 | 2. [Variables and Interactive Programs](02_Variables_and_Interactive_Programs.md) 6 | 3. [Conditional Structures (if)](03_Conditional_Structures.md) 7 | 4. [While Loops (while)](04_While_Loops.md) 8 | 5. [List Structures and Iterative Loops (for)](05_List_Structures_and_Iterative_Loops.md) 9 | 6. [Functions](06_Functions.md) 10 | 7. [Tuple, Set, and Dictionary](07_Tuple,_Set,_and_Dictionary.md) 11 | 8. [Using Relational Databases](08_Using_Relational_Databases.md) 12 | 13 | ## Part 2: Mastering Python 14 | 9. [Fundamentals of Object-Oriented Programming](09_Fundamentals_of_Object-Oriented_Programming.md) 15 | 10. [Association](10_Association.md) 16 | 11. [Inheritance](11_Inheritance.md) 17 | 12. [Using External Interfaces](12_Using_External_Interfaces.md) 18 | 13. [Setting Up a Backend Service With an Interface](13_Setting_Up_a_Backend_Service_With_an_Interface.md) 19 | 20 | ## Exercises 21 | 22 | [Exercise sets](Exercises.md) 23 | 24 | ## Project Assignments 25 | 1. [Preliminary Project Assignment](Project/01_Preliminary_Project_Assignment.md) 26 | 2. [Final Project Assignment](Project/02_Final_Project_Assignment.md) 27 | -------------------------------------------------------------------------------- /English/Software_Testing.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/Software_Testing.pptx -------------------------------------------------------------------------------- /English/img/address.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/img/address.png -------------------------------------------------------------------------------- /English/img/client-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/img/client-server.png -------------------------------------------------------------------------------- /English/img/dictionary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/img/dictionary.png -------------------------------------------------------------------------------- /English/img/first_program.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/img/first_program.png -------------------------------------------------------------------------------- /English/img/flask_json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/img/flask_json.png -------------------------------------------------------------------------------- /English/img/flask_response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/img/flask_response.png -------------------------------------------------------------------------------- /English/img/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/img/list.png -------------------------------------------------------------------------------- /English/img/new_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/img/new_project.png -------------------------------------------------------------------------------- /English/img/new_repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/img/new_repo.png -------------------------------------------------------------------------------- /English/img/new_repo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/img/new_repo2.png -------------------------------------------------------------------------------- /English/img/path_envvar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/img/path_envvar.png -------------------------------------------------------------------------------- /English/img/project_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/img/project_tree.png -------------------------------------------------------------------------------- /English/img/result_set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/img/result_set.png -------------------------------------------------------------------------------- /English/img/schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/img/schema.png -------------------------------------------------------------------------------- /English/img/stop_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/English/img/stop_button.png -------------------------------------------------------------------------------- /Projektityö/01_Esiprojektin_toimeksianto.md: -------------------------------------------------------------------------------- 1 | # Esiprojektin toimeksianto 2 | 3 | Tavoitteenanne on rakentaa **toiminnallinen prototyyppi pelistä**. 4 | 5 | ![Sähkölentokone](img/Pipistrel_WATTsUP_airplane.jpg) 6 | 7 | Pipistrel WATTsUP -sähkölentokoneen kuva: Ymmo, CC BY-SA 4.0 , via Wikimedia Commons 8 | 9 | 10 | Pelin prototyyppi on peli, jonka 11 | avulla käyttäjä voi matkustaa joko koko maailmassa tai sallimallanne alueella. Peli hyödyntää 12 | todellisten lentokenttien sijanteja. 13 | 14 | 15 | 16 | Projekti etenee neljässä vaiheessa: 17 | 18 | 1. Pelin määrittely esiprojektia varten (Ohjelmisto 1) 19 | 2. Esiprojektin toteutus ja testaus (Ohjelmisto 1) 20 | 3. Määrittelyn tarkennus projektityötä varten (Ohjelmisto 2) 21 | 4. Projektityön toteutus ja testaus (Ohjelmisto 2) 22 | 23 | Ensimmäiset kaksi vaihetta tehdään Ohjelmisto 1 -opintojakson aikana. Tällöin toteutettavassa esiprojektissa on tarkoitus rakentaa pelistä toiminnallinen prototyyppi, joka toteutetaan Python-kielellä ja joka hyödyntää relaatiotietokantaa. 24 | 25 | Jälkimmäiset kaksi vaihetta tehdään Ohjelmisto 2 -opintojakson kuluessa. Projektityössä esiprojektin tuloksena syntynyttä toiminnallista prototyyppiä laajennetaan ja muokataan siten, että pelillä on selaimessa toimiva karttapalvelua hyödyntävä käyttöliittymä. Lisäksi peli ohjelmoidaan käyttämään ulkoista tietolähdettä, esimerkiksi sääpalvelua. 26 | 27 | Esiprojektin määrittelyvaiheen tarkoituksena on saavuttaa yhteinen näkemys siitä, minkälaista ohjelmistoa esiprojektissa lähdetään rakentamaan. Vaiheen tuloksena syntyy kirjallisessa muodossa oleva määrittelydokumentti, joka kuvaa ohjelmiston toiminnallisuuden eli sen, 28 | mitä tulevalla ohjelmistolla voidaan tehdä. Vaatimusmäärittelyssä otetaan lisäksi kantaa ohjelmiston sellaisiin 29 | vaatimuksiin, joita ei voida pelkistää toiminnoiksi. Näitä kutsutaan laadullisiksi vaatimuksiksi, ja ne voivat liittyä esimerkiksi 30 | suorituskykyyn, vasteaikoihin tai käytettävyyteen. 31 | 32 | 33 | ## Määrittelydokumentti 34 | 35 | Määrittelydokumentin tärkein hyve on konkreettisuus. Ohjelman toiminta kuvataan sellaisella tarkkudella, että tulkinnalle 36 | jää mahdollisimman vähän sijaa. Asioita ei lähtökohtaisesti jätetä toteutusvaiheessa päätettäviksi. 37 | 38 | Määrittelydokumentissa kuvataan siis toiminta ja laadulliset vaatimukset. Näkökulma on ohjelmiston käyttäjässä: dokumentti 39 | vastaa kysymykseen "Mitä käyttäjä voi ohjelmistolla tehdä?". Toteutustekniikoihin ei oteta kantaa. 40 | 41 | Lentopelin määrittelydokumentin on sisällettävä ainakin seuraavat luvut: 42 | 1. Johdanto 43 | 2. Visio 44 | 3. Toiminnalliset vaatimukset 45 | 4. Laadulliset vaatimukset 46 | 47 | Luku 1 (Johdanto) kuvaa, mikä dokumentin tarkoitus on ja kenelle se on suunnattu. Johdannossa voidaan myös esitellä dokumentin rakenne. 48 | 49 | Luku 2 (Visio) kuvaa lentopelin yleisidean. Mikä on pelin tarkoitus, ja mitä pelissä tehdään? Visio voidaan esittää 50 | myös kuvana, jonka tueksi laaditaan tekstiselostus. Selostakaa tässä vapaamuotoisesti pelin "punainen lanka": miten peli 51 | etenee, ja millaisia vaiheita pelaaja käy läpi? 52 | 53 | Luku 3 (Toiminnalliset vaatimukset) kuvaa periaatteessa kaiken, mitä käyttäjä voi pelillä tehdä. Toiminnalliset vaatimukset esitetään tyypillisesti käyttäjätarinoina, joissa on tekijä, toimenpide ja tavoite. Esimerkki käyttäjätarinasta on "Pelaajana 54 | voin valita karttapohjalla näkyvistä kohteista seuraavan lentokentän, jotta sähkölentokoneeni siirtyy sinne". Käyttäjätarinassa kuvataan siis tekijä (pelaaja), toimenpide (kentän valinta) sekä käyttäjää hyödyttävä tavoite, 55 | joka toimenpiteestä seuraa (kohteeseen siirtyminen). Käyttäjätarinoita laaditaan niin monta, että ne yhdessä kuvaavat pelin toiminnallisuuden. Lentopelille käyttäjätarinoita tarvittaneen useita kymmeniä. Jokaisen käyttäjätarinan on oltava yksiselitteinen ja konkreettinen. Käyttäjätarinan pohjalta on voitava aikanaan todentaa, onko vastaava toiminnallisuus toteutettu ohjelmistossa. 56 | 57 | Luku 4 (Laadulliset vaatimukset) tarkentaa, millaisia muita kuin toiminnallisia vaatimuksia lentopelillä on. Näitä voivat olla esimerkiksi suorituskykyvaatimukset ("Lentokentän tietojen haku tietokannasta saa kestää korkeintaan kaksi sekuntia") tai 58 | käytettävyysvaatimukset ("Käyttäjän on saatava välitön palaute jokaisesta tekemästään toimenpiteestä"). 59 | 60 | Määrittelydokumentin laatua voi arvioida seuraavin kysymyksin: 61 | - Saako vision perusteella yleiskuvan pelistä ja sen ideasta? 62 | - Onko pelin toiminnallisuus kuvattu kattavasti, yksiselitteisesti, konkreettisesti ja toteuttamiskelpoisesti? Saako dokumentista tarkan käsityksen siitä, miten peli toimii? 63 | - Sisältääkö peli uudenlaisia ideoita? 64 | - Onko tarpeelliset laadulliset vaatimukset kuvattu konkreettisesti? 65 | - Huomioiko vaatimusmäärittely pelille asetetut reunaehdot? 66 | - Onko määrittelydokumentti kielensä ja ulkoasunsa puolesta laadukas tekninen asiakirja? 67 | 68 | 69 | ## Pelin reunaehdot 70 | 71 | Määrittelyn pohjaksi asetetaan joukko reunaehtoja. Reunaehtojen tarkoitus on varmistaa, että saavutatte peliprojektin aikana ne oppimistavoitteet, joita opintojaksoille on määritetty. 72 | 73 | Voitte siis määritellä ja toteuttaa minkälaisen pelin tahansa, kunhan se toteuttaa alla kuvatut reunaehdot: 74 | 75 | 1. Käyttäjä pelaa peliä vuorovaikutteisesti näppäimistön avulla. 76 | 2. Peli käyttää relaatiotietokantaa, jonka pohjana on opintojaksolla käytetty lentokenttätaulu. Tietokannan skeemaa saa vapaasti muuttaa ja laajentaa. 77 | 3. Pelissä on konkreettinen tavoite, ja se tuottaa hyvän pelikokemuksen. 78 | 4. Peli huomioi kestävän kehityksen näkökulman. 79 | 5. Peli on hyvien tapojen mukainen ja soveltuu myös nuorille käyttäjille (K12). 80 | 81 | ## Arvosanan määräytyminen 82 | 83 | Projektityön arviointi noudattaa seuraavia suuntaviivoja: 84 | 85 | - **Arvosana 1**: Työstä näkyy pyrkimys täyttää pakolliset reunaehdot 1-5. Teknisessä toteutuksessa 86 | on puutteita ja heikkouksia, tai työ on laajuudeltaan ja kunnianhimon tasoltaan vaatimaton. 87 | - **Arvosana 3**: Peli toteuttaa pakolliset reunaehdot 1-5. Teknisessä toteutuksessa ei ole merkittäviä 88 | puutteita. Työ on laajuutensa ja haastavuutensa näkökulmasta hyvätasoinen. 89 | - **Arvosana 5**: Peli toteuttaa pakolliset reunaehdot 1-5. Työ on tekniseltä toteutukseltaan ja 90 | ohjelmakoodin rakenteellisen laadun näkökulmasta korkeatasoinen. Työssä korostuu opittujen 91 | teknologioiden taitava ja monipuolinen käyttö. 92 | 93 | Projektin arvosanasta johdettavaan yksilöarvosanaan vaikuttaa myös jatkuva näyttö sekä itse- ja vertaisarviointi. -------------------------------------------------------------------------------- /Projektityö/02_Projektityön_toimeksianto.md: -------------------------------------------------------------------------------- 1 | # Projektityön toimeksianto 2 | 3 | Projektityön tavoitteena on muokata ja täydentää esiprojektin aikana rakentamaanne toiminnallista prototyyppiä siten, 4 | että tuloksena on **selaimen kautta pelattava lentopeli**. 5 | 6 | ![Kuumailmapallo](img/640px-Hot_Air_Balloon_Launch_(Unsplash).jpg) 7 | 8 | Kuumailmapallon kuva: Songeunyoung songeunyoung, CC0, via Wikimedia Commons 9 | 10 | 11 | ## Määrittelydokumentti 12 | 13 | Projektityön määrittelydokumentti kuvaa, mitä projektityön tuotoksena saatavalla, laajennetulla lentopelillä voidaan tehdä. 14 | Voitte hyödyntää esiprojektin määrittelydokumentin sisältöä ja osia uudessa määrittelydokumentissanne. Määrittelyvaiheessa hyödynnetään Projektityö-kansiossa olevaa valmista määrittelydokumentin pohjaa. 15 | 16 | Projektityön määrittelydokumentin on sisällettävä ainakin seuraavat luvut: 17 | 1. Johdanto 18 | 2. Visio 19 | 3. Läpäisykuvaus 20 | 4. Poikkeavat suorituspolut 21 | 5. Laadulliset vaatimukset 22 | 6. Muut seikat 23 | 24 | Luku 1 (Johdanto) kuvaa, mikä dokumentin tarkoitus on ja kenelle se on suunnattu. Johdannossa voidaan myös esitellä dokumentin rakenne. 25 | 26 | Luku 2 (Visio) kuvaa uuden, laajennetun lentopelin yleisidean. Miten käyttäjä pelaa peliä, ja mitä pelissä tapahtuu? 27 | Visio voidaan jälleen esittää 28 | myös kuvana, jonka tueksi laaditaan tekstiselostus. 29 | 30 | Luku 3 (Läpäisykuvaus) kuvaa konkreettisesti, miten pelin kulku etenee tyypillisellä käyttökerralla. 31 | 32 | Luku 4 (Poikkeavat suorituspolut) kuvaa toiminnan siltä osin, kuin se ei ilmene läpäisykuvauksesta. Tässä kuvataan muun muassa vika- ja virhetilanteiden käsittely 33 | 34 | Luku 5 (Laadulliset vaatimukset) tarkentaa, millaisia muita kuin toiminnallisia vaatimuksia lentopelillä on. Näitä voivat olla esimerkiksi vasteaika- ja käytettävyysvaatimukset. 35 | 36 | Luku 6 (Muut seikat) esittelee muut mahdolliset määrittelyvaiheessa esiin tulevat seikat. 37 | 38 | Määrittelydokumentin laatua voi arvioida seuraavin kysymyksin: 39 | - Saako vision perusteella yleiskuvan pelistä ja sen ideasta? 40 | - Onko pelin toiminnallisuus kuvattu kattavasti, yksiselitteisesti, konkreettisesti ja toteuttamiskelpoisesti? Saako dokumentista tarkan käsityksen siitä, miten peli toimii? 41 | - Sisältääkö peli uudenlaisia ideoita? 42 | - Onko tarpeelliset laadulliset vaatimukset kuvattu konkreettisesti? 43 | - Huomioiko vaatimusmäärittely pelille asetetut reunaehdot? 44 | - Onko määrittelydokumentti kielensä ja ulkoasunsa puolesta laadukas tekninen asiakirja? 45 | 46 | 47 | ## Pelin reunaehdot 48 | 49 | Projekityön tuotoksena olevaan lentopeliin liittyvät alla kuvatut reunaehdot. 50 | Voitte jälleen määritellä ja toteuttaa minkälaisen pelin tahansa, kunhan se toteuttaa alla kuvatut ehdot: 51 | 52 | 1. Käyttäjä pelaa peliä vuorovaikutteisesti selaimessa. 53 | 2. Käyttöliittymä on toteutettu HTML-sivunmäärityskielen ja CSS-tyylisivujen avulla. Välttämättömän selaintoiminnallisuuden toteuttamiseen käytetään JavaScript-kieltä. 54 | 3. Pelin toimintalogiikka on toteutettu Python-kielisenä taustapalveluna, joka tarjoaa selaimelle rajapinnan (API). 55 | 4. Taustapalvelun ja selaimen välinen kommunikaatio toteutetaan API-pyynnöin ja JSON-vastauksin. 56 | 5. Pelin taustapalvelu käyttää relaatiotietokantaa, jonka pohjana on opintojaksolla käytetty lentokenttätietokanta. Tietokannan skeemaa saa vapaasti muuttaa ja laajentaa. 57 | 6. Pelissä on konkreettinen tavoite, ja se tuottaa hyvän pelikokemuksen. 58 | 7. Peli huomioi kestävän kehityksen näkökulman. 59 | 8. Peli on hyvien tapojen mukainen ja soveltuu myös nuorille käyttäjille (K12). 60 | 61 | Seuraavat seikat eivät ole välttämättömiä, mutta ne katsotaan eduksi: 62 | 63 | 1. Selaimessa näytetään satelliitti- tai karttadataa graafisesti. 64 | 2. Ohjelmoinnissa hyödynnetään Pythonin oliopiirteitä. 65 | 3. Pelin taustapalvelu kommunikoi ulkoisten tietolähteiden kanssa. 66 | 67 | ## Arvosanan määräytyminen 68 | 69 | Projektityön arviointi noudattaa seuraavia suuntaviivoja: 70 | 71 | - **Arvosana 1**: Työstä näkyy pyrkimys täyttää pakolliset reunaehdot 1-8. Teknisessä toteutuksessa 72 | on puutteita ja heikkouksia, tai työ on laajuudeltaan ja kunnianhimon tasoltaan vaatimaton. 73 | - **Arvosana 3**: Peli toteuttaa pakolliset reunaehdot 1-8. Teknisessä toteutuksessa ei ole merkittäviä 74 | puutteita. Työ on laajuutensa ja haastavuutensa näkökulmasta hyvätasoinen. Työn puutteita voi kompensoida 75 | jonkin valinnaisen reunaehdon 1-3 toteutuminen. 76 | - **Arvosana 5**: Peli toteuttaa pakolliset reunaehdot 1-8. Työ on tekniseltä toteutukseltaan ja 77 | ohjelmakoodin rakenteellisen laadun näkökulmasta korkeatasoinen. Työssä korostuu opittujen 78 | teknologioiden taitava ja monipuolinen käyttö. Jos työssä on vähäisiä puutteita, niitä on 79 | kompensoitu toteuttamalla valinnaisia reunaehtoja 1-3. 80 | 81 | Projektin arvosanasta johdettavaan yksilöarvosanaan vaikuttaa myös jatkuva näyttö sekä itse- ja vertaisarviointi. 82 | 83 | -------------------------------------------------------------------------------- /Projektityö/Kestävän kehityksen näkökulma projektissa.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/Projektityö/Kestävän kehityksen näkökulma projektissa.pdf -------------------------------------------------------------------------------- /Projektityö/Määrittelydokumentin_pohja.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/Projektityö/Määrittelydokumentin_pohja.docx -------------------------------------------------------------------------------- /Projektityö/img/640px-Hot_Air_Balloon_Launch_(Unsplash).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/Projektityö/img/640px-Hot_Air_Balloon_Launch_(Unsplash).jpg -------------------------------------------------------------------------------- /Projektityö/img/Pipistrel_WATTsUP_airplane.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/Projektityö/img/Pipistrel_WATTsUP_airplane.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python-kieli 2 | 3 | ## [English content available here](English) 4 | 5 | ## Osa 1: Python-ohjelmoinnin perustaidot 6 | 1. [Ensimmäinen ohjelma](01a_Ensimmäinen_ohjelma.md) sekä [versionhallinnan käyttöönotto (GitHub)](01b_Versionhallinnan_käyttöönotto.md) 7 | 2. [Muuttujat ja vuorovaikutteiset ohjelmat](02_Muuttujat_ja_vuorovaikutteiset_ohjelmat.md) 8 | 3. [Valintarakenne (if)](03_Valintarakenne.md) 9 | 4. [Alkuehdollinen toistorakenne (while)](04_While-toistorakenne.md) 10 | 5. [Listarakenne ja läpikäyvä toistorakenne (for)](05_Listarakenne_ja_for-toistorakenne.md) 11 | 6. [Funktio](06_Funktio.md) 12 | 7. [Monikko, joukko ja sanakirja](07_Monikko,_joukko_ja_sanakirja.md) 13 | 8. [Relaatiotietokannan käyttö](08_Relaatiotietokannan_käyttö.md) 14 | 15 | ## Osa 2: Pythonin hallinta 16 | 9. [Luokka, olio, alustaja](09_Olio-ohjelmoinnin_lähtökohdat.md) 17 | 10. [Assosiaatio](10_Assosiaatio.md) 18 | 11. [Periytyminen](11_Periytyminen.md) 19 | 12. [Ulkoisen rajapinnan käyttö](12_Ulkoisen_rajapinnan_käyttö.md) 20 | 13. [Taustapalvelun ja rajapinnan rakentaminen](13_Taustapalvelun_ja_rajapinnan_rakentaminen.md) 21 | 22 | ## Tehtävät 23 | 24 | [Tehtäväsarja](Tehtävät.md) 25 | 26 | ## Projektityöt 27 | 1. [Esiprojektin toimeksianto](Projektityö/01_Esiprojektin_toimeksianto.md) 28 | 2. [Projektityön toimeksianto](Projektityö/02_Projektityön_toimeksianto.md) 29 | -------------------------------------------------------------------------------- /Testaus_v2.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/Testaus_v2.pptx -------------------------------------------------------------------------------- /img/add-collaborator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/add-collaborator.png -------------------------------------------------------------------------------- /img/asiakas-palvelin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/asiakas-palvelin.png -------------------------------------------------------------------------------- /img/copy-task-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/copy-task-link.png -------------------------------------------------------------------------------- /img/ekaohjelma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/ekaohjelma.png -------------------------------------------------------------------------------- /img/flask_json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/flask_json.png -------------------------------------------------------------------------------- /img/flaskvastaus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/flaskvastaus.png -------------------------------------------------------------------------------- /img/lista.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/lista.png -------------------------------------------------------------------------------- /img/osoite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/osoite.png -------------------------------------------------------------------------------- /img/path_envvar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/path_envvar.png -------------------------------------------------------------------------------- /img/projektipuu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/projektipuu.png -------------------------------------------------------------------------------- /img/sanakirja.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/sanakirja.png -------------------------------------------------------------------------------- /img/skeema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/skeema.png -------------------------------------------------------------------------------- /img/stop-nappi1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/stop-nappi1.png -------------------------------------------------------------------------------- /img/tulosjoukko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/tulosjoukko.png -------------------------------------------------------------------------------- /img/uusi_repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/uusi_repo.png -------------------------------------------------------------------------------- /img/uusi_repo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/uusi_repo2.png -------------------------------------------------------------------------------- /img/uusiprojekti.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesavvo/Python_Ohjelmistoteema/92f6d6c92dcebf30a320ae4a0a98338893e6d94f/img/uusiprojekti.png --------------------------------------------------------------------------------