HyppÀÀ sisÀltöön

💡 Gallium

Avainsanat

Aivan kuten BashissÀ ja PowerShellissÀ, myös Pythonissa on varattuja sanoja, joita ei voi kÀyttÀÀ muuttujaniminÀ. TÀssÀ on lista niistÀ:

False     None      True      and       as
assert    async     await     break     class
continue  def       del       elif      else
except    finally   for       from      global
if        import    in        is        lambda
nonlocal  not       or        pass      raise
return    try       while     with      yield

LisÀksi on sisÀÀnrakennettuja funktioita, joita toki voit kÀyttÀÀ muuttujaniminÀ, mutta jyrÀÀt sivuoireena Pythonin toiminnallisuuksia. TÀmÀ ei siis ole suositeltavaa. NÀitÀ ovat:

abs           aiter         all           anext         any           ascii
bin           bool          breakpoint    bytearray     bytes         callable
chr           classmethod   compile       complex       copyright     credits
delattr       dict          dir           display       divmod        enumerate
eval          exec          execfile      filter        float         format
frozenset     get_ipython   getattr       globals       hasattr       hash
help          hex           id            input         int           isinstance
issubclass    iter          len           license       list          locals
map           max           memoryview    min           next          object
oct           open          ord           pow           print         property
range         repr          reversed      round         runfile       set
setattr       slice         sorted        staticmethod  str           sum
super         tuple         type          vars          zip
Kuinka tulostettiin?

YllÀ olevat listat on tulostettu seuraavalla skriptillÀ.

import keyword
import builtins

def print_as_tabular(words:list[str], columns:int=5, margin:int=2):

    # Calculate variables
    column_width = max([len(x) for x in words]) + margin

    # Loop and print
    for batch in range(0, len(words), columns):
        row_words = words[batch:batch + columns]
        for word in row_words:
            print(f"{word:<{column_width}}", end="")
        print()
    print()


# Print the reserved keywords
print_as_tabular(keyword.kwlist)

# Print the built-ins
low_case_builtins = [x for x in dir(builtins) if x[0].islower()]
print_as_tabular(low_case_builtins, columns=6)

TĂ€rpit

Muuttujat

Koko totuus löytyy Pythonin dokumentaatiosta (esim. Built-In Types), mutta alla on pikaohje, jolla pÀÀset alkuun.

Dynaaminen

Aivan kuten PowerShell, myös Python dynaamisesti tyypitetty kieli. Alla oleva esimerkki on sinulle hyvinkin tuttu PowerShellin puolelta:

x = 1                 # Kokonaisluku (class 'int')
x = 3.12              # Liukuluku (class 'float')
x = "abc"             # Merkkijono (class 'str')
x = [1, 2, 3]         # Lista (class 'list')
x = (1,2,3)           # Tuple (class 'tuple')
x = {1,2,3}           # Set (class 'set')
x = {"a": 1, "b": 2}  # Sanakirja (class 'dict')
x = True              # Boolean (class 'bool')
x = None              # NoneType (class 'NoneType')

KAMK:n opiskelijana olet kÀynyt mitÀ todennÀköisimmin Python Perusteet -kurssin, joten nÀiden pitÀisi olla sinulle tuttuja tyyppejÀ. Jos et ole varma, niin suosittelen kertaamaan materiaalia. Erityisesti listan, tuplen ja setin ero on hyvÀ tunnistaa, koska ne muistuttavat toisiaan, mutta eroavat kÀyttötarkoitukseltaan.

TyypittÀminen

Python ja PowerShell eroavat merkittÀvÀsti toisistaan tyypityksen ja tyypin vaihtamisen suhteen. Muistat toivon mukaan PowerShellistÀ, ettÀ muuttujan voi pakottaa tiettyyn tyyppin kirjoittamalla tyypin hakasulkeisiin ennen muuttujaa (esim. [int]$x = "42"). Pythonissa on vastaava syntaksi, mutta se on vain type hint dokumentaatiota varten, ei pakottava tyypitys. Alla esimerkki:

Python REPL
>>> x: int = 42
>>> x = "thingy"
>>> type(x)
<class 'str'>

🩆 Terminologia

Termi, jota kÀytetÀÀn Pythonin ÀÀrimmÀisen liberaalista tyypityksestÀ, on duck typing. Lause, johon kenties olet jo törmÀnnyt, mÀÀrittelee sen nÀin: If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

Pythonin tyypitys on siis vapaaehtoista, mutta se ei tarkoita, ettÀ sitÀ ei kannattaisi kÀyttÀÀ. KevyimmillÀÀn kannattaa mÀÀrittÀÀ type hinting tyylillÀ. KÀyttÀmÀsi IDE, kuten Visual Studio Code lisÀosineen, osaa hyödyntÀÀ tÀtÀ tietoa ja tarjota parempaa koodiautomaatiota ja -tarkistusta.

⛔ Ei nĂ€in
def add(a, b):
    return a + b

a = 2
b = 3
print(add(a, b))
✅ NĂ€in
def add(a: int, b: int) -> int:
    return a + b

a: int = 2
b: int = 3
print(add(a, b))

Ulkoista apua

Dynaaminen tyypitys ei ole kaikkien mieleen, ja se altistaa koodin virheille, jotka voitaisiin havaita staattisella tyypityksellÀ. Tyyppivirheisiin perustuvat bugit ovat usein vaikeita löytÀÀ ja korjata. TÀmÀn vuoksi on olemassa työkaluja, jotka tarkistavat koodin tyypityksen ennen sen suorittamista. Visual Studio Codessa on Microsoftin yllÀpitÀmÀ ja tÀten hyvinkin virallinen lisÀosa Python, jonka mukana asentuu vakiona Pylance. JÀlkimmÀisen voi vaihtaa sen tuoreeseen kilpailijaan nimeltÀÀn Ruff tai mypy.

Tutustumme Visual Studion Coden asetuksiin tarkemmin live-tunneilla. TÀssÀ materiaalissa neuvotaan työkalujen kÀyttö Dockerin avulla yhtÀ skriptiÀ vasten.

Aloitetaan työkalulla nimeltÀÀn mypy.

# Run mypy against a single script
docker run --rm -v $(pwd):/data:ro cytopia/mypy scripts/hello_turbo.py

# Or maybe with a strict mode to be more pedantic
docker run --rm -v $(pwd):/data:ro cytopia/mypy --strict scripts/hello_turbo.py

Tuore haastaja Python-ekosysteemissÀ on Astral, jonka työkalu Ruff toimii sekÀ linterinÀ ettÀ formatterina. Linter tarkistaa koodin virheistÀ ja formatter muotoilee koodin yhtenÀiseen tyyliin. SitÀ voit kÀyttÀÀ Dockerin kautta nÀin:

# Tarkista virheet
docker run --rm -v $(pwd):/io ghcr.io/astral-sh/ruff:latest check scripts/hello_turbo.py

# Korjaa automaattisesti virheet (1)
docker run --rm -v $(pwd):/io ghcr.io/astral-sh/ruff:latest check scripts/hello_turbo.py --fix

# Muotoile teksti vakiotyyliin
docker run --rm -v $(pwd):/io ghcr.io/astral-sh/ruff:latest format scripts/hello_turbo.py

Warning

Ruff-komennot ajetaan ilman :ro eli read-only volumea. Ruff tarvitsee kirjoitusoikeudet muokatakseen tiedostoa ja luodakseen vÀliaikaisia tiedostoja. NÀmÀ tiedostot ilmestyvÀt .ruff_cache-hakemistoon. KylkiÀisenÀ tulee .gitignore-tiedosto, joka estÀÀ nÀiden tiedostojen pÀÀtymisen versionhallintaan.

VianetsintÀ

Print

BashistÀ ja PowerShellistÀ tuttu echo-komennon vastine Pythonissa on print. Hyvin primitiivinen, mutta silti tehokas tapa debugata koodia on tulostaa muuttujien arvoja konsoliin.

user_input = input("Give me a number: ")
print(f"User input was: {user_input}")

Assert

On tilanteita, joissa haluat skriptin kaatuvan, jos jokin ehto ei tÀyty. TÀmÀ onnistuu assert-lausekkeella. Alla esimerkki:

x = 1
assert x == 2, "x should be 2"

Logging

Toivon mukaan muistat yhÀ PowerShellin puolelta eri virrat, joihin pystyy kirjoittamaan viestejÀ. Pythonissa vastaava toiminnallisuus löytyy logging-moduulista. Alla esimerkki:

logger_practice.py
import logging
import random


def pick_random_level() -> int:
    level = random.choice(
        [
            logging.DEBUG,
            logging.INFO,
            logging.WARNING,
            logging.ERROR,
            logging.CRITICAL,
        ]
    )

    name = logging._levelToName[level]
    print(f"Randomly chosen level: {name} (no. {level})")
    return level


# Set the logging level and format
rnd_level = pick_random_level()
FORMAT: str = "%(asctime)s - %(levelname)s - %(message)s"
logging.basicConfig(level=rnd_level, format=FORMAT, datefmt="%H:%M:%S")

# Call each level once
logging.critical("DON'T PANIC!")
logging.warning("It worked on my machine...")
logging.error("Have you tried turning it off and on again?")
logging.info("Must be a cosmic ray. Try running it again.")
logging.debug("I'm sure it's just a typo.")

Skripti arpoo satunnaisen loggaustason ja tulostaa viestin jokaisella tasolla. Vain valitun tason ylittÀvÀt viestit tulostuvat konsoliin. Alla esimerkki skriptin ajosta:

$ ./runpy.py scripts/logger_practice.py
Randomly chosen level: INFO (no. 20)
11:24:23 - CRITICAL - DON'T PANIC!
11:24:23 - WARNING - It worked on my machine...
11:24:23 - ERROR - Have you tried turning it off and on again?
11:24:23 - INFO - Must be a cosmic ray. Try running it again.

TehtÀvÀt

TehtÀvÀ: DevausympÀristö ja runpy.py

Loit ajo aiemmassa tehtÀvÀssÀ python/-hakemiston, jotta sait kopioitua skriptin avulla virtuaalikoneesta kaikki Python-srkriptit sinun host-koneellesi. Jatketaan saman hakemiston kÀyttöÀ, mutta Multipass-koneen sijaan kÀytetÀÀn Docker-konttia. LisÀksi Bash-skriptin sijasta kÀytÀmme Python-skriptiÀ. Vaiheet:

  1. Lataa gh:sourander/skriptiohjelmointi/exercise-assets/scripts/runpy.py
  2. Tee tiedostosta ajettava (tai aja jatkossa python runpy.py)
  3. Lue tiedoston sisÀltö lÀpi ja selvitÀ, mitÀ se tekee.

Referenssiksi hakemistorakenteen kuuluisi olla:

johnanderton
├── README.md
├── bash
│   └── .gitkeep 
├── pwsh
│   └── .gitkeep 
└── python
    ├── README.md
    ├── getscripts.sh
    └── scripts
        └── hello.py  # <= Seuraava tehtĂ€vĂ€!
TehtÀvÀ: Python Hello World

Luo skripti hello.py, joka tulostaa konsoliin Hello, World!. Aja sitten:

$ ./runpy.py --dryrun scripts/hello.py
[DRY] Cmd that would run: LUE TÄMÄ OUTPUT!!

$ ./runpy.py scripts/hello.py
Hello, World!

$ ./runpy.py --bash --dryrun
[DRY] Cmd that would run: LUE TÄMÄ OUTPUT!!

$ ./runpy.py --bash
# python3 /app/scripts/hello.py
Hello, World!

Huomaa, ettÀ tiedoston ei tarvitse olla executable, koska kontin sisÀllÀ ajetaan komento python <tiedosto> eikÀ ./<tiedosto>.

TehtÀvÀ: Python Turboahdettu Hello World

Luo skripti, joka tulostaa:

  • absoluuttinen polku työhakemistoon
  • absoluuttinen polku skriptin sijaintiin
  • polut, joista Python etsii moduuleja

Tiedoston tulisi alkaa nÀin:

hello_turbo.py
#!/usr/bin/env python3

# Implement

Alla esimerkkikÀyttö Dockerin kanssa:

$ runpy.py scripts/hello_turbo.py

========= Turbo Hello World! =========
Current working dir:          : /app
Skriptin sijainti:            : /app/scripts/hello_turbo.py
Python moduulien hakupolut:
    /app/scripts
    /usr/local/lib/python312.zip
    /usr/local/lib/python3.12
    /usr/local/lib/python3.12/lib-dynload
    /usr/local/lib/python3.12/site-packages

Ja tÀssÀ vielÀ Multipassin kanssa:

$ multipass launch --name helloturbo 24.04
$ multipass transfer scripts/hello_turbo.py helloturbo:.
$ multipass exec helloturbo -- python3 hello_turbo.py
========= Turbo Hello World! =========
Current working dir:          : /home/ubuntu
Skriptin sijainti:            : /home/ubuntu/hello_turbo.py
Python moduulien hakupolut:
    /home/ubuntu
    /usr/lib/python312.zip
    /usr/lib/python3.12
    /usr/lib/python3.12/lib-dynload
    /usr/local/lib/python3.12/dist-packages
Vinkki: Moduulit

Katso, mitÀ sys.path sisÀltÀÀ. Huomaa, ettÀ sys-moduuli pitÀÀ importoida ensin.

TehtÀvÀ: Interaktiivinen Python

Harjoittele tÀssÀ tehtÀvÀssÀ interaktiivista Python ShelliÀ eli REPL:iÀ kontin sisÀllÀ. Saat sen auki ajamalla aiemmin lataamasi runpy.py-skriptin ilman argumentteja. Kokeile seuraavia:

  1. Laske 2+2
  2. Luo muuttuja x ja anna sille arvoksi 42
  3. Tulosta muuttujan x arvo
  4. Luo lista lista ja anna sille arvoksi [1, 2, 3]
  5. Tulosta listan lista arvot
$ runpy.py
Python 3.1x.x (main, ...) [GCC xx.x.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> # TÀssÀ voit kirjoittaa Python-koodia

Kun olet valmis, voit poistua painamalla Ctrl+D tai kirjoittamalla exit().

Miksi REPL?

Interaktiivinen Python on hyvÀ tapa kokeilla nopeasti koodinpÀtkiÀ ja testata, miten Python toimii. Materiaalin kirjoittanut opettaja kÀyttÀÀ sitÀ usein myös nopeana laskimena.

TehtÀvÀ: Interaktiivinen Python Pt. 2

ÄÀrimmĂ€isen nĂ€ppĂ€rĂ€ komento lyhyitĂ€ skriptejĂ€ debugatessa on -i-flagi, joka jĂ€ttÀÀ Pythonin auki skriptin suorituksen jĂ€lkeen interaktiiviseen tilaan. Dokumentaatiossa se on mÀÀritelty nĂ€in: "When a script is passed as first argument (...) enter interactive mode after executing the script (...)" 1. Lokaalisti ajettuna se olisi nĂ€inkin helppoa:

$ python -i scripts/hello_turbo.py
$ ./runpy.py --dryrun --interactive scripts/hello_turbo.py
[DRY] Cmd that would run: LUE TÄMÄ OUTPUT!!

$ ./runpy.py --interactive scripts/hello_turbo.py
========= Turbo Hello World! =========
Current working dir:          : /app
Skriptin sijainti:            : /app/scripts/hello_turbo.py
Python moduulien hakupolut:
    /app/scripts
    /usr/local/lib/python312.zip
    /usr/local/lib/python3.12
    /usr/local/lib/python3.12/lib-dynload
    /usr/local/lib/python3.12/site-packages
>>>

MitĂ€ me voitimme? Sinulla on kaikki skriptissĂ€ importatut moduulit kĂ€ytössĂ€, kuten myös muuttujat ja funktiot. TĂ€mĂ€ on yksi monista vaihtoehdoista debugata ja/tai kehittÀÀ skriptejĂ€. Kukin skriptaaja löytÀÀ oman tyylinsĂ€, joka sopii parhaiten omaan tapaansa työskennellĂ€ – on kuitenkin hyvĂ€ tuntea vaihtoehdot eikĂ€ tarttua ensimmĂ€iseen. Omia työskentelytapojaan kannattaa myös jatkuvasti haastaa ja kehittÀÀ.

TehtÀvÀ: Tiedostoon loggaus

Ota mallia yllÀ olevasta logger_practice.py-skriptistÀ ja luo oma skripti, joka loggaa viestit konsolin sijasta tiedostoon (esim. logger_practice.log). Pythonin oma Logging HOWTO on hyvÀ paikka aloittaa.

Tip

Huomaa, ettÀ jos ajat skriptiÀ kontin sisÀllÀ, niin tiedosto tallentuu kontin sisÀlle. EthÀn yritÀ tallentaa tiedostoa /app-hakemistoon, koska se on read-only. Voit tallentaa tiedoston esimerkiksi /tmp-hakemistoon.

Muista myös, ettÀ kontti on vÀliaikainen, joten tiedosto katoaa, kun kontti tuhotaan. On kannattavaa ajaa kontti siten, ettÀ CMD on bash, ja kÀyt suorittamassa skriptin kÀsin. Eli siis:

đŸ–„ïž Bash
$ ./runpy.py --bash

...joka avaa bashin kontin sisÀllÀ. TÀmÀn jÀlkeen voit ajaa skriptin kÀsin ja kÀydÀ tarkistamassa, ettÀ tiedosto on luotu.

🐳 Bash
# python3 /app/scripts/logger_practice.py
TehtÀvÀ: Ruff

YllÀ on esitelty Ruff-työkalun kÀyttö Docker-kontissa. KÀytÀ sitÀ ja korjaa kaikkien tiedostojen virheet (sekÀ check ettÀ format), jotka olet tÀhÀn asti luonut scripts/-hakemistoon.

Saat kÀyttÀÀ parhaaksi katsomallasi tavalla joko Dockerissa ajettua Ruffia, lokaalisti asettua Ruff:ia (esim. uv tool install ruff) tai Visual Studio Coden lisÀosaa. Docker on helpoin, koska se on neuvottu yllÀ. Muita varten sinun tarvitsee ottaa omatoimisesti selvÀÀ.

Warning

Muista ottaa tĂ€mĂ€ tavaksi jatkossa! On oletus, ettĂ€ kurssilla kirjoittamasi skriptit lĂ€pĂ€isevĂ€t Ruffin tarkistukset. Kenties haluat kirjoittaa skriptin, joka ajaa pitkĂ€n Docker-komennon puolestasi? đŸ€”

LĂ€hteet


  1. Python Docs. Command line and environment. https://docs.python.org/3/using/cmdline.html#cmdoption-i