一种实现编译型嵌入式Python的方法
技术领域
本发明涉及计算机的技术领域,特别是涉及一种实现编译型嵌入式Python的方法。
背景技术
MicroPython是一款能够运行于Windows系统、Unix系统和部分微控制器上的超小型Python解释器,它把庞大的Python解释器压缩到256KB以内并且能够支持大部分的Python3功能。Python语言有着比C语言更优秀的可读性和可靠性,使用MicroPython能够有效提高编程效率。Python解释器占用较大的Flash空间和更多的计算资源,不适合用于开发有着低资源占用和高实时性要求的嵌入式应用。
目前,Cython和PyPy分别提出两种提高Python运行效率的工具,但是都只适用桌面系统编程领域,无法适用于微控制器。Cython提供的方法是用类Python语法编写或引用静态函数供Python解释器调用,本质上还是基于解释器因此不适合用于微控制器编程。PyPy提供了Python的即时编译技术(Just In Time,简称JIT),它在运行时会先把需要使用的函数翻译成本机代码从而提高运行效率,但是JIT本身的运行时间会占用总体运行时间,同时还会造成内存的巨大开销,因此JIT方法也不适合用于微控制器。
因此,针对上述技术问题,有必要提供一种能够满足实时性、可应用于微控制器的实现编译型嵌入式的Python方法。
发明内容
有鉴于此,本发明实施例的目的在于提供一种实现编译型嵌入式Python的方法。本发明实施例提供的实现编译型嵌入式Python的方法基于类型注释和静态分析实现源码翻译器,并将翻译器集成至嵌入式平台中,实现了Python源文件的编辑、编译、链接和烧写。
为了实现上述目的,本发明一实施例提供的技术方案如下:一种实现编译型嵌入式Python的方法,包括步骤S1:遍历Python源码的抽象语法树而获取程序的语义信息并根据所述语义信息生成对应的C++代码;步骤S2:对Python源码的进行类型注释,从而生成C++的变量定义和函数定义;步骤S3:采用翻译器对经过步骤S1和步骤S2处理过的Python源代码翻译成C++源文件;步骤S4:将所述C++源文件与嵌入式芯片相关的文件存储在一起而形成文件包,对所述文件包进行编译和链接并生成ASCII文本文件。
作为本发明的进一步改进,所述嵌入式芯片相关的文件包括头文件、启动文件和链接文件。
作为本发明的进一步改进,所述类型注释包括变量注释和函数注释,所述变量注释包括步骤:对Python源码中的单个变量赋值的同时给出所述变量的类型,从而生成对应的C++变量定义;所述函数注释包括步骤:对Python源码中的函数进行定义的同时给出所述函数中参数的类型和所述函数的返回值的类型,从而生成对应的C++函数定义。
作为本发明的进一步改进,所述翻译器的设计过程包括步骤S31:定义翻译内容的结构,所述结构包括头文件包含区、命名空间定义区和源码区;步骤S32:设计Python的类型至C++的类型的映射关系;步骤S33:采用自底向上的方法对抽象语法树进行语义分析并生成C++代码,将生成的C++代码按照所述翻译内容的结构进行拼接。
作为本发明的进一步改进,所述映射关系如下表所示,其中x,y∈T,T={int,float,str,bytes,bool}
作为本发明的进一步改进,所述步骤S33包括:步骤S331:采用自底向上的子节点访问的方式进行表达式翻译;步骤S332:将语句或者/和语句列表中的每行程序代码翻译成C++代码语句。
作为本发明的进一步改进,所述步骤S332的翻译过程中,需要先将所述语句区分为函数定义语句和非函数定义语句,并将所述函数定义语句所对应翻译成的C++代码语句填入其他作用域语句区域,将所述非函数定义语句所对应翻译成的C++代码语句填入所在作用域的函数体内。
作为本发明的进一步改进,所述抽象语法树为一个结点对象列表,每个对象包括多个子列表或者其他对象的引用,每个列表或者子列表包括任意数量的结点对象。
作为本发明的进一步改进,获取所述Python源码的抽象语法树的过程包括步骤:调用抽象语法树模块的parse()函数,采用dump()函数将parse()函数获取的Python源码的抽象语法树转化为自然语言形式。
作为本发明的进一步改进,将所述ASCII文本文件应用至嵌入式平台时,采用C或者其他语言封装模块实现IO操作。
本发明具有以下优点:
本发明实施例提供的实现编译型嵌入式的Python方法基于类型注释和静态分析实现了源码翻译器,并将翻译器集成到嵌入式平台中,实现了Python源文件的编辑、编译、链接和烧写,突破了现有技术无法将Python实时应用至嵌入式平台领域的缺陷。
附图说明
为了更清楚地说明本发明实施例或现有技术中的技术方案,下面将对实施例或现有技术描述中所需要使用的附图作简单地介绍,显而易见地,下面描述中的附图仅仅是本发明中记载的一些实施例,对于本领域普通技术人员来讲,在不付出创造性劳动的前提下,还可以根据这些附图获得其他的附图。
图1为本发明实施例提供的实现编译型嵌入式的Python方法的流程示意图;
图2为图1所示实施例中的另一种表达方式的流程示意图;
图3为应用本发明实施例提供的实现编译型嵌入式的Python方法在E-Python-AHL-GEC-IDE编译成功界面的示意图;
图4为图3所示实施例中的串口接收亮暗改变信息的界面示意图;
图5为图3所示实施例中的小灯亮起的实际图示。
具体实施方式
为了使本技术领域的人员更好地理解本发明中的技术方案,下面将结合本发明实施例中的附图,对本发明实施例中的技术方案进行清楚、完整地描述,显然,所描述的实施例仅仅是本发明一部分实施例,而不是全部的实施例。基于本发明中的实施例,本领域普通技术人员在没有做出创造性劳动前提下所获得的所有其他实施例,都应当属于本发明保护的范围。
如图1和图2所示,本发明实施例提供的实现编译型嵌入式的Python方法的流程示意图。在该实施例中,实现编译型嵌入式的Python方法包括四个步骤,每个步骤的具体内容如下所示。
步骤S1:遍历Python源码的抽象语法树而获取程序的语义信息并根据所述语义信息生成对应的C++代码。
抽象语法树(Abstract Syntax Tree,简称AST)是源代码到目标代码的一种中间表示,用树状结构描述了程序构造。Python的AST可以表示为一个结点对象列表,每个对象包含多个子列表或其他对象的引用,每个列表或子列表包含任意数量的结点对象。Python的AST通过调用抽象语法树(ast)模块的parse()函数获取,并且可以使用dump()函数将其转为自然语言形式。表1展示了一个Python函数及其调用的抽象语法树。
表1.Python函数及其调用的抽象语法树
在表1中,其中根节点Module表示代码整体,列表body包含一个函数定义FunctionDef和一个表达式语句Expr。函数定义结点中有函数名name、参数列表args、函数体子列表body和返回值注释returns。表达式语句包括了一个函数调用结点Call,函数调用结点中有函数名func和参数列表args。
编译型嵌入式Python通过遍历Python源码的AST获取程序的语义信息,并根据其语义生成对应的C++代码。在遍历过程中主要处理的语法树结点及其表示的内容如表2所示。
表2 Python抽象语法树的结点类型和表示内容
步骤S2:对Python源码的进行类型注释,从而生成C++的变量定义和函数定义。
类型注释(Type Hints,又称类型提示或类型注解)是Python3.5版本之后加入的语法,它将变量或函数的类型信息写入源代码中,使得在语义分析阶段可以直接读出类型信息而不用再进行复杂的推导。该功能旨在开放Python代码来简化程序的静态分析、运行时类型检查和根据类型信息生成其他代码。
类型注释有变量注释和函数注释两种。变量注释是在对单个变量赋值的同时给出变量的类型,该类赋值语句又被成为带标注的赋值语句。函数注释是在函数定义时给出参数的类型和返回值的类型。表3给出了类型注释的例子,一个是int类型变量x的变量注释,另一个是返回值为int、参数为一个int的函数func的函数注释.
表3类型注释举例
编译型嵌入式Python利用类型注释来生成C++的变量定义和函数定义。对于变量,要求第一次出现必须是变量注释,从而能够生成对应的C++变量定义语句。对于函数,要求必须使用函数注释,从而生成参数和返回值类型与注释内容对应的函数定义和声明。
编译型嵌入式Python要求变量先定义类型后再使用,函数必须使用函数注释,示例如表3,对应的语法描述如下:
V::=va:type=value,
type::=B|Dict[B1,B2]|Set[B]
|List[B]|Tuple[B],
B::=int|float|str|bool|bytes
其中,V是变量va定义示使用的带标注的赋值语句语法;F是函数func的定义语法,stmt_list是函数体,
是参数列表,
是返回值类型;type和B定义了能够使用的类型。
步骤S3:采用翻译器对经过步骤S1和步骤S2处理过的Python源代码翻译成C++源文件。
翻译器的设计过程包括三个步骤,具体如下所示。
步骤S31:定义翻译内容的结构,所述结构包括头文件包含区、命名空间定义区和源码区。从Python代码的执行流程与C++的执行流程中抽取共性,可将翻译内容的结构分为头文件包含区、命名空间定义和源码区三部分,如表4所示。在头文件包含区中"builtin.h"必须被包含,该文件内声明了Python的内置函数的C++实现。命名空间用来模拟Python的模块。源码区必须包含当前模块的初始化函数<Module>_init(),该函数用于存放Python源文件中全局作用域内的代码翻译结果。
表4头文件和源文件的结构
步骤S32:设计Python的类型至C++的类型的映射关系。Python的类型分为基础类型和复合类型两种,基础类型包括了整型、浮点型、字符串、布尔型和字节数组,复合类型包括了列表类型、元组类型、集合类型和字典类型。设基础类型集为T={int,float,str,bytes,bool},设Python类型到C/C++的映射关系为f,映射关系如表5所示,其中x,y∈T。
表5基本类型映射关系
注释的类型 |
翻译后的类型 |
int |
int |
float |
float |
str |
string |
bytes |
vector<char> |
bool |
bool |
List[x](列表类型) |
vector<f(x)> |
Tuple[x](元组类型) |
vector<f(x)> |
Set[x](集合类型) |
set<f(x)> |
Dict[x,y](字典类型) |
map<f(x),f(y)> |
步骤S33:采用自底向上的方法对抽象语法树进行语义分析并生成C++代码,将生成的C++代码按照所述翻译内容的结构进行拼接。翻译过程是自底向上对语法树进行语义分析并生成C++代码的过程,生成的代码最终按翻译文件的内容结构进行拼接。按表2中给出的结点类型可以分为表达式结点的翻译和语句结点的翻译两类过程。该步骤具体包括两个步骤,具体内容如下所示。
步骤S331:采用自底向上的子节点访问的方式进行表达式翻译。表达式翻译是语句翻译的基础,它将构成某种语句的Python表达式翻译成C++表达式后返回给语句翻译过程。表1已经描述了Python表达式结点的类型,一个表达式expr的形式化描述如下所示。
expr::=Compare|BoolOp|BinOp|UnaryOp
|Constant|Call|List|Tuple|Dict|Set
|Subscript|Name|Attribute
Compare::=expr1cop expr2,
where cop::=>|<|>=|<=|==|!=
BoolOp::=expr1bop expr2,where bop::=and|or
BinOp::=expr1dop expr2,
where dop::=+|-|*|/|**|//|<<|>>|&|^|\||%
UnaryOp::=uop expr1,where uop::=not|~|+|-
Call::=func(expr1,...,exprn),
where func::=Name|Attribute
List::=[expr1,...,exprn]
Tuple::=(expr1,...,exprn)
Dict::={expr11:expr12,...,exprn1:exprn2}
Set::={expr1,...,exprn}
Subscript::=expr[sub],
where sub::=expr|expr1:expr2|expr1:expr2:expr3
Name::=[_a-zA-Z][_a-zA-z0-9]*
Attribute::=Attribute.Name|Name
表达式由于存在递归定义,所以其翻译过程是一个自底向上的子结点访问过程,设表达式翻译函数名为visit_expr,则其内容就是将传入的表达式结点参数代入对应类型的访问函数中,如表6所示。
表6表达式翻译伪代码
限于篇幅,具体类型的表达式翻译过程不进行详细描述。
步骤S332:将语句或者/和语句列表中的每行程序代码翻译成C++代码语句。语句是关键字和表达式的有序排列,用来表示程序执行的操作,因此语句翻译过程是根据语法树结点内容对表达式与关键字的重新组合。Python中常用的语句有赋值类语句、程序结构控制类语句、函数定义语句、模块导入语句和表达式语句。设Python的单句语句为stmt,语句块为stmt_list,回车换行符为,语句和语句列表的形式化描述如下所示。
stmt::=Import|ImportFrom|Continue|Break|If
|While|For|FunctionDef|Expr|Assign
|AugAssign|AnnAssign|Return
stmt_list::=stmt\nstmt_list|stmt
type::=typeid|Dict[typeid1,typeid2]|Set[typeid]
|List[typeid]|Tuple[typeid]
typeid::=int|float|str|bool|bytes
Import::=import m1,...,mn,
where m::=Name
ImportFrom::=from m import c1,...,cn,
where m::=Name,c::=Name
Continue::=continue
Break::=break
If::=if expr:stmt_list1else:stmt_list2
|if expr:stmt_list
While::=while expr:stmt_list
For::=for expr1in expr2:stmt_list
FunctionDef::=def func(arg1:typea1,...,argn:typean)->
(typer1,...,typerm):stmt_list,
where arg::=Name
Expr::=expr
Assign::=id1,...,idn=expr1,...,exprk,
where id::=Name|Attribute,k<=n
AugAssign::=id op=expr,
where op::=+|-|*|/|//|**|<<|>>|%|&|^|or
AnnAssign::=id:type=expr,
where id::=Name
Return::=return expr1,expr2,...,exprn
在该步骤的翻译过程中,需要先将所述语句区分为函数定义语句和非函数定义语句,并将所述函数定义语句所对应翻译成的C++代码语句填入其他作用域语句区域,将所述非函数定义语句所对应翻译成的C++代码语句填入所在作用域的函数体内。设语句翻译函数名为visit_stmt,输入是一个语句列表L,输出是函数定义翻译列表func_result和其他语句翻译列表other,其伪代码描述如表7所示。
表7语句翻译伪代码
限于篇幅,本发明实施例主要描述流程控制类语句和函数定义语句的翻译过程。
1.流程控制类语句的翻译
Python的程序结构分为顺序结构、选择结构和循环结构三类。顺序结构即按从上到下的顺序执行两句行语句。选择结构使程序按某个条件执行分支程序,在Python中即为if语句,相比C++少了switch语句。循环结构使程序在某个条件内重复执行一段代码,在Python中有while和for两种循环。下面是选择结构和循环结构从Python语法翻译成C++语法的方法。
if语句由分支条件、if主体代码块和else代码块三部分构成。分支条件是一个表达式,主体代码块和else代码块都是语句列表。
翻译if语句需要将分支条件condition、主体代码块body和else代码块orelse代入对应的翻译函数,然后将翻译结果按C++语法拼接。对于任意的If语句结点i,翻译过程函数visit_if可以描述为:
c←visit_expr(condition),
b←visit_stmt(body),
o←visit_stmt(orelse),
while语句由循环控制条件和循环体代码块构成。循环控制条件是一个表达式,循环体代码块是语句列表。
翻译while语句需要将循环条件condition、循环体代码块body代入对应的翻译函数,然后将翻译结果按C++语法拼接。对于任意的While语句结点w,翻译过程函数visit_while可以描述为:
c←visit_expr(condition),
b←visit_stmt(body),
visit_while(w)=while(c){b}
for语句由循环变量、序列对象和循环体代码块构成。循环变量和序列对象都是表达式,循环体代码块是语句列表。
该类循环实现的是从序列对象seq第一个元素循环到最后一个元素,每次循环都用循环变量iter表示当前元素,与C++的迭代器相似。由于序列类型翻译后都使用STL容器类型来表示,因此for语句也是翻译成C++的迭代器语法。对于任意的For语句结点f,翻译过程函数visit_for可以描述为:
i←visit_expr(iter),
s←visit_expr(seq),
b←visit_stmt(body),
visit_for(f)=for(auto i=s.begin();i!=s.end();i++){b}
2.函数定义的翻译
函数定义由函数名func、参数列表args、返回值注释returns和函数内容body构成,嵌入式Python要求参数和返回值都必须加上类型注释,因此函数定义可以描述为:
FunctionDef::=def func(arg1:typea1,...,argn:typean)->
(typer1,...,typerm):\nstmt_list,
arg::=Name,
type::=typeid|Dict[typeid1,typeid2]|Set[typeid]
|List[typeid]|Tuple[typeid],
typeid::=int|float|str|bool|bytes
其中arg1:typea1~argn:typean是参数列表,typer1~typerm是返回值注释。
翻译函数定义需要将第一个返回值类型作为函数类型,将后面的返回值类型对应的引用类型插入到参数列表中。设按基本类型映射关系映射后的参数类型为ta1~tan,返回值类型为tr1~trn,f.stmt_list是函数内的语句块,函数定义的翻译函数visit_func可以描述为:
s←visit_stmt(f.stmt_list)
步骤S4:将所述C++源文件与嵌入式芯片相关的文件存储在一起而形成文件包,对所述文件包进行编译和链接并生成ASCII文本文件。
本发明实施例提供的实现编译型嵌入式的Python方法基于类型注释和静态分析实现了源码翻译器,并将翻译器集成到嵌入式平台中,实现了Python源文件的编辑、编译、链接和烧写,突破了现有技术无法将Python实时应用至嵌入式平台领域的缺陷。
由于Python不具有指针操作功能,不能直接按地址访问存储器,所以Python都是借助C或其他语言封装的模块实现IO操作。IO操作是嵌入式应用的基础,因此必须设计与C/C++的混合编程方法,从而实现嵌入式Python的IO操作。
在实现IO操作过程中,函数和变量的声明规则具体过程如下:函数和变量需要使用一个Python源文件声明,该文件第一行内容是"#Extern Definition",翻译器遇到该类源文件时,只对其生成标识符信息表而不进行内容翻译。函数和变量的声明语法需要符合上文编译型嵌入式Python中函数注释中的中的F和V,并且函数体的内容是"pass",表示空内容。设用来存放函数和变量声明的Python源文件(模块)名为Module.py,则其内容C可以描述如下所示:
C::=#Extern Definition\nS,
S::=DS|D,D::=F|V
在实现IO操作过程中,函数和变量的实现规则具体过程如下:函数和变量使用C++语法进行实现,需要使用一个扩展名为".cpp"的文件作为源文件,和一个文件名为"Module.hpp"的文件作为头文件。
(1)变量
全局变量的实现需要对Module.py中声明的变量按C++语法加上前缀"Py_"。设Module.py中有名称为v变量声明V,它的类型注释为type,经过类型映射后得到t,则该变量的在Module.hpp中的内容E和在源文件中的内容T可以描述为:
E::=extern t Py_v;
T::=t Py_v;
(2)函数
函数的实现需要对函数名、参数名添加"Py_"前缀,并且将第一个返回值作为函数类型,第二个以后的返回值的引用类型加入到参数列表中。
设Module.py中有函数名为func的函数声明F,有k个参数arg1~argk并且它们的类型经过映射后为t1~tk,有z个返回值,并且映射后的类型为r1~rz,函数内容为S(使用C++编写),则Module.hpp中需要的函数声明H和源文件中需要的函数实现C可以描述为:
H::=D;
C::=D{S}
在确定函数和变量的实现内容后,最后对这些内容按翻译文件的结构进行排布,即可完成混编的C++实现部分。表5描述了源文件和Module.hpp的内容,&符号表示文法的与运算,includes是混编中使用的其他文件中定义的函数、变量所需要包含的头文件语句列表。
表8混合编程的C++实现内容
在本发明实施例中,还将上述的ASCII文本文件应用至嵌入式平台进行了实验。在该实验中,嵌入式平台选取为STM32L431RC为硬件平台,将RT-Thread的操作系统API通过混合编程方法提供给嵌入式Python,并在该实时操作系统环境下实现了三盏小灯的闪烁。
STM32L431是基于高性能Cortex-M4内核的32位精简指令集超低功耗微控制器,工作频率最高达80MHz。它有256KB的Flash和64KB的RAM,并提供了一个低功耗RTC、一个通用32位定时器、一个专用于电机控制的16位PWM定时器、四个通用16位定时器和两个16位低功耗定时器。
实验实现的功能是在RT-Thread系统环境下,主线程创建红灯、绿灯和蓝灯三个线程,三个线程控制三色灯的颜色,并通过UART向PC机发送小灯亮暗状态。
实验使用基于苏州大学与ARM公司联合出品的嵌入式开发集成开发环境AHL-GEC-IDE改版的E-Python-AHL-GEC-IDE作为开发环境,编写程序并烧写到目标板上,如图3所示。编译成功后使用串口更新烧写到STM32中运行,可以看到三个线程交替执行,小灯交替改变亮暗,并向上位机发送亮暗状态改变信息,如图4所示。小灯亮起如图5所示。
通过实验,有效地证明了本发明实施例所提出的编译型嵌入式Python的方法可以在嵌入式平台上实现,能够符合嵌入式平台的实时性要求。
对于本领域技术人员而言,显然本发明不限于上述示范性实施例的细节,而且在不背离本发明的精神或基本特征的情况下,能够以其他的具体形式实现本发明。因此,无论从哪一点来看,均应将实施例看作是示范性的,而且是非限制性的,本发明的范围由所附权利要求而不是上述说明限定,因此旨在将落在权利要求的等同要件的含义和范围内的所有变化囊括在本发明内。不应将权利要求中的任何附图标记视为限制所涉及的权利要求。
此外,应当理解,虽然本说明书按照实施方式加以描述,但并非每个实施方式仅包含一个独立的技术方案,说明书的这种叙述方式仅仅是为清楚起见,本领域技术人员应当将说明书作为一个整体,各实施例中的技术方案也可以经适当组合,形成本领域技术人员可以理解的其他实施方式。