TPython :一种扩充的Pyhon语言
- 首页 >> Python编程摘 要 Py tho n 是一种流行的动态语言, 但是由于完全的动态性影响了其执行效率和使用。因此, 在Py thon 基础上进行了静态化扩充, 研制了TPy thon 语言。该语言在注释中引入了可选的类型声明, 并提供了包括类型检查在内的静态检查。相对于动态语言而言, TPy tho n 能够提高程序的可读性和可靠性, 降低调试成本, 相对于静态语言而言, TPytho n 又具有语法简单, 易于开发的优势。
1 引言
动态语言是一种在执行期间进行类型检查的语言。早期环境中, 由于动态语言的性能低下, 严重制约了它的发展。近些年来, 伴随着计算机硬件的不断升级, 性能不再是程序开发中选择语言唯一的决定条件, 而动态语言因为具有学习曲线低, 开发效率高等优点, 日益获得广泛重视。然而, 动态语言也存在一些缺陷, 首先, 因为缺乏类型声明, 在命名不规范的情况下, 获取变量或方法信息变得极其困难, 降低了代码的可读性。特别是在方法调用时, 常需阅读文档注释甚至方法实现来获取参数的类型信息, 使开发效率降低。此外, 因为不能根据类型信息提供静态类型检查和代码优化, 增加了调试成本, 降低了性能。如果能够在动态语言中引入一定的静态性, 就能够很好地解决上面的这些问题。此外在程序开发的实践中可以发现, 虽然动态语言允许变量在运行时更改类型, 但是大量的代码证明大部分变量并不会改变类型[ 10] , 因此引入静态性是可行的。本文针对动态语言完全动态性所带来的问题,对Python2 .5 版本进行扩充, 设计并实现了TPython语言。该语言有效结合了动态语言和静态语言的特征。首先, TPython 是一种动态语言, 有着动态语言的语法简单, 易于开发等优势。此外,TPython又引入了静态语言的类型声明及类型检查, 提高了程序的可读性和可靠性, 降低了调试成本。
2 设计思路当前在关于结合动态语言和静态语言优势的工作上, 大致分为4 类思路:一是模拟静态化, 例如K .D .Smi th 等人提出的“装饰器”[ 11] , 可以为方法提供类型标注。在此基础上,C .Winter 等人开发了ty pecheck[ 4] 工具。该工具可以为方法提供类型标注, 同时还能够在运行时对方法的参数和返回值类型进行类型检查。然而, 这类工作难以提供真正的静态性, 效率不足。相对应的第二种思路是采用类型推导[ 2] 将动态语言静态化, 主要目标在于提高动态语言的效率。相关工作有D .A ncona 等人的RPy thon[ 1] 和M .S .的S tarkiller[ 12] 等。前者将Py thon 的代码经过静态化处理直接编译为运行平台能够识别的中间字节码, 能够充分提高各种运行平台上代码运行的效率。后者则是采用类型推导直接优化Python的字节码来提高效率。这种思路能有效提高程序运行效率, 但是未能提供类型声明机制。与第二种相类似的第三种思路是在静态语言中结合动态语言优势。相关工作有Java/C #的反射机制,C ++的auto 关键字引入, 以及R .B .deO liveira 设计开发的Bo o 语言[ 14] 等。Boo 语言是一种静态语言, 它采用了Python 语言的语法, 因此具有动态语言语法简单带来的优势。这方面的工作尚在进行之中,Boo 语言也尚未推出正式版本。最后一种思路是在动态语言中引入静态类型声明。例如Guido 的在Py tho n 中添加可选类型声明[ 9] , R .E .Masse 的Evo lutionary Pro to typing[ 13] , 都提出在Py tho n 中引入可选类型声明,并提供静态类型检查。E .C .New ton 等人开发的静态检查工具PyChecker[ 7] , 提供了除类型检查以外的静态检查。然而, 这方面大多数还只是理论形式的研究, 没有涉及到具体实现上的某些重要环节, 少数实现的工具也不完善。从上面的介绍来看, 前两种思路都存在着一些重大弊端, 如效率不足, 无法在代码中约束类型等。第三种思路中的Bo o 语言在本质上仍然是静态语言, 而第四种思路暂时还停留在理论阶段, 没有比较完善的实践成果。因此在TPy tho n 的设计中,我们采用了在动态语言中引入静态类型声明的思路。这样不仅能保留了动态语言的特性, 而且其可选类型声明可辅助进行静态类型检查。
3 TPython的类型系统
Python中有两种类型系统, 在Python2 .5 的类型系统中[ 8] , 自定义的类型默认属于“class”类型的对象, 相关联实例的类型都是“instance” , 在该类型系统中直接引入类型声明会造成声明自定义类型的不便。此外, Python3000 的类型系统[ 15] 又与旧版本完全不兼容。我们没有直接引用Py thon的现有的两个类型系统, 而是对Python2 .5 的类型系统进行了一些扩展。
3 .1 虚类型在Python 文档的类型描述中, 为了将几种概念相似的类进行归纳, 引入了几个类型集合的名称, 在实际使用中也需要这种类型。因此, 我们在TPython 的类型系统中引入了虚类型的概念, 虚类型相当于Py tho n 中的类型集合, 它们是Py thon 类型系统中不存在的类型, 没有独立的类型表示。TPython 中允许在语句中声明这种虚类型。
3 .2 内建类型不同于静态语言, Py tho n 中各种用于内建类型的符号不都是关键字, 因此完全可能出现使用内建类型的符号自定义一个新类型的情况。此外,TPython 的类型系统中还定义了虚类型, 这些虚类型的符号很可能会被重复使用。所以对于使用内建类型的符号进行声明, TPython 的设计中采取了如下措施:首先, 对于声明语句中的类型符号默认采取与Py thon 符号搜索相同的作用域规则:对于当前符号的搜索, 首先查找当前代码块, 再查找全局符号域, 最后查找内建符号域。其次, TPy thon还提供特定的类型符号前缀“ &” 来协助重载内建类型符号的情况。例如在代码中自定义Integ ers这个类型后, 如果想在后面的语句中声明变量a 为内建的Integers 类型, 可以使用“ # @var a :&Integers”语句, 声明为自定义“Integ ers”类型则 56 余 超等:TPython:一种扩充的 Py tho n语言 第 37 卷使用“ #@var a :Integers”语句。
3 .3 自定义类型Py thon 中存在“ class”和“instance” 两种内建类型。在Python2 .2 版本以前, 内建类型以外任何自定义的类型都是“class”类型的对象, 相对应的实例也都是“instance”类型的对象, 因此在类型系统上不存在自定义的类型。在2 .2 版本后, Py thon引入了“New -sty le Class”的概念。一个“New -style Class”的实例的类型不再是“instance” , 而是它对应的自定义类型。但是原来的“class”概念仍然保留, 并且作为默认形式存在。如果在这种类型系统的基础上引入类型声明, 会造成使用不便, 为此我们改进了这部分的类型系统表示。出于兼容性的考虑, TPython 的类型系统中保留了“class”和“instance”这两个类型, 并且保留了它们相对应于原来类型系统的位置。同时改进中将自定义类型引入到类型系统中, 默认作为“instance”的子类型进行处理。在T Py thon 的类型系统的设计中, 还有其它的一些细微调整, 例如虚类型全部添加“s”后缀, 以区别实际类型;添加大整数和小整数的虚父类型等。
4 TPython的语法
就如何在Py thon 中引入类型声明语法,Guido提出了添加可选类型声明语法的一套方案[ 9] , Python社区也进行了广泛的讨论, 获得了一些有价值的成果, 并运用于Py tho n 的改进之中[ 3] 。在TPy thon 中, 我们根据这些建议, 提出了一套兼容Py thon2 .5 的类型声明语法。
4 .1 声明方式一般而言, 动态语言有两种类型声明的方式。一是添加类型声明语法, 这种方式能够使代码中类型声明部分简洁明了。但是新建类型声明语法也存在着一些不足:新语法的代码无法通过原来的解释器, 而且大量可选语法的加入违背了Py tho n 的设计理念:“Simple i s bet te r than comple x .”[ 16] 。第二种方法是在注释中引入类型声明。Py thon 的docstring 特性[ 5] 很好地支持了函数注释, 在注释中引入类型声明能够充分利用这一特性。在第三方工具中, Ulipad[ 17] 和Epy do c[ 6] 也采取了这种办法。此外注释内容会被Py tho n 解释器所忽略, 使得新代码能够兼容Py tho n 解释器。因此, TPython采用在注释中引入类型声明的方式。
4 .2 语法我们考虑了已有各种工作的特点, 在TPy thon中采用了类似Ulipad 和Epy do c 的语法, 并对其语法做了相当规模的扩充。语法规则的BNF 范式如下所示:typedef stmt :'@'(typedef re turn ty pedef pa ramtypedef var)NEWLINEtypedef retur n:'return'['@'do tted name inty pe] typelisttypedef pa ram :'param'['@'dotted name inty pe] typedefex prtypedef var :'va r'typedef e xprtypedef expr :dotted name inty pe':'ty pe listtype list :sing le type (' 'sing le type )*single ty pe :(do tted name inty pe '&'NAM E)['['type list[':'type list] ']']do tted name intype :NAME ('.'NAME)*类型声明语句分为两大类:1)变量的类型声明语句这种类型声明一般由注释符“ #” 起始, 用字符串“ @var”来标示, 接着是“符号:类型列表”的变量类型声明格式。其中类型列表可以是单个类型, 也可以是以“ ”分隔的多个类型。变量的类型声明支持容器类型的声明, 而容器内部类型需要声明在容器类型后的一对方括号“[ ” 与“] ”之间。对于字典类型内部的键类型和值类型, 采用与容器类型相同的方式来声明, 声明中使用符号“ :”来分隔键和值。例如, 在程序段#@var MyDict :dict [ int st r :int]MyDict = {1 :15 , “Fi rst Chapter” :15}中, 把MyDict 定义为具有键类型为int 或st r , 值类型为int 的字典类型变量, 可用来查找特定章节或者特定章节名字的起始页码。因此, 表达式MyDict ={1 :15 , “Fi rst Chapter” :15}是合法的。2)函数的类型声明语句这种类型声明的语句一般在函数定义中的docst ring 中, 可以对函数参数和函数返回值进行声明。函数参数的声明由字符串“ @param”标示,后面是可选的“ @” +函数名, 可以在函数体外进行函数声明时用来指定函数名, 最后加以跟变量声明语法相同的“符号:类型列表”格式。函数返回值的声明由“ @return”标示, 同样加以可选的指定函数名部分, 再加以类型列表。例如在下面代码中, Loo kupWo rdInBoo ()函第37 卷(2009)第
5 期 计算机与数字工程 57数的参数w o rd 为st r 类型, 参数page 为int 类型,返回值为int 类型。Lo okupWordInBo ok(w or d, pag e)'@pa ram pag e :int@pa ram wo rd :str@return int'…
4 .3 位置和作用域在静态类型语言中, 某个符号的类型声明一般处于首次使用该符号之前。在动态语言中, 这种做法很容易限制了类型的动态性, 在实际运用中也有不现实的地方。在具体程序开发中, 经常会遇到引用某个没有声明过类型的接口, 让程序员去修改引用模块添加声明是非常困难的。同时出于利用docstring 特性的考虑, 更加无法保证声明一定会处在使用之前。因此, 在TPy tho n 中, 类型声明语句会有一定灵活性, 除了符号使用前的类型声明,还允许在docst ring 中进行函数类型声明, 也允许引用模块语句后进行模块内部的类型声明等。同时TPy thon 中规定了类型声明的作用域将不会被限制在当前代码块中, 但是符号的类型信息只有在声明后才起效。
4 .4 其他类型声明规则TPy tho n 中对于类型声明还定义了一些规则。首先, 任何类型声明都是可选的, 比如可以不声明某个符号的类型而直接使用, 或者对于字典类型可以只声明内部的值类型等。其次, 类型声明只能够声明类型系统中的类型或者已经定义过的自定义类型。再者, 不符合类型声明语法的注释都将被视为一般的注释, 不会给出任何警告信息。
5 T Python的静态检查规则
Python 中类型信息是在运行时绑定, 这是动态语言的特性,但这种特性也导致了大部分检查必须在运行时进行。因此TPython 在设计和实现中, 将提供有效的静态检查, 不仅把大部分的能够提前检查提前到编译时期, 而且还提供了静态类型检查。TPython 提供了Python 原解释器不支持的一些静态类型检查, 包括编译时期的类型转换, 操作数的类型检查等。在代码中, 还可能会遇到重复声明类型的情况。TPython 对于符号的重复声明采用如下措施, 如果当前声明的类型与已经声明的类型相同, 则忽略此次声明, 否则将会给出一个检查错误。在静态检查中, TPy thon 还做了一些其他改动。在语句的类型检查中, 因为w hile 等语句一般需要boo l 类型作为条件判断部分, 因此添加了检查语句特定成分的规则。在类声明和函数定义中,添加了默认声明的方式, 即不需要在注释中声明这些符号的类型, 而默认将这些符号声明为相应的类型, 并进行类型检查。由于引入了可选类型声明, 静态检查中存在没有定义类型等的特殊问题。对于这类问题, TPython只有绝对会在运行时出错的检查结果才会报错, 否则只提示警告。例如, 声明了变量“count”为整数类型, 若代码中存在对count = 3 .2 , 虽然该操作在Py thon 中是允许的, 但是违反了TPy thon的类型规则。若还存在count + x , 而x 的类型未知, 则编译器无法判断这种操作在运行时是否绝对会出错的, 因而对这类操作检查的结果只会返回警告。TPython 将类型检查提前到编译时期, 有效降低调试成本, 能够及时发现程序问题。