<h1>A6 Harjoitus</h1> Harjoituksen aiheena tiedostonkäsittely. 1. [Tietokoneiden muisti ja tallennustila](#tietokoneiden-muisti-ja-tallennustila) 2. [Tiedostot](#tiedostot) 3. [Tiedostokahva (filehandle)](#tiedostokahva-filehandle) 4. [Tiedoston lukeminen](#tiedoston-lukeminen) 5. [Tiedoston kirjoittaminen](#tiedoston-kirjoittaminen) 6. [Tiedostoon lisääminen](#tiedostoon-lisääminen) 7. [Tietovirran positio (stream position)](#tietovirran-positio-stream-position) 8. [Työskentelykansio - CWD](#työskentelykansio---cwd) 9. [Merkkijonon jäsenfunktiot](#merkkijonon-jäsenfunktiot) # Tietokoneiden muisti ja tallennustila <!-- s1. --> **Tietokoneen käynnistys** Kun tietokone käynnistyy, BIOS alkaa määritystensä mukaisesti etsiä käynnistyslatainta (eng. bootloader) sille määritellyistä tallennuslaitteista, noudattaen asetettua käynnistysjärjestystä. Löydettyään käynnistyslataajan BIOS siirtää ohjauksen sille, ja käynnistyslataaja aloittaa käyttöjärjestelmän lataamisen tallennustilasta muistiin. Käyttöjärjestelmän lataaminen muistiin kestää sekunteista minuutteihin. Tämän prosessin tarkoituksena on valmistella järjestelmä toimimaan nopeasti ja tehokkaasti. Käyttäjien näkökulmasta järjestelmän viiveet eivät ole toivottuja, ja sujuva käynnistys on tärkeä käyttökokemuksen kannalta. Käyttöjärjestelmän käynnistystä nopeuttaisi, jos se olisi tallessa pysyvästi muistissa eikä tallennustilassa. Taustalla piilee kuitenkin tallennustila- ja muistiteknologioiden erot tiedon pysyvyyden suhteen. Muisti, jota käytämme tietokoneissa, on väliaikaista ja vaatii sähkövirtaa säilyttääkseen tietoa. Tallennustila on pysyvää, ja säilyttää tiedon ilman sähkövirtaa. Jotta tieto säilyisi käynnistyssyklien yli, on tarvittava data taltioitava aina hitaampaan tallennustilaan. ![storage](https://hackmd.io/_uploads/ryrdwiOy1x.png) Tiedon lataamista tallennustilasta muistiin ei tarvitsisi tehdä, jos tallennustila olisi yhtä nopeaa kuin muisti. Vaikka ero on kaventunut teknologian kehittyessä, on edelleen viiveissä merkittäviä eroja. Muistipiireissä viiveet mitataan usein nanosekunneissa, kun taas nopeimpien tallennustilojen nopeudeudet mitataan mikrosekunneissa. Terminologiaa: - Väliaikainen (RAM) -> empheral -> volatile - Pysyvä (HDD/SSD/NVMe) -> persistent -> non-volatile **Ohjelmien toimintaperiaate** Ohjelmien toiminnassa pätee samat periaatteet kuin käyttöjärjestelmän toiminnassa: viiveet on minimoitava, jotta suorituskyky olisi mahdollisimman hyvä. Käytännössä tämä tarkoittaa, että ohjelmien alussa alustetaan kaikki toiminnan kannalta tarvittavat resurssit, jotta ne ovat heti käytettävissä ohjelman suorituksen aikana. Ohjelmakoodissa tämä alustaminen alkaa kiintoarvojen ja funktioiden määrittelyllä, muuttujien alustamisella, konfiguraatiotiedostojen lataamisella, sekä muilla valmisteluilla. Kaikki nämä toimenpiteet on kirjoitettava ohjelmakoodiksi, jotta prosessori (CPU) voi ne suorittaa. Prosessori vastaa datan käsittelystä ja siirrosta muistin ja tallennustilan välillä. ![loading_files](https://hackmd.io/_uploads/H1BEQYFkye.png) # Tiedostot <!-- s2. --> <h2>Kaikki tiedostot</h2> <!-- s2.1. --> - Sisältävät tietoa binaarimuodossa, eli sarjana bittejä - Bittien mahdolliset arvot 0 ja 1. - Tiedostoformaatti määrittää tavan, jolla bitit järjestellään. - Yleisiä tiedostoformaatteja - Tekstitiedosto: .txt, .csv, ... - Kuvatiedosto: .png, .jpg, ... - Äänitiedosto: .mp3, .wav, ... - Videotiedosto: .mp4, .mkv, ... - Suoritettava tiedosto: .exe, .bin, ... <h2>Tekstitiedostot</h2> <!-- s2.2. --> Tekstitiedosto on tiedostotyyppi, jonka sisältö on merkistön, kuten ASCII:n tai UTF-8:n avulla käännettävissä ihmisluettavaan muotoon. Tekstitiedostosta merkkejä voidaan lukea tekstieditorin avulla. Yleisiä tekstitiedosto formaatteja: - `.txt` – Plain text - `.csv` – Comma Separated Values - `.py` - Python source code - `.md` – Markdown - `.log` - Lokitiedosto Tekstitiedostojen sisältö koostuu peräkkäisistä merkeistä, eli merkkijonosta. Tiedostopääte ilmaisee lähinnä, että millä tavalla merkkijono on tiedostoon jäsennelty. Esimerkiksi yleinen tekstitiedosto `.txt` kertoo, että tiedoston tekstiä ei ole välttämättä jäsennelty ollenkaan. Lähdekoodien tapauksessa, kuten`.py` tulee tekstin olla jäsenneltynä Python ohjelmointikieleen soveltuvaksi. `.csv`-tiedostossa teksti on oletettavasti jäsennelty niin, että arvoja on eroteltu erottimen avulla ja tietorivejä rivinvaihtomerkein `\n`. CSV-formaatti nimensä mukaisesti ehdottaa, että arvoja eroteltaisiin pilkuin `,`, mutta esimerkiksi Suomessa pilkkua käytetään erottelemaan desimaaleja(3,14), joten Suomessa CSV-erotin onkin usein puolipiste `;`. **CSV-tiedosto tekstinkäsittely ohjelmassa** ```csv= column_1;column_2;column_3 row1_data1;row1_data2;row1_data3 row2_data1;row2_data2;row2_data3 ``` Tärkeää on huomata, että tekstinkäsittelyohjelma rivinvaihtomerkki ei ilmene sen kirjoitusasuna `\n`, vaan ohjelma taittaa tekstin seuraavalle riville rivinvaihtomerkin kohdalta. Tavallinen tekstinkäsittely ohjelma käsittelee ohjainmerkkejä usein niiden tarkoituksenmukaisella tavalla. **Ohjainmerkkejä** |Merkki|Selitys FI|Selitys EN| |---|---|---| |`\n`|Rivinvaihto|Newline| |`\t`|Sarkain (tabulaattori)|Tab| |`\'`|Heittomerkki|Single quote| |`\"`|Lainausmerkki|Double quote| |`\\`|Kenoviiva|Backslash| |`\r`|Paluu rivin alkuun|Carriage return| |`\v`|Pystysarkain|Vertical tab| |`\0`|NUL-merkki|Null character| Samainen tieto merkkijonona esiteltynä pitää siis sisällään rivinvaihtomerkit `\n`. Tämä on se muoto, jossa tieto on tiedostoon taltioituna. ```txt= column_1,column_2,column_3\nrow1_data1,row1_data2,row1_data3\nrow2_data1,row2_data2,row2_data3\n ``` Excelin taulukkoon `.csv`-muotoinen data on tuotavissa tuonti toiminnolla ("Import" -> "CSV File"). Olemassa olevasta Excelistä voidaan myös viedä `export`-komennolla ("Save as" -> "File format" -> "Comma-separated Values") taulukon tietoja `.csv`-tiedostoihin. **CSV Sisältö taulukkonäkymässä(Excel)** ```csvpreview column_1;column_2;column_3 row1_data1;row1_data2;row1_data3 row2_data1;row2_data2;row2_data3 ``` <h2>Tyhjä rivi ei välttämättä ole tyhjä</h2> <!-- s2.3. --> Kun tarkastelemme tekstitiedostoa tekstinkäsittelyohjelmalla, voi siellä esiintyä tyhjän näköisiä rivejä. Jollain tapaa tekstinkäsittelyohjelma osaa kuitenkin merkitä tyhjät rivit paikalleen. Ja käytännössä se tarkoittaa, että tyhjäksi visualisoitu rivi ei välttämättä olekaan tyhjä. **Tekstitiedosto tekstinkäsittelyohjelmassa:** ```txt= This is text file. It may contain multiple rows. Empty rows are not empty. Empty row has '\\n' character. It is a newline character. Last row is empty. ``` **Rivinvaihtomerkki visualisoituna:** ```txt= This is text file.\n It may contain multiple rows.\n \n Empty rows are not empty.\n Empty row has '\\n' character.\n It is a newline character.\n \n Last row is empty.\n ``` Yllä olevissa esimerkeissä rivit 3 ja 7 näyttäytyvät tyhjinä riveinä tekstinkäsittelyohjelmassa, mutta rivi pitää sisällään rivinvaihtomerkin `\n`. Rivi 9 näytetään tyhjänä, mutta koska kyseessä on tekstitiedoston loppu, ei siellä olekaan merkkejä. Eli tekstitiedosto päättyy tyhjään riviin `''` Tekstitiedoston säännöt: 1. Rivi([POSIX line](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206)) päättyy rivinvaihtomerkkiin '\n' 2. Tyhjä rivi tekstin keskellä on `'\n'` 3. Tekstitiedosto päättyy tyhjään `''` Näihin sääntöihin tutustutaan vielä tarkemmin tiedostoa luettaessa (rivi kerrallaan). # Tiedostokahva (filehandle) <!-- s3. --> Tiedostoihin käyttöjärjestelmissä pääsee käsiksi tiedostokahvan avulla. Pythonin tiedosto-olion, eli **tiedostokahvan** voi muodostaa `open`-funktion avulla. Tiedostokahvan luonti lähtee liikkeelle tiedostonimen tai -polun määrittelystä, jota seuraa moodin asettaminen. Tekstitiedostojen tapauksessa on myös suositeltavaa välittää merkistö (eng. encoding) ja alkuvaiheessa "UTF-8"-merkistön määrittely riittää. **E3. Tiedostokahva** <div style="display:flex;position:relative;"> <div> ```py= # Filename: e3_filehandle.py print("Opening filehandle.") # Filehandle = open(FilenameOrPath, Mode, encoding="UTF-8") Filehandle = open('file.txt', 'r', encoding="UTF-8") print("Closing filehandle.") Filehandle.close() print("Program ending.") ``` </div> <div style="position:absolute;bottom:0;right:34px;"> ![filehandler](https://hackmd.io/_uploads/HJhfm0I1ke.png) </div> </div> **Tiedostonimi tai -polku** Käyttöjärjestelmissä tiettyyn tiedostoon voidaan viitata joko relatiivisesti taikka absoluuttisesti. Kun määritämme tiedoston aukaisuun pelkän tiedostonimen, tarkoittaa se `open`-funktion näkökulmasta, että viittaamme tiedostoon relatiivisesti. Relatiivista viittausta kannattaa suosia kun mahdollista, sillä se vaikuttaa ohjelman siirrettävyyteen ja ylläpidettävyyteen. Relatiivinen määritys perustuu tämän hetkiseen työskentelykansioon (CWD, Current Working Directory) ja pienessä Python ohjelmassa se tarkoittaa, että tiedoston on oltava samassa kansiossa kuin ohjelmatiedoston. Isommissa ohjelmissa relatiivinen viittaus voidaan osoittaa myös muuhun kansioon. Lisäksi pienissä ohjelmissa tulee käynnistettäessä varmistua siitä, että ohjelman käynnistys lähtee samasta kansiosta liikkeelle, jotta CWD on määritetty kyseiseen kansioon. **Alla olevassa esimerkissä:** 1. Luodaan kansio `A06` hakemistoon `~/projects/ohj` 2. Vaihdetaan työskentelykansio `~/projects/ohj/A06` kansiopolkuun 3. Luodaan kaksi tiedostoa `e3_filehandle.py` ja `file.txt`. 4. Avataan koodieditori `~/projects/ohj/A06` 5. Muokataan e3_filehandle.py tiedostoa (katso E3.1. esimerkki) 6. Listataan näkyville työskentelykansion sisältö 7. Käynnistetään Python ohjelma `e3_filehandle.py` työskentelykansiosta `~/projects/ohj/A06` **E3.2. Bash komennoilla vaiheittain** ```bash devaaja@wink MINGW64 ~ $ mkdir -p ~/projects/ohj/A06 devaaja@wink MINGW64 ~ $ cd ~/projects/ohj/A06/ devaaja@wink MINGW64 ~/courses/ohj/A06 $ touch e3_filehandle.py file.txt devaaja@wink MINGW64 ~/courses/ohj/A06 $ code . devaaja@wink MINGW64 ~/courses/ohj/A06 $ # Write E3.1 code into ~/projects/ohj/A06/e3_filehandle.py devaaja@wink MINGW64 ~/projects/ohj/A06 $ ls -1 e3_filehandle.py file.txt devaaja@wink MINGW64 ~/projects/ohj/A06 $ python e3_filehandle.py Opening filehandle. Closing filehandle. Program ending. ``` Esimerkissä näytettiin Bash komennoilla vaihe kerrallaan, kuinka työskentelykansiota hallitaan. Lopussa ohjelma suorittui ilman virheitä. Tässä kohtaa jos olet tottunut käyttämään ohjelman ajoon `RUN`-painiketta IDE:ssä (Intelligent Development Environment: VSCode, PyCharm etc..), niin on tärkeää varmistua siitä, että projekti on avattu samaan kansioon, jossa ohjelmatiedosto ja luettava tekstitiedosto sijaitsevat. Yleisin virhetilanne syntyy, kun IDE on avattu yläkansioon `~/projects/ohj`, ja ohjelmatiedosto(`./A06/e3_filehandle.py`) ajetaan alikansiosta. Tällöin `open`-komentoon määritelty tiedostonimi `file.txt` viittaa tämänhetkiseen työskentelykansioon, eli `~/projects/ohj`, jossa `file.txt` tiedostoa ei ole. Jos tiedostoa ei löydy luettaessa, ohjelma kaatu seuraavanlaiseen ilmoitukseen: ```bash devaaja@wink MINGW64 ~/courses/ohj $ C:/Users/devaaja/AppData/Local/Programs/Python/Python312/python.exe c:/Users/devaaja/courses/ohj/A06/e3_filehandle.py Opening filehandle. Traceback (most recent call last): File "c:\Users\devaaja\courses\ohj\A06\e3_filehandle.py", line 2, in <module> Filehandle = open('file.txt', 'r', encoding="UTF-8") ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FileNotFoundError: [Errno 2] No such file or directory: 'file.txt' ``` <h2>Moodi</h2> Tiedostokahvan aukaisuun yhteydessä voidaan määrittää tila, eli moodi. Tämä avaustila määrittää tavan, jolla tiedostoa tullaan käsittelemään. Alle on listattu moodissa esitettäviä kirjaimia, joita voidaan joissain tapauksissa yhdistellä. |Moodi|Kuvaus| |---|---| |'r'|Avaa lukemista varten(oletus).| |'w'|Avaa kirjoittamista varten. Tyhjentää tiedoston ensin.| |'x'|Avaa luontia varten. Epäonnistuu, jos tiedosto on jo olemassa.| |'a'|Avaa kirjoittamista varten. Lisää tiedoston loppuun, jos se on jo olemassa.| |'b'|Binääritila.| |'t'|Tekstimuoto (oletus).| |'+'|Avaa päivitystä varten (lukeminen ja kirjoittaminen).| Tiedostonkäsittelyn totuttelu vaiheessa kannattaa hyödyntää ainoastaan. - `r` Lukemismoodi - `w` Kirjoitusmoodia - `a` Lisäysmoodia Tällöin käsittelyvaiheessa tiedostokahvan merkitys on yksiselitteinen. Kun tiedostojenkäsittely hahmottuu, voidaan pohtia binääritilan `b` ja `+` tilojen käyttöä. Nämä muut moodit jätetään siis tulevaisuuteen. <h2>Merkistöistä</h2> Merkistö tekstitiedostojen yhteydessä on määrittely, jolla tiedoston data, eli bitit muunnetaan ihmisluettavaan muotoon. Esimerkiksi ASCII-merkistön avulla käännetään aina 7 bittiä kirjaimeksi. Koska 7 bitillä voidaan esittää vain 128 eri vaihtoehtoa (2^7), on merkistö melko rajallinen. ASCII (American Standard Code for Information Interchange) -merkistön kehitys aloitettiin 1960-luvun alussa Amerikkalaisten tietokoneiden ja tietoliikennejärjestelmien yhteensopivuuden varmistamiseksi. 1990-luvulla merkistön rajallisuus alkoi kuitenkin olla jo ilmeinen tietokoneiden yleistyessä, sillä muiden maiden merkistöt eivät mahtuneet olemassa olevaan ASCII-merkistöön. Kansainvälinen standardointijärjestö (ISO) ryhtyi vuonna 1989 laatimaan universaalia monibittistä merkistöä, mutta aikaisempi UTF-1-merkistö osoittautui tehottomaksi. Useat toimijat, kuten X/Open, Unix System Laboratories ja Bell Labs, kehittivät edelleen merkistöjä. Tästä syntyi FSS-UTF, joka lopulta johti UTF-8-merkistön kehittämiseen. UTF-8 esiteltiin virallisesti vuonna 1993 USENIX-konferenssissa San Diegossa. Historiaosuus on tiivistetty Wikipediasta. Linkki lähteeseen: [https://en.wikipedia.org/wiki/UTF-8](https://en.wikipedia.org/wiki/UTF-8) Merkittäviä piirteitä UTF-8-merkistössä on: - Yhteensopivuus ASCII-merkistön kanssa - Tukee 1112064 eri koodipistettä (mahdollista merkkiä) - Tehokas tilankäyttö yleisille merkeille (1 tavu) - Vikasietoinen ja itse-synkronoituva - Laajasti käytetty tiedosto- ja verkkostandardeissa UTF-8 merkistön ensimmäiset 128 merkkiä ovat samat kuin ASCII-merkistön | Hex | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |------|----|----|----|----|----|----|----|----| | 0x00 | NUL| SOH| STX| ETX| EOT| ENQ| ACK| BEL| | 0x08 | BS | HT | LF | VT | FF | CR | SO | SI | | 0x10 | DLE| DC1| DC2| DC3| DC4| NAK| SYN| ETB| | 0x18 | CAN| EM | SUB| ESC| FS | GS | RS | US | | 0x20 | | ! | " | # | $ | % | & | ' | | 0x28 | ( | ) | * | + | , | - | . | / | | 0x30 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | 0x38 | 8 | 9 | : | ; | < | = | > | ? | | 0x40 | @ | A | B | C | D | E | F | G | | 0x48 | H | I | J | K | L | M | N | O | | 0x50 | P | Q | R | S | T | U | V | W | | 0x58 | X | Y | Z | \[ | \\ | \] | ^ | _ | | 0x60 | ` | a | b | c | d | e | f | g | | 0x68 | h | i | j | k | l | m | n | o | | 0x70 | p | q | r | s | t | u | v | w | | 0x78 | x | y | z | \{ | \| | } | ~ | DEL| Alla esiteltynä muutama merkki UTF-8 merkistöstä: - A - bittimäärä 8, hex `0x41`, merkki: Iso kirjain A (ASCII-yhteensopiva) - é - bittimäärä 16, hex `0xC3 0xA9`, merkki: Pieni e, jossa aksenttimerkki - € - bittimäärä 24, hex `0xE2 0x82 0xAC`, merkki: Euro-symboli - 𐍈 - bittimäärä 32, hex `0xF0 0x90 0x8D 0x88`, merkki: Goottilainen kirjain faihu - 😄 - bittimäärä 32, hex `0xF0 0x9F 0x98 0x84`, merkki: Hymyilevä kasvo emoji Yhteenvetona merkistöistä on, että käytetään yleistä "UTF-8"-merkistöä, kun käsittelemme yleisesti tekstitiedostoja. ```py= print(bytes([0x41]).decode("utf-8")) # A print(bytes([0xC3, 0xA9]).decode("utf-8")) # é print(bytes([0xE2, 0x82, 0xAC]).decode("utf-8")) # € print(bytes([0xF0, 0x90, 0x8D, 0x88]).decode("utf-8")) # 𐍈 print(bytes([0xF0, 0x9F, 0x98, 0x84]).decode("utf-8")) # 😄 ``` <!-- s3.1. --> # Tiedoston lukeminen <!-- s4. --> Tekstitiedoston lukeminen kannattaa jaotella heti alussa kolmeen eri vaiheeseen: 1. Tiedostokahvan aukaisu lukumoodissa `r` 2. Tiedoston lukemisprosessi 3. Tiedostokahvan sulkeminen Vaiheet mielessä voidaan edetä ensimmäiseen vaiheeseen, eli tiedostokahvan aukaisuun luku moodissa. Vaiheita ennen kannattaa taltioida alla olevat teksti tiedostoon tiedostonimellä `e4_file.txt` ```gtf=txt This is first row. This is second row. This is third row. This is fifth row. ``` **1. Tiedostokahvan aukaisu lukumoodissa** Tiedostokahvan aukaisu lähtee liikkeelle kutsumalla `open`-funktiota, johon syötämme argumenttina ensin tiedostonimen. Tässä tapauksessa `e4_file.txt`. Tätä seuraa toinen argumentti (huomaa pilkku), jossa syötämme moodin 'r'. Kolmas argumentti `"UTF-8"` syötetään käyttämällä avainsanaa `encoding`, koska `open`-funktiossa kolmas parametri on `buffering`, johon tässä vaiheessa ei ole tarpeen vielä tutustua. `open`-funktio luo tiedosto-olion(lue tiedostokahva), joka taltioidaan tässä tapauksessa `Filehandle`-nimiseen muuttujaan. ```py Filehandle = open("e4_file.txt", 'r', encoding="UTF-8") ``` **2. Tiedoston lukemisprosessi** Pythonin tiedostokahvassa on kaksi tärkeää tekstitiedoston lukemiseen liittyvää metodia: - `.read()`{.py} lukee koko tiedoston tai määritellyn määrän merkkejä - `.readline()`{.py} lukee yhden rivin - `.readlines()`{.py} lukee koko tiedoston, ja palauttaa listan merkkijonoja (Lista tietorakenne tulee A7 harjoituksessa) Tiedostokahva metodiin viittaus tapahtuu pistenotaation avulla seuraavasti: ```py # Read line and store it into a variable Line = Filehandle.readline() FirstRow = Line.rstrip() # .rstrip() Removes newline character at the end Line = Filehandle.readline() SecondRow = Line.rstrip() print("SecondRow: " + SecondRow) print("FirstRow: " + FirstRow) ``` Yllä tekstitiedostosta luetaan ensimmäiset kaksi riviä kahteen eri muuttujaan. Tärkeää on havaita, että tiedosto-olio muistaa toisen komennon kohdalla, että lukemisprosessi jäi ensimmäisen rivin loppuun. Tämä ominaisuus auttaa myöhemmin rivi kerrallaan käsittelyä silmukkarakenteella. **3. Tiedostokahvan sulkeminen** Tiedostonkäsittelyn viimeinen vaihe on tiedostokahvan sulkeminen. Tämä toimenpide vapauttaa tiedostonkäsittelyyn varattuja resursseja takaisin järjestelmälle. Hyvin toteutettu ohjelmakoodi käyttää järjestelmän resursseja tehokkaasti ja tarkoituksenmukaisesti. ```py Filehandle.close() ``` **Koko E4 esimerkki** ```py= # 1. Open file handle in reading mode Filehandle = open("e4_file.txt", 'r', encoding="UTF-8") # 2. File reading process Line = Filehandle.readline() FirstRow = Line.rstrip() Line = Filehandle.readline() SecondRow = Line.rstrip() print("SecondRow: " + SecondRow) # SecondLine: This is second row. print("FirstRow: " + FirstRow) # FirstLine: This is first row. # 3. Closing file handle Filehandle.close() ``` ```stdio devaaja@wink MINGW64 ~/courses/ohj/A06 $ python e4.py SecondLine: This is second row. FirstLine: This is first row. ``` <h2>E4.1 Koko tiedoston muistiin lukeminen</h2> <!-- s4.1. --> Tiedoston lukeminen kerralla muistiin on kolmivaiheinen operaatio, jonka toteutukseen tarvitaan tiedostonimi. Hyödynnetään tässä esimerkissä aiemmin luotua `e4_file.txt` tiedostoa. Alla olevassa ohjelmakoodissa tiedoston lukeminen on määritelty funktiona `readFile` ja sille on määritelty parametrinä tiedostonimi `PFilename`. Tämän jälkeen ohjelmaan on määritelty pääohjelma `main`, joka käynnistetään ohjelmakoodin lopussa. Pääohjelma pyytää käyttäjää aluksi syöttämään tiedostonimen (rivi 12), johon tässä tapauksessa voidaan syöttää `e4_file.txt`. Seuraavaksi (rivi 13) ohjelma kutsuu `readFile`-funktiota, jolle välitetään käyttäjän syöttämä tiedostonimi. `readFile` funktio palauttaa luettavan tekstitiedoston sisällön ja se sijoitetaan pääohjelmassa `Content`-nimiseen muuttujaan. Lopuksi koko tiedoston sisältö tulostetaan kahden koristerivin väliin (rivit 14 & 16). Näissä koristeriveissä on 4 ristikkomerkkiä ja tiedostonimi tulostettuna merkkijonon keskelle. Tiedostonimi sijoittuu koristerivillä aaltosulkeiden väliin, kun lopussa on kutsuttu merkkijonon `.format` metodia ja asetettu sille argumenttina `Filename`. Lisää merkkijonon `.format`-metodista tämän harjoituksen lopussa. **E4.1. - Koko tiedoston lukeminen kerralla** ```py= # Filename: e4_1.py def readFile(PFilename: str) -> str: # 1. Prepare filehandle for read operation Filehandle = open(PFilename, 'r', encoding="UTF-8") # 2. Read file content and save it to "Content" variable Content = Filehandle.read() # 3. Close the filehandle Filehandle.close() return Content def main() -> None: Filename = input("Insert filename: ") # e4_file.txt Content = readFile(Filename) print("#### {} ####".format(Filename)) print(Content) print("#### {} ####".format(Filename)) return None main() ``` Esimerkki ohjelma-ajo alla: ```stdio devaaja@wink MINGW64 ~/courses/ohj/A06 $ python e4_1.py Insert filename: e4_file.txt #### e4_file.txt #### This is first row. This is second row. This is third row. This is fifth row. #### e4_file.txt #### ``` <h2>Tiedoston lukeminen rivi kerrallaan</h2> <!-- s4.2. --> Tiedoston lukeminen rivi kerrallaan mahdollistaa tarkemman kontrollin tiedostonlukuprosessiin. Kontrollin avulla luettava tiedosto voidaan käsitellä lukutapahtuman aikana, jolloin sen käsittely on tehokkainta resurssien näkökulmasta. Alla olevassa esimerkissä E4.2. tekstitiedostoa luetaan rivi kerrallaan. Rivien lukeminen toteutetaan `while`-silmukalla, koska tiedoston rivimäärää ei tunneta. Silmukkaan liitytään kun ensimmäinen rivi on luettu (rivit 5 & 8). Tämä tapa mahdollistaa "loppuehtoisen" silmukkarakenteen, jota Pythonissa ei ole. Täytyy kuitenkin muistaa lukea myös rivi toistorakenteen lopussa (rivi 13). Rivien käsittelyvaiheisiin on lisätty tyhjien rivien poisto. `e4_file.txt` tapauksessa neljäs rivi on tyhjä rivi, eli pelkkä `\n`. **E4.2. Tiedoston lukeminen rivi kerrallaan** ```py= # Filename: e4_2.py def readFile(PFilename: str) -> str: Filehandle = open(PFilename, 'r', encoding="UTF-8") Content = "" Line = Filehandle.readline() # Line '' = End of file while Line != '': # Handle line e.g., remove empty rows... if Line == '\n': Line = Filehandle.readline() continue Content += Line Line = Filehandle.readline() Filehandle.close() return Content def main() -> None: Filename = input("Insert filename: ") # e4_file.txt Content = readFile(Filename) print("#### {} ####".format(Filename)) print(Content) print("#### {} ####".format(Filename)) return None main() ``` Esimerkki ohjelma-ajo: ```stdio devaaja@wink MINGW64 ~/courses/ohj/A06 $ python e4_2.py Insert filename: e4_file.txt #### e4_file.txt #### This is first row. This is second row. This is third row. This is fifth row. #### e4_file.txt #### ``` Merkkijonon operointi lukemisen aikana on monimutkaisempaa(vertaa E4.1. & E4.2.) kuin pelkkä lukemisoperaatio. Käsittelyä lukemisen aikana vaikeuttaa myös se, että tekstitiedostosta tulevien rivien määrää ei voida ennustaa. Rivimäärän voi selvittää laskemalla rivinvaihtomerkit `\n` yhteen. Monimutkaisuudestaan huolimatta kyky käsitellä tietovirtaa (datastream) sen etenemisen aikana mahdollistaa virran käsittelyn kerralla. Tämä voi säästää laskenta-aikaa verrattuna menetelmiin, joissa koko tiedosto luetaan ensin muistiin, ja käsitellään vasta sen jälkeen. Hyvin optimoitu ohjelma ei myöskään varaa muistia turhaan. Tiedostot ovat toisinaan niin suuria, etteivät ne mahdu kokonaisuudessaan tietokoneen muistiin. Esimerkiksi [open-world](https://en.wikipedia.org/wiki/Open_world) -tyylisissä peleissä tiedostot voivat olla niin suuria, että niiden luomaa pelimaailmaa ladataan muistiin vain sitä mukaa, kun pelaaja liikkuu siinä. Näin tietokoneen muistia käytetään tehokkaasti, varataan sitä vain tarvittava määrä. # Tiedoston kirjoittaminen <!-- s5. --> Tekstitiedoston kirjoittamisen kolme eri vaihetta. 1. Tiedostokahvan aukaisu kirjoitusmoodissa `w` 2. Tiedoston kirjoitusprosessi 3. Tiedoston tallennus sulkemalla tiedostokahva Huom! Kun käytät kirjoitusmoodia, niin ole tarkkana tiedostonimien/-polkujen kanssa. Kun tiedostokahva aukaistaan, pyyhkäisee(eng. truncate) ohjelma tiedoston puhtaaksi kirjoitusoperaatiota varten. Tällöin, jos ohjelma kirjoittaa olemassa olevaan tiedostoon, häviää siinä oleva tieto. Peruutuspainiketta ei ole. <h2>Kirjoitetaan kaikki kerralla</h2> <!-- s5.1. --> Kirjoitusoperaatiota suunniteltaessa kannattaa ensin määrittää tiedoston nimi tai polku ja sen jälkeen kirjoitettava sisältö. Tämä suositus perustuu loogiseen järjestykseen: 1. Tiedostokahvan aukaisu tiedostonimellä 2. Tiedon kirjoittaminen Alle on koottu esimerkki, jossa käyttäjältä kysytään tiedostonimi ja syötteitä, jonka jälkeen kerätyt syötteet kirjoitetaan tiedostoon kerralla. **E5.1. Kirjoitetaan kaikki kerralla** ```py= # Filename: e5_1.py def writeFile(PFilename: str, PContent: str) -> None: # 1. Prepare filehandle for write operation Filehandle = open(PFilename, 'w', encoding="UTF-8") # 2. Add whole content Filehandle.write(PContent) # 3. Save changes Filehandle.close() return None def collectLines() -> str: Lines = "" # Initialized empty string Prompt = "Insert row(empty stops): " Row = input(Prompt) while Row != '': Lines += Row + '\n' Row = input(Prompt) return Lines def main() -> None: print("Program starting.") Filename = input("Insert filename: ") Lines = collectLines() writeFile(Filename, Lines) # Writing at once print("Program ending.") return None main() ``` Esimerkin ohjelma-ajo: ```stdio devaaja@wink MINGW64 ~/courses/ohj/A06 $ python e5_1.py Program starting. Insert filename: e5_1_file.txt Insert row(empty stops): First row here Insert row(empty stops): My code doesn't have bugs Insert row(empty stops): It just develops random features Insert row(empty stops): Program ending. ``` Kirjoitettu tiedosto: ```txt First row here My code doesn't have bugs It just develops random features ``` Ohjelma-ajon jälkeen samaan kansioon tulisi ilmestyä tekstitiedosto nimellä `e5_1_file.txt` riippumatta syötettyjen rivien määrästä. Huomaathan myös, että käyttäjäsyötteiden perään lisättiin ohjelmassa rivinvaihto (rivi 16). Rivinvaihto tulee siis muistaa määrittää ohjelmakoodissa itse. <h2>Osa kerrallaan puskuriin</h2> <!-- s5.2. --> Kirjoitustapahtuma alkaa tiedostokahvan aukaisusta ja päättyy sen sulkemiseen, eli tallennukseen. Jos kirjoituskomento ajetaan näiden välissä useasti, tallentuu tieto puskuriin, josta tieto siirretään sen täyttymisen tai tiedostokahvan sulkemisen yhteydessä tiedostoon. Alle kootussa esimerkissä kirjoitustapahtumia on useita. Tässä ideana on näyttää, että kirjoitusmetodia voidaan käyttää sulkematta tiedostokahvaa välissä. Tämä on täysin ok myös tietokoneen kiintolevyn käytön näkökulmasta, sillä Pythonin kirjoituskomento ei automaattisesti kirjoita yksittäistä kirjainta heti levylle, vaan odottaa että puskuri täyttyy tai että tiedostokahva suljetaan, mikä tallentaa tiedot puskurista. ```py= # Filename: e5_2.py SEPARATOR = ';' def writeFile(PFilename: str, PContent: str) -> None: # 1. Prepare filehandle for write operation Filehandle = open(PFilename, 'w', encoding="UTF-8") # 2. Piece by piece into the buffer for Character in PContent: if Character == SEPARATOR: Filehandle.write('\n') else: Filehandle.write(Character) # 3. Save changes Filehandle.close() return None def askName(PPrompt: str) -> str: Name = input("Insert " + PPrompt + " name: ") return Name def main() -> None: Filename = input("Insert filename: ") FirstName = askName("first") # John LastName = askName("last") # Doe # FirstName: John;LastName: Doe FullName = "FirstName: " + FirstName FullName += SEPARATOR FullName += "LastName: " + LastName FullName += '\n' writeFile(Filename, FullName) return None main() ``` Ohjelma-ajo: ```stdio devaaja@wink MINGW64 ~/courses/ohj/A06 $ python e5_2.py Insert filename: e5_2_file.txt Insert first name: John Insert last name: Doe ``` Kirjoitettu tiedosto: ```txt FirstName: John LastName: Doe ``` <h2>Numeroiden tallettaminen tekstitiedostoon</h2> <!-- s5.3. --> Tässä esimerkissä tallennetaan numeroita tekstitiedostoon. Huomattavaa on, että numeroita ei voida kirjoittaa tekstitiedostoon sellaisenaan, vaan ne tulee kääntää merkkijonomuotoon `str(Numero)` funktiolla ennen kirjoitusta. ```py= # e5_3.py def writeRange(PFilename: str, PStart: int, PStop: int, PStep: int) -> None: # 1. Prepare filehandle for write operation Filehandle = open(PFilename, 'w', encoding="UTF-8") # 2. Add content for i in range(PStart, PStop, PStep): # Value must be converted into string for write operation Line = str(i) + '\n' # Newline at the end of line Filehandle.write(Line) # Repeats multiple times. # 3. Save changes Filehandle.close() return None def main() -> None: print("Program starting.") Filename = input("Insert filename: ") # e5_3_file.txt Feed = input("Insert starting point: ") Start = int(Feed) Feed = input("Insert stopping point: ") Stop = int(Feed) Feed = input("Insert step size: ") Step = int(Feed) writeRange(Filename, Start, Stop, Step) print("Program ending.") return None main() ``` Ohjelma-ajo: ```stdio devaaja@wink MINGW64 ~/courses/ohj/A06 $ python e5_3.py Program starting. Insert filename: e5_3_file.txt Insert starting point: 3 Insert stopping point: 10 Insert step size: 2 Program ending. ``` Kirjoitettu tiedosto: ```txt 3 5 7 9 ``` <h2>Manuaalinen puskurin tyhjennys kirjoittaessa</h2> <!-- s5.4. --> Pythonissa tiedosto-olio huolehtii tiedon kirjoituksesta levylle tarvittavin väliajoin, eli jos kirjoitettavaa tietoa on paljon. Kirjoitettavaa tietoa kerrytetään muistissa "puskuriin", jonka koko vaihtelee käyttöjärjestelmittäin, mutta yleisesti ottaen se on 4 KB:n, eli 4096 tavun luokkaa. **Puskurin koko** ```py= import io print(io.DEFAULT_BUFFER_SIZE) ``` Valmiiksi rakennettuun puskurointiin ei yleensä ole tarvetta puuttua, mutta tässä seuraavaksi esitellään tapa pakottaa puskurin talletus levylle. ```py= # Filename: e5_4.py import os def main() -> None: Filename = input("Insert filename: ") # e5_4_file.txt Filehandle = open(Filename, 'w', encoding="UTF-8") Prompt = "Insert sentence: " Sentence = input(Prompt) while Sentence != "": Line = Sentence + '\n' Filehandle.write(Line) Filehandle.flush() # Flush the program's buffer os.fsync(Filehandle.fileno()) # Force the OS to write to disk Sentence = input(Prompt) Filehandle.close() return None main() ``` Esimerkki ohjelmakoodia ajettaessa pyytää ohjelma käyttäjältä syötteinä rivejä. Kun rivi on syötetty, kirjoittaa ohjelma tiedon puskuriin. Seuraavaksi ohjelman puskuri tyhjennetään käyttöjärjestelmälle. Viimeisenä käyttöjärjestelmää pyydetään kirjoittamaan tiedostotunnisteen mukainen tiedosto levylle. Menettelyä ei tarvitse tehtävissä, mutta kirjoitustapahtuman ja puskurin käyttäytymistä voidaan havainnoida tarkemmin, jos ohjelmakoodia ajetaan Debuggerilla tarkkaillen ohjelmasuoritusta, sekä kirjoitettavaa tekstitiedostoa. # Tiedostoon lisääminen <!-- s6. --> Lisäysmoodi `a` mahdollistaa kirjoittamisen olemassa olevaan tiedostoon ylikirjoittamatta tiedostoa. Tämän tyyppistä kirjoitusmoodia voisi hyödyntää esimerkiksi lokitietojen keräämiseen. ```py= # e6.py from datetime import datetime def logEvent(PFilename: str, PEvent: str) -> None: # 0. Prepare log data Timestamp = datetime.now().replace(microsecond=0).isoformat() LogMessage = "[{}]: {}\n".format(Timestamp, PEvent) # 1. Open file handle in append mode Filehandle = open(PFilename, 'a', encoding="UTF-8") # 2. Append content Filehandle.write(LogMessage) # 3. Save changes Filehandle.close() return None def main() -> None: Filename = "e6_file.txt" print("Program starting.") logEvent(Filename, "Program run") print("Program ending.") return None main() ``` Ohjelmaa ajettaessa useasti, kertyy `e6_file.txt` tiedostoon rivejä, joissa on aikaleima, sekä "Program run" tapahtuma viesti. ```txt [2024-10-13T09:00:43]: Program run [2024-10-13T09:00:50]: Program run [2024-10-13T09:00:55]: Program run ``` Esimerkissä käytettyyn `datetime`-kirjastoon tutustutaan tarkemmin harjoituksessa 8. # Tietovirran positio (stream position) <!-- s7. --> Tietovirran positio (eng. stream position) on sijainti luettavan tiedoston kohdassa, jossa tiedostokahva on menossa. Positio etenee kun tiedostoa luetaan. Tätä voidaan myös siirtää määrittämällä uusi sijainti tiedostokahvan `seek`-metodilla. `seek`-metodi toimii siten, että sille syötetään sijainti kokonaislukuna. Nolla positio vastaa tiedoston alkua ja esimerkiksi `3` siirtää position tiedoston kolmannen kirjaimen kohdalle. Paikannusmetodiin kannattaa tutustua kokeilemalla sitä käytännössä. Alle on koostettu kokonainen esimerkki, jossa pyydetään alkuun syöttämään tekstitiedoston nimi ja sen jälkeen positio numeerisesti. ```py= def readCharAtPos(PFilename: str, PPosition: int) -> str: Filehandle = open(PFilename, 'r', encoding="UTF-8") # Move stream position to the specified location in file Filehandle.seek(PPosition) Character = Filehandle.read(1) # Read only one character Filehandle.close() return Character def askPosition() -> int: Position = -1 Feed = input("Insert position: ") if Feed.isnumeric(): Position = int(Feed) return Position def displayCharacter(PCharacter: str, PPosition: int) -> None: if PCharacter == '': print("Position {} is out of bounds!".format(PPosition)) elif PCharacter == '\n': print("Position {} contains newline character".format(PPosition)) else: print("Character at position {} is {}".format(PPosition, PCharacter)) return None def main() -> None: Filename = input("Insert filename: ") Position = 0 while Position >= 0: Position = askPosition() if Position < 0: continue Character = readCharAtPos(Filename, Position) displayCharacter(Character, Position) return None main() ``` Esimerkki voi siis ajettaessa auttaa ymmärtämään "tietovirran positio", "virran paikka", "kirjanmerkki", "osoitin" käsitettä. Tätä ei kuitenkaan tarvitse tehtävien teossa. # Työskentelykansio - CWD <!-- s8. --> Tämän hetkinen työskentelykansio, eli CWD(Current Working Directory) on kansio, jossa tulkki on suorituksen aikana. Tulkin kulloinenkin työskentelykansio riippuu siitä, miten Python ohjelma käynnistetään. Esimerkiksi IDLE:n interaktiivisen tulkin sijainti oletuksena on Pythonin asennuskansio. ```python-repl >>> import os >>> os.getcwd() 'C:\\Users\\devaaja\\AppData\\Local\\Programs\\Python\\Python312' >>> os.chdir("C:\\Users\\devaaja\\projects\\ohj") >>> os.getcwd() 'C:\\Users\\devaaja\\projects\\ohj' >>> ``` VSCode:lla ajettaessa RUN-painikkeella on työskentelykansio se, mihin kansioon VSCode alussa avattiin. Yleensä tiedostonkäsittely aihepiirissä ongelmat liittyvät juurikin siihen, että kansiota ei ole avattu sopivaan paikkaan, työskentelykansio osoittaa eri paikkaan kuin missä käsiteltävät tiedostot ovat. ![cwd](https://hackmd.io/_uploads/Bk5B_K_kyg.png) Parhaiten työskentelykansion saa haltuun käyttämällä Bash-komentotulkkia ja sen tarjoamaa `cd`-komentoa, mikä tulee sanoista "change directory". Komennon käyttö: ```bash cd path/to/working_dir ``` Kun komennolla on vaihdettu työskentelykansioon, ja sieltä käynnistetään Python-tulkki, on tulkki samassa työskentelykansiossa. ```bash devaaja@wink MINGW64 ~ $ cd ~/courses/ohj/A06/ devaaja@wink MINGW64 ~/courses/ohj/A06 $ pwd /c/Users/devaaja/courses/ohj/A06 devaaja@wink MINGW64 ~/courses/ohj/A06 $ python Python 3.12.4 (tags/v3.12.4:8e8a4ba, Jun 6 2024, 19:30:16) [MSC v.1940 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> os.getcwd() 'C:\\Users\\devaaja\\courses\\ohj\\A06' >>> exit() devaaja@wink MINGW64 ~/courses/ohj/A06 $ ``` Huomioita yllä olevasta esimerkistä: 1. Polussa näkyvä tilde merkki `~` viittaa käyttäjän kotihakemistoon. 2. Windows käyttäjillä `~` vastaa `/c/Users/username` 3. Linux käyttäjillä `~` vastaa `/home/username` 4. MacOS käyttäjillä `~` vastaa `/Users/username` 3. Bash-komento `pwd` tulee sanoista "print working directory". 4. `python`-komento ilman tiedostonimeä aukaisee interaktiivisen Python session 5. Python komento `exit()` lopettaa interaktiivisen session. <kbd>CTRL</kbd> + <kbd>C</kbd> toimii myös. # Merkkijonon jäsenfunktiot <!-- s9. --> Pythonissa merkkijono on monipuolinen olio, joka tarjoaa jäsenfunktioita eri tarkoituksiin. Se ei siis ole pelkästään tietorakenne, jossa on taltioituna merkkejä, vaan merkkijonolle voidaan suorittaa toimenpiteitä jäsenfunktioiden avulla. Jäsenfunktioihin viittaminen selvisi aiemmin tiedostokahvaa käsiteltäessä. Silloin jäsenfunktioihin kuten `read`, `write` ja `close` viitattiin kirjoittamalla muuttujan nimi, jossa tiedostokahva oli tallessa ja sen perään pistenotaatiolla haluttu jäsenfunktio. Tämä sama pistenotaatio toimii myös muiden olioiden jäsenfunktioita kutsuttaessa. Merkkijonojen tapauksessa merkkijonon perään voidaan kirjoittaa pistenotaatiolla haluttu kutsu. ```python-repl >>> "Tähän tulee numero {} ja sen perään kirjain '{}'".format(1.234, 'e') ... "Tähän tulee numero 1.234 ja sen perään kirjain 'e'" ``` Äskeinen `.format`-jäsenfunktio toimii hyvin merkkijonon perässä, mutta saman voi myös tehdä viittaamalla ensin muuttujanimeen ja sitten jäsenfunktioon. ```py Content = "Value {0} rounded in 2 decimal is {0:.2f}" print(Content.format(1.2345)) # Value 1.2345 rounded in 2 decimal is 1.23 ``` **Osa merkkijonojen jäsenfunktioista** |jäsenfunktio|selitys| |---|---| |`capitalize()` |Palauttaa kopion merkkijonosta, jossa ensimmäinen merkki on isolla ja loput pienellä.| |`upper()` |Palauttaa kopion merkkijonosta, jossa kaikki merkit on isolla.| |`lower()` |Palauttaa kopion merkkijonosta, jossa kaikki merkit on pienellä.| |`startswith(prefix)`|Vertailee, että löytyykö etuliite(prefix) merkkijonon alusta ja palauttaa vertailun perusteella totuusarvon| |`endswith(prefix)` |Vertailee, että löytyykö pääte(suffix) merkkijonon lopusta ja palauttaa vertailun perusteella totuusarvon| |`isnumeric()` |Tarkistaa, koostuuko merkkijono pelkästään numeroarvoista ja palauttaa totuusarvon| |`lstrip(prefix)` |Vertailee etuliite parametriä merkkijonoon ja poistaa merkit merkkijonon vasemmalta, jos etuliite vastaa merkkijonon alkua. | |`rstrip(suffix)` |Vertailee pääte parametriä merkkijonoon ja poistaa merkit merkkijonon oikealta, jos pääte vastaa merkkijonon loppua.| |`split(sep)` |Pilkkoo merkkijonon useampaan merkkijonoon erottimen avulla| |`join(sequence)` |Yhdistää sekvenssin merkkijonot merkkijonon sisällöllä| Tarkemmat kuvailut Pythonin [merkkijonojen metodeista](https://docs.python.org/3/library/stdtypes.html#string-methods) **Esimerkkejä merkkijonon jäsenfunktioiden käytöstä:** **capitalize()** ```py print("hello".capitalize()) # Hello ``` **startswith()** ```py PhoneNum = "+358123456789" if PhoneNum.startswith("+358"): print("Phone number is from Finland") # Phone number is from Finland ``` **endswith()** ```py if "game_status.txt".endswith(".txt"): print("Should be a plain text file") # Should be a plain text file ``` **isnumeric()** ```py Feed = input("Insert number: ") # hello if Feed.isnumeric(): print("Yes it is a number") else: print("No it's not a number") # No it's not a number ``` **rstrip()** ```py Filename = input("Insert filename: ") Filehandle = open(Filename, 'r', encoding="UTF-8") Line = Filehandle.readline() while Line != '': Row = Line.rstrip() # Removes newline character from end of the line print(Row) # Adds newline character to the end... Line = Filehandle.readline() Filehandle.close() ``` https://docs.python.org/3/library/stdtypes.html#str.format <!-- Edistynyt merkkijonojen käsittely --> <h2>Merkkijonon muotoilusyntaksi</h2> Merkkijonon `format`-jäsenfunktio on todella monipuolinen toiminto. Se kätkee sisälleen [muotoilumäärityksen minikielen](https://docs.python.org/3/library/string.html#formatspec), jolla voi toteuttaa erikoisia tulostus muotoiluja. Tutustutaan aluksi kuitenkin argumenttien järjestykseen `format`-jäsenfunktion yhteydessä. Alla olevassa esimerkissä `format`-funktiolle on välitetty kaksi argumenttia, joista arvo `100` on ensimmäinen argumentti ja `200` on toinen argumentti. Merkkijonoon argumentit sijoitetaan aaltosulkeiden kohdalle, jossa numero indikoi argumentin järjestysnumeroa. Laskeminen aloitetaan nollasta, eli ensimmäinen argumentti sijoitetaan `o` tilalle ja toinen argumentti `1` tilalle. **Argumenttien järjestys:** ```python-repl >>> "First value: {0}, second value: {1}".format(100, 200) 'First value: 100, second value: 200' >>> "Second value: {1}, first value: {0}".format(100, 200) 'Second value: 200, first value: 100' >>> ``` Esimerkissä argumenttien järjestystä ei muutettu, mutta merkkijonon tulosteessa ne sijoitettiin eri kohtiin. **Desimaaliluvun esittäminen 2 desimaalin tarkkuudella:** Muotoilumäärityksen minikieli mahdollistaa liukulukujen pyöristämisen tiedon esitystä varten. Syntaksi alkaa kaksoispisteellä, jota seuraa kokonaislukujen määrä tai piste(kokonaislukujen määrän voi jättää pois kuten esimerkeissä). Pisteen jälkeen haluttu desimaalien määrä ja `f`-kirjain. ```python-repl >>> "{:.2f}".format(2.55) '2.55' >>> "{:.2f}".format(2.555) '2.56' >>> "{0:.2f}".format(2.555) '2.56' >>> "{:.2f}".format(2.5) '2.50' ``` Huomaa, että desimaaliosa on nyt vakiopituinen. Python dokumentaatio [tekstin sekvenssityypistä](https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str)