Hlavní navigace

Nejdůležitější novinka v Pythonu 3.10: strukturální pattern matching

30. 8. 2022
Doba čtení: 33 minut

Sdílet

 Autor: Depositphotos
V relativně nedávno vydaném Pythonu verze 3.10 se objevila dlouho očekávaná novinka. Jedná se o takzvaný strukturální pattern matching, kterému je tento článek věnovaný. Ukážeme si i další jazyky, v nichž se používá.

Obsah

1. Nejdůležitější novinka v Pythonu 3.10: strukturální pattern matching

2. SNOBOL – jazyk založený na práci se vzorky

3. Strukturální pattern matching v programovacím jazyku ML

4. Strukturální pattern matching v Rustu

5. Strukturální pattern jazyk v jazyku Coconut: první krok k Pythonu

6. Strukturální pattern matching v programovacím jazyku Python verze 3.10

7. Překlad a instalace Pythonu 3.10

8. Nejjednodušší příklad – rozeskok

9. Větší množství hodnot v každé větvi

10. Zachycení hodnoty proměnné v rozhodovací větvi

11. Podmínka v rozhodovací větvi, větev „else“

12. Test obsahu n-tic a seznamů

13. n-tice a podmínky pro hodnoty prvků

14. Zpracování příkazů či strukturovaných textových souborů

15. Proměnné části víceslovních příkazů

16. Vnořené řídicí struktury match

17. Strukturální pattern matching a objekty

18. Příloha: část gramatiky Pythonu s pattern matchingem

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Nejdůležitější novinka v Pythonu 3.10: strukturální pattern matching

V relativně nedávno vydaném Pythonu 3.10 se objevila dlouho očekávaná novinka – takzvaný strukturální pattern matching. Jedná se o novou programovou konstrukci, která vzdáleně připomíná konstrukci switch-case z jazyka C (odkud byla převzata do dalších programovacích jazyků, včetně C++ či Javy). Ovšem strukturální pattern matching nabízí programátorům mnohem více možností než původní jednoduchá konstrukce switch-case. Některé z těchto možností si ukážeme v navazujícím textu a pro úplnost si řekneme, jak a kde se pattern matching vlastně začal v informatice používat. Poměrně zajímavá by pro programátory mohla být i zmínka o jazyce Coconut, v němž se pattern matching objevil již před relativně dlouhou dobou a navíc je tento v mnoha ohledech zajímavý programovací jazyk transpilován do Pythonu.

2. SNOBOL – jazyk založený na práci se vzorky

Prvním významným jazykem s pattern matchingem (kde je dokonce pattern matching ústřední prvek jazyka) je jazyk SNOBOL (StriNg Oriented and symBOlic Language). Bellovy laboratoře jsou v oblasti elektroniky a samozřejmě taktéž informatiky velmi známou institucí. Právě v Bellových laboratořích byl zkonstruován první tranzistor (William Shockley, Gerald Pearson, John Bardeen, Walter Brattain), vytvořena první verze dodnes široce používaného programovacího jazyka C (Dennis Ritchie) a v neposlední řadě taktéž funkční prototyp operačního systému UNIX (Ken Thompson, Dennis Ritchie, Brian Kernighan, Douglas McIlroy, Joe Ossanna a další). Kromě těchto známých projektů, z nichž například vynález tranzistoru byl odměněn Nobelovou cenou, však v Bellových laboratořích vzniklo i poměrně velké množství dalších více či méně úspěšných projektů. Jedním z těchto dnes již poněkud méně známých projektů je i programovací jazyk SNOBOL, který byl v Bellových laboratořích vyvíjen v průběhu let 1962 až 1967. Jeho autory jsou David J. Farber, Ralph E. Griswold a Ivan P. Polonsky.

snobol

Obrázek 1: Učebnice SNOBOLu, kterou napsali mj. i dva z autorů tohoto z dnešního pohledu poněkud zvláštního jazyka.

Syntaxe programovacího jazyka SNOBOL je velmi jednoduchá, protože tento jazyk ve své základní podobě neobsahuje žádné podmíněné příkazy, programové smyčky ani jiné jazykové konstrukce známé z jiných (strukturovaných) jazyků. Namísto toho se struktura programů nejvíce podobá zápisu stavů a přechodů konečného automatu, což je ostatně při zpracovávání řetězců (či jiných dat) jeden ze základních nástrojů prakticky každého programátora. Nejprve si popišme základní syntaxi programovacího jazyka SNOBOL. Na každém programovém řádku se může nacházet až pět bloků, z nichž ovšem žádný není povinný:

  1. návěští (label)
  2. subjekt (subject)
  3. vzor (pattern)
  4. objekt (object)
  5. odkaz(y) na jiné příkazy (goto)

V případě, že se neprovádí vyhledávání řetězců podle zadaného vzoru, ani náhrada části řetězců za jiné řetězce, obsahuje programový řádek pouze trojici bloků:

  1. návěští (label)
  2. příkaz (ostatement)
  3. odkaz(y) na jiné příkazy (goto)

Nepovinné návěští (to je použito jako cíl skoku nebo skoků) je od příkazu odděleno mezerou, podmíněné či nepodmíněné odkazy jsou od příkazu odděleny dvojtečkou. V programech je možné používat běžné aritmetické výrazy (jejich části se od sebe musí oddělovat mezerou), proměnné (s automatickými konverzemi mezi celočíselným typem, reálným číslem a řetězcem) i některé speciální proměnné, jejichž použití ve skutečnosti vede k zavolání nějaké funkce operačního systému. Mezi speciální proměnné patří OUTPUT (standardní výstup) a INPUT (standardní vstup). Přiřazení nějaké hodnoty do proměnné OUTPUT způsobí její tisk na standardní výstup, naopak čtení z proměnné INPUT odpovídá načtení řetězce ze standardního vstupu (pojmy „standardní vstup“ a „standardní výstup“ nemusí odpovídat tomu, co si pod těmito souslovími představujeme v Unixu, neboť SNOBOL se používal a dokonce někdy dodnes používá na velkém množství různých platforem, které mají vstupně/výstupní část řešenou odlišným způsobem).

Příklad zápisu programu ve SNOBOLu:

BEGIN LINE = INPUT
      &ANCHOR = 1
      NUM     = SPAN("0123456789")
      TERM    = ANY("XYZ")
      OP      = ANY("+-*/")
      EXPR    = TERM | *EXPR OP TERM
      LINE    EXPR "=" NUM            :S(END)
      OUTPUT = LINE                   :(BEGIN)
END
Poznámka: samotný SNOBOL byl sice používán, a to poměrně intenzivně (typicky nikoli profesionálními programátory, ale odborníky z jiných oblastí, kteří potřebují zpracovávat textová data), ovšem z dnešního pohledu je mnohem důležitější, že SNOBOL ovlivnil sémantiku dalšího programovacího jazyka. Jedná se o jazyk AWK, jenž je používán dodnes, typicky pro tvorbu pomocných skriptů pro zpracování či transformaci textových dat. Mnoho algoritmů lze s využitím AWK realizovat velmi efektivním způsobem, a to právě díky (prakticky implicitnímu) pattern matchingu (a opět – AWK je mnohdy používán tou samou skupinou uživatelů, jako tomu bylo v případě SNOBOLu).
snobol

Obrázek 2: Další učebnice SNOBOLu, opět napsaná jedním z autorů tohoto jazyka.

3. Strukturální pattern matching v programovacím jazyku ML

Koncept pattern matchingu, který byl představen v šedesátých letech ve SNOBOLu, by dále rozvinut v programovacím jazyku ML, odkud se dále rozšířil do dalších programovacích jazyků: na přímé linii především do jazyka Caml a OCamp (a vlastně i do F#, který je v přímé linii ML) a na nepřímé linii do Rustu, Pythonu 3.10 atd. Podívejme se nyní ve stručnosti na praktické použití této programovací techniky v ML.

Obecný zápis funkce s pattern matchingem vypadá následovně:

fun <jméno> <vzorek> = <tělo/výraz>

Většinou se však v praxi používá větší množství vzorků, které jsou spojeny znakem „|“ neboli „or“. V takovém případě jsou vzorky postupně procházeny a pokud budou vyhovovat předaným datům, bude příslušná větev funkce vykonána:

fun <jméno> <vzorek> = <tělo/výraz>
 |  <jméno> <vzorek> = <tělo/výraz>
 |  <jméno> <vzorek> = <tělo/výraz>
 |  <jméno> <vzorek> = <tělo/výraz>

Naproti tomu příkladem běžné funkce bez pattern matchingu je funkce length v této podobě (pro seznamy):

fun length(x) = if null(x) then 0
                else 1 + length(tl(x));

V této funkci se provede vždy jedna z větví na základě zapsané podmínky (case analysis). Ovšem díky existenci pattern matchingu lze stejnou funkci zapsat odlišně – a to vyjmenováním invariantů:

fun length([]) = 0
  | length(a::x) = 1 + length(x)

Tímto zápisem vlastně popisujeme, že pro prázdný seznam je délka rovna nule a pro seznam skládající se z hlavy a těla je délka tohoto seznamu rovna délce těla + 1 (což je elegantní rekurzivní vyjádření nějaké vlastnosti, s níž se velmi často setkáme v matematice).

Podobným způsobem lze zapsat výpočet Fibonacciho řady, tentokrát se třemi větvemi:

fun fib 0 = 0
  | fib 1 = 1
  | fib n = fib (n - 1) + fib (n - 2);
 
 
fib 0;
fib 1;
fib 10;
Poznámka: podle mého názoru se jedná o zcela nejpřímější způsob zápisu, jaký je možný.

Prakticky stejným způsobem lze zapsat výpočet v jazyku F#, který je, jak již ostatně víme, potomkem ML:

let rec fib n =
    match n with
    | 0 -> 0
    | 1 -> 1
    | n -> fib(n-1) + fib(n-2)
 
 
printfn "Fib 0=%i" (fib 0)
printfn "Fib 1=%i" (fib 1)
printfn "Fib 10=%i" (fib 10)
Poznámka: až na nepatrné syntaktické odlišnosti (a jednu odlišnost v sémantice – explicitní vyjádření rekurzivní funkce), je tento zápis totožný.

Vraťme se k jazyku ML. Existují i zajímavější vzorky. Například funkci car vracející hlavu seznamu (první prvek) lze zapsat opět vyjmenováním invarianty:

fun car(x::y) = x;

V tomto případě ML rozezná, že nejsou pokryty všechny možné varianty a vypíše varování:

> val car = fn: ∀ 'a . 'a list → 'a;
 
WARN: Pattern matching is not exhaustive.
Poznámka: nejprve si povšimněte typu funkce (generická funkce, ovšem hlídající správné typy) a taktéž varování, ke kterému se hned dostaneme (a které není radno ignorovat).

To, zda tato funkce pracuje podle očekávání, si můžeme snadno ověřit:

car([1,2,3]);
> val it = 1: int;
 
car(["foo", "bar"]);
> val it = "foo": string;
 
car(["first"]);
> val it = "first": string;
 
car([]);
Uncaught SML exception: Match

Obrázek 3: Otestování výše uvedené funkce ve webovém prostředí.

Na posledním příkladu je patrné, že ML měl pravdu – kód funkce nepokrývá všechny okrajové podmínky. Náprava je snadná, protože stačí přidat vzorek pro prázdný seznam na vstupu:

fun car([]) = 0
  | car(x::y) = x;
> val car = fn: int list → int;
 
car([1,2,3]);
> val it = 1: int;
 
car([]);
> val it = 0: int;

Nebo ještě lépe – vyhodíme výjimku:

fun car nil = raise Empty
  | car(x::y) = x;
> val car = fn: ∀ 'a . 'a list → 'a;
 
car([1,2,3]);
> val it = 1: int;
 
car([]);
Uncaught SML exception: Empty
Poznámka: na těchto jednoduchých příkladech to sice není tak patrné, ale spojením pattern matchingu se silným typovým systémem vzniká velmi elegantní a současně i bezpečný programovací jazyk, zejména pokud programátor skutečně reaguje na všechna varování vypisovaná při analýze jeho zdrojového kódu. Tento koncept je doveden ještě dál v jazyku F#.

4. Strukturální pattern matching v Rustu

Programovací jazyk Rust sice nepodporuje (ostatně stejně jako Python a vlastně i programovací jazyk Go) klasickou céčkovskou rozhodovací konstrukci typu switch-case (kterou převzala a jen mírně rozšířila Java atd.), což však ve skutečnosti není žádná škoda, protože namísto toho nabízí mnohem robustnější konstrukci match, kterou lze taktéž použít pro volbu některé větve kódu, jenž se má provést. Nejprve se bez dlouhých popisů podívejme na způsob zápisu této konstrukce v její nejjednodušší podobě, tedy při klasickém rozvětvení:

fn main() {
    let x:i32 = 1;
 
    match x {
        0 => println!("zero"),
        1 => println!("one"),
        2 => println!("two"),
        3 => println!("three"),
        _ => println!("something else"),
    }
}

Vidíme, že v konstrukci match je několik větví, přičemž každá z těchto větví začíná porovnávanou hodnotou a výraz či příkaz je uveden za šipkou tvořenou znaky =>. Poslední řádek obsahuje symbol _, který vlastně nahrazuje větev default. Ve skutečnosti je však sémantika tohoto symbolu poněkud odlišná, protože před šipkou ⇒ je uveden takzvaný vzorek (pattern) a nikoli pouhopouhá konstanta, což si ostatně ukážeme na dalších příkladech.

V době překladu zdrojového kódu překladač Rustu kontroluje, zda je _ použit, protože je vyžadováno, aby větve v konstrukci match pokryly všechny možné hodnoty testovaného výrazu. Zkusme přeložit následující příklad:

fn main() {
    let x:i32 = 1;
 
    match x {
        0 => println!("zero"),
        1 => println!("one"),
        2 => println!("two"),
        3 => println!("three"),
    }
}

Podle očekávání skončí pokus o překlad tohoto zdrojového kódu s chybou:

   Compiling playground v0.0.1 (/playground)
error[E0004]: non-exhaustive patterns: `i32::MIN..=-1_i32` and `4_i32..=i32::MAX` not covered
 --> src/main.rs:4:11
  |
4 |     match x {
  |           ^ patterns `i32::MIN..=-1_i32` and `4_i32..=i32::MAX` not covered
  |
  = note: the matched value is of type `i32`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms
  |
8 ~         3 => println!("three")
9 ~         i32::MIN..=-1_i32 | 4_i32..=i32::MAX => todo!(),
  |

For more information about this error, try `rustc --explain E0004`.
error: could not compile `playground` due to previous error

Podobně, jako tomu bylo u programové konstrukce if-else či u programového bloku {}, je i konstrukce match považována za výraz, nikoli za příkaz. To znamená, že celou konstrukci match je možné použít v nějakém výpočtu, uvnitř jiného výrazu atd. V dalším demonstračním příkladu je deklarována funkce nazvaná classify, které se předá celočíselná hodnota a funkce vrátí konstantní řetězec, který tuto hodnotu popisuje. Tato funkce obsahuje ve svém bloku jediný výraz a tím je match (proto ostatně za složenou závorkou není středník):

fn classify(x:i32) -> &'static str {
    match x {
        0 => "zero",
        1 => "one",
        2 => "two",
        3 => "three",
        _ => "something else",
    }
}
 
fn main() {
    for x in 0..10 {
        println!("{}:{}", x, classify(x))
    }
}

Po překladu a spuštění dostaneme na standardním výstupu tyto zprávy:

0:zero
1:one
2:two
3:three
4:something else
5:something else
6:something else
7:something else
8:something else
9:something else

Fibonacciho řadu lze vypočítat rekurzivně, taktéž s matchingem:

fn fib(n: u32) -> u32 {
    match n {
        0 | 1 => 1,
        _ => fib(n - 1) + fib(n - 2),
    }
}
 
fn main() {
    for x in 0..10 {
        println!("{}:{}", x, fib(x))
    }
}

Použití rozsahu:

match number {
    1 => println!("One!"),
    2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
    13..=19 => println!("A teen"),
    _ => println!("Ain't special"),
}
Poznámka: možné jsou i mnohem složitější konstrukce.

5. Strukturální pattern jazyk v jazyku Coconut: první krok k Pythonu

Funkcionální jazyk Coconut, s nímž jsme se seznámili v článcích [1] a [2] (a jenž je transpřekládán právě do Pythonu), taktéž podporuje pattern matching, jenž je ostatně poměrně běžný i v dalších funkcionálních jazycích. Pattern matching je v Coconutu realizován jazykovou konstrukcí používající nová klíčová slova case a match. V nejjednodušším případě dokážeme pomocí těchto slov nahradit poměrně špatně čitelnou konstrukci if-elif-elif…-else:

def factorial_variant_A(n):
    case n:
        match 0:
            return 1
        match 1:
            return 1
        match x:
            return x * factorial_variant_A(x-1)
    else:
        raise TypeError("expecting integer >= 0")

V tomto případě se bude interpret (resp. přesněji řečeno transpřekladač) snažit najít ten vzor (pattern), který nejlépe a jednoznačně odpovídá vstupu, tj. hodnotě proměnné n. Postupně se testují vzorky 0, 1 a jakákoli jiná hodnota.

Povšimněte si toho, že se na třetím řádku match x objevuje x, což je proměnná platná jen v rámci dané větve a to (samozřejmě) pouze v tom případě, že se tato větev bude provádět (do této proměnné se zachytí konkrétní hodnota n).

Ve skutečnosti se však mohou v části match použít i další klauzule, zejména klauzule s podmínkou. Předchozí příklad nepracuje korektně pro záporná čísla, takže program opravíme – doplníme příslušnou podmínku:

def factorial_variant_B(n):
    case n:
        match 0:
            return 1
        match 1:
            return 1
        match x if x > 1:
            return x * factorial_variant_B(x-1)
    else:
        raise TypeError("expecting integer >= 0")

Zde klademe na třetí vzorek další podmínku, takže mu nebude odpovídat úplně jakákoli hodnota proměnné n, ale pouze hodnota větší než 1.

Ani tato úprava však nezajistí zcela korektní chování. Ostatně si sami vyzkoušejte, co se stane při volání s hodnotou 1.5. Přidáme tedy další klauzuli na kontrolu typu s využitím operátoru is:

def factorial_variant_C(n):
    case n:
        match 0:
            return 1
        match 1:
            return 1
        match x is int if x > 1:
            return x * factorial_variant_C(x-1)
    else:
        raise TypeError("expecting integer >= 0")

Ukažme si nyní některé poněkud složitější příklady použití pattern matchingu v programovacím jazyku Coconut. Například funkci, která rozhodne o tom, jak velký seznam je jí předán:

def type(x):
    case x:
        match [a, b]:
            return "list, 2 items"
        match [a]:
            return "list, 1 item"
        match []:
            return "empty list"

Otestování vzájemného vztahu dvou hodnot ve dvojici (seznamu, n-tici):

def pair(p):
    case p:
        match [x,x]:
            return "same values!"
        match [x,y] if x>y:
            return "1st value is greater"
        match [x,y]:
            return "2nd value is greater"
    else:
        return "other"

V případě, že se další funkci předá řetězec začínající na „My name is “, vezme se z něj další text a přiřadí do proměnné name:

def say_hello(s):
    case s:
        match "My name is " + name:
            return "Hi " + name

Specifikovat je možné začátek i konec řetěze (ovšem ne regulárním výrazem):

def get_name(s):
    case s:
        match name + "@root.cz":
            return name
 
def say_hello2(s):
    case s:
        match "My name is " + name + ".":
            return "Hi " + name
Poznámka: zdaleka ne všechny výše uvedené příklady bude možné přepsat do Pythonu 3.10! Taktéž je zapotřebí dát si pozor na to, že slova match a case mají v Coconatu přesně opačný význam, než je tomu v Pythonu!

6. Strukturální pattern matching v programovacím jazyku Python verze 3.10

Určitá – a nutno říci, že dosti zjednodušená – varianta výše uvedeného strukturálního pattern matchingu byla přidána i do Pythonu 3.10. V té nejjednodušší variantě se můžeme na strukturální pattern matching dívat jako na variantu programové konstrukce switch, která doposud v Pythonu chyběla. Ovšem již na následujícím příkladu je patrné, že možnosti mohou být poněkud větší (a to se jedná a triviální příklad použití):

def fib(value):
    match value:
        case 0:
            return 0
        case 1:
            return 1
        case n if n>1:
            return fib(n-1) + fib(n-2)
        case _ as wrong:
            raise ValueError("Wrong input", wrong)
 
 
for n in range(0, 11):
    print(n, fib(n))
 
fib(-1)
Poznámka: v dalším textu si samozřejmě uvedeme i komplikovanější příklady.

7. Překlad a instalace Pythonu 3.10

Vzhledem k tomu, že Python 3.10 prozatím nebývá nabízen standardními distribucemi Linuxu, provedeme si jeho lokální překlad. Nejedná se o nic složitého (otestováno na Fedoře a Mintu).

Nejdříve je nutné nainstalovat tooling jazyka C a některé použité knihovny:

$ sudo dnf install wget yum-utils make gcc openssl-devel bzip2-devel libffi-devel zlib-devel
Poznámka: pochopitelně nahraďte dnf za apt.get atd., podle toho, jakou používáte distribuci.

Dále si stáhněte zdrojové kódy Pythonu 3.10 (zde konkrétně se jedná o 3.10.6):

$ wget https://www.python.org/ftp/python/3.10.6/Python-3.10.6.tgz

Rozbalte stažený tarball:

$ tar xzf Python-3.10.6.tgz

Následuje přesun do adresáře se zdrojovými kódy a konfigurace na základě možností poskytovaných operačním systémem:

$ cd Python-3.10.6
$ ./configure --with-system-ffi --with-computed-gotos --enable-loadable-sqlite-extensions

Překlad je proveden příkazem make, kterému je vhodné předat počet paralelně běžících úloh. Ten by měl odpovídat počtu procesorových jader (například 8):

$ make -j ${nproc}

Tedy konkrétně pro osm jader:

$ make -j 8

Výsledkem je spustitelný soubor s interpretrem Pythonu, který má velikost přibližně 24MB (ovšem v závislosti na použité architektuře):

Python-3.10.6$ ls -l -h python
-rwxrwxr-x 1 ptisnovs ptisnovs 24M Aug 25 12:55 python

Tato velikost je až směšně velká (a to i na dnešní poměry), ovšem lze ji zmenšit do poněkud rozumnější podoby:

Python-3.10.6$ strip python
 
Python-3.10.6$ ls -l -h python
-rwxrwxr-x 1 ptisnovs ptisnovs 3,9M Aug 25 12:57 python

Nově přeložený interpret si můžeme ihned spustit:

Python-3.10.6$ ./python 
 
Python 3.10.6 (main, Aug 25 2022, 12:55:31) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Popř. si můžete právě přeložený interpret Python 3.10 nainstalovat do systému příkazem:

$ sudo make altinstall

Potom je spuštění mnohem jednodušší:

$ python3.10
 
Python 3.10.6 (main, May 20 2022, 04:51:23) [GCC 11.3.1 20220421 (Red Hat 11.3.1-2)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

8. Nejjednodušší příklad – rozeskok

Vraťme se nyní k problematice pattern matchingu v Pythonu. Pokusíme se nahradit následující konstrukci (mimochodem se slavnou otázkou, kterou jsem vlastně nikdy nepochopil :-) za rozumnější řešení:

print("Not ready reading drive A")
 
 
def abort_retry_fail():
    response = input("Abort, Retry, Fail? ")
 
    if response == "a":
        return "Abort"
    elif response == "r":
        return "Retry"
    elif response == "f":
        return "Fail"
    else:
        return "Wrong response"
 
 
print(abort_retry_fail())

V tomto případě je možné sekvenci if-elif-elif-else nahradit mapou:

print("Not ready reading drive A")
 
 
def abort_retry_fail():
    response = input("Abort, Retry, Fail? ")
 
    commands = {
            "a": "Abort",
            "r": "Retry",
            "f": "Fail"
            }
 
    return commands.get(response, "Wrong response")
 
 
print(abort_retry_fail())

Nás ovšem bude zajímat především nová konstrukce match. Tu lze použít následovně:

print("Not ready reading drive A")
 
 
def abort_retry_fail():
    response = input("Abort, Retry, Fail? ")
 
    match response:
        case "a":
            return "Abort"
        case "r":
            return "Retry"
        case "f":
            return "Fail"
        case _:
            return "Wrong response"
 
 
print(abort_retry_fail())
Poznámka: Python, na rozdíl od Rustu, nevyžaduje, aby byly pokryty všechny možné varianty, takže můžete psát i:
print("Not ready reading drive A")
 
 
def abort_retry_fail():
    response = input("Abort, Retry, Fail? ")
 
    match response:
        case "a":
            return "Abort"
        case "r":
            return "Retry"
        case "f":
            return "Fail"
 
 
print(abort_retry_fail())

V případě neočekávaného vstupu se v tomto případě vrátí hodnota (nebo raději symbol představující neexistující hodnotu) None.

9. Větší množství hodnot v každé větvi

Nyní se pokusme předchozí demonstrační příklady rozšířit takovým způsobem, aby skript reagoval nejenom na odpovědi „a“, „r“ a „f“, ale například i na zápis odpovědí velkými písmeny, na zápis celých slov atd. Ve „starém“ Pythonu spočívá jedno možné řešení ve využití množinové operace in použité ve větvích in a elif:

print("Not ready reading drive A")
 
 
def abort_retry_fail():
    response = input("Abort, Retry, Fail? ")
 
    if response in {"a", "A", "Abort", "abort"}:
        return "Abort"
    elif response in {"r", "R"}:
        return "Retry"
    elif response in {"f", "F"}:
        return "Fail"
    else:
        return "Wrong response"
 
 
print(abort_retry_fail())

Nová programová konstrukce match-case umožňuje v každé větvi case použít operátor „or“, který je zapisovaný znakem „|“. Zápis je tak relativně stručný, což je ostatně patrné i z následující ukázky:

print("Not ready reading drive A")
 
 
def abort_retry_fail():
    response = input("Abort, Retry, Fail? ")
 
    match response:
        case "a" | "A":
            return "Abort"
        case "r" | "R":
            return "Retry"
        case "f" | "F":
            return "Fail"
        case _:
            return "Wrong response"
 
 
print(abort_retry_fail())
Poznámka: pochopitelně nejsme omezeni pouze na dvě hodnoty spojené operátorem „or“.

10. Zachycení hodnoty proměnné v rozhodovací větvi

Následující příklad je prozatím poněkud umělý. Je v něm ukázáno, jak lze v zápisu vzorku ve větvi case zachytit hodnotu (nebo hodnoty). Jedná se o poslední větev typu „else“, kde vstupní hodnotu zachytíme do lokální proměnné x, kterou dále v této větvi použijeme:

print("Not ready reading drive A")
 
 
def abort_retry_fail():
    response = input("Abort, Retry, Fail? ")
 
    match response:
        case "a" | "A":
            return "Abort"
        case "r" | "R":
            return "Retry"
        case "f" | "F":
            return "Fail"
        case _ as x:
            return f"Wrong response {x}"
 
 
print(abort_retry_fail())

Výše uvedený příklad je umělý z toho důvodu, že v tomto konkrétním případě můžeme přímo použít proměnnou response. Ovšem můžeme ho snadno upravit do podoby, v němž žádná taková proměnná nebude existovat:

print("Not ready reading drive A")
 
 
def abort_retry_fail():
 
    match input("Abort, Retry, Fail? "):
        case "a" | "A":
            return "Abort"
        case "r" | "R":
            return "Retry"
        case "f" | "F":
            return "Fail"
        case _ as x:
            return f"Wrong response {x}"
 
 
print(abort_retry_fail())

11. Podmínka v rozhodovací větvi, větev „else“

Vyzkoušejme si nyní zapsat rekurzivní výpočet faktoriálu s využitím pattern matchingu. První varianta může vypadat následovně – až na nepatrně odlišnou rekurzi se jedná o stejný kód, jako tomu bylo v příkladu s Fibonacciho posloupností (povšimněte si, že x odpovídá libovolné hodnotě, přičemž hodnoty 0 a 1 se zde již nebudou vyskytovat):

def factorial(n):
    match n:
        case 0:
            return 1
        case 1:
            return 1
        case x:
            return x * factorial(x-1)
 
 
for i in range(0, 10):
    print(i, factorial(i))

Ve skutečnosti ovšem výpočet nebude korektní pro záporné vstupy. Proto do struktury match dopíšeme podmínku omezující hodnotu zachycené proměnné x. To Python umožňuje – viz podtrženou část zdrojového kódu – a výsledkem je tento skript:

def factorial(n):
    match n:
        case 0:
            return 1
        case 1:
            return 1
        case x if x>1:
            return x * factorial(x-1)
        case _:
            raise TypeError("expecting integer >= 0")
 
 
for i in range(-1, 10):
    try:
        print(i, factorial(i))
    except Exception as e:
        print(e)

Můžeme dokonce deklarovat (prozatím nepříliš elegantně), jakého typu má hodnota být. Později si ukážeme mnohem lepší řešení:

def factorial(n):
    match n:
        case 0:
            return 1
        case 1:
            return 1
        case x if isinstance(x, int) and x>1:
            return x * factorial(x-1)
        case _:
            raise TypeError("expecting integer >= 0")
 
 
for i in range(-1, 10):
    try:
        print(i, factorial(i))
    except Exception as e:
        print(e)
 
try:
    print(factorial(3.14))
except Exception as e:
    print(e)

try:
    print(factorial("hello"))
except Exception as e:
    print(e)

Nakonec spojíme první dvě větve, protože se vrací stejná hodnota:

def factorial(n):
    match n:
        case 0 | 1:
            return 1
        case x if isinstance(x, int) and x>1:
            return x * factorial(x-1)
        case _:
            raise TypeError("expecting integer >= 0")
 
 
for i in range(-1, 10):
    try:
        print(i, factorial(i))
    except Exception as e:
        print(e)
 
try:
    print(factorial(3.14))
except Exception as e:
    print(e)
 
try:
    print(factorial("hello"))
except Exception as e:
    print(e)

12. Test obsahu n-tic a seznamů

V dalším demonstračním příkladu je ukázáno, jak lze v konstrukci match testovat nikoli pouze jedinou hodnotu, ale dvojici hodnot předaných v n-tici (tedy zde konkrétně pochopitelně ve dvojici). Automat, který je schován za pattern matcherem, správně rozpozná všechny čtyři možné situace, které mohou nastat:

def test_number(value):
    match value:
        case (0, 0):
            print("Zero")
        case (real, 0):
            print(f"Real number {real}")
        case (0, imag):
            print(f"Imaginary number {imag}")
        case (real, imag):
            print(f"Complex number {real}+i{imag}")
        case _:
            raise ValueError("Not a complex number")
 
 
test_number((0,0))
test_number((1,0))
test_number((0,1))
test_number((1,1))

Vše si můžeme otestovat:

Zero
Real number 1
Imaginary number 1
Complex number 1+i1

Úprava pro seznamy je triviální:

def test_number(value):
    match value:
        case [0, 0]:
            print("Zero")
        case [real, 0]:
            print(f"Real number {real}")
        case [0, imag]:
            print(f"Imaginary number {imag}")
        case [real, imag]:
            print(f"Complex number {real}+i{imag}")
        case _:
            raise ValueError("Not a complex number")
 
 
test_number([0,0])
test_number([1,0])
test_number([0,1])
test_number([1,1])

13. n-tice a podmínky pro hodnoty prvků

Výše uvedený demonstrační příklad můžeme rozšířit o stanovení podmínek, které musí splňovat prvek nebo prvky n-tice. Například můžeme rozlišit mezi kladným a záporným reálným číslem či kladným a záporným imaginárním číslem. Tyto podmínky se zapíšou následovně:

def test_number(value):
    match value:
        case (0, 0):
            print("Zero")
        case (real, 0) if real>0:
            print(f"Positive real number {real}")
        case (real, 0):
            print(f"Negative real number {real}")
        case (0, imag) if imag<0:
            print(f"Negative imaginary number {imag}")
        case (0, imag):
            print(f"Negative imaginary number {imag}")
        case (real, imag):
            print(f"Complex number {real}+i{imag}")
        case _:
            raise ValueError("Not a complex numbe")
 
 
test_number((0,0))
test_number((1,0))
test_number((-1,0))
test_number((0,1))
test_number((0,-1))
test_number((1,1))

S výsledky:

Zero
Positive real number 1
Negative real number -1
Negative imaginary number 1
Negative imaginary number -1
Complex number 1+i1

Pro úplnost si ukažme úpravu příkladu pro prvky seznamů a nikoli jen n-tic:

def test_number(value):
    match value:
        case [0, 0]:
            print("Zero")
        case [real, 0] if real>0:
            print(f"Positive real number {real}")
        case [real, 0]:
            print(f"Negative real number {real}")
        case [0, imag] if imag<0:
            print(f"Negative imaginary number {imag}")
        case [0, imag]:
            print(f"Negative imaginary number {imag}")
        case [real, imag]:
            print(f"Complex number {real}+i{imag}")
        case _:
            raise ValueError("Not a complex number")
 
 
test_number([0,0])
test_number([1,0])
test_number([-1,0])
test_number([0,1])
test_number([0,-1])
test_number([1,1])

14. Zpracování příkazů či strukturovaných textových souborů

Poměrně často se můžeme setkat s požadavkem na zpracování víceslovních příkazů nebo (častěji) strukturovaných textových souborů, kde jednotlivé řádky začínají několika slovy či znaky určujícími význam dalšího textu (neboli – svět se zdaleka neotáčí jen okolo XML a JSONů). I v takovém případě můžeme využít pattern matching. Zcela nejjednodušší je varianta, ve které je seznam slov předem daný a není velký. Tehdy lze využít mapu nebo jednoduché rozvětvení:

def perform_command():
    response = input("> ")
 
    match response:
        case "quit":
            return "Quit"
        case "list employees":
            return "List employees"
        case "list departments":
            return "List departments"
        case "list rooms":
            return "List rooms"
        case _:
            return "Wrong command"
 
 
print(perform_command())

Jednodušší bývá rozdělení vstupu na jednotlivá slova. V takovém případě nám opět pomůže pattern matching, tentokrát při hledání prvků v seznamech:

def perform_command():
    response = input("> ")
 
    match response.split():
        case ["quit"]:
            return "Quit"
        case ["list", "employees"]:
            return "List employees"
        case ["list", "departments"]:
            return "List departments"
        case ["list", "rooms"]:
            return "List rooms"
        case _:
            return "Wrong command"
 
 
print(perform_command())

15. Proměnné části víceslovních příkazů

Některá slova v příkazech nemusí pocházet z krátkého a předem známého „číselníku“. To je naznačeno v dalším příkladu, v němž se používá víceslovní příkaz „info <subjekt>“, přičemž druhé slovo (tedy subjekt) může být jakékoli, ale musí být zapsáno. Toto slovo lze „zachytit“ ve větvi, která je ve výpisu vyznačena tučně:

def perform_command():
    response = input("> ")
 
    match response.split():
        case ["quit"]:
            return "Quit"
        case ["list", "employees"]:
            return "List employees"
        case ["list", "departments"]:
            return "List departments"
        case ["list", "rooms"]:
            return "List rooms"
        case ["info", subject]:
            return f"Info about subject '{subject}'"
        case _:
            return "Wrong command"
 
 
print(perform_command())

16. Vnořené řídicí struktury match

Kromě snahy o tvorbu snadno pochopitelného a testovatelného kódu nám nic jiného nebrání v tom, abychom ve skriptech používali vnořené řídicí struktury match. Tento koncept je ukázán v dalším příkladu, v němž se rozlišují příkazy typu „list employees“, „list departments“ atd. v konstrukci match vložené do jedné z větví case:

def perform_command():
    response = input("> ")
 
    match response.split():
        case ["quit"]:
            return "Quit"
        case ["list", obj]:
            match obj:
                case "employees":
                    return "List employees"
                case "departments":
                    return "List departments"
                case "rooms":
                    return "List rooms"
                case _:
                    return "Invalid object type: employees, departments, or rooms expected"
        case ["info", subject]:
            return f"Info about subject '{subject}'"
        case _:
            return "Wrong command"
 
 
print(perform_command())

Ve skutečnosti můžeme příklad upravit, a to tak, aby se do „větve list“ řízení programu dostalo pouze za předpokladu, že druhé slovo je známé a pochází z předem zadané množiny. Příslušný vzorek je zvýrazněn:

def perform_command():
    response = input("> ")
 
    match response.split():
        case ["quit"]:
            return "Quit"
        case ["list", ("employees" | "departments" | "rooms") as obj]:
            match obj:
                case "employees":
                    return "List employees"
                case "departments":
                    return "List departments"
                case "rooms":
                    return "List rooms"
        case ["info", subject]:
            return f"Info about subject '{subject}'"
        case _:
            return "Wrong command"
 
 
print(perform_command())

17. Strukturální pattern matching a objekty

Možnosti nové programové konstrukce zajišťující strukturální pattern matching jdou však ještě dále, například existuje možnost testu, zda je hodnotou nějaký objekt popř. jaké jsou hodnoty atributů tohoto objektu. Pro ukázku upravíme příklad ze třinácté kapitoly tak, aby dokázal rozpoznat komplexní čísla reprezentovaná instancí třídy Complex:

class Complex():
 
    def __init__(self, real, imag):
        self.real = real
        self.imag = imag
 
    def __str__(self):
        return f"Complex number {self.real}+i{self.imag} represented as object"
 
 
def test_number(value):
    match value:
        case (0, 0):
            print("Zero")
        case (real, 0) if real>0:
            print(f"Positive real number {real}")
        case (real, 0):
            print(f"Negative real number {real}")
        case (0, imag) if imag<0:
            print(f"Negative imaginary number {imag}")
        case (0, imag):
            print(f"Negative imaginary number {imag}")
        case (real, imag):
            print(f"Complex number {real}+i{imag}")
        case Complex():
            print(value)
        case _:
            raise ValueError("Not a complex number")
 
 
test_number((0,0))
test_number((1,0))
test_number((-1,0))
test_number((0,1))
test_number((0,-1))
test_number((1,1))
 
test_number(Complex(0,0))
test_number(Complex(1,0))
test_number(Complex(-1,0))
test_number(Complex(0,1))
test_number(Complex(0,-1))
test_number(Complex(1,1))

Po spuštění získáme na výstupu tyto řádky:

Zero
Positive real number 1
Negative real number -1
Negative imaginary number 1
Negative imaginary number -1
Complex number 1+i1
Complex number 0+i0 represented as object
Complex number 1+i0 represented as object
Complex number -1+i0 represented as object
Complex number 0+i1 represented as object
Complex number 0+i-1 represented as object
Complex number 1+i1 represented as object

Samozřejmě nám nic nebrání v testu hodnot atributů objektu. Musíme začít, stejně jako v dalších případech, konkrétními objekty a navázat obecnější podmínkou:

    match value:
        ...
        ...
        ...
        case Complex(real=0, imag=0):
            print(f"Zero complex represented as object")
        case Complex():
            print(value)

Pro zajímavost se nyní podívejme, jak jednoduché či naopak složité je rozšíření předchozího demonstračního příkladu o podporu rozeznávání zlomků. Ty jsou v Pythonu již realizovány, a to konkrétně třídou Fraction. Nově přidané řádky kódu jsou zvýrazněny:

from fractions import Fraction
 
 
class Complex():
 
    def __init__(self, real, imag):
        self.real = real
        self.imag = imag
 
    def __str__(self):
        return f"Complex number {self.real}+i{self.imag} represented as object"
 
 
def test_number(value):
    match value:
        case (0, 0):
            print("Zero")
        case (real, 0) if real>0:
            print(f"Positive real number {real}")
        case (real, 0):
            print(f"Negative real number {real}")
        case (0, imag) if imag<0:
            print(f"Negative imaginary number {imag}")
        case (0, imag):
            print(f"Negative imaginary number {imag}")
        case (real, imag):
            print(f"Complex number {real}+i{imag}")
        case Complex(real=0, imag=0):
            print(f"Zero complex represented as object")
        case Complex():
            print(value)
        case Fraction():
            print(f"Fraction {value}")
        case _:
            raise ValueError("Not a complex number")
 
 
test_number((0,0))
test_number((1,0))
test_number((-1,0))
test_number((0,1))
test_number((0,-1))
test_number((1,1))
 
test_number(Complex(0,0))
test_number(Complex(1,0))
test_number(Complex(-1,0))
test_number(Complex(0,1))
test_number(Complex(0,-1))
test_number(Complex(1,1))
 
test_number(Fraction(0,1))
test_number(Fraction(1,1))
test_number(Fraction(1,2))
test_number(Fraction(1,3))

Výsledek po spuštění tohoto příkladu:

Zero
Positive real number 1
Negative real number -1
Negative imaginary number 1
Negative imaginary number -1
Complex number 1+i1
Zero complex represented as object
Complex number 1+i0 represented as object
Complex number -1+i0 represented as object
Complex number 0+i1 represented as object
Complex number 0+i-1 represented as object
Complex number 1+i1 represented as object
Fraction 0
Fraction 1
Fraction 1/2
Fraction 1/3

18. Příloha: část gramatiky Pythonu s pattern matchingem

Syntaxe Pythonu je v současnosti popsána s využitím PEG neboli parsing expression grammar. Víme již, že nové programové konstrukce pro strukturální pattern matching byly přidány do Pythonu 3.10. Z celé (velmi dlouhé) gramatiky Pythonu byly do této přílohy vybrány pouze ty části, které se týkají právě pattern matchingu. Zejména byla upravena gramatika pro složené výrazy:

compound_stmt[stmt_ty]:
    | &('def' | '@' | ASYNC) function_def
    | &'if' if_stmt
    | &('class' | '@') class_def
    | &('with' | ASYNC) with_stmt
    | &('for' | ASYNC) for_stmt
    | &'try' try_stmt
    | &'while' while_stmt
    | match_stmt

Samotná syntaxe bloku začínajícího klíčovým slovem match:

ict ve školství 24

match_stmt[stmt_ty]:
    | "match" subject=subject_expr ':' NEWLINE INDENT cases[asdl_match_case_seq*]=case_block+ DEDENT {
        CHECK_VERSION(stmt_ty, 10, "Pattern matching is", _PyAST_Match(subject, cases, EXTRA)) }
    | invalid_match_stmt
subject_expr[expr_ty]:
    | value=star_named_expression ',' values=star_named_expressions? {
        _PyAST_Tuple(CHECK(asdl_expr_seq*, _PyPegen_seq_insert_in_front(p, value, values)), Load, EXTRA) }
    | named_expression
case_block[match_case_ty]:
    | invalid_case_block
    | "case" pattern=patterns guard=guard? ':' body=block {
        _PyAST_match_case(pattern, guard, body, p->arena) }
guard[expr_ty]: 'if' guard=named_expression { guard }

A nakonec následuje syntaxe samotných vzorků (patterns):

patterns[pattern_ty]:
    | patterns[asdl_pattern_seq*]=open_sequence_pattern {
        _PyAST_MatchSequence(patterns, EXTRA) }
    | pattern
pattern[pattern_ty]:
    | as_pattern
    | or_pattern
as_pattern[pattern_ty]:
    | pattern=or_pattern 'as' target=pattern_capture_target {
        _PyAST_MatchAs(pattern, target->v.Name.id, EXTRA) }
    | invalid_as_pattern
or_pattern[pattern_ty]:
    | patterns[asdl_pattern_seq*]='|'.closed_pattern+ {
        asdl_seq_LEN(patterns) == 1 ? asdl_seq_GET(patterns, 0) : _PyAST_MatchOr(patterns, EXTRA) }
 
closed_pattern[pattern_ty] (memo):
    | literal_pattern
    | capture_pattern
    | wildcard_pattern
    | value_pattern
    | group_pattern
    | sequence_pattern
    | mapping_pattern
    | class_pattern
 
# Literal patterns are used for equality and identity constraints
literal_pattern[pattern_ty]:
    | value=signed_number !('+' | '-') { _PyAST_MatchValue(value, EXTRA) }
    | value=complex_number { _PyAST_MatchValue(value, EXTRA) }
    | value=strings { _PyAST_MatchValue(value, EXTRA) }
    | 'None' { _PyAST_MatchSingleton(Py_None, EXTRA) }
    | 'True' { _PyAST_MatchSingleton(Py_True, EXTRA) }
    | 'False' { _PyAST_MatchSingleton(Py_False, EXTRA) }
 
# Literal expressions are used to restrict permitted mapping pattern keys
literal_expr[expr_ty]:
    | signed_number !('+' | '-')
    | complex_number
    | strings
    | 'None' { _PyAST_Constant(Py_None, NULL, EXTRA) }
    | 'True' { _PyAST_Constant(Py_True, NULL, EXTRA) }
    | 'False' { _PyAST_Constant(Py_False, NULL, EXTRA) }

19. Repositář s demonstračními příklady

Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Python 3.10 (nikoli ovšem pro starší verze Pythonu 3!) byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má velikost zhruba několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Demonstrační příklad Stručný popis příkladu Cesta
1 abort_retry_fail1.py rozvětvení realizované konstrukcí if-elif-else https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/abort_retry_fail1.py
2 abort_retry_fail2.py náhrada rozvětvení za mapu https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/abort_retry_fail2.py
3 abort_retry_fail3.py rozvětvení realizované konstrukcí match-case https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/abort_retry_fail3.py
4 abort_retry_fail4.py složitější podmínky v konstrukci if-elif-else https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/abort_retry_fail4.py
5 abort_retry_fail5.py konstrukce match-case a operátor or https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/abort_retry_fail5.py
6 abort_retry_fail6.py zachycení hodnoty v konstrukci match-case https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/abort_retry_fail6.py
7 abort_retry_fail7.py odstranění pomocné proměnné https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/abort_retry_fail7.py
       
8 factorial1.py rekurzivní výpočet faktoriálu založený na konstrukci match-case https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/factorial1.py
9 factorial2.py dodatečný test ve větvi case https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/factorial2.py
10 factorial3.py dodatečné testy ve větvi case https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/factorial3.py
11 factorial4.py operátor or ve větvi case https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/factorial4.py
       
12 complex1.py test hodnot v n-ticích, zachytávání hodnot https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/complex1.py
13 complex2.py test hodnot v n-ticích, zachytávání hodnot https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/complex2.py
14 complex1B.py test hodnot v n-ticích, zachytávání hodnot, úprava pro seznamy https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/complex1B.py
15 complex2B.py test hodnot v n-ticích, zachytávání hodnot, úprava pro seznamy https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/complex2B.py
       
16 multiword_commands1.py zpracování víceslovních příkazů, základní varianta https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/multiword_commands.py
17 multiword_commands2.py zpracování víceslovních příkazů, zpracování rozděleného příkazu https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/multiword_commands.py
18 multiword_commands3.py zpracování víceslovních příkazů, zachycení druhého slova https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/multiword_commands.py
19 multiword_commands4.py zpracování víceslovních příkazů, vnořené struktury match-case https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/multiword_commands.py
20 multiword_commands5.py zpracování víceslovních příkazů, vnořené struktury match-case https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/multiword_commands.py
       
21 object1.py test na typ objektu v konstrukci match-case https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/object1.py
22 object2.py test na typ a hodnotu objektu v konstrukci match-case https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/object2.py
       
23 fib.py rekurzivní výpočet Fibonacciho řady https://github.com/tisnik/most-popular-python-libs/blob/master/pattern_mat­ching/fib.py

20. Odkazy na Internetu

  1. Pattern matching (Wikipedia)
    https://en.wikipedia.org/wi­ki/Pattern_matching
  2. Python 3.10 and the Elegance of Pattern Matching
    https://python.plainenglish.io/python-3–10-and-the-elegance-of-pattern-matching-2620a02b2379
  3. More Pattern Matching in Python 3.10
    https://towardsdatascience.com/more-advanced-pattern-matching-in-python-3–10–2dbd8598302a
  4. Pattern Matching in Python 3.10
    https://towardsdatascience.com/pattern-matching-in-python-3–10–6124ff2079f0
  5. Python 3.10.0
    https://www.python.org/dow­nloads/release/python-3100/
  6. PEP 634 – Structural Pattern Matching: Specification
    https://peps.python.org/pep-0634/
  7. PEP 635 – Structural Pattern Matching: Motivation and Rationale
    https://peps.python.org/pep-0635/
  8. PEP 636 – Structural Pattern Matching: Tutorial
    https://peps.python.org/pep-0636/
  9. PEP 622 – Structural Pattern Matching
    https://peps.python.org/pep-0622/
  10. Python 3.10 se strukturálním pattern matchingem
    https://www.root.cz/zpravicky/python-3–10-se-strukturalnim-pattern-matchingem/
  11. Coconut: funkcionální jazyk s pattern matchingem kompatibilní s Pythonem
    https://www.root.cz/clanky/coconut-funkcionalni-jazyk-s-pattern-matchingem-kompatibilni-s-pythonem/
  12. How to Install Python 3.10 on CentOS/RHEL 8 & Fedora 35/34
    https://tecadmin.net/how-to-install-python-3–10-on-centos-rhel-8-fedora/
  13. Pattern matching functions in Clojure?
    https://stackoverflow.com/qu­estions/8596980/pattern-matching-functions-in-clojure
  14. Clojure core.match
    https://github.com/clojure/core.match
  15. The Rust Programming Language: Patterns and Matching
    https://doc.rust-lang.org/book/ch18–00-patterns.html#patterns-and-matching
  16. The Rust Programming Language: Pattern Syntax
    https://doc.rust-lang.org/book/ch18–03-pattern-syntax.html
  17. Coconut: funkcionální jazyk s pattern matchingem kompatibilní s Pythonem
    https://www.root.cz/clanky/coconut-funkcionalni-jazyk-s-pattern-matchingem-kompatibilni-s-pythonem/
  18. Coconut aneb funkcionální nadstavba nad Pythonem (2.část)
    https://www.root.cz/clanky/coconut-aneb-funkcionalni-nadstavba-nad-pythonem-2-cast/
  19. Programovací jazyky používané v SSSR (část 2 – SNOBOL)
    https://www.root.cz/clanky/pro­gramovaci-jazyky-pouzivane-v-sssr-cast-2-ndash-snobol/
  20. Coconut: Simple, elegant, Pythonic functional programming
    http://coconut-lang.org/
  21. Parsing expression grammar
    https://en.wikipedia.org/wi­ki/Parsing_expression_gram­mar
  22. Abort, Retry, Fail?
    https://en.wikipedia.org/wi­ki/Abort,_Retry,_Fail%3F
  23. SNOBOL4 and SPITBOL Information
    http://www.snobol4.com/
  24. Vanilla Snobol4 Reference Manual
    http://burks.bton.ac.uk/bur­ks/language/snobol/catspaw/ma­nual/contents.htm
  25. SNOBOL4.ORG – SNOBOL4 Resources
    http://www.snobol4.org/
  26. Snobol3 – Snobol 3 Interpreter Implemented in Java
    http://serl.cs.colorado.e­du/~dennis/software/s3.html
  27. Exploring Beautiful Languages – A guick look at SNOBOL
    http://langexplr.blogspot­.com/2007/12/quick-look-at-snobol.html
  28. Rosetta Code: Roman_numerals
    http://rosettacode.org/wi­ki/Roman_numerals
  29. Category:SNOBOL4
    http://rosettacode.org/wi­ki/Category:SNOBOL4
  30. An introduction to SNOBOL by James Ford
    http://drofmij.awardspace.com/snobol/
  31. AWK
    https://en.wikipedia.org/wiki/AWK
  32. Get started with GAWK: AWK language fundamentals
    https://web.archive.org/web/20150427143548/htt­ps://www6.software.ibm.com/de­veloperworks/education/au-gawk/au-gawk-a4.pdf
  33. Pattern Matching
    https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/pattern-matching

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.