Как перенаправить/рендерить вывод Pyodide в браузере?

Недавно я наткнулся на Пиодид проект.

Я создал небольшую демонстрацию, используя Pyodide, но, хотя я потратил много времени на просмотр исходного кода, для меня (пока) не очевидно, как перенаправить print вывод из python (кроме изменения исходного кода CPython), и также, как перенаправить вывод из matplotlib.pyplot в браузер.

Из исходного кода ФигураХолстWasm имеет метод show() с соответствующим бэкендом для вывода на холст браузера, однако мне не ясно, как создать экземпляр этого класса и вызвать его метод show() или, действительно, если есть другой более очевидный способ перенаправления сюжетов на холст.

Поэтому мои вопросы:

  1. Как перенаправить print() сообщения
  2. Как заставить pyodide отображать цифры matplotlib в браузере?

Вот моя тестовая страница:

<!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.info(image);
      });});

    </script>
<html>
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
16
0
3 589
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Прежде всего, давайте посмотрим, сможем ли мы заставить что-либо отображаться в браузере; например нормальная струна. Переменные Python хранятся в атрибуте pyodide.globals. Следовательно, мы можем взять оттуда объект python и поместить его в элемент <div> на странице.

<!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>

Теперь я думаю, мы можем сделать то же самое с фигурой matplotlib. Следующее покажет сохраненное изображение png в документе.

<!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>

Я еще не изучал backends.wasm_backend, так что это может позволить более автоматизированный способ вышеперечисленного.

+1 Это начало фантастический. Это очень хорошая спелеология! ... :) однако я ищу несколько более низкий уровень выполнения этого (как бы за кулисами), чтобы пользователь мог просто запускать «обычные» сценарии Python, не прибегая к механизмам, которые вы занят. В идеале я хочу перехватывать операторы print() и все сообщения, отправляемые на консоль.

Homunculus Reticulli 19.06.2019 17:26

Вот каждый из этих примеров на codepen: Пример 1, Пример 2.

zelusp 17.12.2019 22:16

При использовании бэкенда wasm свойство холста фигуры является экземпляром ФигураХолстWasm. Вызова метода show() холста должно быть достаточно для отображения рисунка в браузере. К сожалению, небольшая ошибка в методе холста create_root_element() не позволяет отображать фигуру. Этот метод создает элемент div, который будет содержать фигуру. Сначала он пытается создать элемент div вывода йодида. Если это не удается, создается обычный HTML-элемент div. Однако этот элемент никогда не добавляется к документу и поэтому остается невидимым.

Ниже приведены строки кода из ФигураХолстWasm, где это произошло

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')

Комментарий предполагает, что код без йодида является заглушкой, которую необходимо расширить, переопределив метод. Для этого потребуется создать подкласс ФигураХолстWasm, установить его как модуль pyodide и настроить matplotlib для использования этого бэкэнда.

Однако есть ярлык, потому что python позволяет переопределить метод экземпляра без изменения класса в соответствии с вопросом 394770. Помещение следующего кода в ваш HTML-документ дает цифру в браузере

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_element1.__get__(
    create_root_element1, f.canvas.__class__)

f.canvas.show()

Изначально на панели инструментов не отображались значки. Мне пришлось скачать, разархивировать и установить потрясающий вместе с pyodide и включить следующую строку в заголовок, чтобы получить эти

<link rel = "stylesheet" href = "font-awesome-4.7.0/css/font-awesome.min.css">

Редактировать: Что касается первой части вашего вопроса, перенаправления выходного потока в браузер, вы можете посмотреть, как это делается в файле console.html pyodide.

Он заменяет sys.stdout объектом StringIO.

pyodide.runPython(`
    import sys
    import io
    sys.stdout = io.StringIO()
`);

Затем запустите код Python (который может совершенно не обращать внимания на тот факт, что он работает в контексте wasm)

pyodide.runPython(`
    print("Hello, world!")
`);

Наконец, отправьте содержимое буфера stdout в выходной элемент.

var stdout = pyodide.runPython("sys.stdout.getvalue()")
var div = document.createElement('div');
div.innerText = stdout;
document.body.appendChild(div);

Хорошо работает спасибо! Ссылка на консоль pyodide не работает

Sylvain 17.08.2021 10:52

Я создал простую интерактивную оболочку для Python. Прочтите мой руководство, если вам нужна более подробная информация.

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) })
}
<!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>

Вот пример для matplotlib. Обратите внимание, что это загрузит кучу зависимостей, что займет до нескольких минут.

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!'))
<!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>

Чтобы показать вызовы print() из pyodide, вы можете использовать параметры loadPyodide для перенаправления stdout:

var paragraph = document.getElementById("p");

pyodide = await loadPyodide({
    indexURL : "https://cdn.jsdelivr.net/pyodide/v0.18.1/full/",
    stdin: window.prompt,
    stdout: (text) => {paragraph.textContent += text;},
    stderr: (text) => {paragraph.textContent += text;}
  });

https://github.com/pyodide/pyodide/blob/main/src/js/pyodide.js

Другие вопросы по теме