Je suis récemment tombé sur le projet Pyodide .
J'ai créé une petite démo en utilisant Pyodide, mais bien que j'aie passé beaucoup de temps à regarder la source, il ne m'est pas (encore) évident de rediriger la sortie print
de python (autre que de modifier la source CPython), et aussi, comment rediriger la sortie de matplotlib.pyplot vers le navigateur.
Depuis le code source, FigureCanvasWasm a une méthode show ()
avec le backend approprié pour le traçage dans le canevas du navigateur - cependant, c'est Je ne sais pas comment instancier cette classe et invoquer sa méthode show ()
ou, en fait, s'il existe un autre moyen plus évident de rediriger les graphiques vers le canevas.
Mes questions sont donc :
print ()
Voici ma page de test:
<!doctype html> <meta charset="utf-8"> <html lang="en"> <html> <head> <title>Demo</title> <script src="../../pyodide/build/pyodide.js"></script> </head> <body> </body> <script type="text/javascript"> languagePluginLoader.then(() => { pyodide.loadPackage(['matplotlib']).then(() => { pyodide.runPython(` import matplotlib.pyplot as plt plt.plot([1, 2, 3, 4]) plt.ylabel('some numbers') #fig = plt.gcf() #fig.savefig(imgdata, format='png') print('Done from python!')` ); //var image = pyodide.pyimport('imgdata'); //console.log(image); });}); </script> <html>
3 Réponses :
Tout d'abord, voyons si nous pouvons faire apparaître n'importe quoi dans le navigateur; par exemple. une chaîne normale. Les variables Python sont stockées dans le Maintenant, je suppose que nous pouvons faire la même chose avec un figure de matplotlib. Ce qui suit affichera une image png enregistrée dans le document. Je n'ai pas encore examiné les pyodide.globals
attribut. Par conséquent, nous pouvons prendre l'objet python à partir de là et le placer dans un élément <!doctype html>
<meta charset="utf-8">
<html lang="en">
<html>
<head>
<title>Demo</title>
<script src="../pyodide/pyodide.js"></script>
</head>
<body>
</body>
<script type="text/javascript">
languagePluginLoader.then(() => {
pyodide.loadPackage(['matplotlib']).then(() => {
pyodide.runPython(`
import matplotlib.pyplot as plt
import io, base64
fig, ax = plt.subplots()
ax.plot([1,3,2])
buf = io.BytesIO()
fig.savefig(buf, format='png')
buf.seek(0)
img_str = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8')`
);
document.getElementById("pyplotfigure").src=pyodide.globals.img_str
});});
</script>
<div id="textfield">A matplotlib figure:</div>
<div id="pyplotdiv"><img id="pyplotfigure"/></div>
<html>
<!doctype html>
<meta charset="utf-8">
<html>
<head>
<title>Demo</title>
<script src="../pyodide/pyodide.js"></script>
</head>
<body>
</body>
<script type="text/javascript">
languagePluginLoader.then(() => {
pyodide.runPython(`my_string = "This is a python string." `);
document.getElementById("textfield").innerText = pyodide.globals.my_string;
});
</script>
<div id="textfield"></div>
<html>
backends.wasm_backend
, donc cela peut permettre pour une manière plus automatisée de ce qui précède.
+1 C'est un début fantastique . C'est une superbe spéléologie! ... :) cependant, je recherche un niveau légèrement plus bas de faire cela (dans les coulisses - pour ainsi dire), afin qu'un utilisateur puisse simplement exécuter des scripts python "normaux" sans avoir à recourir aux mécanismes que vous employé. Idéalement, je souhaite intercepter les instructions print () et tous les messages envoyés à la console.
Voici chacun de ces exemples en direct sur codepen: Exemple 1 , Exemple 2
Lors de l'utilisation du backend wasm, la propriété canvas d'une figure est une instance de FigureCanvasWasm . L'appel de la méthode show ()
du canevas devrait être suffisant pour afficher la figure dans le navigateur. Malheureusement, un bug mineur dans la méthode create_root_element ()
du canevas empêche l'affichage de la figure. Cette méthode crée un élément div
qui contiendra la figure. Il essaie d'abord de créer un élément div de sortie iodure. Si cela échoue, un élément div HTML brut est créé. Cet élément n'est cependant jamais annexé au document et reste donc invisible.
Voici les lignes de code de FigureCanvasWasm où cela se produit
var stdout = pyodide.runPython("sys.stdout.getvalue()") var div = document.createElement('div'); div.innerText = stdout; document.body.appendChild(div);
Le commentaire suggère que le code sans iodure est un stub qui a besoin à étendre, en remplaçant la méthode. Cela nécessiterait de sous-classer FigureCanvasWasm , en l'installant en tant que module pyodide et en configurant matplotlib pour utiliser ce backend.
Il existe cependant un raccourci, car python permet de surcharger une méthode d'une instance, sans modifier la classe, selon la question 394770 . Mettre le code suivant dans votre document HTML donne une figure dans le navigateur
pyodide.runPython(` print("Hello, world!") `);
Au départ, la barre d'outils n'affichait pas d'icônes. J'ai dû télécharger, décompresser et installer fontawesome à côté de pyodide et inclure la ligne suivante dans l'en-tête pour les obtenir p>
pyodide.runPython(` import sys import io sys.stdout = io.StringIO() `);
Modifier: À propos de la première partie de votre question, redirigeant le flux de sortie vers le navigateur, vous pouvez jeter un œil à la façon dont cela est fait dans console.html .
Il remplace sys.stdout par un objet StringIO
<link rel="stylesheet" href="font-awesome-4.7.0/css/font-awesome.min.css">
Ensuite, exécutez le code python (qui peut être complètement inconscient du fait qu'il s'exécute dans un contexte wasm)
import numpy as np from matplotlib import pyplot as plt from js import document x = np.linspace(0, 2*np.pi, 100) y = np.sin(x) f = plt.figure() plt.plot(x,y) # ordinary function to create a div def create_root_element1(self): div = document.createElement('div') document.body.appendChild(div) return div #ordinary function to find an existing div #you'll need to put a div with appropriate id somewhere in the document def create_root_element2(self): return document.getElementById('figure1') #override create_root_element method of canvas by one of the functions above f.canvas.create_root_element = create_root_element.__get__( create_root_element1, f.canvas.__class__) f.canvas.show()
Enfin, envoyez le contenu du tampon stdout vers un élément de sortie
def create_root_element(self): # Designed to be overridden by subclasses for use in contexts other # than iodide. try: from js import iodide return iodide.output.element('div') except ImportError: return document.createElement('div')
J'ai créé un shell interactif simple pour Python. Lisez mon tutoriel si vous avez besoin d'informations plus détaillées.
<!DOCTYPE html> <head> <script type="text/javascript"> // this variable should be changed if you load pyodide from different source window.languagePluginUrl = 'https://pyodide-cdn2.iodide.io/v0.15.0/full/'; </script> <script src="https://pyodide-cdn2.iodide.io/v0.15.0/full/pyodide.js"></script> </head> <body> Status: <strong id='status'>Initializing...</strong> <br><br> mu: <input id='mu' value='1' type="number"> <br><br> sigma: <input id='sigma' value='1' type="number"> <br><br> <button onclick='pyodide.globals.generate_plot_img()'>Plot</button> <br> <img id="fig" /> </body> </html>
let python_code = ` from js import document import numpy as np import scipy.stats as stats import matplotlib.pyplot as plt import io, base64 def generate_plot_img(): # get values from inputs mu = int(document.getElementById('mu').value) sigma = int(document.getElementById('sigma').value) # generate an interval x = np.linspace(mu - 3*sigma, mu + 3*sigma, 100) # calculate PDF for each value in the x given mu and sigma and plot a line plt.plot(x, stats.norm.pdf(x, mu, sigma)) # create buffer for an image buf = io.BytesIO() # copy the plot into the buffer plt.savefig(buf, format='png') buf.seek(0) # encode the image as Base64 string img_str = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8') # show the image img_tag = document.getElementById('fig') img_tag.src = img_str buf.close() ` languagePluginLoader.then(()=>pyodide.runPythonAsync(python_code).then(()=>document.getElementById('status').innerHTML='Done!'))
Voici l'exemple pour matplotlib
. Notez que cela chargera un tas de dépendances qui prendra jusqu'à plusieurs minutes.
<!DOCTYPE html> <head> <script type="text/javascript"> // this variable should be changed if you load pyodide from different source window.languagePluginUrl = 'https://pyodide-cdn2.iodide.io/v0.15.0/full/'; </script> <script src="https://pyodide-cdn2.iodide.io/v0.15.0/full/pyodide.js"></script> </head> <body> Output: </div> <textarea id='output' style='width: 100%;' rows='10' disabled></textarea> <textarea id='code' value='' rows='2'></textarea> <button id='run' onclick='evaluatePython()'>Run</button> <p>You can execute any Python code. Just enter something in the box above and click the button (or Ctrl+Enter).</p> <div><a href='https://github.com/karray/truepyxel/demo.html'>Source code</a></div> </body> </html>
const output = document.getElementById("output") const code = document.getElementById("code") code.addEventListener("keydown", function (event) { if (event.ctrlKey && event.key === "Enter") { evaluatePython() } }) function addToOutput(s) { output.value += `>>>${code.value}\n${s}\n` output.scrollTop = output.scrollHeight code.value='' } output.value = 'Initializing...\n' // init pyodide languagePluginLoader.then(() => { output.value += 'Ready!\n' }) function evaluatePython() { pyodide.runPythonAsync(code.value) .then(output => addToOutput(output)) .catch((err) => { addToOutput(err) }) }