Hlavní navigace

PyScript: další technologie umožňující využití Pythonu v prohlížeči

20. 9. 2022
Doba čtení: 28 minut

Sdílet

 Autor: Depositphotos
Seznámíme se projektem nazvaným PyScript, což je nástroj umožňující běh skriptů napsaných v Pythonu přímo na HTML stránce. Namísto transpřekladu do JavaScriptu se používá klasický CPython přeložený do WebAssembly.

Obsah

1. PyScript: další technologie umožňující využití Pythonu ve webovém browseru

2. Programovací jazyk Python a front end webových aplikací

3. Python ve funkci programovacího jazyka pro front end

4. Projekt Pyodide

5. PyScript se představuje

6. Zobrazení textu „Hello, World!“ vypsaného Pythonem na webové stránce

7. Uložení skriptu do externího souboru, problematika načtení z lokálního souborového systému

8. Využití vlastního HTTP serveru pro vývoj a ladění

9. Programová smyčka zapsaná do webové stránky, „chytré“ řešení problematiky odsazení

10. Smyčka v externím modulu

11. Výpis informací funkcí print

12. Zápis výsledků do vybraného elementu HTML stránky

13. Podpora knihovny NumPy v projektu PyScript

14. Maticový součin realizovaný v knihovně NumPy

15. Zobrazení matice v čitelné podobě

16. Zápis matic do většího množství HTML elementů

17. Zobrazení grafů vykreslených knihovnou Matplotlib přímo do plochy webové stránky

18. Modifikace DOMu stránky z PyScriptu, reakce na události

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

20. Odkazy na Internetu

1. PyScript: další technologie umožňující využití Pythonu ve webovém browseru

Na stránkách Roota a v minulosti taktéž na serveru Moje Fedora jsme se již několikrát setkali s různými mainstreamovými ale i alternativními implementacemi programovacího jazyka Python. Připomeňme si, že kromě klasického CPythonu (ať již dnes již zastaralé verze 2 nebo verze 3.x) se používá či používal například projekt nazvaný Jython [1] [2], což je (dnes již bohužel poněkud zastaralá) implementace Pythonu určená pro běh ve virtuálním stroji Javy, popř. IronPython určený pro prostředí .NET. Dále jsme se zmínili o RustPythonu, neboli implementaci Pythonu vytvořené (jak název správně napovídá) v programovacím jazyce Rust. Podobným způsobem je implementován i interpret Pythonu v jazyce Go v projektu gpython.

Obrázek 1: Logo programovacího jazyka Jython – dnes již poněkud zastaralé varianty Pythonu pro běh nad virtuálním strojem Javy.

Věnovali jsme se i poměrně neznámému jazyku Coconut. Jedná se o funkcionální programovací jazyk s pattern matchingem, který je navržen takovým způsobem, aby byl kompatibilní s Pythonem, tj. aby skripty vyvinuté v Pythonu byly v Coconutu spustitelné (zpětně ovšem nikoli, protože syntaxe a sémantika Coconutu byla v mnoha ohledech rozšířena). Přesnější informace o tom, se kterými verzemi Pythonu je Coconut kompatibilní, naleznete na adrese http://coconut.readthedoc­s.io/en/master/DOCS.html#com­patible-python-versions. Zapomenout nesmíme ani na MyPy, což je projekt, kterému bude věnován samostatný článek.

Zatímco výše zmíněné nástroje JythonRustPython jsou skutečnými alternativními implementacemi interpretru Pythonu navazujícími na referenční CPython (interpret Pythonu naprogramovaný v céčku), existují i další implementace, které se zaměřují například na překlad programů vyvinutých v Pythonu do strojového kódu, a to jak před spuštěním aplikace (AOT – Ahead Of Time compilation) nebo během její činnosti (JIT – Just In Time compilation). Mezi tyto nástroje patří především PyPy (používaný v mnoha projektech) a dále Cython (pozor – ne CPython), RPython a Numba, které k problematice překladu skriptů vytvořených v Pythonu přistupují různými (zcela odlišnými cestami). RPython používá AOT a striktně omezuje některé vlastnosti jazyka zatímco Numba a PyPy používá JIT a nabízí programátorům prakticky stejné možnosti, jako klasický CPython.

Obrázek 2: Logo projektu Cython.

Obrázek 3: Logo projektu PyPy.

S bližšími informacemi o Cythonu, RPythonu i nástroji Numba jsme se seznámili v následujících článcích:

  1. RPython: překvapivě výkonný dialekt Pythonu, na němž je založen PyPy
    https://www.root.cz/clanky/rpython-prekvapive-vykonny-dialekt-pythonu-na-nemz-je-zalozen-pypy/
  2. RPython vs Cython aneb dvojí přístup k překladu Pythonu do nativního kódu
    https://www.root.cz/clanky/rpython-vs-cython-aneb-dvoji-pristup-k-prekladu-pythonu-do-nativniho-kodu/
  3. Praktické použití nástroje Cython při překladu Pythonu do nativního kódu
    https://www.root.cz/clanky/prakticke-pouziti-nastroje-cython-pri-prekladu-pythonu-do-nativniho-kodu-1/
  4. Projekt Numba aneb další přístup k překladu Pythonu do nativního kódu
    https://www.root.cz/clanky/projekt-numba-aneb-dalsi-pristup-k-prekladu-pythonu-do-nativniho-kodu/

Obrázek 4: Porovnání doby výpočtu Mandelbrotovy množiny RPythonem, Cythonem (bez i s type hinty) a variantou naprogramovanou přímo v ANSI C. Z tohoto grafu je dobře patrné, jak důležité je pro Cython mít k dispozici o typech proměnných, parametrů a návratových kódů funkcí (jinými slovy – provedené optimalizace v tomto případě nezískáme zadarmo; ostatně v IT nic nedostaneme zadarmo).

2. Programovací jazyk Python a front end webových aplikací

Programovací jazyk Python se v současnosti používá v mnoha aplikačních oblastech – od jednoduchých skriptů umožňujících a zjednodušujících administraci systému přes složitější utility, desktopové aplikace (PyQt, PySide, wxPython, Tkinter, PyGObject či Wax) a webové služby až po zpracování dat, strojové učení (ML – machine learning) a umělou inteligenci (AI – artificial intelligence). Dnes nás ovšem bude zajímat především použití Pythonu při tvorbě webových služeb a webových aplikací. V této oblasti se Python používá především na back endu, tj. pro tu část webové služby/aplikace, která je provozována na serveru a nějakým způsobem komunikuje s ostatními komponentami vytvářeného systému: front endem (typicky webový prohlížeč s interpretrem JavaScriptu), databází a dalšími (mikro)službami, například s využitím message brokerů, systémů pro monitoring a správu událostí atd. Tato oblast samozřejmě není pokryta pouze Pythonem, ale najdeme zde i další programovací jazyky a technologie, zejména Javu (a celý její middleware), JavaScript či TypeScript (node.js) a dnes taktéž programovací jazyk Go (i když není problém využít například i Rust či další jazyky).

Zatímco pozice Pythonu v oblasti back endu je poměrně zřejmá, je situace na front endu (tedy v současnosti na straně webového prohlížeče) mnohem složitější. Důvod je jednoduchý – v této oblasti z historických důvodů kraluje JavaScript, přičemž veškeré snahy a náhradu tohoto jazyka byly prozatím neúspěšné (pokusů bylo hned několik, připomeňme například browsery s TCL, VBScript či projekt Dart). Ovšem stále se můžeme setkat se snahami JavaScript nepoužívat přímo, popř. ho obejít jinými nástroji. Příkladem mohou být technologie umožňující tvorbu webových aplikací podobným způsobem, jakoby se jednalo o aplikace desktopové. Příkladem tohoto přístupu mohou být projekty Pyjamas (pro Python), Google Web Toolkit (GWT), Apache Cordova atd. (což jsou v současnosti většinou již nepodporované projekty).

3. Python ve funkci programovacího jazyka pro front end

Je tedy vůbec možné a praktické použít Python přímo na webovém front endu a pokud to je možné, jakou za to zaplatíme cenu? Ve skutečnosti existuje hned několik projektů, které se více či méně úspěšným způsobem snaží o to, aby byl Python na front endu skutečně použitelný, a to i pro reálně provozované aplikace. Mezi tyto projekty patří například:

  1. Brython, což je transpřekladač z Pythonu do JavaScriptu, přičemž samotný překlad (resp. přesněji řečeno transpřeklad) probíhá na pozadí, což znamená, že přímo v kódu webové stránky je umístěn (či načítán) kód v Pythonu. Podobné řešení jsme již viděli, například u projektu Wisp (jazyk podobný Clojure) či lua.js (transpřeklad z jazyka Lua). Výhodou Brythonu je, že se jakékoli úpravy ihned projeví v aplikaci po znovunačtení stránky (F5), nevýhodou celková pomalost inicializace webové aplikace.
  2. Transcrypt je taktéž transpřekladačem Pythonu (konkrétně Pythonu 3) do JavaScriptu. Samotný transpřeklad je napsaný velmi dobře – zhruba platí, že velikost vygenerovaného kódu v JavaScriptu odpovídá velikosti původního kódu napsaného v Pythonu (případné zvětšení je o jednotky procent). Musíme však počítat s tím, že je nutné načíst i jádro Transcriptu, jehož velikost je přibližně 20 kB (tedy čím větší je kód samotné aplikace, tím (poměrově) menší budou režijní náklady. Zajímavá a užitečná je i podpora type hintů (informací o datovém typu parametrů, proměnných atd.)
  3. Podobným způsobem pracuje i projekt nazvaný pyjaco, který naleznete na adrese https://github.com/chrivers/pyjaco.
  4. Skulpt je naproti tomu v mnoha ohledech podobný projektu Brython, protože taktéž umožňuje provádět transpřeklad na pozadí (programátor tedy pracuje pouze se zdrojovým kódem vytvořeným v Pythonu).
  5. Velmi zajímavým nástrojem, který názorně ukazuje, jak vlastně transpřeklad funguje (a že jeho výsledek může být čitelný), je nástroj s poněkud zvláštním jménem fiddlesalad, který naleznete na adrese http://fiddlesalad.com/python/. Tento nástroj umožňuje zápis zdrojového kódu v Pythonu, který je ihned (již v době zápisu) transpřekládán do JavaScriptu a současně i spouštěn.
Poznámka: povšimněte si, že všechny projekty zmíněné v předchozím textu jsou ve skutečnosti transpřekladači neboli transpilery. Jedná se o technologii zajišťující, aby se i ve webovém prohlížeči mohlo (nepřímo) pracovat s dalšími programovacími jazyky, nejenom se všudypřítomným a nativně podporovaným JavaScriptem. Další variantou je interpretace s využitím interpretru psaného v JavaScriptu popř. použití WebAssembly (viz též navazující kapitoly).

4. Projekt Pyodide

Nástroj PyScript, kterým se budeme podrobněji zabývat v navazujících kapitolách, ovšem není založen na transpřekladači. Je primárně postaven na projektu nazvaném Pyodide. Jedná se v prvé řadě o upravený překlad celého standardního Pythonu (konkrétně Pythonu, resp. přesněji řečeno CPythonu 3.9), ovšem nikoli do nativního kódu spustitelného přímo z příkazové řádky, ale do bajtkódu WebAssembly. To mj. znamená, že interpret Pythonu, resp. přesněji řečeno programů napsaných pro Python, lze spustit přímo z webového prohlížeče, a to dokonce bez nutnosti mít Python lokálně nainstalovaný – musíme mít pouze k dispozici vhodný webový server (pro jednoduché projekty dokonce ani to ne – stačí lokálně uložené soubory), stránky na github.io atd.

Kromě toho je součástí projektu Pyodide i poměrně velké množství důležitých balíčků používaných ve vědeckotechnických výpočtech, pro zpracování numerických dat, ve statistických výpočtech, v oblasti strojového učení atd. Primárně se jedná o balíčky NumPy, Pandas, Matplotlib, SciPy a taktéž scikit-learn. S mnohými z nich jsme se ostatně na stránkách Roota již setkali a NumPy a Matplotlib dnes dokonce použijeme v některých demonstračních příkladech. Seznam všech balíčků použitých v aktuální verzi Pyodide nalezneme na adrese https://github.com/pyodide/py­odide/tree/main/packages:

CLAPACK Jinja2 MarkupSafe Pygments
asciitree astropy atomicwrites attrs
autograd beautifulsoup4 biopython bleach
bokeh cloudpickle cssselect cycler
cytoolz decorator distlib docutils
freesasa future glpk html5lib
imageio jedi joblib kiwisolver
libiconv libxml libxslt libyaml
lxml matplotlib micropip mne
more-itertools mpmath msgpack networkx
nlopt nltk nose numcodecs
numpy optlang packaging pandas
parso patsy pillow pluggy
py pyodide-interrupts pyparsing pyrtl
pytest python-dateutil python-sat pytz
pywavelets pyyaml regex retrying
scikit-image scikit-learn scipy setuptools
six soupsieve statsmodels swiglpk
sympy toolz traits typing-extensions
uncertainties webencodings xlrd yt
zarr zlib    

5. PyScript se představuje

Projekt PyScript je založen, jak jsme se již ostatně dozvěděli v předchozí kapitole, na projektu Pyodide, což znamená, že ústředním prvkem celého projektu je klasický CPython (tedy referenční implementace Pythonu) přeložený do bajtkódu WebAssembly a tedy spustitelný na HTML stránkách. Navíc ovšem PyScript přidává i podporu pro nové HTML značky, zejména <py-script> a <py-env>, jenž umožňují snadný zápis skriptu a navíc i specifikaci cest, modulů a knihoven, které budou na stránce použity. Autoři PyScriptu navíc museli zajistit „chytrou“ práci s odsazeným kódem, což při kombinaci HTML (kde se na bílé znaky prakticky nehledí) a Pythonu (kde mají bílé znaky syntaktický význam) není úplně triviální. V navazujících kapitolách si na několika demonstračních příkladech ukážeme, jakým způsobem je možné PyScript použít na HTML stránkách

6. Zobrazení textu „Hello, World!“ vypsaného Pythonem na webové stránce

Podívejme se nyní na způsoby využití projektu PyScript. Samozřejmě začneme klasickým příkladem „Hello, World!“ – necháme si tedy vypsat zprávu „Hello, World!“ na plochu HTML stránky Pythonem. Základem je párová značka <py-script>, do níž se zapíše volání funkce print Pythonu. Povšimněte si, že volání funkce může být odsazeno, i když to ve standardním interpretru možné není:

<html>
    <head>
        <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
        <script defer src="https://pyscript.net/latest/pyscript.js"></script>
    </head>
    <body>
        <py-script>
            print('Hello, World!')
        </py-script>
    </body>
</html>

Tuto stránku nyní můžeme otevřít přímo ve webovém prohlížeči:

Obrázek 5: Inicializace stránky resp. skriptu na ní, trvá poměrně dlouho – někdy až několik sekund. Ovšem poté jsou již data uložena v cache, takže další spuštění již není tak pomalé.

Obrázek 6: Skript napsaný v Pythonu se skutečně spustil.

Obrázek 7: Mezitím se ovšem přeneslo zhruba 15MB dat! (v mém případě jsou uložena do cache, ovšem stejně je musí prohlížeč načíst).

7. Uložení skriptu do externího souboru, problematika načtení z lokálního souborového systému

Kombinace HTML a Pythonu v jediném dokumentu není příliš čitelná ani praktická, už jen z toho důvodu, že programátorské editory mohou mít problémy s rozeznáním a zvýrazněním syntaxe. Pokusme se tedy vlastní kód skriptu uložit do souboru hello.py, jehož obsah je vlastně triviální:

print('Hello, World!')

HTML stránku upravíme takovým způsobem, že do značky <py-script> pouze vložíme atribut src s uvedením jména souboru:

<html>
    <head>
        <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
        <script defer src="https://pyscript.net/latest/pyscript.js"></script>
    </head>
    <body>
        <py-script src="hello.py">
        </py-script>
    </body>
</html>

Což lze zkrátit spojením počáteční a koncové značky na:

<html>
    <head>
        <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
        <script defer src="https://pyscript.net/latest/pyscript.js"></script>
    </head>
    <body>
        <py-script src="hello.py" />
    </body>
</html>

Při pokusu o načtení takto upravené stránky však dostaneme jen chybové hlášení:

Obrázek 8: Chybové hlášení na webové stránce po jejím přímém otevření v prohlížeči.

Jedno z možných řešení tohoto problému si ukážeme v navazující kapitole.

8. Využití vlastního HTTP serveru pro vývoj a ladění

Proč se stránka s externím skriptem nenačetla bez chyby? Ve skutečnosti se načte pouze daná stránka a pomocné skripty naprogramované v JavaScriptu, ale nikoli již Pythonovský skript:

Obrázek 9: Příčina chyby.

Důvody pro toto chování jsou vysvětleny na stránce https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Error­s/CORSRequestNotHttp.

Samozřejmě je možné změnit konfiguraci webového prohlížeče (buď při jeho spuštění nebo natrvalo). Alternativně lze spustit jednoduchý webový server v adresáři s naší „webovou aplikací“, což již webový prohlížeč uspokojí:

$ python3 -m http.server 9000
 
Serving HTTP on 0.0.0.0 port 9000 (http://0.0.0.0:9000/) ...

Dále otevřít stránku na adrese localhost:9000:

Obrázek 10: Soubory nabízené právě spuštěným webovým serverem.

A vybrat soubor hello2.html:

127.0.0.1 - - [18/Sep/2022 09:02:50] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [18/Sep/2022 09:02:59] "GET /hello2.html HTTP/1.1" 200 -
127.0.0.1 - - [18/Sep/2022 09:03:01] "GET /hello.py HTTP/1.1" 200 -
127.0.0.1 - - [18/Sep/2022 09:03:05] "GET /hello2.html HTTP/1.1" 304 -

S tímto výsledkem:

Obrázek 11: Nyní se již webová aplikace bez problémů spustí.

Jako poslední se skutečně načte náš skript hello.py:

Obrázek 12: Všechny načítané soubory.

Poznámka: v této chvíli si můžeme stáhnout ZIP archiv s celým PyScriptem, rozbalit ho, změnit cesty na HTML stránce a vše spouštět lokálně.

9. Programová smyčka zapsaná do webové stránky, „chytré“ řešení problematiky odsazení

Zkusme nyní do HTML stránky vložit programovou smyčku zapsanou v Pythonu:

<html>
    <head>
        <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
        <script defer src="https://pyscript.net/latest/pyscript.js"></script>
    </head>
    <body>
        <py-script>
            for i in range(1,11):
                print(i)
        </py-script>
    </body>
</html>

Tato aplikace se v prohlížeči spustí bez problémů:

Obrázek 13: Jednoduchá webová aplikace s programovou smyčkou.

To je zajímavé, protože stejně odsazený kód není možné v běžném Pythonu spustit, protože programový blok na nejvyšší úrovni nesmí být odsazen:

$ python3
 
Python 3.8.10 (default, Jun 22 2022, 20:18:18)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>             for i in range(1,11):
  File "<stdin>", line 1
    for i in range(1,11):
    ^
IndentationError: unexpected indent
>>>                 print(i)
  File "<stdin>", line 1
    print(i)
    ^
IndentationError: unexpected indent
>>>
Poznámka: můžeme zde vidět, že se PyScript snaží programový kód zpracovávat inteligentně.

10. Smyčka v externím modulu

Samozřejmě nám nic nebrání v tom, aby se vlastní programový kód oddělil od HTML stránky do samostatného souboru:

def run():
    for i in range(1,11):
        print(i)
 
run()

Samotný kód s HTML stránkou se tedy zjednoduší do této podoby:

<html>
    <head>
        <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
        <script defer src="https://pyscript.net/latest/pyscript.js"></script>
    </head>
    <body>
        <py-script src="loop.py">
        </py-script>
    </body>
</html>

Popř. do této podoby:

<html>
    <head>
        <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
        <script defer src="https://pyscript.net/latest/pyscript.js"></script>
    </head>
    <body>
        <py-script src="loop.py" />
    </body>
</html>
Poznámka: v tomto případě opět musíme aplikaci spustit přes webový server.

11. Výpis informací funkcí print

Pokusme se nyní do HTML stránky vložit poněkud složitější kód. Konkrétně se bude jednat o výpočet prvočísel ze zadaného rozsahu (zde konkrétně v rozsahu od 2 do 100). Výsledek opět vypíšeme standardní Pythonovskou funkcí print na plochu webové stránky:

<html>
    <head>
        <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
        <script defer src="https://pyscript.net/latest/pyscript.js"></script>
    </head>
    <body>
        <py-script>
            # original code
            # http://www.rosettacode.org/wiki/Sieve_of_Eratosthenes#Using_array_lookup
            def primes2(limit):
                is_prime = [False] * 2 + [True] * (limit - 1)
                for n in range(int(limit ** 0.5 + 1.5)):  # stop at ``sqrt(limit)``
                    if is_prime[n]:
                        for i in range(n * n, limit + 1, n):
                            is_prime[i] = False
                return [i for i, prime in enumerate(is_prime) if prime]
 
 
            print(primes2(100))
        </py-script>
    </body>
</html>

Výpočet sice dopadl korektně, ovšem způsob zobrazení výsledků asi není příliš pěkný, o čemž se můžeme snadno přesvědčit:

Obrázek 14: Výsledek výpočtů provedených předchozím příkladem.

12. Zápis výsledků do vybraného elementu HTML stránky

Výsledek činnosti nějakého výpočtu provedeného v Pythonu můžeme v PyScriptu uložit do libovolné HTML značky, což je operace, která je původně implementována funkcí pyscript.write, které se předá jméno elementu a vlastní výsledek (což musí být řetězec). Na další HTML stránce máme vytvořen odstavec s identifikátorem result, do kterého všechna vypočtená prvočísla zapíšeme:

<html>
    <head>
        <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
        <script defer src="https://pyscript.net/latest/pyscript.js"></script>
    </head>
    <body>
        <div id="result" style="font-size:150%;color:#800000">
        </div>
 
        <py-script>
            # original code
            # http://www.rosettacode.org/wiki/Sieve_of_Eratosthenes#Using_array_lookup
            def primes2(limit):
                is_prime = [False] * 2 + [True] * (limit - 1)
                for n in range(int(limit ** 0.5 + 1.5)):  # stop at ``sqrt(limit)``
                    if is_prime[n]:
                        for i in range(n * n, limit + 1, n):
                            is_prime[i] = False
                return [i for i, prime in enumerate(is_prime) if prime]
 
 
            primes = primes2(100)
            asStrings = map(str, primes)
            pyscript.write('result', ", ".join(asStrings))
        </py-script>
    </body>
</html>

S tímto výsledkem:

Obrázek 15: Vypočtená prvočísla zapsaná do elementu result.

V nové verzi PyScriptu by se však měla pro stejnou operaci používat metoda write pro objekt získaný konstruktorem Element. Nyní se tedy preferuje tento styl zápisu:

<html>
    <head>
        <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
        <script defer src="https://pyscript.net/latest/pyscript.js"></script>
    </head>
    <body>
        <div id="result" style="font-size:150%;color:#800000">
        </div>
 
        <py-script>
            # original code
            # http://www.rosettacode.org/wiki/Sieve_of_Eratosthenes#Using_array_lookup
            def primes2(limit):
                is_prime = [False] * 2 + [True] * (limit - 1)
                for n in range(int(limit ** 0.5 + 1.5)):  # stop at ``sqrt(limit)``
                    if is_prime[n]:
                        for i in range(n * n, limit + 1, n):
                            is_prime[i] = False
                return [i for i, prime in enumerate(is_prime) if prime]
 
 
            primes = primes2(100)
            asStrings = map(str, primes)
            Element('result').write(", ".join(asStrings))
        </py-script>
    </body>
</html>

A samozřejmě je možné samotný výpočet:

# original code
# http://www.rosettacode.org/wiki/Sieve_of_Eratosthenes#Using_array_lookup
def primes2(limit):
    is_prime = [False] * 2 + [True] * (limit - 1)
    for n in range(int(limit ** 0.5 + 1.5)):  # stop at ``sqrt(limit)``
        if is_prime[n]:
            for i in range(n * n, limit + 1, n):
                is_prime[i] = False
    return [i for i, prime in enumerate(is_prime) if prime]

…zcela oddělit od HTML stránky. Nyní ovšem namísto atributu src budeme specifikovat cestu ke skriptu ve značce py-env, a to takto:

<html>
    <head>
        <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
        <script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-env>
        - paths:
          - ./primes.py
</py-env>
    </head>
    <body>
        <div id="result" style="font-size:150%;color:#800000"></div>
 
        <py-script>
            from primes import *
 
            primes = primes2(100)
            asStrings = map(str, primes)
            Element('result').write(", ".join(asStrings))
        </py-script>
    </body>
</html>
Poznámka: povšimněte si nutnost použít příkaz import.

Obrázek 16: Prakticky stejný výsledek: vypočtená prvočísla zapsaná do elementu result.

13. Podpora knihovny NumPy v projektu PyScript

V PyScriptu je možné využívat poměrně velké množství populárních Pythonovských knihoven, mezi jinými i knihovnu NumPy. A to ve skutečnosti není tak triviální podpora, jak by se mohlo na první pohled zdát, protože knihovna NumPy kromě kódu v Pythonu obsahuje i množství nativního kódu, který je nutné přeložit do bajtkódu WebAssembly. Pokud budeme chtít na webové stránce provést nějaký výpočet založený na NumPy, je nejdříve (ideálně v hlavičce) nutné uvést následující deklaraci, která zajistí načtení všech potřebných souborů:

<py-env>
    - numpy
</py-env>
Poznámka: do této značky se zapisuje konfigurace ve formátu YAML, takže si zejména ve složitějších případech musíme dát pozor na správné odsazení jednotlivých řádků.

Pro zajímavost se podívejme, jaké soubory se v tomto případě musí do prostředí webového prohlížeče načíst:

Obrázek 17: Mezi dalšími soubory se načetla i knihovna NumPy přeložená do WebAssembly.

14. Maticový součin realizovaný v knihovně NumPy

Vyzkoušejme si nyní použití knihovny NumPy v praxi. V prvním příkladu je realizován maticový součin, přičemž se zobrazí matice v takovém formátu, jak je implicitně převedena na řetězec metodou __str__.

<html>
    <head>
        <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
        <script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-env>
    - numpy
</py-env>
    </head>
    <body>
        <div id="result" style="font-size:150%;color:#800000"></div>
 
        <py-script>
            import numpy as np
 
            p = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
            q = [[3, 2, 1], [6, 5, 4], [3, 2, 1]]
 
            result = np.dot(p, q)
 
            Element('result').write(result)
        </py-script>
    </body>
</html>

Obrázek 18: Výpočet maticového součinu.

15. Zobrazení matice v čitelné podobě

Matice, kterou jsme si ukázali na osmnáctém obrázku, není příliš čitelná (a to se jedná o pole s pouhými dvěma dimenzemi). Pro čitelnější výstup lze použít například funkci numpy.array_str nabízenou přímo knihovnou NumPy. Tato funkce převede matici do podoby řetězce, jenž můžeme předat metodě write:

Element('result').write(np.array_str(result))

V takovém případě je ale nutné zajistit, že daný HTML element dokáže správně interpretovat konce řádků. To platí pro element (značku) pre, takže ji použijeme (s korektním ID):

<pre id="result" style="font-size:150%;color:#800000"></pre>

Výsledek:

Obrázek 19: Zobrazení obsahu matice (2D pole) v lidsky čitelné podobě.

Celý zdrojový kód takto upraveného příkladu vypadá následovně:

<html>
    <head>
        <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
        <script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-env>
    - numpy
</py-env>
    </head>
    <body>
        <pre id="result" style="font-size:150%;color:#800000"></pre>
 
        <py-script>
            import numpy as np
 
            p = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
            q = [[3, 2, 1], [6, 5, 4], [3, 2, 1]]
 
            result = np.dot(p, q)
 
            Element('result').write(np.array_str(result))
        </py-script>
    </body>
</html>

16. Zápis matic do většího množství HTML elementů

Samozřejmě můžeme na plochu HTML stránky zobrazit větší počet výsledků například obě matice, které se násobí, a pod nimi i výslednou matici. Nejdříve si připravíme příslušné HTML značky (viz jejich ID):

<h1>p</h1>
<pre id="p" style="color:#000080"></pre>
 
<h1>q</h1>
<pre id="q" style="color:#000080"></pre>
 
<h1>result</h1>
<pre id="result" style="color:#800000"></pre>

Zápis výsledků do všech tří značek (elementů) je v tomto případě triviální:

Element('p').write(np.array_str(np.array(p)))
Element('q').write(np.array_str(np.array(q)))
Element('result').write(np.array_str(result))

S tímto výsledkem:

Obrázek 20: Obě matice, které se násobí a současně i výsledek maticového součinu.

Celý zdrojový kód takto upraveného demonstračního příkladu vypadá následovně:

<html>
    <head>
        <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
        <script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-env>
    - numpy
</py-env>
    </head>
    <body>
        <h1>p</h1>
        <pre id="p" style="color:#000080"></pre>
 
        <h1>q</h1>
        <pre id="q" style="color:#000080"></pre>
 
        <h1>result</h1>
        <pre id="result" style="color:#800000"></pre>
 
        <py-script>
            import numpy as np
 
            p = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
            q = [[3, 2, 1], [6, 5, 4], [3, 2, 1]]
 
            result = np.dot(p, q)
 
            Element('p').write(np.array_str(np.array(p)))
            Element('q').write(np.array_str(np.array(q)))
            Element('result').write(np.array_str(result))
        </py-script>
    </body>
</html>

17. Zobrazení grafů vykreslených knihovnou Matplotlib přímo do plochy webové stránky

Kromě knihovny NumPy je možné v PyScriptu využívat i mnohé další knihovny. Jedná se zejména o známou knihovnu Matplotlib, o níž jsme se zmínili například v článku Tvorba grafů v Jupyter Notebooku s využitím knihovny Matplotlib. Aby bylo možné tuto knihovnou skutečně použít, je nutné do značky py-env přidat zvýrazněný řádek:

<py-env>
    - numpy
    - matplotlib
</py-env>

Zajímavé je, že graf se nemusí vykreslit metodou plt.show() (ta v tomto kontextu ani nefunguje). Ovšem postačuje, aby výsledkem skriptu zapsaného do značky py-script byl graf Matplotlibu – a ten je posléze automaticky vykreslen. Povšimněte si, že ve značce py-script je skutečně posledním výrazem pouze plt, což je objekt reprezentující graf, který chceme vykreslit:

<py-script>
    import numpy as np
    import matplotlib.pyplot as plt
 
    x = np.linspace(0, 2 * np.pi, 100)
 
    y = np.sin(x)
 
    plt.plot(x, y)
 
    plt.xlabel("x")
    plt.ylabel("sin(x)")
 
    plt
</py-script>

Obrázek 21: Graf vykreslený přímo do plochy HTML stránky.

Poznámka: někdy je situace poněkud složitější, což si ukážeme příště.

Celý zdrojový kód popsaného demonstračního příkladu vypadá následovně:

skolení ELK

<html>
    <head>
        <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
        <script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-env>
    - numpy
    - matplotlib
</py-env>
    </head>
    <body>
        <py-script>
            import numpy as np
            import matplotlib.pyplot as plt
 
            x = np.linspace(0, 2 * np.pi, 100)
 
            y = np.sin(x)
 
            plt.plot(x, y)
 
            plt.xlabel("x")
            plt.ylabel("sin(x)")
 
            plt
        </py-script>
    </body>
</html>

18. Modifikace DOMu stránky z PyScriptu, reakce na události

Prakticky všechny demonstrační příklady, které jsme si v dnešním článku až doposud ukázali, komunikovaly s HTML stránkou (tedy s jejím DOMem – objektovým modelem dokumentu) pouze nepřímo. Dokážeme zapsat výsledek nějakého výpočtu do zvoleného prvku (HTML značky) a zobrazit graf vykreslený Matplotlibem. Poněkud více interaktivity nabízí značka <py-repl>, jejíž chování je však již dopředu naprogramováno a nelze ho příliš modifikovat (alespoň ne v současných verzích PyScriptu). V reálných front endových aplikacích je však v naprosté většině případů nutné s DOMem stránky interagovat velmi často, popř. musí skript reagovat na události, které vznikají při běhu aplikace na webové stránce (kliknutí myší atd.). Jedná se o zajímavou problematiku, které se budeme podrobněji věnovat v navazující části tohoto článku.

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 spuštění ve webovém prohlížeči s využitím nástroje PyScript 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 hello1.html program „Hello, World“ vytvořený pro PyScript https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/hello1.html
2 hello2.html spuštění externího skriptu načteného přes src https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/hello2.html
3 hello3.html kratší zápis značky py-script https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/hello3.html
4 hello.py skript načtený předchozími dvěma příklady https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/hello.py
       
5 loop1.html smyčka v Pythonu zapsaná přímo do HTML stránky https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/loop1.html
6 loop2.html spuštění externího skriptu načteného přes src https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/loop2.html
7 loop3.html kratší zápis značky py-script https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/loop3.html
8 loop.py skript načtený předchozími dvěma příklady https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/loop.py
       
9 primes1.html výpočet řady prvočísel s jejich zobrazením https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/primes1.html
10 primes2.html výpočet řady prvočísel, zápis výsledku do vybraného elementu HTML stránky https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/primes2.html
11 primes3.html výpočet řady prvočísel, vylepšený zápis výsledku do vybraného elementu HTML stránky https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/primes3.html
12 primes4.html specifikace cesty k lokálnímu modulu https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/primes4.html
13 primes.py skript načtený předchozím příkladem https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/primes.py
       
14 numpy1.html využití knihovny NumPy, zobrazení maticového součinu https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/numpy1.html
15 numpy2.html využití knihovny NumPy, pretty printing matice https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/numpy2.html
16 numpy3.html využití knihovny NumPy, zobrazení většího množství matic https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/numpy3.html
       
17 matplotlib1.html liniový graf vykreslený knihovnou Matplotlib https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/mat­plotlib1.html
18 matplotlib2.html konturový graf vykreslený knihovnou Matplotlib https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/mat­plotlib2.html
19 matplotlib3.html trojrozměrný graf vykreslený knihovnou Matplotlib https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/mat­plotlib3.html
       
20 repl1.html zobrazení REPLu na webové stránce https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/repl1.html
21 repl2.html zobrazení REPLu na webové stránce, předvyplnění zdrojového kódu https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/repl2.html
22 repl3.html zobrazení REPLu na webové stránce, využití kombinace NumPy + Matplotlib https://github.com/tisnik/most-popular-python-libs/blob/master/pyscript/repl3.html

20. Odkazy na Internetu

  1. PyScript
    https://pyscript.net/
  2. PyScript na GitHubu
    https://github.com/pyscript/pyscript
  3. Getting started with PyScript
    https://github.com/pyscrip­t/pyscript/blob/main/docs/tu­torials/getting-started.md
  4. PyScript examples
    https://github.com/pyscrip­t/pyscript/tree/main/exam­ples
  5. What is PyScript
    https://docs.pyscript.net/la­test/concepts/what-is-pyscript.html
  6. Pyodide
    https://pyodide.org/en/stable/
  7. Compiling to WebAssembly: It’s Happening!
    https://hacks.mozilla.org/2015/12/com­piling-to-webassembly-its-happening/
  8. WebAssembly
    https://webassembly.org/
  9. Blogy o WASM a Emscripten
    https://www.jamesfmackenzi­e.com/sitemap/#emscripten
  10. wat2wasm demo
    https://webassembly.github­.io/wabt/demo/wat2wasm/
  11. WABT: The WebAssembly Binary Toolkit
    https://github.com/WebAssembly/wabt
  12. Programming using Web Assembly
    https://medium.com/@alexc73/pro­gramming-using-web-assembly-c4c73a4e09a9
  13. Experiments with image manipulation in WASM using Go
    https://agniva.me/wasm/2018/06/18/shim­mer-wasm.html
  14. Fable
    https://fable.io/
  15. Využití WebAssembly z programovacího jazyka Go
    https://www.root.cz/clanky/vyuziti-webassembly-z-programovaciho-jazyka-go/
  16. WebAssembly prošlo standardizací ve W3C, byla vydána verze 1.0
    https://www.root.cz/zpravic­ky/webassembly-proslo-standardizaci-ve-w3c-byla-vydana-verze-1–0/
  17. WebAssembly na Wiki Golangu
    https://github.com/golang/go/wi­ki/WebAssembly
  18. The future of WebAssembly – A look at upcoming features and proposals
    https://blog.scottlogic.com/2018/07/20/wasm-future.html
  19. Writing WebAssembly By Hand
    https://blog.scottlogic.com/2018/04/26/we­bassembly-by-hand.html
  20. WebAssembly Specification
    https://webassembly.github­.io/spec/core/index.html
  21. Index of Instructions
    https://webassembly.github­.io/spec/core/appendix/in­dex-instructions.html
  22. The WebAssembly Binary Toolkit
    https://github.com/WebAssembly/wabt
  23. Will WebAssembly replace JavaScript? Or Will WASM Make JavaScript More Valuable in Future?
    https://dev.to/vaibhavshah/will-webassembly-replace-javascript-or-will-wasm-make-javascript-more-valuable-in-future-5c6e
  24. Roadmap (pro WebAssemly)
    https://webassembly.org/roadmap/
  25. S-expression
    https://en.wikipedia.org/wiki/S-expression
  26. Understanding WebAssembly text format
    https://developer.mozilla.org/en-US/docs/WebAssembly/Under­standing_the_text_format
  27. Learning Golang through WebAssembly – Part 1, Introduction and setup
    https://www.aaron-powell.com/posts/2019–02–04-golang-wasm-1-introduction/
  28. Learning Golang through WebAssembly – Part 2, Writing your first piece of Go
    https://www.aaron-powell.com/posts/2019–02–05-golang-wasm-2-writing-go/
  29. Learning Golang through WebAssembly – Part 3, Interacting with JavaScript from Go
    https://www.aaron-powell.com/posts/2019–02–06-golang-wasm-3-interacting-with-js-from-go/
  30. Golang webassembly (wasm) testing with examples
    https://jelinden.fi/blog/golang-webassembly-wasm-testing-with-examples/qB7Tb2KmR
  31. Use Cases (of WebAssembly)
    https://webassembly.org/docs/use-cases/
  32. JupyterLite na PyPi
    https://pypi.org/project/jupyterlite/
  33. JupyterLite na GitHubu
    https://github.com/jupyter­lite/jupyterlite
  34. Dokumentace k projektu JupyterLite
    https://github.com/jupyter­lite/jupyterlite
  35. A quick guide about Python implementations
    https://blog.rmotr.com/a-quick-guide-about-python-implementations-aa224109f321
  36. How Brython works
    https://github.com/brython-dev/brython/wiki/How%20Brython%20works
  37. Brython – A Python 3 implementation for client-side web programming
    http://www.brython.info/
  38. Brython videos and talks
    https://github.com/brython-dev/brython/wiki/Brython-videos-and-talks
  39. What is Brython?
    https://medium.com/frontendweb/what-is-brython-6edb424b07f6
  40. Python in browser (tabulka s porovnáními)
    http://stromberg.dnsalias­.org/~strombrg/pybrowser/pyt­hon-browser.html
  41. JavaScript is Assembly Language for the Web: Sematic Markup is Dead! Clean vs. Machine-coded HTML
    http://www.hanselman.com/blog/Ja­vaScriptIsAssemblyLanguage­ForTheWebSematicMarkupIsDe­adCleanVsMachinecodedHTML­.aspx
  42. pyscript VS brython
    https://www.libhunt.com/compare-pyscript-vs-brython
  43. PyScript – Run Python in the Browser! THE END of JavaScript???
    https://www.youtube.com/wat­ch?v=du8vQC44PC4
  44. PyScript is Python in Your Browser
    https://www.youtube.com/wat­ch?v=MJvCeKwr4z4
  45. JupyterLite na PyPi
    https://pypi.org/project/jupyterlite/
  46. JupyterLite na GitHubu
    https://github.com/jupyter­lite/jupyterlite
  47. Dokumentace k projektu JupyterLite
    https://github.com/jupyter­lite/jupyterlite
  48. Matplotlib Home Page
    http://matplotlib.org/
  49. Matplotlib (Wikipedia)
    https://en.wikipedia.org/wi­ki/Matplotlib
  50. Popis barvových map modulu matplotlib.cm
    https://gist.github.com/en­dolith/2719900#id7
  51. Ukázky (palety) barvových map modulu matplotlib.cm
    http://matplotlib.org/exam­ples/color/colormaps_refe­rence.html
  52. Galerie grafů vytvořených v Matplotlibu
    https://matplotlib.org/3.2.1/gallery/

Autor článku

Pavel Tišnovský vystudoval VUT FIT a v současné době pracuje ve společnosti Red Hat, kde vyvíjí nástroje pro OpenShift.io.