Brython浏览器中的 Python(2)

网友投稿 827 2022-05-29

目录

在浏览器中运行 Python:好处

实现同构 Web 开发

访问 Web API

原型设计和 JavaScript 库

向学生教授 Python

考虑性能

Having Fun

安装 Brython

CDN安装

GitHub 安装

PyPI 安装

安装

Brython 安装选项回顾

了解 Brython 的工作原理

Brython 核心组件

Brython 标准库

Brython in Action

Brython 的内部结构

在浏览器中使用 Brython

Brython 中的 DOM API

在 Brython 中导入

Reduce Import Size

与 JavaScript 交互

JavaScript

浏览器网页 API

网页界面框架

WebAssembly

在 Brython 中应用异步开发

Brython 中的 JavaScript Promise

Ajax in Brython

Brython 中的异步 IO

分发和打包 Brython 项目

手动和自动 Web 部署

部署到 PyPI

部署到 CDN

创建 Google Chrome 扩展

JS 中的 Hello World 扩展

Python 中的 Hello World 扩展

测试和调试 Brython

Python 单元测试

Selenium

JavaScript 单元测试

在 Brython 中调试

探索 Brython 的替代品

Skulpt

转密

Pyodide

pypy.js

结论

与 JavaScript 交互

Brython 允许 Python 代码与 JavaScript 代码交互。最常见的模式是从 Brython 访问 JavaScript。反过来,虽然可能,但并不常见。您将在JavaScript 单元测试部分看到 JavaScript 调用 Python 函数的示例。

JavaScript

到目前为止,您已经体验了一些 Python 代码与 JavaScript 代码交互的场景。特别是,您已经能够通过调用 来显示消息框browser.alert()。

您可以alert在 Brython 控制台中运行的以下三个示例中看到实际操作,而不是在标准的 CPython 解释器 shell 中:

>>>

>>> import browser >>> browser.alert("Real Python")

或者你可以使用window:

>>>

>>> from browser import window >>> window.alert("Real Python")

或者你可以使用this:

>>>

>>> from javascript import this >>> this().alert("Real Python")

由于Brython暴露出的新层,二者的全球性质alert()和window,你可以调用alert上browser.window,甚至上javascript.this。

以下是允许访问 JavaScript 函数的主要 Brython 模块:

除了浏览器中可用的 JavaScript 函数和 API,您还可以访问您编写的 JavaScript 函数。以下示例演示了如何从 Brython 访问自定义 JavaScript 函数:

1 2 3 4 5 19 20

这是它的工作原理:

第 9 行定义myMessageBox()了 JavaScript 块中的自定义函数。

第 17 行调用myMessageBox().

您可以使用相同的功能访问 JavaScript 库。您将在Web UI 框架部分了解如何与 Vue.js(一种流行的 Web UI 框架)进行交互。

浏览器网页 API

浏览器公开了您可以从 JavaScript 访问的 Web API,而 Brython 可以访问相同的 API。在本节中,您将扩展 Base64 计算器以在浏览器页面重新加载之间存储数据。

允许此功能的Web API是Web Storage API。它包括两种机制:

sessionStorage

localStorage

您将localStorage在接下来的示例中使用。

如前所述,Base64 计算器会创建一个字典,其中包含映射到此字符串的 Base64 编码值的输入字符串。加载页面后数据会保留在内存中,但会在您重新加载页面时清除。保存数据localStorage将在页面重新加载之间保留字典。这localStorage是一个键值存储。

要访问localStorage,您需要导入storage. 为了贴近初步实现,你会加载和字典数据保存到localStorage的JSON格式。保存和获取数据的关键是b64data. 修改后的代码包括新的导入和一个load_data()函数:

from browser.local_storage import storage import json, base64 def load_data(): data = storage.get("b64data") if data: return json.loads(data) else: storage["b64data"] = json.dumps({}) return {}

load_data()在加载 Python 代码时执行。它从localStoragePython 字典中获取 JSON 数据并填充该字典,该字典将用于在页面生命周期内将数据保存在内存中。如果没有找到b64data的localStorage,然后它创建一个空的字典键b64data中localStorage,并返回一个空的字典。

您可以load_data()通过展开下面的框来查看完整的 Python 代码。它展示了如何使用localStorageWeb API 作为持久存储,而不是依赖于临时内存存储,就像本示例的前一个实现一样。

可访问 localStorage 的完整源代码显示隐藏

您可以从browser和其他子模块访问所有 Web API 函数。Brython 文档中提供了有关访问 Web API 的高级文档。有关更多详细信息,您可以查阅Web API 文档并使用Brython 控制台来试验 Web API。

在某些情况下,您可能需要在熟悉的 Python 函数和来自 Web API 的函数之间进行选择。例如,在上面的代码中,您使用 Python Base64 编码base64.b64encode(),但您可以使用 JavaScript 的btoa():

>>>

>>> from browser import window >>> window.btoa("Real Python") 'UmVhbCBQeXRob24='

您可以在在线控制台中测试这两种变体。Usingwindow.btoa()仅适用于 Brython 上下文,而base64.b64encode()可以使用常规 Python 实现(如CPython )执行。请注意,在 CPython 版本中,base64.b64encode()将 abytearray作为参数类型,而 JavaScriptwindow.btoa()则采用字符串。

如果性能是一个问题,那么请考虑使用 JavaScript 版本。

网页界面框架

Angular、React、Vue.js或Svelte等流行的 JavaScript UI 框架已成为前端开发人员工具包的重要组成部分,Brython 与其中一些框架无缝集成。在本节中,您将使用 Vue.js 版本 3 和 Brython 构建一个应用程序。

您将构建的应用程序是一个计算字符串散列的表单。这是正在运行的 HTML 页面的屏幕截图:

在bodyHTML页面中定义的绑定和模板声明:

Hash Calculator

如果您不熟悉 Vue,那么您将在下面快速介绍一些内容,但请随时查阅官方文档以获取更多信息:

Vue.js 指令是特殊的属性值,以 为前缀v-,提供 DOM 和Vue.js 组件值之间的动态行为和数据映射:

v-model.trim="input_text"将输入值绑定到Vue 模型 input_text并修剪该值。

v-model="algo"将下拉列表的值绑定到algo.

v-for="name in algos"将选项值绑定到name.

Vue 模板用双花括号括起来的变量表示。Vue.js 将相应的占位符替换为 Vue 组件中的相应值:

hash_value

name

事件处理程序由 at 符号 (@)标识,如 in@click="compute_hash"。

对应的 Python 代码描述了 Vue 和附加的业务逻辑:

1from browser import alert, window 2from javascript import this 3import hashlib 4 5hashes = { 6 "sha-1": hashlib.sha1, 7 "sha-256": hashlib.sha256, 8 "sha-512": hashlib.sha512, 9} 10 11Vue = window.Vue 12 13def compute_hash(evt): 14 value = this().input_text 15 if not value: 16 alert("You need to enter a value") 17 return 18 hash_object = hashes[this().algo]() 19 hash_object.update(value.encode()) 20 hex_value = hash_object.hexdigest() 21 this().hash_value = hex_value 22 23def created(): 24 for name in hashes: 25 this().algos.append(name) 26 this().algo = next(iter(hashes)) 27 28app = Vue.createApp( 29 { 30 "el": "#app", 31 "created": created, 32 "data": lambda _: {"hash_value": "", "algos": [], "algo": "", "input_text": ""}, 33 "methods": {"compute_hash": compute_hash}, 34 } 35) 36 37app.mount("#app")

Vue.js 的声明性质显示在带有 Vue 指令和模板的 HTML 文件中。它还在 Python 代码中通过第 11 行和第 28 至 35 行的 Vue 组件声明进行了演示。这种声明性技术将 DOM 的节点值与 Vue 数据连接起来,允许框架的反应性行为。

这消除了您必须在前一个示例中编写的一些样板代码。例如,请注意,您不必从 DOM 中使用类似document["some_id"]. 创建 Vue 应用程序并调用app.mount()处理 Vue 组件到相应 DOM 元素的映射以及 JavaScript 函数的绑定。

在 Python 中,访问 Vue 对象字段需要您通过以下方式引用 Vue 对象javascript.this():

第 14 行获取组件字段的值this().input_text。

第 21 行更新数据组件this().hash_value。

第 25 行向列表中添加了一个算法this().algos。

第 26 行this().algo使用 的第一个键进行实例化hashes{}。

如果 Vue 与 Brython 的结合引起了您的兴趣,那么您可能想查看vuepy 项目,它为 Vue.js 提供完整的 Python 绑定并使用 Brython 在浏览器中运行 Python。

WebAssembly

在某些情况下,您可以使用WebAssembly来提高 Brython 甚至 JavaScript 的性能。WebAssembly或Wasm是所有主要浏览器都支持的二进制代码。它可以在浏览器中提供优于 JavaScript 的性能改进,并且是C、C++和Rust等语言的编译目标。如果您不使用 Rust 或 Wasm,则可以跳过本节。

在以下演示使用 WebAssembly 的方法的示例中,您将在 Rust 中实现一个函数并从 Python 调用它。

这不是一个彻底的 Rust 教程。它只会划伤表面。有关 Rust 的更多详细信息,请查看Rust 文档。

通过启动安装锈使用rustup。要编译 Wasm 文件,您还需要添加wasm32目标:

$ rustup target add wasm32-unknown-unknown

使用cargo在 Rust 安装期间安装的项目创建一个项目:

$ cargo new --lib op

上面的命令在名为op. 在此文件夹中,您将找到Cargo.tomlRust 构建配置文件,您需要修改该文件以表明您要创建动态库。您可以通过添加突出显示的部分来做到这一点:

[package] name = "op" version = "0.1.0" authors = ["John "] edition = "2018" [lib] crate-type=["cdylib"] [dependencies]

src/lib.rs通过将其内容替换为以下内容进行修改:

#[no_mangle] pub extern fn double_first_and_add(x: u32, y: u32) -> u32 { (2 * x) + y }

在项目的根目录中Cargo.toml,编译你的项目:

$ cargo build --target wasm32-unknown-unknown

接下来,创建一个web包含以下内容的目录index.html:

1 2 3 4 5 7 8 9 10

11

Custom Operation using Wasm + Brython

12
13 Multiply first number by 2 and add result to second number 14 16 18 21
22
23 24
25
26 27

上面的第 6 行main.py从同一目录加载以下内容:

1from browser import document, window 2 3double_first_and_add = None 4 5def add_rust_fn(module): 6 global double_first_and_add 7 double_first_and_add = module.instance.exports.double_first_and_add 8 9def add_numbers(evt): 10 nb1 = document["number-1"].value or 0 11 nb2 = document["number-2"].value or 0 12 res = double_first_and_add(nb1, nb2) 13 document["result"].innerHTML = f"Result: ({nb1} * 2) + {nb2} = {res}" 14 15document["submit"].bind("click", add_numbers) 16window.WebAssembly.instantiateStreaming(window.fetch("op.wasm")).then(add_rust_fn)

突出显示的行是允许 Brython 访问 Rust 函数的粘合剂double_first_and_add():

第 16 行读取op.wasmusing WebAssembly,然后add_rust_fn()在下载 Wasm 文件时调用。

第 5 行实现了add_rust_fn(),它将 Wasm 模块作为参数。

第 7 行分配double_first_and_add()给本地double_first_and_add名称以使其可用于 Python。

在同一web目录中,op.wasm从target/wasm32-unknown-unknown/debug/op.wasm以下位置复制:

$ cp target/wasm32-unknown-unknown/debug/op.wasm web

项目文件夹布局如下所示:

├── Cargo.lock ├── Cargo.toml ├── src │ └── lib.rs ├── target │ ... └── web ├── index.html ├── main.py └── op.wasm

这显示了使用cargo new. 为清楚起见,target部分省略。

现在在web以下位置启动服务器:

$ python3 -m http.server Serving HTTP on :: port 8000 (http://[::]:8000/) ...

最后,将您的 Internet 浏览器指向http://localhost:8000. 您的浏览器应呈现如下所示的页面:

这个项目展示了如何创建一个可以从 JavaScript 或 Brython 使用的 WebAssembly。由于构建 Wasm 文件会产生大量开销,因此这不应是您解决特定问题的首选方法。

如果 JavaScript 不能满足您的性能要求,那么 Rust 可能是一个选择。如果您已经拥有要与之交互的 Wasm 代码(您构建的代码或现有的 Wasm 库),这将非常有用。

使用 Rust 生成 WebAssembly 的另一个可能的好处是它可以访问 Python 或 JavaScript 中不存在的库。如果您想使用用 C 语言编写并且不能与 Brython 一起使用的 Python 库,它也很有用。如果 Rust 中存在这样的库,那么您可能会考虑构建一个 Wasm 文件以将其与 Brython 一起使用。

在 Brython 中应用异步开发

同步编程是您可能最熟悉的计算行为。例如,当执行 A、B 和 C 三个语句时,程序首先执行 A,然后是 B,最后是 C。每个语句在将其传递到下一个之前都会阻塞程序的流程。

想象一种技术,A 将首先被执行,B 将被调用但不会立即执行,然后 C 将被执行。您可以将 B 视为将来被执行的承诺。因为 B 是非阻塞的,所以它被认为是异步的。有关异步编程的其他背景知识,您可以查看Python 中的异步功能入门。

JavaScript 是单线程的,特别是在涉及网络通信时依赖于异步处理。例如,获取 API 的结果不需要阻止其他 JavaScript 函数的执行。

使用 Brython,您可以通过许多组件访问异步功能:

JavaScript 回调

JavaScript 承诺

browser.ajax

browser.aio

随着 JavaScript 的发展,回调已逐渐被承诺或异步函数取代。在本教程中,您将学习如何使用来自 Brython 的 promise 以及如何使用browser.ajax和browser.aio模块,它们利用了 JavaScript 的异步特性。

CPython 库中的asyncio模块不能在浏览器上下文中使用,在 Brython 中被替换为browser.aio.

Brython 中的 JavaScript Promise

在 JavaScript 中,promise是一个可能在未来某个时候产生结果的对象。完成后产生的值将是一个值或错误的原因。

下面的示例说明了如何使用Promise来自 Brython的 JavaScript对象。您可以在在线控制台中使用此示例:

>>>

1>>> from browser import timer, window 2>>> def message_in_future(success, error): 3... timer.set_timeout(lambda: success("Message in the future"), 3000) 4... 5>>> def show_message(msg): 6... window.alert(msg) 7... 8>>> window.Promise.new(message_in_future).then(show_message) 9

在 Web 控制台中,您可以立即获得有关 Python 代码执行的反馈:

第 1 行导入timer设置超时并window访问Promise对象。

第 2 行定义了一个executor,message_in_future()当承诺成功时,在超时结束时返回一条消息。

第 5 行定义了一个show_message()显示警报的函数。

第 8 行创建了一个带有 executor 的 promise,用一个then块链接,允许访问 promise 的结果。

在上面的例子中,超时人为地模拟了一个长时间运行的函数。承诺的实际使用可能涉及网络调用。经过3秒,许用值成功完成"Message in the future"。

如果执行程序函数message_in_future()检测到错误,则它可以error()将错误原因作为参数进行调用。您可以使用新的链式方法.catch(), 在Promise对象上实现这一点,如下所示:

>>>

>>> window.Promise.new(message_in_future).then(show_message).catch(show_message)

您可以在下图中看到成功完成承诺的行为:

这个项目展示了如何创建一个可以从 JavaScript 或 Brython 使用的 WebAssembly。由于构建 Wasm 文件会产生大量开销,因此这不应是您解决特定问题的首选方法。

如果 JavaScript 不能满足您的性能要求,那么 Rust 可能是一个选择。如果您已经拥有要与之交互的 Wasm 代码(您构建的代码或现有的 Wasm 库),这将非常有用。

使用 Rust 生成 WebAssembly 的另一个可能的好处是它可以访问 Python 或 JavaScript 中不存在的库。如果您想使用用 C 语言编写并且不能与 Brython 一起使用的 Python 库,它也很有用。如果 Rust 中存在这样的库,那么您可能会考虑构建一个 Wasm 文件以将其与 Brython 一起使用。

在 Brython 中应用异步开发

同步编程是您可能最熟悉的计算行为。例如,当执行 A、B 和 C 三个语句时,程序首先执行 A,然后是 B,最后是 C。每个语句在将其传递到下一个之前都会阻塞程序的流程。

想象一种技术,A 将首先被执行,B 将被调用但不会立即执行,然后 C 将被执行。您可以将 B 视为将来被执行的承诺。因为 B 是非阻塞的,所以它被认为是异步的。有关异步编程的其他背景知识,您可以查看Python 中的异步功能入门。

JavaScript 是单线程的,特别是在涉及网络通信时依赖于异步处理。例如,获取 API 的结果不需要阻止其他 JavaScript 函数的执行。

使用 Brython,您可以通过许多组件访问异步功能:

JavaScript 回调

JavaScript 承诺

browser.ajax

browser.aio

随着 JavaScript 的发展,回调已逐渐被承诺或异步函数取代。在本教程中,您将学习如何使用来自 Brython 的 promise 以及如何使用browser.ajax和browser.aio模块,它们利用了 JavaScript 的异步特性。

CPython 库中的asyncio模块不能在浏览器上下文中使用,在 Brython 中被替换为browser.aio.

Brython 中的 JavaScript Promise

在 JavaScript 中,promise是一个可能在未来某个时候产生结果的对象。完成后产生的值将是一个值或错误的原因。

下面的示例说明了如何使用Promise来自 Brython的 JavaScript对象。您可以在在线控制台中使用此示例:

>>>

1>>> from browser import timer, window 2>>> def message_in_future(success, error): 3... timer.set_timeout(lambda: success("Message in the future"), 3000) 4... 5>>> def show_message(msg): 6... window.alert(msg) 7... 8>>> window.Promise.new(message_in_future).then(show_message) 9

在 Web 控制台中,您可以立即获得有关 Python 代码执行的反馈:

第 1 行导入timer设置超时并window访问Promise对象。

第 2 行定义了一个executor,message_in_future()当承诺成功时,在超时结束时返回一条消息。

第 5 行定义了一个show_message()显示警报的函数。

第 8 行创建了一个带有 executor 的 promise,用一个then块链接,允许访问 promise 的结果。

在上面的例子中,超时人为地模拟了一个长时间运行的函数。承诺的实际使用可能涉及网络调用。经过3秒,许用值成功完成"Message in the future"。

如果执行程序函数message_in_future()检测到错误,则它可以error()将错误原因作为参数进行调用。您可以使用新的链式方法.catch(), 在Promise对象上实现这一点,如下所示:

>>>

>>> window.Promise.new(message_in_future).then(show_message).catch(show_message)

您可以在下图中看到成功完成承诺的行为:

在控制台中运行代码时,可以看到Promise先创建了对象,然后在超时后显示消息框。

Ajax in Brython

当函数被限定为I/O bound时,异步函数特别有用。这与受CPU 限制的函数形成对比。一个I / O绑定函数是大多花费时间等待输入或输出到结束,而函数CPU限制功能被计算。通过网络调用 API 或查询数据库是受 I/O 限制的执行,而计算素数序列则受 CPU 限制。

Brython的browser.ajax自曝HTTP一样的功能get()和post()是,默认情况下,异步的。这些函数采用blocking可以设置为True同步呈现相同函数的参数。

要异步调用HTTPGET,请ajax.get()按如下方式调用:

ajax.get(url, oncomplete=on_complete)

要以阻塞模式获取 API,请将blocking参数设置为True:

ajax.get(url, blocking=True, oncomplete=on_complete)

以下代码显示了进行阻塞 Ajax 调用和非阻塞 Ajax 调用之间的区别:

1from browser import ajax, document 2import javascript 3 4def show_text(req): 5 if req.status == 200: 6 log(f"Text received: '{req.text}'") 7 else: 8 log(f"Error: {req.status} - {req.text}") 9 10def log(message): 11 document["log"].value += f"{message} \n" 12 13def ajax_get(evt): 14 log("Before async get") 15 ajax.get("/api.txt", oncomplete=show_text) 16 log("After async get") 17 18def ajax_get_blocking(evt): 19 log("Before blocking get") 20 try: 21 ajax.get("/api.txt", blocking=True, oncomplete=show_text) 22 except Exception as exc: 23 log(f"Error: {exc.__name__} - Did you start a local web server?") 24 else: 25 log("After blocking get") 26 27document["get-btn"].bind("click", ajax_get) 28document["get-blocking-btn"].bind("click", ajax_get_blocking)

上面的代码说明了同步和异步两种行为:

第 13 行定义了ajax_get(),它使用ajax.get(). 的默认行为ajax.get()是异步的。ajax_get()返回,并show_text()分配给参数oncomplete在接收到远程文件后回调/api.txt。

第 18 行定义了ajax_get_blocking(),它演示了如何使用ajax.get()阻塞行为。在这种情况下,show_text()在ajax_get_blocking()返回之前调用。

当您运行完整示例并单击Async Get和Blocking Get 时,您将看到以下屏幕:

可以看到,在第一个场景中,ajax_get()是完全执行的,API 调用的结果是异步发生的。在第二种情况下,在从 返回之前显示 API 调用的结果ajax_get_blocking()。

Brython 中的异步 IO

随着asyncio,Python 3.4 开始公开新的异步功能。在 Python 3.5 中,异步支持通过async/await语法得到了丰富。由于与浏览器事件循环不兼容,Brython 实现browser.aio为标准的替代品asyncio。

Brython 模块browser.aio和 Python 模块asyncio都支持使用asyncandawait关键字并共享通用函数,如run()and sleep()。这两个模块都实现了其他不同的函数,这些函数属于它们各自的执行上下文、CPython 上下文环境asyncio和浏览器环境browser.aio。

您可以使用run()和sleep()来创建协程。为了说明在 Brython 中实现的协程的行为,您将实现CPython 文档中提供的协程示例的变体:

1from browser import aio as asyncio 2import time 3 4async def say_after(delay, what): 5 await asyncio.sleep(delay) 6 print(what) 7 8async def main(): 9 print(f"started at {time.strftime('%X')}") 10 11 await say_after(1, 'hello') 12 await say_after(2, 'world') 13 14 print(f"finished at {time.strftime('%X')}") 15 16asyncio.run(main())

除了第一import行,代码与您在 CPython 文档中找到的相同。它演示了关键字asyncand的使用await和显示run()和sleep()实际操作:

1层线的用途asyncio为的别名browser.aio。尽管它隐藏了aio,但它使代码接近 Python 文档示例以方便比较。

第 4 行声明了协程say_after()。注意使用async.

第 5 行调用asyncio.sleep()withawait以便当前函数将控制权交给另一个函数直到sleep()完成。

第 8 行声明了另一个协程,该协程本身将调用该协程say_after()两次。

第 9 行调用run(),一个非阻塞函数,它接受一个协程——main()在这个例子中——作为参数。

请注意,在浏览器的上下文中,aio.run()利用了内部 JavaScript 事件循环。这与asyncio.run()CPython 中的相关函数不同,后者完全管理事件循环。

要执行此代码,请将其粘贴到在线 Brython 编辑器中,然后单击Run。您应该得到类似于以下屏幕截图的输出:

首先执行脚本,然后"hello"显示,最后"world"显示。

有关 Python 中协程的更多详细信息,您可以查看Python 中的异步 IO:完整演练。

异步 I/O 的通用概念适用于所有采用这种模式的平台。在 JavaScript 中,事件循环本质上是环境的一部分,而在 CPython 中,这是使用asyncio.

上面的示例是有意练习,以保持代码与 Python 文档示例中显示的完全相同。使用 Brython 在浏览器中进行编码时,建议显式使用browser.aio,正如您将在下一节中看到的。

要向 API 发出异步调用,如上一节所述,您可以编写如下函数:

async def process_get(url): req = await aio.get(url)

请注意关键字async和的使用await。该函数需要定义为async使用await. 在此函数的执行过程中,当到达对 的调用时await aio.get(url),该函数将控制权交还给主事件循环,同时等待网络调用aio.get()完成。其余的程序执行不会被阻塞。

以下是如何调用的示例process_get():

aio.run(process_get("/some_api"))

该函数aio.run()执行协程process_get()。它是非阻塞的。

一个更完整的代码示例展示了如何使用关键字asyncandawait以及如何aio.run()和aio.get()是互补的:

1from browser import aio, document 2import javascript 3 4def log(message): 5 document["log"].value += f"{message} \n" 6 7async def process_get(url): 8 log("Before await aio.get") 9 req = await aio.get(url) 10 log(f"Retrieved data: '{req.data}'") 11 12def aio_get(evt): 13 log("Before aio.run") 14 aio.run(process_get("/api.txt")) 15 log("After aio.run") 16 17document["get-btn"].bind("click", aio_get)

在 Python 3 的最新版本中,您可以使用async和await关键字:

第 7 行定义process_get()了关键字async。

第 9 行aio.get()使用关键字 进行调用await。Usingawait要求用 定义封闭函数async。

第 14 行显示了如何使用aio.run(),它将async要调用的函数作为参数。

要运行完整示例,您需要启动 Web 服务器。您可以使用python3 -m http.server. 它在端口 8000 和默认页面上启动本地 Web 服务器index.html:

屏幕截图显示了单击Async Get后执行的步骤序列。使用aio模块和关键字的组合,async并await展示了如何接受 JavaScript 推广的异步编程模型。

分发和打包 Brython 项目

您用于安装 Brython 的方法可能会影响您部署 Brython 项目的方式和位置。特别是,要部署到 PyPI,最好的选择是首先从 PyPI 安装 Brython,然后使用brython-cli. 但是到私有服务器或云提供商的典型 Web 部署可以利用您选择的任何安装方法。

您有几个部署选项:

Brython:浏览器中的 Python(2)

手动和自动部署

部署到 PyPI

部署到 CDN

您将在以下部分中探索其中的每一个。

手动和自动 Web 部署

您的应用程序包含网站所需的所有静态依赖项、CSS、JavaScript、Python 和图像文件。Brython 是 JavaScript 文件的一部分。所有文件都可以按原样部署在您选择的提供商上。您可以查阅Web 开发教程和使用 Fabric 和 Ansible 自动化 Django 部署,了解有关部署 Brython 应用程序的详细信息。

如果你决定使用brython-cli --modules预编译Python代码,然后将文件部署不会有任何Python源代码,只brython.js和brython_modules.js。您也不会包含,brython_stdlib.js因为所需的模块brython_modules.js已经包含在其中。

部署到 PyPI

当你从 PyPI 安装 Brython 时,你可以brython-cli用来创建一个可以部署到 PyPI 的包。创建这样一个包的目标是扩展默认的 Brython 模板作为自定义项目的基础,并使 Brython 网站可以从 PyPI 中使用。

按照从 PyPI 安装部分中的说明进行操作后,在新web项目中执行以下命令:

$ brython-cli --make_dist

系统会提示您回答几个旨在创建 的问题brython_setup.json,您可以稍后修改这些问题。完成命令后,您将拥有一个名为的目录,__dist__其中包含创建可安装包所需的文件。

您可以在本地测试这个新包的安装,如下所示:

$ pip install -e __dist__

随后,您还可以web通过执行以下命令来确认新命令是否与包一起部署在本地:

$ python -m web --help usage: web.py [-h] [--install] optional arguments: -h, --help show this help message and exit --install Install web in an empty directory

请注意,该web命令的行为与 Brython 在初始安装后的行为完全相同。您刚刚创建了一个可部署到 PyPI 的自定义可安装 Brython 包。有关如何将包部署到 PyPI 的详细说明,请查看如何将开源 Python 包发布到 PyPI。

部署到 PyPI 后,您可以pip在Python 虚拟环境中安装 Brython 包。您将能够使用您创建的新命令创建新的自定义应用程序:

$ python -m --install

总而言之,以下是部署到 PyPI 的步骤:

从 PyPI 安装 Brython。

使用brython-cli --install.

使用brython-cli --make-dist.

将此包部署到 PyPI。

其他安装方法(CDN、GitHub 和 npm)不包括在内brython-cli,因此不太适合准备 PyPI 包。

部署到 CDN

就像CDN 服务器上可用的brython.js和brython_stdlibs.js一样,您还可以将静态资产、图像、样式和 JavaScript 文件(包括 Python 文件或 )部署brython_modules.js到 CDN。CDN 的示例包括:

Cloudflare

Google Cloud CDN

Azure CDN

Amazon CloudFront

Akamai

如果您的应用程序是开源的,那么您可以获得免费的 CDN 支持。示例包括CDNJS和jsDelivr。

创建 Google Chrome 扩展

Chrome 扩展程序是使用网络技术构建的组件,并集成到 Chrome 中以自定义您的浏览环境。通常,这些扩展程序的图标显示在 Chrome 窗口的顶部,地址栏的右侧。

公共扩展程序可从Chrome 网上应用店获得。要学习,您将从本地文件安装 Google Chrome 扩展程序:

在 Brython 中实现 Google Chrome 扩展之前,您将首先实现一个 JavaScript 版本,然后将其转换为 Brython。

JS 中的 Hello World 扩展

作为初学者,您将实现一个将执行以下操作的扩展:

单击扩展程序图标时打开一个弹出窗口

单击弹出窗口按钮时打开提示消息

在初始弹出窗口的底部附加您输入的消息

以下屏幕截图说明了此行为:

在一个空文件夹中,创建文件manifest.json来配置扩展:

1// manifest.json 2{ 3 "name": "JS Hello World", 4 "version": "1.0", 5 "description": "Hello World Chrome Extension in JavaScript", 6 "manifest_version": 2, 7 "browser_action": { 8 "default_popup": "popup.html" 9 }, 10 "permissions": ["declarativeContent", "storage", "activeTab"] 11}

此示例的重要字段是默认弹出文件 ,popup.html您还必须创建该文件。有关其他字段的信息以及更多信息,您可以查阅清单文件格式文档。

在同一文件夹中,创建popup.html用于定义扩展用户界面的文件:

1 2 3 4 5 6 7 8 9 10 11

12 13

HTML 代码与您用于在 JavaScript 中创建 Chrome 扩展程序的代码非常相似。有几个细节值得注意:

第 5 行brython.min.js从本地包加载。出于安全原因,仅加载本地脚本,您无法从 CDN 等外部源加载。

第 6 行加载init_brython.js,它调用brython().

7号线负载popup.py。

第 9 行声明body没有通常的onload="brython()".

另一个安全约束会阻止您brython()在onload发生body标记时调用。解决方法是在文档中添加一个-brython(),并在文档内容加载后指示浏览器执行:

// init_brython.js document.addEventListener('DOMContentLoaded', function () { brython(); });

最后,您可以在以下 Python 代码中看到此应用程序的主要逻辑:

# popup.py from browser import document, prompt def hello(evt): default = "Real Python" name = prompt("Enter your name:", default) if not name: name = default document["hello"].innerHTML = f"Hello, {name}!" document["hello-btn"].bind("click", hello)

这样,您就可以像处理 JavaScript chrome 扩展一样继续安装和测试了。

测试和调试 Brython

目前没有方便的库来单元测试 Brython 代码。随着 Brython 的发展,您将看到更多用于在浏览器中测试和调试 Python 代码的选项。可以将 Python 单元测试框架用于可在浏览器外部使用的独立 Python 模块。在浏览器中,带有浏览器驱动程序的 Selenium 是一个不错的选择。调试也是有限的,但也是可能的。

Python 单元测试

Python 单元测试框架,如内置的unittest和pytest,在浏览器中不起作用。您可以将这些框架用于 Python 模块,这些模块也可以在 CPython 的上下文中执行。任何 Brython 特定的模块browser都无法在命令行中使用此类工具进行测试。有关 Python 单元测试的更多信息,请查看 Python 测试入门。

Selenium

Selenium是一个自动化浏览器的框架。它与浏览器中使用的语言无关,无论是 JavaScript、Elm、Wasm 还是 Brython,因为它使用WebDriver概念来表现得像用户与浏览器交互。您可以查看使用 Python 和 Selenium进行现代 Web 自动化以获取有关此框架的更多信息。

JavaScript 单元测试

有许多专注于 JavaScript 的测试框架,如Mocha、Jasmine和QUnit,在整个 JavaScript 生态系统中表现良好。但它们不一定非常适合对浏览器中运行的 Python 代码进行单元测试。一种选择需要将 Brython 函数全局公开给 JavaScript,这与最佳实践背道而驰。

为了说明将 Brython 函数公开给 JavaScript 的选项,您将使用QUnit,这是一个 JavaScript 单元测试套件,可以在 HTML 文件中独立运行:

1 2 3 4 5 6 7 8 Test Suite 9 10 25 26 46 47 48

在一个 HTML 文件中,您编写了 Python 代码、JavaScript 代码和 JavaScript 测试来验证在浏览器中执行的两种语言的函数:

第 11 行导入 QUnit 框架。

第 23 行暴露python_add()给 JavaScript。

第 28 行定义js_add_test测试 JavaScript 函数js_add()。

第 34 行定义py_add_test了测试 Python 函数python_add()。

第 40 行定义py_add_failed_test了测试 Python 函数python_add()的错误。

您不需要启动 Web 服务器来执行单元测试。index.html在浏览器中打开,您应该会看到以下内容:

该页面显示了两个成功的测试,js_add_test()和py_add_test(),一个失败的测试,py_add_failed_test()。

将 Python 函数暴露给 JavaScript 展示了如何使用 JavaScript 单元测试框架在浏览器中执行 Python。虽然可以进行测试,但一般不建议这样做,因为它可能与现有的 JavaScript 名称冲突。

在 Brython 中调试

在撰写本文时,还没有用户友好的工具来调试您的 Brython 应用程序。您无法生成允许您在浏览器开发工具中逐步调试的源映射文件。

这不应该阻止您使用 Brython。以下是一些有助于调试和排除 Brython 代码故障的提示:

使用print()或browser.console.log()在浏览器的开发人员工具控制台中打印变量值。

使用Python 3.8中的酷新功能中所述的f-string 调试。

偶尔使用开发者工具清除浏览器的 IndexedDB。

通过选中浏览器开发人员工具的网络选项卡中的禁用缓存复选框,在开发过程中禁用浏览器缓存。

添加选项以brython()启用要在 JavaScript 控制台中显示的其他调试信息。

复制brython.js和brython_stdlib.min.js本地复制以加快开发过程中的重新加载。

在您import编写 Python 代码时启动本地服务器。

对Chrome 扩展程序进行故障排除时,从扩展程序打开检查器。

Python 的优点之一是REPL(读取-评估-打印循环)。在线 Brython 控制台提供了一个平台来试验、测试和调试一些代码片段的行为。

探索 Brython 的替代品

Brython 并不是在浏览器中编写 Python 代码的唯一选择。有一些替代方案可用:

Skulpt

Transcrypt

Pyodide

PyPy.js

每个实现都从不同的角度解决问题。Brython 试图通过提供对与 JavaScript 相同的 Web API 和 DOM 操作的访问来替代 JavaScript,但具有 Python 语法和习语的吸引力。与某些可能具有不同目标的替代方案相比,它被打包为一个小下载。

这些框架如何比较?

Skulpt

雕塑在浏览器Python 代码编译为 JavaScript。编译发生在页面加载后,而在 Brython 中,编译发生在页面加载期间。

虽然它没有内置函数来操作 DOM,但 Skulpt 在其应用程序上非常接近 Brython。这包括教育用途和成熟的 Python 应用程序,如Anvil 所示。

Skulpt 是一个朝着 Python 3 发展的维护项目。 Brython 在与 CPython 3.9 的模块上与浏览器中的执行兼容。

转密

Transcrypt包含一个命令行工具,用于将 Python 代码编译为 JavaScript 代码。编译据说是提前(AOT)。然后可以将生成的代码加载到浏览器中。Transcrypt 占用空间小,大约 100KB。它速度快并且支持 DOM 操作。

Skulpt 和 Brython 的区别在于,Transcrypt 在下载并在浏览器中使用之前,先使用 Transcrypt 编译器编译为 JavaScript。这实现了速度和小尺寸。但是,它阻止了 Transcrypt 像其他平台一样被用作教育平台。

Pyodide

Pyodide是 CPython 解释器的 WebAssembly 编译。它在浏览器中解释 Python 代码。没有 JavaScript 编译阶段。尽管 Pyodide 与 PyPy.js 一样,需要您下载大量数据,但它加载了NumPy、Pandas、Matplotlib等科学库。

您可以将 Pyodide 视为完全在浏览器中运行的Jupyter Notebook环境,而不是由后端服务器提供服务。您可以使用一个活生生的例子来试验 Pyodide 。

pypy.js

PyPy.js 使用通过emscripten编译为 JavaScript的PyPy Python 解释器,使其兼容在浏览器中运行。

除了项目目前处于休眠状态之外,PyPy.js 是一个大包,大约 10 MB,对于典型的 Web 应用程序来说是望而却步。通过打开PyPy.js 主页,您仍然可以使用 PyPy.js 作为在浏览器中学习 Python 的平台。

PyPy.js 使用 emscripten 编译为 JavaScript。Pyodide 更进一步,特别是利用 emscripten 和 Wasm 将 Python C 扩展(如NumPy)编译为 WebAssembly。

在撰写本文时,PyPy.js 似乎没有得到维护。对于与编译过程相同的内容,请考虑 Pyodide。

结论

在本教程中,您深入了解了在浏览器中编写 Python 代码的几个方面。这可能会让您对尝试使用 Python 进行前端开发产生一些兴趣。

在本教程中,您学习了如何:

在本地环境中安装和使用 Brython

在前端 Web 应用程序中用 Python 替换 JavaScript

操作DOM

与JavaScript交互

创建浏览器扩展

比较Brython 的替代品

除了访问通常为 JavaScript 保留的功能之外,Brython 的最佳用途之一是作为学习和教学工具。现在,您可以访问在浏览器中运行的 Python编辑器和控制台,开始探索 Brython 的多种用途。

API JavaScript Python

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:《MXNet深度学习实战》—1.3.2 MXNet的优势
下一篇:Angular 依赖的测试和 Fake
相关文章