RPython 语言¶
定义¶
RPython 是 Python 的一个受限子集,适合进行静态分析。尽管语言中有一些新增功能,并且有些事情可能令人惊讶地有效,但这仍然是一个应该考虑的限制粗略列表。请注意,在您使用的过程中,会遇到大量的特殊情况限制。确切的定义是“RPython 是我们的翻译工具链可以接受的一切” :)
流程限制¶
变量
变量在每个控制流点上最多应包含一种类型的值,如对象限制中所述,这意味着例如必须避免使用相同的变量在连接控制路径时同时包含字符串和整数。允许将 None(基本上充当空指针的角色)与许多其他类型混合:包装对象、类实例、列表、字典、字符串等,但不与整数、浮点数或元组混合。
常量
所有模块全局变量都被视为常量。在运行时不能更改它们的绑定。此外,全局(即预构建)列表和字典应该是不变的:修改例如全局列表将导致不一致的结果。但是,全局实例没有此限制,因此如果您需要可变的全局状态,请将其存储在一些预构建的单例实例的属性中。
控制结构
所有结构都允许使用,for
循环限制为内置类型,生成器受到很大限制。
范围
range
和xrange
是相同的。range
不一定创建数组,只有在修改结果时才会创建。它在任何地方都允许使用并且完全实现。与 CPython 唯一的可见区别是无法访问xrange
字段 start、stop 和 step。
定义
不允许在运行时定义类或函数。
生成器
支持生成器,但它们的精确范围非常有限。您不能在一个控制点合并两个不同的生成器。
异常
完全支持。有关内置操作引发的异常的限制,请参阅下面的异常规则
对象限制¶
我们正在使用
整数、浮点数、布尔值
有效。
字符串
很多,但并非所有字符串方法都受支持,并且那些受支持的方法不一定接受所有参数。索引可以为负数。如果它们不是负数,那么如果翻译器可以证明它们是非负数,则可以获得稍微更有效的代码。在切片字符串时,必须证明切片起始和结束索引是非负数。在任何地方都没有隐式 str 到 unicode 的转换。使用%
运算符进行简单的字符串格式化有效,只要格式字符串在翻译时已知即可;唯一支持的格式说明符是%s
、%d
、%x
、%o
、%f
,以及%r
,但仅限于用户定义的实例。转换标志、精度、长度等修饰符不受支持。此外,在格式化时禁止混合使用 unicode 和字符串。
元组
没有可变长度的元组;使用它们来存储或返回值对或 n 元组。元素和长度的每种类型组合都构成一种单独且不可混合的类型。
没有将列表转换为元组的通用方法,因为结果的长度在静态时是未知的。(当然,您可以执行
t = (lst[0], lst[1], lst[2])
,如果您知道lst
有 3 个项目。)
列表
列表用作分配的数组。列表是过度分配的,因此 list.append() 速度相当快。但是,如果您使用固定大小的列表,则代码效率更高。注释器可以在大多数情况下弄清楚您的列表是固定大小的,即使您使用列表推导式也是如此。
- 索引:允许正索引和负索引。当由 IndexError 异常子句请求时,会检查索引。
- 切片:切片起始必须在范围内。停止不需要,但它不能小于开始。所有负索引都不允许,除了 [:-1] 特殊情况。没有步长。切片删除遵循相同的规则。
- 切片赋值:仅支持
lst[x:y] = sublist
,如果len(sublist) == y - x
。换句话说,切片赋值不能更改列表的总长度,而只是替换项目。- 其他运算符:
+
、+=
、in
、*
、*=
、==
、!=
按预期工作。- 方法:append、index、insert、extend、reverse、pop。pop() 中使用的索引遵循上面索引的相同规则。insert() 中使用的索引必须在范围内且不为负数。
字典
仅具有唯一键类型的字典,前提是它是可散列的。不会使用自定义散列函数和自定义相等性。对于自定义散列函数,请使用rpython.rlib.objectmodel.r_dict
。
集合
RPython 中不直接支持集合。相反,您应该使用普通字典并将值填充为 None。该字典中的值不会占用空间。
列表推导式
可用于创建分配的、已初始化的数组。
函数
- 函数声明可以使用默认值和
*args
,但不能使用**keywords
。 - 可以对已知函数或变量函数或方法进行函数调用。您可以使用位置参数和关键字参数进行调用,并且可以传递
*args
参数(它必须是元组)。 - 如上所述,元组的长度不是可变的。如果您需要使用动态数量的参数调用函数,请重构函数本身以接受单个参数,该参数是一个常规列表。
- 动态分派强制使用对所有可能的被调用函数都相等,或者至少“足够兼容”的签名。这主要涉及方法调用,当方法被重写或以任何方式在不同的类中给出不同的定义时。它还涉及不太常见的情况,即显式操作函数对象。描述确切的兼容性规则相当复杂(但是,如果您违反了这些规则,您应该从 rtyper 获得明确的错误,而不是模糊的崩溃。)
内置函数
可以使用许多内置函数。精确的集合可以在rpython/annotator/builtin.py中找到(请参阅
def builtin_xxx()
)。不过,某些内置函数在其支持的功能方面可能受到限制。
int, float, str, ord, chr
… 可用作简单的转换函数。请注意,int, float, str
… 在 isinstance 内部仅具有类型特殊含义。
类
- 方法和其他类属性在启动后不会更改
- 完全支持单一继承
- 在类体中使用 rpython.rlib.objectmodel.import_from_mixin(M) 来复制类 M 的全部内容。这可以用来实现 mixin:函数和静态方法被复制(其他类属性只是被简单地复制,没有修改)。
- 类也是一等对象
对象
适用普通规则。唯一受重视的特殊方法是
__init__
、__del__
、__len__
、__getitem__
、__setitem__
、__getslice__
、__setslice__
和__iter__
。为了处理切片,必须使用__getslice__
和__setslice__
;不支持使用__getitem__
和__setitem__
进行切片。此外,即使使用__getslice__
,也不支持使用负索引进行切片。请注意,析构函数
__del__
应该只包含简单操作;对于任何更复杂的析构函数,请考虑改用rpython.rlib.rgc.FinalizerQueue
。
此布局使需要处理的类型数量非常有限。
整数类型¶
在实现整数类型时,我们遇到了一个问题,即CPython中的整数目前处于不断变化的状态。从Python 2.4开始,整数在溢出时会自动转换为长整数。相比之下,我们需要一种方法,默认情况下执行环绕式机器大小的算术运算,同时仍然能够在需要时显式检查溢出。此外,我们还需要在转换前后保持一致的行为。
我们使用普通整数进行有符号算术运算。这意味着在转换前,如果发生溢出,我们将得到长整数;在转换后,我们将得到静默的环绕式结果。每当我们需要更多控制时,我们使用以下辅助函数(位于rpython/rlib/rarithmetic.py)
ovfcheck()
此特殊函数应该只与单个算术运算作为参数一起使用,例如
z = ovfcheck(x+y)
。其预期含义是在溢出检查模式下执行给定的运算。在运行时,在Python中,ovfcheck()函数本身会检查结果,如果结果是
long
,则会引发OverflowError。但是代码生成器将ovfcheck()用作提示:它们将整个ovfcheck(x+y)
表达式替换为C中的单个溢出检查加法。
intmask()
此函数用于环绕式算术运算。它返回其参数的低位,屏蔽掉任何不适合C“有符号长整数”的内容。其目的是在Python中,将先前运算产生的Pythonlong
转换回Pythonint
。代码生成器完全忽略intmask(),因为它们默认情况下始终执行环绕式有符号算术运算。(我们目前没有C中“int”与“long int”区别的等价物,并且在所有地方都假设为“long int”。)
r_uint
在某些情况下(例如哈希表操作),我们需要机器大小的无符号算术运算。对于这些情况,存在r_uint类,它是字大小无符号整数的纯Python实现,可以静默地环绕。(“字大小”和“机器大小”等效使用,表示原生大小,在C中使用“unsigned long”获得。)此类的目的(与上面的辅助函数相反)是一致的类型:Python和注释器都将在程序中传播r_uint实例,并将它们之间的所有运算解释为无符号运算。r_uint的实例由代码生成器特殊处理,以使用适当的底层类型和运算。在运算中混合(有符号)整数和r_uint将产生r_uint,这意味着无符号结果。要将r_uint转换回有符号整数,请使用intmask()。
类型强制和检查¶
RPython提供了一个辅助装饰器,用于强制在函数参数上使用RPython级别的类型。该装饰器称为enforceargs()
,它接受作为参数的类型,这些类型应与函数的参数匹配。
使用enforceargs()
装饰的函数会对其函数签名进行分析,并在导入时推断其RPython级别的类型(有关RPython中执行的翻译类型的更多详细信息,请参阅注释传递文档)。遇到RPython不支持的类型将引发TypeError
。
enforceargs()
默认情况下还会在每次调用函数时执行参数类型的类型检查。要禁用此行为,可以将typecheck=False
参数传递给装饰器。
异常规则¶
默认情况下,对于简单情况不会生成异常。
#!/usr/bin/python
lst = [1,2,3,4,5]
item = lst[i] # this code is not checked for out-of-bound access
try:
item = lst[i]
except IndexError:
# complain
没有异常处理程序的代码不会引发异常(也就是说,在它被转换之后。当您在CPython之上运行它时,它当然可能会引发异常)。通过提供异常处理程序,您要求进行错误检查。如果没有,您向系统保证操作不会失败。此规则不适用于函数调用:假设任何被调用的函数都允许引发任何异常。
例如
x = 5.1
x = x + 1.2 # not checked for float overflow
try:
x = x + 1.2
except OverflowError:
# float result too big
但是
z = some_function(x, y) # can raise any exception
try:
z = some_other_function(x, y)
except IndexError:
# only catches explicitly-raised IndexErrors in some_other_function()
# other exceptions can be raised, too, and will not be caught here.
上面描述的ovfcheck()函数遵循相同的规则:如果发生溢出,它会显式引发OverflowError,该错误可以在任何地方被捕获。
显式引发或重新引发的异常将始终生成。
PyPy在CPython之上是可调试的¶
PyPy的优势在于它可以在标准CPython上运行。这意味着,我们可以启用所有异常处理来运行所有PyPy,因此我们可能会捕获无法遵守隐式断言的情况。