python 如何在浏览器中重定向/渲染Pyodide输出?

ufj5ltwl  于 2023-02-28  发布在  Python
关注(0)|答案(4)|浏览(194)

我最近偶然发现了Pyodide project
我已经使用Pyodide构建了一个小演示,但是尽管我花了很多时间查看源代码,但对我来说,如何从python重定向print输出(除了修改CPython源代码),以及如何将matplotlib.pyplot的输出重定向到浏览器还不明显。
从源代码来看,FigureCanvasWasm确实有一个show()方法,该方法具有适当的后端,用于将绘图绘制到浏览器画布-但是,我不清楚如何示例化该类并调用它的show()方法,或者是否有其他更明显的方法将绘图重定向到画布。
因此,我的问题是:
1.如何重定向print()邮件
1.如何强制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.log(image);
      });});

    </script>
<html>
de90aj5v

de90aj5v1#

首先,让我们看看是否可以在浏览器中显示任何内容;例如一个普通的字符串,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,所以这可能会允许一种更自动化的方式。

vc9ivgsu

vc9ivgsu2#

当使用WASM后端时,图形的canvas属性是FigureCanvasWasm的示例。调用canvas的show()方法应该足以在浏览器中显示图形。不幸的是,canvas的create_root_element()方法中的一个小错误阻止了图形的显示。此方法创建了一个将包含图形的div元素。它首先尝试创建一个碘化物输出div元素,如果失败,则创建一个普通的HTML div元素,但是这个元素不会附加到文档中,因此保持不可见。
下面是FigureCanvasWasm中发生这种情况时的代码行

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

注解表明,非iodide代码是一个存根,需要通过覆盖方法来扩展,这需要将FigureCanvasWasm子类化,将其安装为pyodide模块,并配置matplotlib以使用后端。
但是,有一个捷径,因为python允许覆盖示例的方法,而不修改类,如问题394770所示。

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

最初工具栏没有显示图标,我不得不下载、解压缩并安装fontawesome以及pyodide,并在标题中包含以下行来获得这些图标

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

编辑:关于问题的第一部分,将输出流重定向到浏览器,您可以看看在pyodide的console.html中是如何完成的。
它用StringIO对象替换sys.stdout

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);
xytpbqjk

xytpbqjk3#

要显示来自pyodide的print()调用,可以使用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

ws51t4hk

ws51t4hk4#

我为Python创建了一个简单的交互式shell,如果你需要更详细的信息,请阅读tutorial

(async () => { // create anonymous async function to enable await

  var output = document.getElementById("output")
  var code = document.getElementById("code")
  
  output.value = 'Initializing...\n'

  window.pyodide = await loadPyodide({stdout: addToOutput, stderr: addToOutput}) // redirect stdout and stderr to addToOutput
        output.value += 'Ready!\n' 
})()

function addToOutput(s) {
  output.value += `${s}\n`
  output.scrollTop = output.scrollHeight
}

async function evaluatePython() {
  addToOutput(`>>>${code.value}`)

  await pyodide.loadPackagesFromImports(code.value, addToOutput, addToOutput)
  try {
    let result = await pyodide.runPythonAsync(code.value)
    addToOutput(`${result}`)
  }
  catch (e) {
    addToOutput(`${e}`)
  }
  code.value = ''
}
<script src="https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js"></script>

Output:
<textarea id="output" style="width: 100%;" rows="10" disabled=""></textarea>
<textarea id="code" rows="3">import numpy as np
np.ones((10,))
</textarea>
<button id="run" onclick="evaluatePython()">Run</button>

下面是matplotlib的例子,注意这将加载一堆依赖项,这将花费几分钟的时间。
一个二个一个一个

相关问题