纵有疾风起
人生不言弃

PyPy和CPython之间的区别

本页介绍了PyPy Python解释器和CPython之间的一些差异和不兼容性。其中一些差异是“按设计”,因为我们认为有些情况下CPython的行为是错误的,我们不想复制错误。

此处未列出的差异应视为PyPy的错误。

内置类型的子类

正式地说,CPython根本没有规则,因为内置类型的子类的完全重写方法是否被隐式调用。作为近似,这些方法永远不会被同一对象的其他内置方法调用。例如,不会通过内置 方法调用__getitem__()子类中 的重写。dictget()

以上在CPython和PyPy中都是如此。关于内置函数或方法是否会调用另一个对象的重写方法,可能会出现差异self。在PyPy中,它们通常在CPython不支持的情况下被调用。两个例子:

class D(dict):
    def __getitem__(self, key):
        return "%r from D" % (key,)

class A(object):
    pass

a = A()
a.__dict__ = D()
a.foo = "a's own foo"
print a.foo
# CPython => a's own foo
# PyPy => 'foo' from D

glob = D(foo="base item")
loc = {}
exec "print foo" in glob, loc
# CPython => base item
# PyPy => 'foo' from D

改变已经用作字典键的对象类

请考虑以下代码段:

class X(object):
    pass

def __evil_eq__(self, other):
    print 'hello world'
    return False

def evil(y):
    d = {X(): 1}
    X.__eq__ = __evil_eq__
    d[y] # might trigger a call to __eq__?

在CPython中,可能会调用__evil_eq__ ,尽管没有办法编写可靠地调用它的测试。它发生在和,在何处计算何时插入字典中。如果偶然的条件满足,那么 被调用。y is not xhash(y) == hash(x)hash(x)x__evil_eq__

PyPy使用一种特殊的策略来优化字典的键是不覆盖缺省的用户定义的类的实例__hash__, __eq____cmp__:使用此策略时,__eq__并且 __cmp__在上述情况下都不会被调用,而是查找被身份完成,所以保证__eq__不会被调用。

请注意,在所有其他情况下(例如,如果您有自定义__hash__和 __eq__in y),行为与CPython完全相同。

忽略异常

在许多极端情况下,CPython可以默默地吞下异常。虽然大多数情况非常罕见,但发生这种情况的确切列表相当长。最知名的地方是定制丰富的比较方法(如__eq__); 字典查找; 调用一些内置函数,如isinstance()。

除非这种行为明确地存在于设计中并且如此记录(例如对于hasattr()),在大多数情况下PyPy允许异常传播。

对象的原始值的身份,isid

原始值的对象标识通过值相等而不是通过包装器的标识来工作。对于任意整数,这意味着总是如此。该规则适用于以下类型:x + 1 is x + 1x

  • int
  • float
  • long
  • complex
  • str (仅限空字符或单字符字符串)
  • unicode (仅限空字符或单字符字符串)
  • tuple (仅限空元组)
  • frozenset (仅限空冻结)
  • 未绑定的方法对象(仅适用于Python 2)

此更改也需要进行一些更改idid满足以下条件:。因此,上述类型将返回从参数计算的值,因此可以大于(即,它可以是任意长度)。x is y <=> id(x) == id(y)idsys.maxint

注意,长度为2或更大的字符串可以相等而不相同。同样,即使包含一个元组也不一定是真的 。唯一性规则仅适用于上述特定情况。的,, 和规则PyPy 5.4加入; 在此之前,类似测试或可能即使不能等于或。在PyPy 5.4中添加的新行为更接近CPython,它精确地缓存空元组/冻结集,并且(通常但不总是)长度<= 1的字符串和unicode。x is (2,)xx == (2,)strunicodetuplefrozensetif x is "?"if x is ()x"?"()

请注意,对于浮点数,“浮点数” is每个“位模式”只有一个对象。所以是真正的PyPy,但不能在CPython的,因为它们是两个对象; 但 由于位模式不同,因此总是假的。像往常一样, 总是假的。当在容器中使用时(例如列表项或集合中),使用的确切相等规则是“ ”(在CPython和PyPy上); 因此,因为PyPy中的所有内容都相同,所以在CPython中不能将它们中的几个放在一个集合中。(问题#1974)。另一个结果是,因为 首先检查参数是否相同(从此调用返回没有好的值,因为float('nan') is float('nan')0.0 is -0.0float('nan') == float('nan')if x is y or x == ynanscmp(float('nan'), float('nan')) == 0cmpiscmpcmp 假装浮点数有一个总订单,但对于NaN来说这是错误的)。

C-API差异

外部C-API已在PyPy中重新实现为内部cpyext模块。我们支持大多数记录在案的C-API,但有时内部C-abstractions在CPython上泄露并被滥用,甚至可能在不知情的情况下被滥用。例如,PyTupleObject在内部使用元组之后,即使通过另一个C-API函数调用,也不支持对a的赋值。在CPython上,只要refcount为1,这就会成功。在PyPy上,这将始终引发 异常(此处为搜索引擎明确列出)。SystemError('PyTuple_SetItem called on tuple after  use of tuple")

另一个类似的问题是tp_as_*在调用之后为任何结构分配新的函数指针 PyType_Ready。例如,tp_as_number.nb_int在调用PyType_Ready CPython 之后覆盖 不同的函数将导致调用旧函数x.__int__() (通过类__dict__查找)并调用新函数int(x) (通过插槽查找)。在PyPy上,我们将始终调用__new__函数,而不是旧函数,不幸的是,这种古怪的行为是完全支持NumPy所必需的。

性能差异

CPython有一个优化,可以使重复的字符串连接不是二次的。例如,这种代码在O(n)时间内运行:

s = ''
for string in mylist:
    s += string

在PyPy中,此代码将始终具有二次复杂性。另请注意,CPython优化很脆弱,无论如何都可能因代码略有变化而中断。所以你应该用以下代码替换代码:

parts = []
for string in mylist:
    parts.append(string)
s = "".join(parts)

杂项

  • PyPy中忽略了哈希随机化(-R)。在3.4之前的CPython中它没什么意义。CPython> = 3.4和PyPy3都实现了随机SipHash算法并忽略。-R

  • 您不能将非字符串键存储在类型对象中。例如:

    class A(object):
        locals()[42] = 3
    

    不行。

  • sys.setrecursionlimit(n)通过将可用堆栈空间设置为字节,仅限大约设置限制。在Linux上,根据编译器设置,默认值为768KB就足以支持大约1400次调用。n * 768

  • 由于字典的实现不同,所以调用__hash____eq__调用的确切次数是不同的。由于CPython也没有给出任何具体的保证,所以不要依赖它。

  • 赋值__class__仅限于在CPython 2.5上运行的情况。在CPython 2.6和2.7上,它可以在更多情况下工作,到目前为止PyPy不支持这种情况。(如果需要的话,它可以支持,但随后将在许多很成功 更多情况下对PyPy比CPython的2.6 / 2.7)。

  • __builtins__名字总是引用__builtin__模块,从来没有一本字典,因为它有时是CPython的。分配 __builtins__无效。(对于像RestrictedPython这样的工具的使用,请参阅问题#2653。)

  • 直接调用一些带有无效参数的内置类型的内部魔术方法可能会产生稍微不同的结果。例如,[].__add__(None)(2).__add__(None)都返回 NotImplemented上PyPy; 在CPython上,只有后者才有,前者提升TypeError。(当然,[]+None2+None 既提高TypeError无处不在。)这种差异是一个实现细节示出,因为这PyPy没有内部C级时隙组成。

  • 在CPython上,[].__add__是一个method-wrapper,而且 list.__add__是一个。在PyPy上,这些是正常的绑定或未绑定的方法对象。这有时会混淆一些检查内置类型的工具。例如,标准库模块有一个函数,在未绑定的方法对象上返回True,但在方法包装器或插槽包装器上返回False。在PyPy上,我们无法区分,所以。slot wrapperinspectismethod()ismethod([].__add__) == ismethod(list.__add__) == True

  • 在CPython中,内置类型具有可以以各种方式实现的属性。根据方式,如果您尝试写入(或删除)只读(或不可删除)属性,则可以获得a TypeError或a AttributeError。PyPy试图在完全一致性和完全兼容性之间取得一些中间立场。这意味着一些极端情况不会引发相同的异常,例如。del (lambda:None).__closure__

  • 在纯Python中,如果你编写 并且有一个不覆盖的子类,那么 仍然检查它是一个实例。在CPython中,用C编写的类型使用不同的规则。如果用C语言编写,则任何实例都将被接受 (实际上,在这种情况下)。一些可以在CPython上工作但在PyPy上没有的代码包括:( 这里 是超类)。无论如何,正确的解决方案可以说是首先使用常规方法调用:class A(object): def f(self): passBf()B.f(x)xBAAB.f(x)B.f is A.fdatetime.datetime.strftime(datetime.date.today(), ...)datetime.datedatetime.datetimedatetime.date.today().strftime(...)

  • gc模块的某些功能和属性的行为略有不同:例如,gc.enable并且 gc.disable受支持,但它们不是启用和禁用GC,而是启用和禁用终结器的执行。

  • PyPy在启动时以交互模式打印过去#pypy IRC主题的随机行。在已发布的版本中,此行为被禁止,但设置环境变量PYPY_IRC_TOPIC将使其恢复。请注意,已知下游包提供程序完全禁用此功能。

  • PyPy的readline模块从头开始重写:它不是GNU的readline。它应该是大多数兼容的,它增加了多线支持(参见参考资料multiline_input())。另一方面, parse_and_bind()忽略呼叫(问题#2072)。

  • sys.getsizeof()总是加油TypeError。这是因为使用此函数的内存分析器最有可能在PyPy上给出与现实不一致的结果。可以 sys.getsizeof()返回一个数字(有足够的工作量),但这可能代表也可能不代表对象使用的内存量。询问一个对象使用了多少,与系统的其他部分隔离,甚至没有意义。例如,实例具有映射,这些映射通常在许多实例之间共享; 在这种情况下,地图可能会被实现忽略sys.getsizeof()但是,如果它们是具有唯一地图的许多实例,则它们的开销在某些情况下很重要。相反,相等的字符串可以共享其内部字符串数据,即使它们是不同的对象 – 或者空容器可以共享其内部的部分,只要它们是空的。更奇怪的是,有些列表会在您阅读时创建对象; 如果你试图估计内存中的大小range(10**6)作为所有项目大小的总和,那么该操作本身就会创建一百万个从未存在过的整数对象。请注意,CPython中也存在其中一些问题,更不用说了。出于这个原因,我们明确地没有实现sys.getsizeof()

  • timeit模块PyPy下的行为有所不同:它打印的平均时间和标准偏差,而不是最小的,因为最小的往往是误导性的。

  • get_config_vars方法sysconfigdistutils.sysconfig 不完整。在POSIX平台上,CPython从用于构建解释器的Makefile中捕获配置变量。PyPy应该在编译期间烘焙值,但是还没有这样做。

  • "%d" % x和类似的构造,其中一个子类的实例覆盖特殊方法或或:PyPy不调用特殊方法; CPython确实 – 但只有它是子类 ,而不是。CPython的行为非常混乱:例如对于 它的调用,它应该返回一个类似的字符串; 然后去掉了最后一个,剩下的就被保留下来了。如果从中返回一个意外的字符串, 则会出现异常(或者在CPython 2.7.13之前崩溃)。"%x" % xxlong__str____hex____oct__longint%x__hex__()-0x123L0xL__hex__()

  • 在PyPy中,传递的字典**kwargs只能包含字符串键,即使是dict()dict.update()。CPython 2.7允许在这两种情况下使用非字符串键(据我们所知,只有那里)。例如,此代码TypeError在CPython 3.x以及任何PyPy上生成: 。(注意相当于 。)dict(**{1: 2})dict(**d1)dict(d1)

  • PyPy3:__class__heaptypes和非heaptypes之间的属性赋值。CPython允许模块子类型,但不适用于例如int 或float子类型。目前,PyPy不支持__class__任何非heaptype子类型的 属性赋值。

  • 在PyPy中,模块和类字典在假设从它们中删除属性很少的情况下进行了优化。由于这个原因,例如, 其中,是包含的功能的模块(或类),是与CPython显著慢。del foo.barfoobar

  • CPython中的各种内置函数只接受位置参数而不接受关键字参数。这可以被认为是一个长期运行的历史细节:较新的函数倾向于接受关键字参数,并且旧函数偶尔也会被修复。在PyPy中,大多数内置函数接受关键字参数(help()显示参数名称)。但是不要太依赖它,因为如果CPython也开始接受它们,未来版本的PyPy可能必须重命名参数。

  • PyPy3:distutils已得到增强,允许VsDevCmd.batVS%0.f0COMNTOOLS(通常VS140COMNTOOLS)环境变量指向的目录中查找。CPython搜索高于 该值的vcvarsall.bat某个地方。

  • SyntaxError s更加努力地提供有关失败原因的详细信息,因此错误消息与CPython中的错误消息不同

扩展模块

我们支持的扩展模块列表:

  • 作为内置模块支持(在pypy / module /中):

    __builtin__ __pypy__ _ast _codecs _collections _continuation _ffi _hashlib _IO _locale _lsprof _md5 _minimal_curses _multiprocessing _random _rawffi _sha _socket _sre _ssl _warnings _weakref _winreg阵列binascii BZ2 cStringIO CMATH cpyext 隐窝错误号例外的fcntl GC小鬼itertools编组数学MMAP操作者解析器POSIX pyexpat选择信号结构符号SYS termios的螺纹时间令牌unicodedata zipimport zlib

    在Windows上进行翻译时,会跳过一些仅限Unix的模块,而是构建以下模块:

    _winreg

  • 通过在纯Python中重写(可能使用cffi)来支持:请参阅lib_pypy /目录。我们支持这样的模块的例子:ctypescPicklecmathdbmdatetime…请注意,某些模块都在那里,在上面的列表; 默认情况下,使用内置模块(但可以在转换时禁用)。

PyPy中不提供上面既未提及也未在lib_pypy /中提供的扩展模块(即用C语言编写,在标准CPython中的模块)。(无论如何,您可能有机会使用cpyext。)

原文链接:https://pypy.readthedocs.io/en/latest/cpython_differences.html

未经允许不得转载:起风网 » PyPy和CPython之间的区别
分享到: 生成海报

评论 抢沙发

评论前必须登录!

立即登录