Obsah
1. PyScript: další technologie umožňující využití Pythonu ve webovém browseru (dokončení)
2. Složitější graf vykreslený knihovnou Matplotlib: kontury funkce z=f(x,y)
3. Zobrazení grafu s konturami funkce na ploše webové stránky
4. Zobrazení 3D grafu s průběhem funkce z=f(x,y)
5. 3D graf vykreslený na ploše webové stránky
6. Změna velikosti obrázku s grafem
7. Pythonovská interaktivní smyčka REPL přímo na ploše webové stránky
8. REPL bez předvyplněného obsahu (základní podoba značky py-repl)
9. Vstupní prvek smyčky REPL s předvyplněným obsahem
10. Interaktivní vykreslování grafů na webové stránce
11. Interaktivní změna parametrů zobrazovaných funkcí
12. Reakce na události: zavolání funkce naprogramované v Pythonu po stisku tlačítka
13. Propojení události s Pythonovským kódem přes proxy
14. Přečtení hodnoty uložené v interaktivním prvku webové stránky
15. Zobrazení grafu po stisku tlačítka na webové stránce
16. Interaktivní prvky pro zadání amplitudy, frekvence a posunutí zobrazované funkce
17. Úprava předchozího demonstračního příkladu – reakce na nesprávné vstupy
19. Repositář s demonstračními příklady
1. PyScript: další technologie umožňující využití Pythonu ve webovém browseru (dokončení)
Na úvodní článek o projektu PyScript, který umožňuje přímo do webových stránek vkládat kód zapsaný v Pythonu, dnes navážeme. Nejprve dokončíme část věnovanou tvorbě grafů s využitím knihovny Matplotlib a následně si ukážeme použití nové značky nazvané py-repl, která umožňuje vložit do plochy webové stránky Pythonovskou interaktivní smyčku REPL, což mnohdy znamená velkou podporu; například se nemusí tolik investovat do GUI formulářů (navíc může být vstup již předvyplněn nebo je možné do stránky vložit větší množství REPLů). Ovšem skripty psané v Pythonu musí nějakým způsobem komunikovat přímo s webovou stránkou i reagovat na akce prováděné uživatelem (tedy musí reagovat na události). Zde je již nutné alespoň do určité míry využít JavaScript a propojit tak zdroj události s Pythonovskou funkcí přes takzvanou proxy.

Obrázek 1: Graf vykreslený přímo do plochy HTML stránky – využití Pythonu, NumPy a Matplotlibu.
2. Složitější graf vykreslený knihovnou Matplotlib: kontury funkce z=f(x,y)
První způsob zobrazení funkce typu z=f(x,y) spočívá ve vykreslení takzvaných kontur, které si pro zjednodušení můžeme představit jako vrstevnice na mapě – body spojené konturou/vrstevnicí mají stejnou hodnotu funkce (tj. stejnou hodnotu z-ové souřadnice; řekněme výšky). Při vyhodnocování a následném vykreslení funkce budeme postupovat následovně:
- Vytvoříme vektor s hodnotami nezávislé proměnné x.
- Vytvoříme vektor s hodnotami nezávislé proměnné y.
- S využitím funkce numpy.meshgrid necháme vygenerovat dvojici matic souřadnic [x,y].
- Necháme vypočítat body ležící na ploše funkce (z-ové souřadnice se uloží do matice Z).
- Vlastní vykreslení kontur zajistí funkce matplotlib.pyplot.contour(X, Y, Z).
Skript zapsaný v čistém Pythonu by mohl vypadat následovně:
import matplotlib import numpy as np import matplotlib.cm as cm import matplotlib.mlab as mlab import matplotlib.pyplot as plt delta = 0.1 # průběh nezávislé proměnné x x = np.arange(-10.0, 10.0, delta) # průběh nezávislé proměnné y y = np.arange(-10.0, 10.0,delta) # vytvoření dvou polí se souřadnicemi [x,y] X, Y = np.meshgrid(x, y) # vzdálenost od bodu [0,0] R1 = np.sqrt(X*X+Y*Y) # vzdálenost od bodu [3,3] R2 = np.sqrt((X-3)*(X-3)+(Y-3)*(Y-3)) # výpočet funkce, kterou použijeme při vykreslování grafu Z = np.sin(R1)-np.cos(R2) # povolení zobrazení mřížky plt.grid(True) # vytvoření grafu s konturami funkce z=f(x,y) plt.contour(X, Y, Z) # zobrazení grafu plt.show()

Obrázek 2: Výsledný graf.

Obrázek 3: V případě, že je hodnota „delta“ příliš vysoká, vypočte se menší počet bodů tvořících plochu funkce, takže i kontury budou vykresleny velmi nepřesně (knihovna bude mít k dispozici jen málo bodů, které bude moci spojit).
3. Zobrazení grafu s konturami funkce na ploše webové stránky
Skript z předchozí kapitoly můžeme snadno umístit na webovou stránku a nechat si ho spustit ve webovém prohlížeči. Využijeme přitom dvě nové značky, tedy py-env i py-script. První z těchto značek obsahuje konfiguraci, zde konkrétně specifikaci, které knihovny budeme využívat. V našem konkrétním případě bude obsah této značky následující:
<py-env> - numpy - matplotlib </py-env>
Do značky py-script vložíme celý skript, přičemž namísto volání plt.show() bude posledním příkazem ve skriptu pouze výraz plt (bez return). Právě tento příkaz po svém vyhodnocení povede k zobrazení grafu:
<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 matplotlib import numpy as np import matplotlib.cm as cm import matplotlib.mlab as mlab import matplotlib.pyplot as plt delta = 0.1 x = np.arange(-10.0, 10.0, delta) y = np.arange(-10.0, 10.0, delta) X, Y = np.meshgrid(x, y) R1 = np.sqrt(X * X + Y * Y) R2 = np.sqrt((X - 3) * (X - 3) + (Y - 3) * (Y - 3)) Z = np.sin(R1) - np.cos(R2) plt.grid(True) plt.contour(X, Y, Z) plt </py-script> </body> </html>
Načítání a inicializace stránky bude v tomto případě pomalejší:

Obrázek 4: Načítání a inicializace stránky.
Po několika sekundách by se ovšem měl graf zobrazit a měl by vypadat následovně:

Obrázek 5: Graf vykreslený do plochy stránky.
4. Zobrazení 3D grafu s průběhem funkce z=f(x,y)
Použití grafu s konturami sice může být v mnoha ohledech velmi užitečné (například při zjišťování lokálních minim a maxim i při potřebě nezkresleného pohledu na průběh funkce), v praxi se však spíše setkáme s odlišným typem grafů zobrazujících funkce typu z=f(x,y). Jedná se o trojrozměrné grafy, v nichž se zobrazuje plocha funkce. Nejjednodušším typem tohoto grafu je takzvaný drátový model, který je spíše známý pod svým anglickým názvem wireframe.

Obrázek 6: Drátový model.
V tomto typu grafu je zobrazena série křivek či spíše lomených čar. Jedna série je vypočtena takovým způsobem, že x-ová souřadnice se postupně mění v nastaveném intervalu zatímco y-ová souřadnice je konstantní. Druhá série lomených čar se vykresluje kolmo na sérii první, tj. x-ová souřadnice je konstantní a postupně se mění hodnota y-ových souřadnic. Výsledkem je tedy plocha, která má při pohledu z osy z tvar pravidelné mřížky. Pro vykreslení tohoto typu grafu se používá funkce nazvaná plot_wireframe(), které se předá trojice polí odpovídajících x-ovým, y-ovým a z-ovým souřadnicím bodů ležících na ploše představujících obraz funkce:
from mpl_toolkits.mplot3d import axes3d import matplotlib.pyplot as plt import numpy as np fig = plt.figure() ax = fig.add_subplot(111, projection='3d') delta = 0.1 # průběh nezávislé proměnné x x = np.arange(-10.0, 10.0, delta) # průběh nezávislé proměnné y y = np.arange(-10.0, 10.0, delta) # vytvoření dvou polí se souřadnicemi [x,y] X, Y = np.meshgrid(x, y) # vzdálenost od bodu [0,0] R = np.sqrt(X*X+Y*Y) # výpočet funkce, kterou použijeme při vykreslování grafu Z = np.sin(R)/R # zobrazení 3D grafu ax.plot_wireframe(X, Y, Z, rstride=7, cstride=7) # zobrazení grafu plt.show()

Obrázek 7: Výsledný graf vykreslený předchozím skriptem, změna hustoty mřížky.
Drátový model je možné v případě potřeby nahradit vykreslením vyplněné plochy namísto pouhé mřížky. V tomto případě je nutné namísto funkce plot_wireframe() použít funkci pojmenovanou plot_surface(). První tři povinné parametry obou zmíněných funkcí jsou shodné, dokonce lze použít i stejně pojmenované parametry cstride a rstride, o jejichž významu jsme se taktéž zmiňovali v předchozích kapitolách. Kromě toho se však navíc většinou používá i další pojmenovaný parametr cmap, kterému se předá barvová paleta (či barvová mapa), která typicky definuje jeden gradientní přechod i větší množství gradientních přechodů mezi různými odstíny. Pro účely vytváření gradientních přechodů či pro použití již předem připravených barvových map se používá specializovaný modul nazvaný matplotlib.cm (color map). Seznam všech předdefinovaných barvových map naleznete na adrese https://gist.github.com/endolith/2719900#id7, ukázky (palety) pak na adrese http://matplotlib.org/examples/color/colormaps_reference.html. My využijeme barvovou mapu pojmenovanou „coolwarm“:

Obrázek 8: Výsledný graf vykreslený předchozím skriptem.
Vzhledem k tomu, že se pro zobrazení trojrozměrného grafu na 2D obrazovce musí používat axonometrické promítání popř. promítání s perspektivou, nemusí být z výsledného obrázku s grafem na první pohled zřejmé, jak přesně vlastně průběh funkce vypadá. Knihovna Matplotlib nám však nabízí určité řešení tohoto problému – na plochy (které jsou kolmé na osy souřadného systému) se promítnou kontury průběhu funkce. Podívejme se nejdříve na to, jak vypadá výsledek:

Obrázek 9: Výsledný graf vykreslený předchozím skriptem.
Samotná plocha představující funkci se vykreslí příkazem (přesněji řečeno funkcí) matplotlib.pyplot.plot_surface(), podobně jako v předchozím příkladu. Dále se metodou ax.contour() mohou vykreslit kontury grafu na jednotlivé plochy, ve skutečnosti je však ještě nutné korektně nastavit přesné umístění těchto kontur do grafu. K tomu slouží explicitní nastavení rozsahů na jednotlivých osách (set_xlim(), set_ylim(), set_zlim()) a vlastní posun reprezentovaný pojmenovaným parametrem offset předaným do metody ax.contour(). Podívejme se na odladěný příklad:
from mpl_toolkits.mplot3d import axes3d from matplotlib import cm import matplotlib.pyplot as plt import numpy as np fig = plt.figure() ax = fig.gca(projection='3d') delta = 0.1 # průběh nezávislé proměnné x x = np.arange(-10.0, 10.0, delta) # průběh nezávislé proměnné y y = np.arange(-10.0, 10.0, delta) # vytvoření dvou polí se souřadnicemi [x,y] X, Y = np.meshgrid(x, y) # vzdálenost od bodu [0,0] R = np.sqrt(X*X+Y*Y) # výpočet funkce, kterou použijeme při vykreslování grafu Z = np.sin(R)/R # zobrazení 3D grafu formou plochy surface = ax.plot_surface(X, Y, Z, rstride=2, cstride=2, cmap=cm.coolwarm, linewidth=0, antialiased=False) # kontutra: průmět na rovinu x-y cset = ax.contour(X, Y, Z, zdir='z', offset=-5, cmap=cm.coolwarm) # kontutra: průmět na rovinu y-z cset = ax.contour(X, Y, Z, zdir='x', offset=-15, cmap=cm.coolwarm) # kontutra: průmět na rovinu x-z cset = ax.contour(X, Y, Z, zdir='y', offset=15, cmap=cm.coolwarm) # rozměry grafu ve směru osy x ax.set_xlabel('X') ax.set_xlim(-15, 15) # rozměry grafu ve směru osy y ax.set_ylabel('Y') ax.set_ylim(-15, 15) # rozměry grafu ve směru osy z ax.set_zlabel('Z') ax.set_zlim(-5, 5) # zobrazení grafu plt.show()
5. 3D graf vykreslený na ploše webové stránky
Výše uvedený skript je již velmi snadné přenést do webové stránky. Buď můžeme skript vložit přímo do HTML kódu, jak je to ostatně ukázáno níže, nebo je možné skript načíst přes pyscript src="plot3.py, což však již vyžaduje použití nějakého HTTP serveru, a to i při vývoji (popř. je nutné změnit konfiguraci webového prohlížeče, aby neblokoval načítání lokálních souborů):
<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> from matplotlib import cm import matplotlib.pyplot as plt import numpy as np fig = plt.figure() ax = fig.gca(projection="3d") delta = 0.1 x = np.arange(-10.0, 10.0, delta) y = np.arange(-10.0, 10.0, delta) X, Y = np.meshgrid(x, y) R = np.sqrt(X * X + Y * Y) Z = np.sin(R) / R surface = ax.plot_surface( X, Y, Z, rstride=2, cstride=2, cmap=cm.coolwarm, linewidth=0, antialiased=False ) cset = ax.contour(X, Y, Z, zdir="z", offset=-5, cmap=cm.coolwarm) cset = ax.contour(X, Y, Z, zdir="x", offset=-15, cmap=cm.coolwarm) cset = ax.contour(X, Y, Z, zdir="y", offset=15, cmap=cm.coolwarm) ax.set_xlabel("X") ax.set_xlim(-15, 15) ax.set_ylabel("Y") ax.set_ylim(-15, 15) ax.set_zlabel("Z") ax.set_zlim(-5, 5) plt </py-script> </body> </html>

Obrázek 10: 3D graf zobrazený přímo na ploše webové stránky.
6. Změna velikosti obrázku s grafem
Nyní si ukažme, jakým způsobem je možné změnit velikost grafu, resp. rozlišení rastrového obrázku, do kterého se graf vykreslí. Jak jste si mohli povšimnout, je implicitní velikost obrázků s grafem na dnešní poměry relativně malá. Graf je však možné zvětšit, konkrétně vypnutím takzvaných bounding boxů a především nastavením velikosti v palcích:
plt.figure(1, figsize=(8,6), dpi=100)
Upravená HTML stránka, která by měla při svém načtení do webového prohlížeče zobrazit zvětšený graf kontury funkce, 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 - matplotlib </py-env> </head> <body> <py-script> import matplotlib import numpy as np import matplotlib.cm as cm import matplotlib.mlab as mlab import matplotlib.pyplot as plt plt.figure(1, figsize=(8,8), dpi=100) delta = 0.1 x = np.arange(-10.0, 10.0, delta) y = np.arange(-10.0, 10.0, delta) X, Y = np.meshgrid(x, y) R1 = np.sqrt(X * X + Y * Y) R2 = np.sqrt((X - 3) * (X - 3) + (Y - 3) * (Y - 3)) Z = np.sin(R1) - np.cos(R2) plt.grid(True) plt.contour(X, Y, Z) plt </py-script> </body> </html>
Povšimněte si, že nyní se již celý graf ani nevejde na plochu mého monitoru:

Obrázek 11: Graf s konturami funkce zobrazený přímo na ploše webové stránky.
7. Pythonovská interaktivní smyčka REPL přímo na ploše webové stránky
S principem a použitím interaktivní smyčky REPL jsme se již na stránkách Rootu setkali, a to dokonce mnohokrát. Kromě článků, které se věnovaly klasickým Unixovým shellům typu BASH, tcsh či zsh (a rozhraní shellů není nic jiného, než interaktivní REPL), jsme smyčku REPL použili například při popisu programovacího jazyka Julia či jazyka Clojure. Historií vzniku REPL jsme se zabývali i zde.
PyScript taktéž umožňuje přímo na plochu webové stránky vložit REPL resp. přesněji řečeno vstupní pole, do kterého je možné zapisovat příkazy, které se ihned poté vykonají. Dokonce je možné na stránku vložit větší množství REPLů. Jedná se tak vlastně o mezikrok mezi klasickými REPLy a diáři (notebooky), mezi které patří i známý Jupyter Notebook.
8. REPL bez předvyplněného obsahu (základní podoba značky py-repl)
Nejjednodušší forma vstupního prvku pro interaktivní smyčku REPL může vypadat následovně. Povšimněte si, že jsme do stránky vložili pouze prázdnou značku py-repl:
<html> <head> <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <script defer src="https://pyscript.net/latest/pyscript.js"></script> </head> <h1>Python REPL</h1> <py-repl></py-repl> </html>
Výsledkem bude stránka, která by po své inicializaci měla vypadat následovně:

Obrázek 12: Webová stránka se zobrazeným vstupem do smyčky REPL.
Přes REPL si pochopitelně můžeme nechat vyhodnotit nějaký jednoduchý výraz:

Obrázek 13: Vyhodnocení jednoduchého výrazu ve smyčce REPL.
Zápis programového bloku na více řádcích je taktéž možný. Zde však již musíme explicitně zapsat příkaz print, protože by se zobrazil jen výsledek posledního výrazu (což je None):

Obrázek 14: Programová smyčka zapsaná na dvou řádcích v REPLu.
Ukázka, že je skutečně zobrazen výsledek posledního výrazu:

Obrázek 15: Zobrazení výsledku posledního výrazu.
9. Vstupní prvek smyčky REPL s předvyplněným obsahem
Vzhledem k tomu, že py-repl je párovou značkou, nic nám nezabraňuje v tom, abychom do ní přímo zapsali nějaký obsah – tedy běžný Pythonovský skript nebo jeho část. To se hodí například pro výukové účely atd.:
<html> <head> <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <script defer src="https://pyscript.net/latest/pyscript.js"></script> </head> <h1>Python REPL</h1> <py-repl> def run(): for i in range(1,11): print(i) run() </py-repl> </html>
Výsledná webová stránka bude nyní vypadat následovně:

Obrázek 16: Smyčka REPL s předvyplněným obsahem.
Po stisku zelené šipky nebo po použití klávesové zkratky Shift+Enter se kód zapsaný v REPLu spustí a vyhodnotí:

Obrázek 17: Spuštění a vyhodnocení kódu zapsaného do REPLu.
10. Interaktivní vykreslování grafů na webové stránce
Nic nám pochopitelně nebrání ve zkombinování možností poskytovaných značkami py-env a py-repl. V dalším demonstračním příkladu je ukázáno, jakým způsobem lze předvyplnit REPL kódem, který po svém spuštění vykreslí do plochy webové stránky graf:
<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> <h1>Python REPL</h1> <py-repl> 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-repl> </html>
Výsledek bude vypadat následovně:

Obrázek 18: Předvyplněná smyčka REPL-

Obrázek 19: Graf vykreslený po spuštění kódu ve smyčce REPL-
11. Interaktivní změna parametrů zobrazovaných funkcí
V případě, že budeme chtít uživatelům umožnit pouze změnit parametry zobrazované funkce (resp. dvou funkcí a jejich součtu), můžeme celý kód pro vykreslení „schovat“ do značky py-script a z py-repl pouze volat definovanou funkci plot:
<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> <h1>Python REPL</h1> <py-script> import numpy as np import matplotlib.pyplot as plt def plot(amplitude, frequency, offset): print("plot begin") x = np.linspace(0, 2 * np.pi, 100) y1 = np.sin(x) y2 = amplitude * np.sin(x*frequency) + offset plt.plot(x, y1, x, y2, x, y1+y2) print("plot end") return plt </py-script> <py-repl> plot(0.6, 2, 0) </py-repl> </html>

Obrázek 20: Předvyplněná smyčka REPL-

Obrázek 21: Graf vykreslený po spuštění kódu ve smyčce REPL-
<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> <h1>Python REPL</h1> <py-script> import numpy as np import matplotlib.pyplot as plt def plot(amplitude, frequency, offset): print("plot begin") x = np.linspace(0, 2 * np.pi, 100) y1 = np.sin(x) y2 = amplitude * np.sin(x*frequency) + offset plt.clf() plt.plot(x, y1, x, y2, x, y1+y2) print("plot end") return plt </py-script> <py-repl> plot(0.6, 2, 0) </py-repl> </html>
12. Reakce na události: zavolání funkce naprogramované v Pythonu po stisku tlačítka
I když značka py-repl do webových stránek přináší interaktivitu, je v mnoha případech zapotřebí zajistit, aby se nějaká Pythonovská funkce zavolala ve chvíli, kdy uživatel například stiskne tlačítko zobrazené na webové stránce popř. když provede nějakou další podobnou operaci. Jinými slovy – potřebujeme umět reagovat na události, které mohou na webové stránce v průběhu jejího zobrazení nastat.
Vložme tedy do webové stránky tlačítko, přesněji řečeno značku buttob. Povšimněte si, že této značce není možné přímo do atributu on-click předat Pythonovskou funkci (ta by nebyla webovým prohlížečem nalezena, neboť by hledal ve jmenném prostoru JavaScriptu):
<button class="button" type="button" class="py-button" id="calculate-button">Calculate!</button>
V reakci na stisk výše uvedeného tlačítka budeme chtít spustit tuto funkci, s níž jsme se ostatně již seznámili minule:
def calculate(event): print("begin") # 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))
13. Propojení události s Pythonovským kódem přes proxy
Aby se po stisku tlačítka zobrazeného na webové stránce spustila funkce zapsaná v Pythonu, je nutné tyto dvě entity propojit přes takzvanou proxy, která interně vše zařídí s využitím JavaScriptu (což je však z pohledu programátora skryto v hlubinách PyScriptu). Náš úkol je jednoduchý – propojit událost „click“ prvku s identifikátorem „calculate-button“ s Pythonovskou funkcí nazvanou calculate. Příslušnou proxy lze vytvořit zavoláním create_proxy a vzhledem k tomu, že z pohledu webového prohlížeče se jedná o běžnou JavaScriptovou funkci, můžeme ji zaregistrovat jako handler příslušné události:
def setup(): click_proxy = create_proxy(calculate) e = document.getElementById("calculate-button") e.addEventListener("click", click_proxy)
Výsledná webová stránka, na které se tlačítko použité přes proxy využije, 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> </head> <body> <div id="result" style="font-size:150%;color:#800000"></div> <py-script> def calculate(event): print("begin") # 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)) from js import document from pyodide import create_proxy def setup(): click_proxy = create_proxy(calculate) e = document.getElementById("calculate-button") e.addEventListener("click", click_proxy) setup() </py-script> <button class="button" type="button" class="py-button" id="calculate-button">Calculate!</button> </html>
Výsledné chování webové stránky si můžeme velmi snadno ověřit:

Obrázek 22: Webová stránka před stiskem tlačítka.

Obrázek 23: Webová stránka po stisku tlačítka.
14. Přečtení hodnoty uložené v interaktivním prvku webové stránky
Předchozí demonstrační příklad se můžeme pokusit dále upravit, například takovým způsobem, že uživateli umožníme specifikaci horního limitu při výpočtu prvočísel. Konkrétně tedy nebudeme hledat prvočísla ležící v rozsahu 2 až 100, ale v rozsahu 2 až X, kde hodnota X bude zapsána uživatelem do tohoto vstupního prvku:
Limit: <input type="text" id="limit" value="100" size="5" />
Hodnotu zapsanou do tohoto prvku získáme v Pythonovském kódu snadno:
value = Element('limit').value
V tom nejjednodušším případě nebudeme kontrolovat typ zapsané hodnoty ani to, jestli náhodou není zapsána záporná hodnota atd.:
limit = int(Element('limit').value)

Obrázek 24: Běhová chyba, která vznikne při pokusu o převod řetězce „xyzzy“ na celé číslo.
Opět se podívejme na to, jak se oba dva výše popsané řádky vloží do zdrojového kódu interaktivní webové stránky:
<html> <head> <script defer src="https://pyscript.net/latest/pyscript.js"></script> </head> <body> <py-script> def calculate(event): print("begin") # 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] limit = int(Element('limit').value) primes = primes2(limit) asStrings = map(str, primes) Element('result').write(", ".join(asStrings)) from js import document from pyodide import create_proxy def setup(): click_proxy = create_proxy(calculate) e = document.getElementById("calculate-button") e.addEventListener("click", click_proxy) setup() </py-script> Limit: <input type="text" id="limit" value="100" size="5" /> <br /> <button class="button" type="button" class="py-button" id="calculate-button">Calculate!</button> <br /> <div id="result" style="font-size:150%;color:#800000"><u>...result...</u></div> </html>
Chování webové stránky si snadno ověříme:

Obrázek 25: Webová stránka před stiskem tlačítka.

Obrázek 26: Webová stránka po vyplnění limitu a stisku tlačítka.
15. Zobrazení grafu po stisku tlačítka na webové stránce
V rámci závěrečných kapitol si ukážeme, jakým způsobem je možné s využitím PyScriptu vytvořit webovou stránku, která bude obsahovat několik vstupních prvků určených pro specifikaci parametrů nějaké funkce. Stránka navíc bude umožňovat zobrazení průběhu této funkce. Celý úkol si tedy můžeme rozdělit na několik kroků, z nichž všechny jsme si vlastně již vysvětlili v předchozím textu:
- Neinteraktivní zobrazení grafu ihned po inicializaci webové stránky.
- Zobrazení grafu po stisku k tomu určeného tlačítka.
- Vložení dalších interaktivních prvků na webovou stránku.
- Přečtení a interpretace hodnot zapsaných do těchto interaktivních prvků před zobrazením grafu.
Podívejme se nicméně na to, jak je možné realizovat druhý bod z uvedeného seznamu (první bod jsme již realizovali v úvodním článku). Vytvořená webová stránka obsahuje kód určený pro zobrazení grafu a tento kód je zavolán až po stisku tlačítka Plot!:
<html> <head> <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <script defer src="https://pyscript.net/latest/pyscript.js"></script> </head> <py-env> - numpy - matplotlib </py-env> <body> <div id="plot"></div> <py-script> import numpy as np import matplotlib.pyplot as plt from js import document from pyodide import create_proxy def plot(event): amplitude = 1.0 frequency = 2 offset = 0 print("plot begin") x = np.linspace(0, 2 * np.pi, 100) y1 = np.sin(x) y2 = amplitude * np.sin(x*frequency) + offset plt.clf() plt.plot(x, y1, x, y2, x, y1+y2) print("plot end") Element('plot').write(plt) return plt def setup(): click_proxy = create_proxy(plot) e = document.getElementById("plot-button") e.addEventListener("click", click_proxy) setup() </py-script> <button class="button" type="button" class="py-button" id="plot-button">Plot!</button> </html>
Výsledek by měl vypadat následovně:

Obrázek 27: Webová stránka po své inicializaci obsahuje pouze jediné tlačítko.

Obrázek 28: Graf zobrazený po stisku tlačítka.
16. Interaktivní prvky pro zadání amplitudy, frekvence a posunutí zobrazované funkce
Na plochu HTML stránky nyní přidáme další ovládací prvky (formulář) s možností zadání amplitudy, frekvence a posunutí zobrazované funkce:
<div class="input-form"> <div>Amplitude</div> <input type="text" id="amplitude" value="1" size="5" /> <div>Frequency</div> <input type="text" id="frequency" value="2" size="5" /> <div>Offset</div> <input type="text" id="offset" value="0" size="5" /> <div></div> <button type="button" id="plot-button" class="button">Plot!</button> </div>
Stránka by po této úpravě měla vypadat následovně:

Obrázek 29: Webová stránka po své inicializaci obsahuje pouze ovládací prvky.
Po stisku tlačítka Plot! se graf vykreslí, přičemž se získají a použijí hodnoty z formuláře:
amplitude = float(Element('amplitude').value) frequency = float(Element('frequency').value) offset = float(Element('offset').value)

Obrázek 30: Graf zobrazený po vyplnění hodnot a stisku tlačítka.
Celý skript by měl nyní vypadat následovně:
<html> <head> <script defer src="https://pyscript.net/latest/pyscript.js"></script> <style type="text/css"> .input-form { display: grid; grid-template-columns: 16ex 10ex; grid-column: 2; background-color: #c0c0c0; padding: 5px; } .button { background-color: #f08080; } </style> </head> <py-env> - numpy - matplotlib </py-env> <body> <py-script> import numpy as np import matplotlib.pyplot as plt from js import document from pyodide import create_proxy def plot(event): amplitude = float(Element('amplitude').value) frequency = float(Element('frequency').value) offset = float(Element('offset').value) print("plot begin") x = np.linspace(0, 2 * np.pi, 100) y1 = np.sin(x) y2 = amplitude * np.sin(x*frequency) + offset plt.clf() plt.plot(x, y1, x, y2, x, y1+y2) print("plot end") Element('plot').write(plt) return plt def setup(): click_proxy = create_proxy(plot) e = document.getElementById("plot-button") e.addEventListener("click", click_proxy) setup() </py-script> <div class="input-form"> <div>Amplitude</div> <input type="text" id="amplitude" value="1" size="5" /> <div>Frequency</div> <input type="text" id="frequency" value="2" size="5" /> <div>Offset</div> <input type="text" id="offset" value="0" size="5" /> <div></div> <button type="button" id="plot-button" class="button">Plot!</button> </div> <div id="plot"></div> </html>
17. Úprava předchozího demonstračního příkladu – reakce na nesprávné vstupy
A konečně do skriptu přidáme kontroly, zda uživatel do formuláře skutečně zapsal numerické hodnoty a nikoli například řetězec, ponechal vstupní pole prázdné atd. Tyto kontroly je nutné provést, protože samotné HTML sice obsahuje možnost specifikace typu hodnot, ale to platí pouze pro odesílání formulářů a v našem případě je tedy nepoužitelné. Úprava, tedy přidání kontrol, je snadná:
def retrieve_float_from_element(name, element): value = Element(element).value try: return float(value) except Exception as e: alert(f"Incorrect {name} value: {value}") raise def plot(event): try: amplitude = retrieve_float_from_element("amplitude", "amplitude") frequency = retrieve_float_from_element("frequency", "frequency") offset = retrieve_float_from_element("offset", "offset") ... ... ...
Příklad reakce na nesprávný vstup:

Obrázek 31: Reakce na nesprávný vstup.
Upravený kód webové stránky bude vypadat následovně:
<html> <head> <script defer src="https://pyscript.net/latest/pyscript.js"></script> <style type="text/css"> .input-form { display: grid; grid-template-columns: 16ex 10ex; grid-column: 2; background-color: #c0c0c0; padding: 5px; } .button { background-color: #f08080; } </style> </head> <py-env> - numpy - matplotlib </py-env> <body> <py-script> import numpy as np import matplotlib.pyplot as plt from js import document from js import alert from pyodide import create_proxy def retrieve_float_from_element(name, element): value = Element(element).value try: return float(value) except Exception as e: alert(f"Incorrect {name} value: {value}") raise def plot(event): try: amplitude = retrieve_float_from_element("amplitude", "amplitude") frequency = retrieve_float_from_element("frequency", "frequency") offset = retrieve_float_from_element("offset", "offset") print("plot begin") x = np.linspace(0, 2 * np.pi, 100) y1 = np.sin(x) y2 = amplitude * np.sin(x*frequency) + offset plt.clf() plt.plot(x, y1, x, y2, x, y1+y2) print("plot end") Element('plot').write(plt) except Exception as e: pass def setup(): click_proxy = create_proxy(plot) e = document.getElementById("plot-button") e.addEventListener("click", click_proxy) setup() </py-script> <div class="input-form"> <div>Amplitude</div> <input type="text" id="amplitude" value="1" size="5" /> <div>Frequency</div> <input type="text" id="frequency" value="2" size="5" /> <div>Offset</div> <input type="text" id="offset" value="0" size="5" /> <div></div> <button type="button" id="plot-button" class="button">Plot!</button> </div> <div id="plot"></div> </html>

Obrázek 32: Graf zobrazený po vyplnění hodnot a stisku tlačítka.
18. Závěrečné zhodnocení
PyScript je velmi zajímavý a potenciálně i užitečný projekt, který umožňuje přímo na webové stránce používat programovací jazyk Python a současně i jeho nejpoužívanější knihovny (zde konkrétně často používanou trojici NumPy+Pandas+Matplotlib). Nevýhodou je zejména příliš dlouhý interval nutný pro inicializaci stránky, především při prvním načítání jakékoli stránky s PyScriptem, neboť se do cache prohlížeče musí nahrát i všechny potřebné knihovny (při dalším načítání jsou již načítány z cache). Výhodou (a někdy i nevýhodou) řešení postaveného na PyScriptu je přenesení zátěže na klienta, zatímco samotný server pouze přenáší požadované soubory popř. zpřístupňuje databáze. Z těchto důvodů je PyScript v jeho současné podobě vhodný spíše pro intranetové aplikace – analýzy dat, tvorbu reportů, různé dashboardy atd. Na webové aplikace typu obchod či dokonce pro realizaci serveru typu Root se PyScript prozatím příliš nehodí.
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:
20. Odkazy na Internetu
- PyScript
https://pyscript.net/ - PyScript na GitHubu
https://github.com/pyscript/pyscript - Getting started with PyScript
https://github.com/pyscript/pyscript/blob/main/docs/tutorials/getting-started.md - PyScript examples
https://github.com/pyscript/pyscript/tree/main/examples - What is PyScript
https://docs.pyscript.net/latest/concepts/what-is-pyscript.html - Pyodide
https://pyodide.org/en/stable/ - PyScript: JavaScript and Python Interoperability
https://www.jhanley.com/blog/pyscript-javascript-and-python-interoperability/ - Pyscript: JavaScript Event Callbacks
https://www.jhanley.com/blog/pyscript-javascript-callbacks/ - Compiling to WebAssembly: It’s Happening!
https://hacks.mozilla.org/2015/12/compiling-to-webassembly-its-happening/ - WebAssembly
https://webassembly.org/ - Blogy o WASM a Emscripten
https://www.jamesfmackenzie.com/sitemap/#emscripten - wat2wasm demo
https://webassembly.github.io/wabt/demo/wat2wasm/ - WABT: The WebAssembly Binary Toolkit
https://github.com/WebAssembly/wabt - Programming using Web Assembly
https://medium.com/@alexc73/programming-using-web-assembly-c4c73a4e09a9 - Experiments with image manipulation in WASM using Go
https://agniva.me/wasm/2018/06/18/shimmer-wasm.html - Fable
https://fable.io/ - Využití WebAssembly z programovacího jazyka Go
https://www.root.cz/clanky/vyuziti-webassembly-z-programovaciho-jazyka-go/ - WebAssembly prošlo standardizací ve W3C, byla vydána verze 1.0
https://www.root.cz/zpravicky/webassembly-proslo-standardizaci-ve-w3c-byla-vydana-verze-1–0/ - WebAssembly na Wiki Golangu
https://github.com/golang/go/wiki/WebAssembly - The future of WebAssembly – A look at upcoming features and proposals
https://blog.scottlogic.com/2018/07/20/wasm-future.html - Writing WebAssembly By Hand
https://blog.scottlogic.com/2018/04/26/webassembly-by-hand.html - WebAssembly Specification
https://webassembly.github.io/spec/core/index.html - Index of Instructions
https://webassembly.github.io/spec/core/appendix/index-instructions.html - The WebAssembly Binary Toolkit
https://github.com/WebAssembly/wabt - 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 - Roadmap (pro WebAssemly)
https://webassembly.org/roadmap/ - S-expression
https://en.wikipedia.org/wiki/S-expression - Understanding WebAssembly text format
https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format - Learning Golang through WebAssembly – Part 1, Introduction and setup
https://www.aaron-powell.com/posts/2019–02–04-golang-wasm-1-introduction/ - 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/ - 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/ - Golang webassembly (wasm) testing with examples
https://jelinden.fi/blog/golang-webassembly-wasm-testing-with-examples/qB7Tb2KmR - Use Cases (of WebAssembly)
https://webassembly.org/docs/use-cases/ - JupyterLite na PyPi
https://pypi.org/project/jupyterlite/ - JupyterLite na GitHubu
https://github.com/jupyterlite/jupyterlite - Dokumentace k projektu JupyterLite
https://github.com/jupyterlite/jupyterlite - A quick guide about Python implementations
https://blog.rmotr.com/a-quick-guide-about-python-implementations-aa224109f321 - How Brython works
https://github.com/brython-dev/brython/wiki/How%20Brython%20works - Brython – A Python 3 implementation for client-side web programming
http://www.brython.info/ - Brython videos and talks
https://github.com/brython-dev/brython/wiki/Brython-videos-and-talks - What is Brython?
https://medium.com/frontendweb/what-is-brython-6edb424b07f6 - Python in browser (tabulka s porovnáními)
http://stromberg.dnsalias.org/~strombrg/pybrowser/python-browser.html - JavaScript is Assembly Language for the Web: Sematic Markup is Dead! Clean vs. Machine-coded HTML
http://www.hanselman.com/blog/JavaScriptIsAssemblyLanguageForTheWebSematicMarkupIsDeadCleanVsMachinecodedHTML.aspx - pyscript VS brython
https://www.libhunt.com/compare-pyscript-vs-brython - PyScript – Run Python in the Browser! THE END of JavaScript???
https://www.youtube.com/watch?v=du8vQC44PC4 - PyScript is Python in Your Browser
https://www.youtube.com/watch?v=MJvCeKwr4z4 - JupyterLite na PyPi
https://pypi.org/project/jupyterlite/ - JupyterLite na GitHubu
https://github.com/jupyterlite/jupyterlite - Dokumentace k projektu JupyterLite
https://github.com/jupyterlite/jupyterlite - Matplotlib Home Page
http://matplotlib.org/ - Matplotlib (Wikipedia)
https://en.wikipedia.org/wiki/Matplotlib - Popis barvových map modulu matplotlib.cm
https://gist.github.com/endolith/2719900#id7 - Ukázky (palety) barvových map modulu matplotlib.cm
http://matplotlib.org/examples/color/colormaps_reference.html - Galerie grafů vytvořených v Matplotlibu
https://matplotlib.org/3.2.1/gallery/ - Replacing Javascript with Python
https://stackoverflow.com/questions/69510962/replacing-javascript-with-python - Can Python Replace Javascript in the Future?
https://dev.to/katholder/can-python-replace-javascript-in-the-future-4bbn - asm.js
http://asmjs.org/ - asm.js: Working Draft
http://asmjs.org/spec/latest/ - Manual asm.js Demonstration
https://www.youtube.com/watch?v=qkiqMuf5M84 - asm.js – frequently asked questions
http://asmjs.org/faq.html - When asm.js is faster than normal JS code, why should I write new code in JS?
https://stackoverflow.com/questions/16527195/when-asm-js-is-faster-than-normal-js-code-why-should-i-write-new-code-in-js - Faster Canvas Pixel Manipulation with Typed Arrays
https://hacks.mozilla.org/2011/12/faster-canvas-pixel-manipulation-with-typed-arrays/