Skip to content

llpy 开发基础

llpy 是一款本项目专用的自研 Python 解释运行引擎,也可以叫做 Python 虚拟机。它和 PC 上标准版的 Python 以及 ARM 的 MicroPython 都兼容 Python 3.x 语法,但又有着本质的不同:

  • llpy 从一开始就是为 ESP32 或类似低内存、低性能嵌入式硬件开发的,对 ESP32 上极为敏感的内存使用以及代码效率做了很多特殊设计。
  • llpy 的设计目标是希望将 ESP32 用于控制面板、交互式设备、AI 玩具、人机智能等更复杂的应用领域,而非传统 IoT 小节点。因此重点支持了 LVGL、音频、摄像头等能力。

与 MicroPython 的差异

  • Python 3.x 绝大部分的语法和语言特性都实现了,应该是目前支持得较为全面的引擎,但同时又有着不输于标准版 Python 的性能。
  • 借鉴了 FFI 的理念并将其完全融合进了 Python 的语法,使得 C 语言 API 的导入变得简单,内存和 Bin 占用都不大。

语言支持情况

  • 所有一元和二元数学运算全部支持。整数默认为 32 位整型数,但支持 64 位数的表示、传参、存储和展示。如果要进行数学计算,需要使用内置的 bigint 库(无须 import 直接使用),如 bigint.lshbigint.rshbigint.orbigint.andbigint.invertbigint.fdivbigint.mod 等。
  • def 定义的函数可以拥有一个层级的闭包函数,但是闭包函数中不可以再定义闭包函数。闭包捕捉的宏默认未开启,因此闭包函数不可以对父级函数域的变量进行写操作。
  • try...except 不支持跨函数捕捉异常,except Exception as e 这个 e 变量仅仅是一个字符串,就是异常的文本信息。
  • 所有内置常用库的名称、函数名、参数数量、用途等,都和标准版 Python 3.x 一样。可用的内置库有:datedatetimedictlisttuplejsonmathrandomosstringsysthreadingtime
  • 全局变量需预先定义,在 module 范围内赋任意初值即创建了全局变量,不能在运行时使用 global 动态创建新的全局变量。
  • 可以在任意位置进行 import,但在 import 所在的函数退出的时候模块就会被销毁。
  • 赋值语句只支持单对单 a = b、多对多 a,b,c = c,b,a 和自动解包 a,b = c
  • 字符串全部默认为 UTF-8 编码。
  • 使用 match...case 代替 if...elif,尤其是 match 下所有 case 的比较值全部都是常量的时候,性能相比 if...elif 组合快不少。
  • list/tuple/str/bytes 这些支持下标访问的类型仅在 get/set/数组 slice 时可以使用负数索引。
  • *vargs**kargs 需要写在最后面,如果有 **kargs 则必须是最后一个参数,否则 *vargs 必须是最后一个参数。

未支持或部分支持的语法

  • List 和 Dict 的推导式暂不支持。
  • @staticmethod@classmethod 等绝大多数装饰器暂不支持。
  • isinstance(x, object) 会报错,因为不是所有的类型都派生自 object。如果想要判断一个变量是否是某类的实例,使用 isinstance(x, instance),这个 instance 是本引擎独有的类型。

性能提示

  • 如果只是简单的计数循环,while 的性能比 for 更好。
  • 直接使用变量或全局变量,肯定比把变量放在 Dict 或 Class 中再通过间接操作效率要高得多。

FFI 与指针

  • llpy 添加了一种新的变量类型名为 pointer,也就是指针。该变量类型是为了与 C 语言更好的交互而设计的。它支持了多数 C 语言中指针变量的特性,可以参与二元计数、通过指针操作结构体成员、作为参数直接传给 C 函数等。
  • 有大量的函数是通过 C 语言直接映射的方式导入 llpy 的,最典型的是 lvgl 库以及 ESP-IDF 框架的大部分 API。这部分类和函数的手册请参考 API 文档。
  • 可以在 Python 代码中使用 '''!C {block} ''' 来直接嵌入 C 语言的头文件,或者使用 #!include "{C文件路径}" 来包含 C 语言头文件。C 头文件中被处理的只有常量(#definestatic/const 以及 enum 定义的量)和 struct/union,函数定义会被忽略。

示例:

python
'''!C
typedef struct {
  int16_t x;
  int16_t y;
} lv_point_t;
'''

pt = malloc(C.lv_point_t)
pt.C.x = 100    # 写值
print(pt.C.x)   # 读值

规则就是所有的 C 成员变量的使用前面必须加上特殊属性 C。如果不加 C 而直接访问如 pt.x 也是可以的,但是性能会有略微损失。同一个 .py 文件中的所有 C 语言代码,其 struct 成员变量名称都是全局的,可能需要对 struct 成员进行改名以防止重名。

  • RGB565 / RGB565Swap / membersizeof / offsetof 这 4 个函数是编译期函数,其值在编译后就直接被替换为常量了。

基于 llpython ESP 平台构建