Skip to main content

2.3 Programovací jazyk MicroPython

Mikrokontroléry môžeme programovať rôznymi spôsobmi, spomínali sme si už blokové (vizuálne) i textové programovanie. Z klasických textových programovacích jazykov sú k dispozícii:

  • C++ v známom prostredí Arduino IDE;
  • MicroPython ako špeciálna verzia Python pre MCU;
  • CircuitPython ako špeciálna pohodlná verzia MicroPython.

2.3 MicroPython vs. C++.webp

Predstavenie jazyka Python

Python je jazyk interpretovaný, rovnako jeho MCU verzie, čo má svoje výhody i nevýhody, ktorými sme sa už vo všeobecnosti zaoberali. Najväčšími strašiakmi Pythonu v MCU sú:

Python je pomalší ako C.
Python spotrebuje veľa operačnej pamäte.
Zariadenie s Pythonom nevydrží dlho bežať na batériu.

Tieto tvrdenia v súčasnosti nie sú celkom pravdivé. Výkon je možné zvýšiť až na úroveň skompilovaného kódu - výkonovo kritické časti je možné viacerými spôsobmi optimalizovať, či dokonca „predkompilovať“, čo pre bežné funkcie využíva aj samotný MicroPython. K zaujímavým výsledkom merania výkonu prišiel Vláďa Smitka z Makerclass, ako vidno v jeho prezentácii „Python na MCU - Je to dobrý nápad?“. V dokumentácii MicroPython je možné nájsť pokročilé tipy pre zvýšenie rýchlosti programu - nie je to však čítanie pre začiatočníkov! 🙂 Takejto vysokej úrovni optimalizácie sa v tomto kurze nebudeme venovať.

Vyššia spotreba pamäte je fakt - prekladač musí byť aktívny v pamäti, no samotný program už nemá výrazne vyššie požiadavky na pamäť. Keďže sú však už bežne dostupné lacné MCU rádovo s MiB operačnej pamäte, tento problém sa postupne stáva nepodstatným.

Čo sa týka výdrže batérie, teda spotreby energie, najpodstatnejšie je písať programy rozumne, s dôrazom na šetrenie energie a s využívaním „spánku“. Až na druhom mieste je spotreba energie pre samotný beh programu - je možné použiť podobné optimalizácie, ako pri výkone.

Každopádne, výkon behu programu nie je všetko a niekedy je oveľa podstatnejšia rýchlosť vývoja aplikácie. A koľko trvá kompilácia C++ pre MCU, sme mali možnosť vidieť a zažiť s ESPHome. 😒

MicroPython vs. Python

MicroPython je jednoduchou a efektívnou implementáciou programovacieho jazyka Python 3.4, pričom obsahuje aj niektoré novšie prvky z vyšších verzií - v dokumentácii MicroPython je možné nájsť rozdiely oproti štandardnému Python. Zahŕňa len časť štandardných knižníc Python, má však knižnice pre prácu s hardvérom a je optimalizovaný pre mikrokontroléry s obmedzenými zdrojmi. Snaží sa byť kompatibilný s plnohodnotným jazykom Python.

Základné konštrukcie jazyka MicroPython sú totožné s Python a môžeme teda využívať dokumentáciu jazyka Python. Oplatí sa však oboznámiť so špecifikami MicroPython - či už po stránke optimalizácie zápisov alebo špeciálnych knižníc určených pre ovládanie hardvéru.

2.3 MicroPython.webp

Základná syntax MicroPython

Pre komentár v jazyku Python slúži znak „#“ a pre viacriadkový komentár je možné využiť viacriadkový text, ktorý začína i končí trojicou apostrofov ('''):

# toto je bežný jednoriadkový komentár

'''
A toto je viacriadkový komentár.
Ono to vlastne ani nie je komentár, ale len tak „voľne pohodený“ viacriadkový textový reťazec.
Na rýchle znefunkčnenie časti programu však výborne poslúži.
'''

Operátory

Matematické operátory sú podobné C++:

  • +, -, *, /, %: sčítanie, odčítanie, násobenie, delenie, zvyšok po delení;
  • na rozdiel od C++ delenie celých čísel vráti desatinné číslo;
  • //, **: celočíselné delenie, mocnina - navyše oproti C++.

Operátory porovnávania sú rovnaké ako v C++:

  • ==, !=, <, >, <=, >=: rovný, nerovný, menší, väčší, …

Zaujímavou črtou jazyka Python je zreťazenie porovnania. Teda napríklad podmienka overujúca, či je hodnota medzi 2 a 4 nemusí byť zložená z dvoch častí (ako v C++): 2 <= x and x <= 4, stačí zapísať skrátene: 2 <= x <= 4

Logické operátory sa píšu slovne:

  • not, or, and: negácia, logický súčet („alebo“), logický súčin („a súčasne“).

Ďalšie informácie: W3Schools - Python Operators

Premenné a dátové typy

V názvoch premenných sa rozlišujú malé a veľké písmená, môžeme používať dokonca aj diakritiku (i keď v dlhších programoch to nie je veľmi praktické a v medzinárodnej komunikácii ani vhodné). Premenné nie je potrebné deklarovať, priamo môžeme definovať hodnotu: {názov premennej} = {hodnota}

počet = 10
pi = 3.14

Na mikrokontroléroch sa veľmi často využívajú konštanty - na rozdiel od premenných nemíňajú operačnú pamäť. Formálne sa zapisuje v tvare funkcie const():

PIN_LED = const(2)
POCET = const(100)
print(POCET)

Logické hodnoty (boolean) sú označované TrueFalse:

je_horúco = False

Textové reťazce môžu byť v úvodzovkách i apostrofoch - môžeme si vybrať:

meno = "Dušan"
priezvisko = 'Zervan'

Typ premennej je možné zistiť funkciou type({premenná}) - tá vracia objekt, no pre získanie názvu typu vo forme textového reťazca môžeme využiť jeho vlastnosť .__name__:

print(type(meno)) # <class 'str'>
print(type(meno).__name__) # str

Je možné priraďovať naraz do viacerých premenných:

poradie, osoba, vek = 3, "Anna", 15

Vstup a výstup

Výpis z MicroPython na štandardný výstup sa nerealizuje „na obrazovku“, ale na konzolu / sériový port.

Príkaz print({výraz 1}, {výraz 2},) vypíše zadané výrazy oddelené medzerou, na konci bude ENTER:

print(osoba, "má", vek, "rokov.")
# Anna má 15 rokov.

Ukončenie riadku určuje parameter end (predvolený je ENTER - "\n"), oddeľovanie jednotlivých častí výpisu určuje parameter sep (predvolená je medzera):

print("údaje v CSV:", end=" ")
print(osoba, vek, poradie, sep=";")
# údaje v CSV: Anna;15;3

Premennú môžeme načítať z konzoly:

  • {premenná} = input({text výzvy})
vek = input("Zadaj vek: ")

Používanie funkcií z modulov

Niektoré funkcie sú priamo v jazyku Python, no viac je v rôznych „moduloch“. Ide o podobnú situáciu ako predstavujú knižnice v C++. Pred použitím funkcií z modulu ich musíme importovať, čo je možné rôzne:

  • môžeme importovať celý modul: import {modul}
  • môžeme importovať konkrétnu funkciu (alebo i viaceré) z modulu: from {modul} import {funkcie}
  • prípadne môžeme importovať všetky funkcie z modulu: from {modul} import *

Prvý spôsob, teda import modulu, vyžaduje zadávať pred názov funkcie aj názov modulu:

import time, math
time.sleep(1)
print(time.time_ns(), math.pi)

Import funkcií z modulu umožňuje písať priamo názov funkcie, len je potrebné dať si pozor, aby sa funkcie z rôznych používaných modulov nevolali rovnako:

from time import *
from math import pi
sleep(1)
print(time_ns(), pi)

V oboch variantoch sa takto neimportujú len funkcie, ale aj konštanty a premenné.

Dostupné funkcie v MicroPython

Matematika a čísla

MicroPython ponúka niekoľko vstavaných funkcií pre prácu s číslami:

  • abs({číslo}): vráti absolútnu hodnotu (zruší znamienko mínus);
  • round({číslo}): zaokrúhli číslo na celé číslo (podľa bežných pravidiel);
  • round({číslo}, {počet miest}): zaokrúhli desatinné číslo na určený počet desatinných miest;
  • min({hodnoty}): zistí najnižšiu hodnotu v zozname hodnôt (nemusí ísť len o čísla);
  • max({hodnoty}): zistí najvyššiu hodnotu v zozname hodnôt (nemusí ísť len o čísla).
teplota = 23.6789
print(round(teplota)) # 24
print(round(teplota, 2)) # 23.68

Ďalšie funkcie pre prácu s číslami sú v module math:

  • floor({číslo}): vráti číslo zaokrúhlené nadol;
  • ceil({číslo}): vráti číslo zaokrúhlené nahor;
  • sqrt({číslo}): vráti druhú odmocninu čísla;
  • log10({číslo}): vráti logaritmus čísla so základom 10;
  • log({číslo}): vráti prirodzený logaritmus čísla (základom je Eulerovo číslo e);
  • log({číslo}, {základ}): vráti logaritmus čísla s uvedeným základom;
  • sin({uhol})cos({uhol}), tan({uhol}): trigonometrické funkcie, uhol sa zadáva v radiánoch, nie v stupňoch!;
  • degrees({radiány})radians({stupne}): prevody medzi stupňami a radiánmi;
  • pi: konštanta s číslom π.
import math
print("číslo π:", math.pi)
print("číslo π zaokrúhlené nadol:", math.floor(math.pi)) # 3
print("číslo π zaokrúhlené nahor:", math.ceil(math.pi)) # 4
print("odmocnina z 2:", math.sqrt(2)) # 1.414214
print("desiatkový logaritmus zo 1000:", math.log10(1000)) # 3.0
print("dvojkový logaritmus z 1024:", math.log(1024, 2)) # 10.0
print("uhol 180° v radiánoch:", round(math.radians(180), 2)) # 3.14
print("sínus 90°:", math.sin(math.radians(90))) # 1.0

Náhodné čísla

Funkcie z modulu random umožňujú pohodlnú prácu s náhodnými číslami:

  • random(): vráti náhodné reálne číslo v intervale [0.0, 1.0];
  • uniform({a}, {b}): vráti náhodné reálne číslo v uzavretom intervale [a, b] - vrátane ab;
  • randint({a}, {b}): vráti náhodné celé číslo v uzavretom intervale [a, b] - vrátane ab;
  • randrange({posun}, {hranica}, {n}): vráti náhodný násobok celého čísla n menšieho ako hranica, zvýšený o posun;
  • choice({zoznam}): vráti náhodný prvok zo zoznamu alebo tuply.
import random
print("náhodná výška:", round(random.uniform(1.5, 2), 2), "m")
print("náhodná známka:", random.randint(1, 5))
print("náhodné jednociferné nepárne číslo:", random.randrange(1, 10, 2))
print("náhodné raňajky:", random.choice(("chlieb s medom", "ovsená kaša", "volské oko")))

Funkcie pre čas

Funkcie pre prácu s časom z modulu time umožňujú nielen získať systémový čas, ale aj pozastaviť vykonávanie programu a šetriť energiu, či merať trvanie úseku programu:

  • sleep({čas}) / sleep_ms({čas}): vykoná úspornú pauzu trvajúcu zadaný počet sekúnd / milisekúnd;
  • time() / time_ns(): vráti aktuálny čas v podobe uplynutých sekúnd / nanosekúnd (pozri upozornenie nižšie);
  • ticks_ms() / ticks_us(): vráti počet milisekúnd / mikrosekúnd od spustenia;
  • ticks_diff({čas2}, {čas1}): vráti rozdiel časov z funkcií ticks_ms()ticks_us().
from time import time, time_ns, sleep_ms, ticks_us, ticks_diff
čas = time()
print("Už pracujem", čas, "sekúnd, čo je cca", round(čas / 31557600), "rokov!")
čas1 = ticks_us()
print("Po vypísaní tohoto textu si na 10 ms zdriemnem.")
sleep_ms(10)
print("Dobre som si oddýchol a teraz zistím, koľko mi to trvalo aj s výpismi.")
čas2 = ticks_us()
print("Takže mi to trvalo presne", ticks_diff(čas2, čas1) / 1000, "ms")

Funkcie time()time_ns() vracajú čas z interných hodín. V klasickom jazyku Python sa čas ráta od 1. 1. 1970 (UNIX epocha), no MicroPython počíta čas od 1. 1. 2000, a to s presnosťou mikrosekundy. Väčšina MCU však nemá také hodiny, ktoré by bežali aj odpojení, takže pokiaľ ich vyslovene nenastavíme (napríklad z NTP servera), tak sa v skutočnosti dozvieme len čas od zapnutia.

Funkcie pre hardvér a systém

V module machine nájdeme v MicroPython funkcie týkajúce sa hardvéru, pre nás bude potrebných len zopár:

  • unique_id(): vráti identifikátor zariadenia - nie však v podobe textového reťazca, ale poľa bajtov (pozri poznámku nižšie);
  • freq(): vráti frekvenciu procesora (v Hz) alebo ju nastaví na zadaný parameter;
  • reset(): vykoná reštart.

Funkcia unique_id() vracia bajtové pole, ktoré po priamom vypísaní nie je veľmi čitateľné. Ak ho chceme zobraziť v prehľadnom šestnástkovom (hexadecimálnom) formáte, môžeme použiť funkciu hexlify() z modulu binascii a následne výsledok (ktorý je stále bajtovým poľom) ešte previesť na bežný text metódou .decode(): hexlify(unique_id()).decode()

Funkcie textového reťazca

Textový reťazec je objekt, jedná sa teda o objektové funkcie. Tieto funkcie vracajú nový objekt, nemenia pôvodný:

  • .upper() / .lower(): prevod na malé / veľké písmená;
  • .strip(): odstráni „biele znaky“ zo začiatku a konca (trim);
  • .replace({čo}, {čím}): nahradí reťazec iným reťazcom;
  • .split({oddeľovač}): rozdelí reťazec do poľa (zoznamu reťazcov);
  • .join({pole}): spojí prvky poľa (zoznam / tupla) do textového reťazca, oddeľovačom je textový reťazec;
  • .format({parametre}): mocná funkcia na spájanie textov s premennými, viď pyformat.info.

V MicroPythone metódy .upper().lower() fungujú spoľahlivo len na znaky anglickej abecedy (A až Z). Je to kvôli úspore operačnej pamäte - plná podpora všetkých svetových jazykov a ich špeciálnych znakov by zabrala príliš veľa miesta. Ak je potrebné spracovávať slovenskú diakritiku, museli by sme si napísať vlastnú funkciu s nahradzovacou tabuľkou.

Textové reťazce je možné spojiť operátorom + a opakovať operátorom *:

print(3 * "Učiť sa! " + "- povedal súdruh Lenin")

Ďalšie funkcie pre prácu s textovým reťazcom: W3Schools - Python String Methods

Formátované reťazce (f-string)

Pre formátovaný výstup je možné využívať takzvaný f-string, ktorý je približne 2× rýchlejší ako funkcia format() a 3× rýchlejší ako spájanie textov pomocou operátora +. Pre tento špeciálny textový reťazec platí:

  • začína sa písmenom f bezprostredne pred úvodnými úvodzovkami;
  • reťazec obsahuje bežný text, do ktorého sa vkladajú dynamické časti;
  • tieto dynamické časti (výrazy) sa zapisujú v zložených zátvorkách {výraz};
  • v každom výraze sa môžu nachádzať premenné, konštanty, operátory i volania funkcií;
  • za každým výrazom sa môže nachádzať dvojbodka, po ktorej nasledujú špeciálne formátovacie symboly, ktoré definujú výstupný formát výrazu, používa sa teda zápis v tvare {výraz:symboly}.

Užitočné formátovacie symboly pre f-string.format():

  • :.g - všeobecné číslo (general) - zbytočné nuly nepíše,
    • :.{p}g - zaokrúhlené na p platných číslic;
  • :.{m}f - reálne číslo (float) zaokrúhlené na m desatinných miest - tie vypíše, ak aj sú 0,
    • :{d}.{m}f - doplnené zľava medzerami do dĺžky d znakov,
    • :0{d}.{m}f - doplnené zľava nulami do dĺžky d znakov;
  • :x - číslo v 16-kovej sústave malými písmenami,
    • je možné použiť :X pre veľké písmená a :b pre 2-kovú sústavu,
    • :0{d}x - doplnené zľava nulami do dĺžky d znakov,
    • :#0{d}x - doplnené predponou (0x) a nulami - pozor, do dĺžky zaráta aj predponu;
  • :_ - v čísle použije symbol _ na oddelenie vyšších rádov;
  • :<{d} - výpis bude vľavo a sprava ho doplní medzerami do dĺžky d znakov,
    • :{z}<{d} - namiesto medzery dopĺňa znakom z;
  • :>{d} - výpis zarovná vpravo - zľava ho doplní medzerami do dĺžky d znakov,
    • :{z}>{d} - namiesto medzery dopĺňa znakom z;
  • :^{d} - výpis vycentruje - zľava i sprava ho doplní medzerami do dĺžky d znakov,
    • :{z}^{d} - namiesto medzery dopĺňa znakom z;

Názorné ukážky:

print(f"číslo 100/2 bez definovania formátu: {100/2}") # 50.0
print(f"číslo 100/2 vo všeobecnom tvare: {100/2:g}") # 50

print(f"\nčíslo 7/4 na nanajvýš 4 platné číslice: {7/4:.4g}") # 1.75
print(f"číslo 7/4 na nanajvýš 2 platné číslice: {7/4:.2g}") # 1.8
print(f"číslo 7/4 na 3 desatinné miesta: {7/4:.3f}") # 1.750
print(f"číslo 350 na 2 miesta, doplnené nulami do dĺžky 7: {350:07.2f}") # 0350.00

print(f"\nčíslo 250 v 16-kovej sústave malými písmenami: {250:x}") # fa
print(f"číslo 250 v 16-kovej sústave malými písmenami s predponou: {250:#x}") # 0xfa
print(f"číslo 250 v 16-kovej sústave veľkými písmenami s predponou: {250:#X}") # 0XFA
print(f"číslo 250 v 16-kovej sústave, doplnené nulami a predponou na 6 miest: {250:#06x}") # 0x00fa

print(f"\nčíslo 14 v 2-kovej sústave s predponou: {14:#b}") # 0b1110
print(f"\nčíslo 14 v 2-kovej sústave, doplnené nulami na 8 miest: {14:08 b}") # 00001110

# táto časť nemusí fungovať v starších verziách MicroPython
print(f"\npočet možných IPv4 adries: {2**32:_}") # 4_294_967_296
print(f"číslo 123 v 2-kovej sústave: {123:09_b}") # 0111_1011
print(f"číslo 12345678 v 16-kovej sústave: {12345678:09_X}") # 00BC_614E
print(f"reálne číslo s oddelením tisícov: {1999.9:_.2f}") # Python 1_999.90 / MicroPython zatiaľ ignoruje podčiarkovník

print("\n{:_^28}\n{:>12}: Dušan\n{:>12}: Zervan".format(" OSOBA ", "meno", "priezvisko"))
# __________ OSOBA ___________
#         meno: Dušan
#   priezvisko: Zervan

Automatické zarovnávanie v MicroPython verzii formátovania nepočíta správne šírku slovenských znakov (v rámci zjednodušenia počíta bajty namiesto písmen). Ak chceme precízne zarovnať tabuľku s diakritikou, musíme si počet medzier vypočítať pomocou funkcie len(), ktorá znaky počíta správne.

Kolekcie údajov

Python rozlišuje 3 typy „polí“:

  • list (zoznam), zátvorky [] - ako pole v C++, teda prvky majú svoje poradie, môžu sa opakovať a môžu sa meniť ich hodnoty;
  • tuple („tupla“), zátvorky () - ako list, ale prvky sa nemôžu meniť, ide teda o pevne daný nemenný zoznam, rýchlejšie spracovávaný;
  • set (množina), zátvorky {} - prvky nemajú svoje poradie, nie je ich možné indexovať, nemôžu sa opakovať (žiadne duplikáty).

K nim sa pridáva ešte typ dictionary (slovník), ktorý organizuje údaje v pároch kľúč : hodnota rovnako ako JSON.

Rozdiely zachytáva prehľadová tabuľka:

typ kolekcie údajov zátvorky poradie duplikáty zmena
list (zoznam) [ ] ✓ áno ✓ áno ✓ áno
tuple (tupla) ( ) ✓ áno ✓ áno ✕ nie
set (množina) { } ✕ nie ✕ nie ✓ áno
dict (slovník) { : } ✕ nie nie kľúče / áno údaje ✓ áno

Najbohatšiu ponuku funkcií majú zoznamy, niektoré funkcie sú dostupné aj pre tuple a množiny.

Všetky kolekcie údajov je možné priamo vypísať, no MicroPython v priamom výpise nedokáže zobraziť slovenské znaky (mäkčene a dĺžne), vhod preto príde metóda .join() pre textový reťazec:

ovocie = {"čerešňa", "višňa", "hruška", "marhuľa"}
print(ovocie)
# {'hru\u0161ka', 'marhu\u013ea', '\u010dere\u0161\u0148a', 'vi\u0161\u0148a'}
print(", ".join(ovocie))
# hruška, marhuľa, čerešňa, višňa

Zoznam (list)

Zoznam v Python sa správa podobne ako pole v C++, prvky sa číslujú od 0. Pre vytvorenie zoznamu môžeme do hranatých zátvoriek zapísať prvky oddelené čiarkou:

  • [{prvok1}, {prvok2}, …]

Indexovať prvky je možné aj od konca (záporným číslom) a prvky nemusia byť rovnakého dátového typu:

čísla = ["dvanásť", 15, "štyri", 3.14]
print(čísla[-1]) # 3.14

Zoznam môžeme vytvoriť aj explicitnou konverziou:

  • list({objekt})

Pokiaľ je objektom tupla alebo množina, vytvorí sa jeho dvojička v podobe zoznamu, no pozor na text - vytvorí sa zoznam z jednotlivých písmen!

Prvky zoznamu je možné uložiť do premenných - musí ich však byť rovnaký počet:

  • {premenná 1}, {premenná 2},= {zoznam}
mince = [1, 2, 5, 10]
a, b, c, d = mince

Zistenie výskytu prvku v poli - vráti bool:

  • {hľadaný prvok} in {pole}
print(15 in čísla)

Delenie poľa od uvedeného začiatočného prvku (vrátane) po konečný prvok (bez neho):

  • {pole}[{začiatok}:{koniec}]
print("druhé a tretie číslo:", čísla[1 : 3])

Pre začiatok a koniec platí:

  • môžu to byť aj záporné hodnoty (od konca);
  • ak neuvedieme začiatok, začne prvým prvkom (č. 0);
  • ak neuvedieme koniec, skončí posledným prvkom.

Rovnakým spôsobom môžeme aj prepisovať prvky na uvedenej pozícii:

  • ak uvedieme prvky v rovnakom počte, prepíšu sa;
  • ak ich bude viac, nadbytočné sa vložia za prepísané.

Objektové funkcie (metódy) zoznamu

Rovnako ako textové reťazce, aj zoznamy je možné spojiť operátorom + a opakovať operátorom *. A rovnako aj zoznam predstavuje objekt, vloženie prvku do zoznamu je možné vykonať objektovými funkciami:

  • .append({prvok}) - na koniec zoznamu;
  • .insert({pozícia}, {prvok}) - na uvedenú pozíciu.

Odobratie prvku zo zoznamu:

  • .pop({pozícia}) - z uvedenej pozície (ak neuvedieme, z konca);
  • .remove({hodnota}) - prvý prvok obsahujúci uvedenú hodnotu;
  • .clear() - vyprázdni celý zoznam (zostane prázdny).

Ďalšie zaujímavé funkcie zoznamu:

  • .reverse() - v zozname otočí poradie prvkov;
  • .sort() - abecedne / číselne zoradí zoznam,
    • voliteľný parameter reverse zariadi opačné poradie,
    • voliteľný parameter key odkazuje na funkciu, podľa ktorej má zoraďovať;
  • .count({hodnota}) - vráti počet prvkov s uvedenou hodnotou.
zvierata = ["lev", "tiger", "slon", "panda", "zebra", "surikata", "adax"]
print("zvieratá v pôvodnom poradí:", ", ".join(zvierata))
# lev, tiger, slon, panda, zebra, surikata, adax

zvierata.sort()
print("zvieratá v abecednom poradí:", ", ".join(zvierata))
# adax, lev, panda, slon, surikata, tiger, zebra

zvierata.sort(key = len) # použije len dĺžku, rovnako dlhé názvy budú v MicroPython nepredvídateľne
print("zvieratá v poradí podľa dĺžky slova:", ", ".join(zvierata))
# lev, slon, adax, zebra, tiger, panda, surikata

# funkcia lambda bude vysvetlená v samostatnej sekcii nižšie
zvierata.sort(key = lambda s: (len(s), s)) # ak bude rovnaká dĺžka, pozrie na obsah
print("zvieratá v poradí podľa dĺžky slova a názvu:", ", ".join(zvierata))
# lev, adax, slon, panda, tiger, zebra, surikata

Funkcia .sort(), pokiaľ ju neprepojíme na zoraďovaciu funkciu, vie správne abecedne zoradiť len znaky anglickej abecedy (písmená A až Z, číslice a iné ASCII symboly). V jazyku Python je možné využiť modul locale a funkciu strxfrm(), ktorá dokáže správne usporiadať aj slovenské písmená no v MicroPythone tento modul implementovaný nie je kvôli úspore operačnej pamäte. Ak je potrebné spracovávať slovenskú diakritiku, museli by sme si napísať vlastnú funkciu s nahradzovacou tabuľkou.

Viac funkcií pre prácu so zoznamom: W3Schools - Python List Methods

Tupla (tuple): zátvorky ()

Tupla (alebo tiež „n-tica“) je podobná kolekcia údajov ako zoznam, ale prvky sa nemôžu meniť, ide teda o pevne daný - nemenný zoznam, rýchlejšie spracovávaný. Pre vytvorenie tuply môžeme do obyčajných zátvoriek zapísať prvky oddelené čiarkou. Ak má tupla len jediný prvok, musí končiť čiarkou:

  • ({prvok1}, {prvok2}, …)
  • ({prvok},)
ovocie = ("jablko", "hruška", "slivka")
strom = ("jabloň", )
print(strom[0], "urodí", ovocie[0])
# jabloň urodí jablko

Tuplu môžeme vytvoriť aj explicitnou konverziou:

  • tuple({objekt})

Pokiaľ je objektom zoznam alebo množina, vytvorí sa nemenná kópia, no pozor na text - vytvorí sa tupla z jednotlivých písmen!

prvočísla = [2, 3, 5, 7, 11, 13]
print(prvočísla, tuple(prvočísla))
# [2, 3, 5, 7, 11, 13] (2, 3, 5, 7, 11, 13)

písmená = tuple("abeceda")
print(písmená)
# ('a', 'b', 'e', 'c', 'e', 'd', 'a')

tupla1 = ("abeceda", )
print(tupla1)
# ('abeceda',)

Tupla poskytuje tie isté operácie a funkcie, ako majú zoznamy - s výnimkou tých, ktoré menia hodnoty.

Viac funkcií pre tuple: W3Schools - Python Tuple Methods

Funkcie pre prácu s poľom (zoznam, tupla)

Okrem objektových funkcií (metód) zoznamu a tuply je dobré poznať aj samostatné funkcie, ktoré s poľami pracujú. S prvkami je možné prevádzať základnú štatistiku:

  • len({pole}): zistí počet prvkov;
  • sum({pole}): spočíta súčet hodnôt v poli čísel;
  • min({pole}): zistí najnižšiu hodnotu v poli;
  • max({pole}): zistí najvyššiu hodnotu v poli.

A tiež je možné vytvoriť zoradenú kópiu v novom zozname:

  • sorted({pole}) - vráti abecedne / číselne zoradený zoznam,
    • pôvodné pole (zoznam / tupla) zostane bez zmeny,
    • voliteľný parameter reverse zariadi opačné poradie,
    • voliteľný parameter key odkazuje na funkciu, podľa ktorej má zoraďovať.

Funkcie sorted().sort() sa na prvý pohľad zdajú byť rovnaké, no sú medzi nimi rozdiely.
Funkcia .sort() mení zoznam samotný, nevytvára nový, z čoho vyplýva, že:
- je k dispozícii len pre zoznam a nie pre tuplu;
- je pamäťovo úspornejšia, a teda vhodnejšia pre MCU.

Množina (set): zátvorky {}

Na rozdiel od zoznamu a tuply, prvky množiny nemajú svoje poradie, teda nie je ich možné indexovať a nemôžu sa opakovať (žiadne duplikáty). Pre vytvorenie množiny môžeme do zložených zátvoriek zapísať prvky oddelené čiarkou. Pokiaľ potrebujeme vytvoriť prázdnu množinu, nie je to možné rovnakým spôsobom (vytvoril by sa prázdny slovník, pozri nižšie ďalšiu sekciu), nutné je použiť príkaz set():

  • {{prvok1}, {prvok2}, …}
  • prazdna_mnozina = set()

Do množiny môžeme prvok pridávať:

  • .add({prvok}) - pridá jeden prvok;
  • .update({pole}) - pridá celú množinu / zoznam / tuplu.

A tiež odoberať:

  • .remove({hodnota}) - ak prvok s uvedenou hodnotou neexistuje, spôsobí chybu;
  • .discard({hodnota}) - ak neexistuje, ignoruje.
osoby = {"Eva", "Gita", "Hana"}
print(osoby)
# {'Eva', 'Gita', 'Hana'} - poradie zachová, ale negarantuje

osoby.add("Jozef")
print(osoby)
# {'Hana', 'Jozef', 'Gita', 'Eva'} - poradie je už nepredvídateľné

osoby.update(("Hana", "Fero", "Adam", "Cyril")) # ďalšiu Hanu bude ignorovať
print(osoby)
# {'Cyril', 'Jozef', 'Fero', 'Hana', 'Adam', 'Gita', 'Eva'}

osoby.discard("Jozef")
print(osoby)
# {'Cyril', 'Fero', 'Hana', 'Adam', 'Gita', 'Eva'}

Viac funkcií pre množinu: W3Schools - Python Set Methods

Slovník (dictionary)

Slovník v Pythone organizuje údaje v pároch kľúč:hodnota rovnako ako JSON: {slovník} = { {kľúč}: {hodnota},}
Využiť ho možno podobne ako štruktúry v C++:

osoba = {
    "meno": "Fero",
    "priezvisko": "Kováč",
    "vek": 12,
    "bicykel": "horský"   
}
print(osoba["meno"])

Ide o podobnú formu ako množina - neumožňuje duplikáty, čiže dva údaje nemôžu mať rovnaký kľúč. Prístup k jednotlivým prvkom je podobný ako pri zozname, no namiesto indexu uvádzame kľúč. Pokiaľ kľúč neexistuje, program skončí s chybou, preto je vhodnejšie využiť funkciu get():

  • .get({kľúč}, {náhradná hodnota}) - vráti hodnotu s uvedeným kľúčom a ak neexistuje, vráti náhradnú hodnotu (ak nie je uvedená, tak None).

Kľúčom v slovníku môže byť len nemenný typ (číslo, text, tupla), zoznam ako kľúč použiť nemožno.

Pre prechádzanie slovníkom (nižšie uvedeným cyklom for) sú užitočné tieto funkcie:

  • .keys() - vráti pohľad na zoznam kľúčov;
  • .values() - vráti pohľad na zoznam hodnôt;
  • .items() - vráti pohľad na slovník v podobe dvojíc (kľúč, hodnota).

Pojem „pohľad“ vyjadruje skutočnosť, že prepojenie je „živé“, teda pri zmene údajov v pôvodnom slovníku sa zmenia aj údaje cez pohľad - a to aj v prípade, že sme ho priradili do premennej. Funkcie je niekedy možné využiť aj bez cyklu, napríklad:

print("O osobe evidujeme tieto údaje:", ", ".join(osoba.keys()))
# O osobe evidujeme tieto údaje: meno, priezvisko, vek, bicykel

Prehľad ďalších funkcií pre slovník: W3Schools - Python Dictionary Methods

Podmienky a cykly

Podmienený príkaz: if - elif - else

Je veľmi podobný C++, má mierne odlišnú syntax a je ho nutné správne odsadiť!

  • if {podmienka 1}:
    • {príkazy pri splnení podmienky 1}
  • elif {podmienka 2}:
    • {príkazy pri splnení podmienky 2}
  • else:
    • {príkazy pri nesplnení žiadnej podmienky}

Časť elif sa môže opakovať viackrát, no nie je povinná ani časť elif, ani časť else. Môžeme samozrejme využívať aj vnorené podmienky, opäť treba správne odsadiť.

V jazyku C++ je k dispozícii kondicionál, ktorý predstavuje skrátenú formu if-else v jednom výraze. V jazyku Python takáto minimalistická verzia nie je, no skrátený if-else je možné využiť v tvare:

  • {hodnota1} if {podmienka} else {hodnota2}

Takúto skrátenú formu je možné aj zreťaziť:

počet = const(3)
print(počet, "jablko" if počet == 1 else "jablká" if počet < 5 else "jabĺk")

Cyklus podľa podmienky: while

Funguje rovnako ako v C++, no je mierne odlišná syntax:

  • while {podmienka}:
    • {príkazy tela cyklu}
  • else:
    • {príkazy po riadnom ukončení}

Tiež je možné použiť break (predčasné ukončenie cyklu) a continue (predčasný prechod na ďalšiu iteráciu) - rovnako ako v C++. Navyše je k dispozícii výraz pass, ktorý nerobí nič a slúži na formálne vyplnenie prázdneho tela cyklu. Cyklus môže obsahovať aj nepovinnú časť else - vykoná sa pri riadnom ukončení po nesplnení podmienky (teda nie po ukončení cez break):

from time import ticks_ms, ticks_diff
print("veľmi hlúpym spôsobom počkám 2 sekundy...", end = "")
t1 = ticks_ms()
while ticks_diff(ticks_ms(), t1) < 2000:
    pass # prázdne telo = nerobí „nič“, ale ani nešetrí batériu - toto sa nerobí!
else:
    print(" OK")

Cyklus pre zoznam a interval hodnôt: for

Cyklus for sa odlišuje sa od C++, používa sa na prechod sekvenciou (zoznam, tupla, textový reťazec):

  • for {premenná} in {zoznam}:
    • {príkazy tela cyklu}
  • else:
    • {príkazy po riadnom ukončení}

Cyklus teda vytvorí lokálnu premennú a bude v nej iterovať hodnoty z uvedenej sekvencie. Výrazy break, continueelse je možné používať rovnako ako v cykle while:

for x in "Písmenká":
    print(x, end = ", ")
else:
    print("\b\b.")
# napíše: P, í, s, m, e, n, k, á.

Tip: Pokiaľ v cykle for nie je premenná iterácie potrebná (ide len o opakovanie príkazov), zvykne sa nazvať „_“.

Môžeme tiež prechádzať slovníkom, využijúc jeho funkcie keys(), values(), či items() - pozri vyššie pri opise slovníka:

osoba = {"meno": "Fero", "priezvisko": "Kováč", "vek": 12, "bicykel": "horský"}
print("O osobe evidujeme tieto údaje:")
for názov, hodnota in osoba.items():
    print(f"- {názov}: {hodnota}")

Niekedy pri prechádzaní zoznamom potrebujeme nielen hodnotu prvku, ale aj jeho poradové číslo (index). Na to slúži funkcia enumerate(), ktorá pre každú hodnotu poľa vráti dvojicu (index, hodnota):

  • enumerate({pole}) - generuje dvojice s indexom a hodnotou, pričom index bude od 0;
  • enumerate({pole}, {číslo}) - index bude od zadaného čísla.
klienti = ("Ján", "Mária", "Peter")
for cislo, klient in enumerate(klienti, 100):
    print(f"{cislo}: {klient}")
# 100: Ján
# 101: Mária
# 102: Peter

Vlastné funkcie a generátory

Cyklus for a generátory

Pre vytvorenie zoznamu čísel môžeme využiť funkciu range(). Táto funkcia ani pri veľkom počte čísel nemíňa zbytočne pamäť, pretože čísla sa vytvárajú postupne v každej iterácii a nie vopred - v tomto sa podobá na generátor. Môžeme ju používať v rôznych variantoch:

  • range({počet}) - od čísla 0 po {počet- 1 };
  • range({začiatok}, {koniec}) - od začiatku (vrátane) po koniec (bez);
  • range({začiatok}, {koniec}, {krok}) - s uvedeným krokom.
for i in range(10, 30, 5):
    print(i, end = " ")
# napíše: 10 15 20 25

Môžeme si tiež vytvoriť vlastný generátor - generovanie cez vlastnú funkciu a yield je opísané v ďalšej sekcii. Zaujímavé je však kompaktné vytváranie zoznamov cez cyklus for - označuje sa list comprehension a môže mať tvar:

  • [{výraz} for {položka} in {zoznam}]
  • [{výraz} for {položka} in {zoznam} if {podmienka}]

Napríklad zoznam s mocninami čísla 2 môžeme vytvoriť klasickou iteráciou, i kompaktne:

mocniny2 = []
for i in range(11):
    mocniny2.append(2**i)
print(mocniny2)
# [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]

mocniny2 = [2**i for i in range(11)]
print(mocniny2)
# [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]

Oba spôsoby vytvoria v operačnej pamäti zoznam, čo by v inej situácii s vysokým počtom veľkých prvkov zoznamu spôsobilo značné pamäťové nároky.

Viac informácií o kompaktnom vytváraní zoznamov: W3Schools - Python List Comprehension

Oveľa efektívnejší je generátor - ten nevytvára v pamäti žiadne pole, ale v každej iterácii odovzdá ďalšiu hodnotu. Pozor, generátor je jednorazový - po prečítaní hodnôt sa „vyčerpá“ a pre opätovné použitie ho treba vytvoriť znova.

# generátor - pamäťovo úsporné riešenie, každý prvok sa priamo vypíše:
mocniny2g = (2**i for i in range(11))

for m in mocniny2g: print(m, end = " ")
else: print()
# 1 2 4 8 16 32 64 128 256 512 1024

for m in mocniny2g: print(m, end = " ")
else: print()
# už nenapíše nič - hodnoty sa vybrali všetky

Ďalšia ukážka lepšie demonštruje výhodu generátora - spočíta druhé mocniny celých čísel od 1 po 1476 (hodnota je zvolená tak, aby výsledok ešte vošiel do 30 bitov):

import gc

pocet = const(1477)

gc.collect() # uvoľní pamäť
m1 = gc.mem_free()
# generátor nevytvára zoznam v pamäti
mocniny_gen = (i**2 for i in range(pocet))
print(sum(mocniny_gen), ": ", m1 - gc.mem_free(), " B", sep = "")
# 1072948926: 112 B

gc.collect() # uvoľní pamäť
m1 = gc.mem_free()
# vytvorenie zoznamu v pamäti
mocniny = [i**2 for i in range(pocet)]
print(sum(mocniny), ": ", m1 - gc.mem_free(), " B", sep = "")
# 1072948926: 8240 B

Definícia funkcie

Pri definícii funkcie neuvádzame ani návratovú hodnotu, ani typ parametrov, inak je podobná C++:

  • def {názov} ({parametre}):
    • {príkazy tela funkcie}

Podobne ako v C++, funkcia nemusí mať parametre, no zátvorky musia byť uvedené. Parametre môžu byť nepovinné, teda mať predvolenú hodnotu. Výsledok (jedinú návratovú hodnotu) je možné odovzdať a funkciu ukončiť cez return. Volanie (použitie) funkcie je podobné C++.

Funkcia môže používať globálne premenné, v tele funkcie ich je treba deklarovať výrazom global:

def ZvysPocet(pridaj = 1):
    global pocet
    pocet += pridaj

pocet = 100
ZvysPocet(5)
print(pocet) # 105

Špecialitou je možnosť vrátiť viac výsledkov cez yield - ide o generátor, funkcia sa neukončí:

def Mocniny(zaklad, pocet):
    m = 1
    for _ in range(pocet):
        m = m * zaklad
        yield m

for m in Mocniny(2, 10):
    print(m, end = " ")

Obrovskou výhodou generátorov na mikrokontroléroch je šetrenie operačnej pamäte, keďže hodnoty sa generujú postupne a nemusia sa držať naraz v pamäti ako pri zozname. Generátory poskytujú podstatne viac možností: W3Schools - Python Generators

Parametre funkcie

Pri volaní funkcie je možné vynechať ktorýkoľvek nepovinný parameter a menovite nastaviť potrebné parametre v ľubovoľnom poradí:

def AbsPriemer(sucet, pocet = 1):
    if pocet: return abs(sucet / pocet)
    return None

print(AbsPriemer(pocet = 7, sucet = -22))

Funkcia si môže vynútiť použitie pomenovaných parametrov alebo pozičných parametrov, či rôzne kombinácie: W3Schools - Python Function Arguments

Funkcia môže mať aj viacnásobný parameter - ak je pred názvom parametra znak *, všetky nadbytočné pozičné parametre sa zabalia do tuply s uvedeným názvom:

def Pozdrav(pozdrav, *mena):
    for meno in mena:
        print(f"{pozdrav} {meno}!")
        
Pozdrav("Nazdar", "Fero", "Adam", "Cyril")
# Nazdar Fero!
# Nazdar Adam!
# Nazdar Cyril!

Pokročilý tip: Ak sú pred názvom parametra dva znaky **, používa sa ako slovník s názvom parametra pri volaní:

def AbsPriemer(**parametre):
    sucet = pocet = 0
    for p in parametre:
        if p == "sucet":
            sucet = parametre[p]
        elif p == "pocet":
            pocet = parametre[p]
        else:
            print(f"neznámy parameter: {p}")
    if pocet: return abs(sucet / pocet)
    return None

print(AbsPriemer(pocet = 7, sucet = -22, pokus = 3))

Viac príkladov na viacnásobné parametre: W3Schools - Python *args and **kwargs

Pokročilé prostriedky

Lambda

Lambda je jednoduchá anonymná funkcia:

  • lambda {parametre}: {výraz s parametrami}

Lambdu môžeme formálne priradiť aj do premennej, vtedy sa z nej v podstate stane pomenovaná funkcia:

SucetCisel = lambda x: (1 + x) * x // 2
print(SucetCisel(10)) # 55

Užitočnejšia je však v situácii, keď potrebujeme poslať funkciu ako parameter inej funkcie:

def UpravZoznam(zoznam, funkcia):
    for i in range(len(zoznam)):
        zoznam[i] = funkcia(zoznam[i])

cisla = [2, 3, 5, 7, 11, 13]
UpravZoznam(cisla, lambda x: x**2)
print(cisla)
# [4, 9, 25, 49, 121, 169]

Ešte zaujímavejšie (a pokročilejšie) využitie: W3Schools - Python Lambda

Mapovanie cez funkciu

V súvislosti s funkciami je veľmi užitočné poznať mapovaciu funkciu map(), ktorá umožňuje postupne volať zadanú funkciu pre každý z parametrov:

  • map({funkcia}, {zoznam}) - pre každý prvok zoznamu / tuply zavolá uvedenú funkciu, výsledkom nie je priamo zoznam, ale objekt mapy, ktorý je možné explicitne previesť na zoznam, či tuplu.

Užitočné to môže byť aj na konverziu typu - napríklad textová funkcia join() vyžaduje textové parametre a nefungovala by s číslami:

cisla = (2, 3, 5, 7, 11, 13)
print("zoznam čísel ako texty:", list(map(str, cisla)))
print("zoznam čísel spojený:", ", ".join(map(str, cisla)))
# zoznam čísel ako text: ['2', '3', '5', '7', '11', '13']
# zoznam čísel spojený: 2, 3, 5, 7, 11, 13

Viac príkladov a možností: W3Schools - Python map() Function

Zachytávanie výnimiek (chýb)

Princíp je rovnaký ako v C++, mierne odlišná syntax:

  • try:
    • {príkazy, v ktorých hrozí chyba}
  • except {typ chyby}:
    • {príkazy, ak nastala konkrétna chyba}
  • except:
    • {príkazy, ak nastala akákoľvek chyba}
  • else:
    • {príkazy, ak nenastala chyba}
  • finally:
    • {príkazy spustené vždy na záver}

Časti elsefinally nie sú povinné. Za výrazom except môže byť uvedený typ zachytávanej chyby (všeobecne akákoľvek chyba sa označuje Exception) a môže sa opakovať s rôznymi typmi chýb. Prípadne je možné zachytiť typ chyby a uložiť ho do premennej pre výpis:

try:
    print(10/0)
except Exception as e:
    print("Nastala chyba:", e)

Podrobnejšie informácie: W3Schools - Python Try Except

Inštalácia rozširujúcich modulov

Rozširujúce moduly (knižnice) môžeme nahrať ručne do priečinku /lib alebo do aktuálneho / koreňového priečinku. Ak je modul prítomný na viacerých miestach, uplatní sa priorita definovaná v sys.path.

Predvolená priorita v MicroPython je:

  1. aktuálny priečinok;
  2. zabudovaný modul (frozen);
  3. priečinok /lib.

V školskej inštalácii je priorita zmenená nasledovne:

  1. aktuálny priečinok;
  2. priečinok /lib;
  3. zabudovaný modul (frozen).

Pokiaľ má mikrokontrolér aktívne pripojenie do internetu (napríklad cez Wi-Fi), je možné automatizovane stiahnuť modul do /lib, stačí poznať jeho názov:

import mip
mip.install("názov modulu")