pypy支持的擴展模塊(對應Python/Modules/
中的模塊)
-
pypy支持的內建模塊:
-
__builtin__
:內建模塊,包含一些常用的函數,如abs()
等; -
__pypy__
:提供一個由pypy解析器提供的特殊功能模塊; -
_ast
:抽象句法樹模塊的內建模塊,一般直接使用ast
模塊; -
_codecs
:注冊表與基類的編解碼器的內建模塊,一般直接使用codecs
模塊; -
_collections
:容器數據類型的內建模塊,一般直接使用collections
模塊; -
_continuation
: -
_ffi
: -
_hashlib
:安全散列與消息摘要的內建模塊,一般直接使用hashlib
模塊; -
_io
:io內建模塊,一般直接使用io
模塊; -
_locale
:國際化的內建模塊,一般直接使用locale
模塊; -
_lsprof
:Python分析器的內建模塊,一般直接使用lsprof
模塊; -
_md5
:md5的內建模塊,一般直接使用md5
模塊; -
_minimal_curses
:字符顯示的終端處理curses的內建模塊,一般直接使用curses
模塊,_minimal_curses
僅是一個殘留,只實現了部分功能,一般使用_curses
; -
_multiprocessing
:基于進程的并行的multiprocessing
的內建模塊,一般直接使用multiprocessing
模塊; -
_random
:生成偽隨機數的內建模塊,一般直接使用random
模塊; -
_rawffi
:_rawffi是一個通過libffi來調用C的動態庫方法和創建C對象(數組或結構體) 的內建模塊; -
_sha
:SHA-1的內建模塊,在pypy中還有sha
模塊,但是一般調用hashlib
,在Ptyhon2.7中sha
仍存在,但已經是Deprecated,Python3.X中sha
已不再使用,一般直接使用hashlib
模塊; -
_socket
:Socket的內建模塊,一般直接使用socket
模塊; -
_sre
:是實現正則表達式re
大部分功能的內建模塊,一般直接使用re
模塊; -
_ssl
:TLS/SSL的內建模塊,一般直接使用ssl
模塊; -
_warnings
:警告控制的內建模塊,一般直接使用warnings
模塊; -
_weakref
:弱引用的內建模塊,一般直接使用weakref
模塊; -
_winreg
:Windows注冊表訪問的內建模塊,一般直接使用winreg
模塊; -
array
:高效數值數組的內建模塊; -
binascii
:二進制碼與ASCII碼間轉化的內建模塊; -
bz2
:對bzip2壓縮支持的內建模塊; -
cStringIO
:對內存進行文件操作(Read and write strings as files)的C實現的內建模塊; -
cmath
:提供數學運算的C實現的內建模塊; -
cpyext
:cpyext內建模塊提供讓pypy使用CPython的擴展模塊; -
crypt
:Unix密碼驗證的內建模塊; -
errno
:標準錯誤記號的內建模塊; -
exceptions
:異常處理的內建模塊; -
fcntl
:系統調用文件鎖fcntl()和ioctl()的內建模塊; -
gc
:垃圾回收的內建模塊; -
imp
:訪問import模塊接口的內建模塊; -
itertools
:高效循環的迭代函數集合的內建模塊; -
marshal
:序列化與反序列化的內建模塊之一; -
math
:提供數學運算的內建模塊; -
mmap
:內存映射文件支持的內建模塊; -
operator
:針對函數標準操作的內建模塊; -
parser
:訪問Python解析樹的內建模塊; -
posix
:POSIX調用的內建模塊; -
pyexpat
:解析xml的內建模塊,一般直接使用xml
模塊;; -
select
:提供系統內I/O多路復用的內建模塊; -
struct
:將字節解析為打包的二進制數據的內建模塊; -
symbol
:Python解析樹中的常量的內建模塊; -
sys
:系統相關的參數與函數的內建模塊; -
termios
:POSIX風格的tty控制的內建模塊; -
thread
:線程的內建模塊; -
time
:時間日期的內建模塊; -
token
:Python解析樹中的常量的內建模塊; -
unicodedata
:Unicode字符數據庫的內建模塊; -
zipimport
:從ZIP歸檔中導入的內建模塊; -
zlib
:兼容gzip壓縮的內建模塊;
-
通過純Python重寫的模塊在 lib_pypy/ (部分使用了
cffi
),比如:ctypes
,cPickle
,cmath
,dbm
,datetime
;
垃圾回收機制(gc)的不同
pypy的垃圾回收機制實現并不是使用引用計數,所以Object并不會在不被引用的情況下立即釋放掉。最明顯的影響是,文件(socket等)將不會在離開作用范圍后立馬被關閉掉。對于寫打開的文件,這可能會導致寫入的數據在緩沖區中一段時間,是的磁盤上文本被截斷或還未寫入。還可能導致文件打開數量超過系統的限制。
如果需要調試程序中哪里沒有正確關閉文件,可以使用-X track-resources
來運行程序。這樣,每當GC關閉一個文件(或者socket)將會產生一個ResourceWarning
。這個警告會包含文件(或者socket)打開創建的位置,方便定位問題。
測試代碼:
import time
import gc
print("start")
i = 1
while True:
print(i)
i += 1
open("/data/test.log", "rw")
if (i % 10) == 0:
gc.collect()
print("finished")
對__del__
和弱引用的影響
這個問題,會影響到__del__
方法調用的準確時間,因為pypy的回收是不確定的。這同樣影響到弱引用(weak references),使得弱引用會比預期的存活時間長。這導致弱引用代理(由weakref.proxy(object[, callback])
返回)的實用性降低:這使得弱引用代理在目標對象的引用失效后仍然能夠被訪問,且會在某個時刻突然失效并在下次訪問時引起ReferenceError
錯誤。所有使用到弱引用代理的必須小心處理ReferenceError
(或者,更好的方法是使用weakref.ref()
而不是weakref.proxy()
)。
測試代碼:
>>>> import weakref
>>>> import gc
>>>> class A(object):
.... def __init__(self):
.... self.test_attr = 100
....
>>>> def test_func(refrence):
.... print("callback function!")
....
>>>> a = A()
>>>>
>>>> x = weakref.proxy(a, test_func)
>>>> x.test_attr
100
>>>> a.test_attr
100
>>>> del a
>>>>
>>>> x.test_attr
100
>>>> gc.collect()
callback function!
0
>>>> x
<weakproxy at 0x00007f6f19011dc0; dead>
>>>> x.test_attr
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ReferenceError: weakly referenced object no longer exists
>>>>
>>>> a = A()
>>>> x = weakref.ref(a, test_func)
>>>> print x
<weakref at 0x00007f6f190631a0; to 'A'>
>>>> b = x()
>>>> b.test_attr
100
>>>> del a
>>>> gc.collect()
0
>>>> x
<weakref at 0x00007f6f190631a0; to 'A'>
>>>> b.test_attr
100
某些情況下,由于CPython的引用計數,弱引用會在它指向的對象之前或之后立即釋放掉,如果在之后釋放掉,那么回調函數將會被調用。但在pypy類似的情況下,對象和弱引用會被認為同時釋放,回調將不會被調用。
GC的其他影響
如果一個對象有__del__
方法,在pypy中,__del__
被調用的次數不會多于1次。但在CPython中,__del__
在對象被“復活”然后“死亡”,__del__
可能會被調用多次。(此處有一段不是很懂,詳見Blog[1] [2].)
GC的差異也會間接的影響到其他方面。比如,pypy代碼中的生成器,將會比CPython中更遲的被垃圾回收。這會影響到yield
關鍵字,如果yield
在try:
或者with:
中,這會有一個issue 736。
# import gc
def g():
try:
yield 1
finally:
print "finally"
g().next()
# gc.collect()
這段代碼,在pypy中是沒有打印出finally
,在CPython中是有打印的。原因是,在pypy中,finally
只有在對象被垃圾回收機制回收時才會打印。如果在最后調用gc.collect()
,將能夠打印出來。
使用默認的GC(minimark
),內建函數id()
將會像CPython中那樣工作。但使用其他GC,id()
返回的數字,并不是內存地址(因為一個對象的地址可能會改變),而且太頻繁調用將會引起性能問題。
如果程序中有很長的鏈表對象,每一個中都有指向下一個的引用,并且都有__del__
,這會導致pypy的GC的性能下降。但在其他情況下,pypy的GC性能普遍比CPython好。
__del__
還有另外一個不同的地方,如果往一個已經存在的類中動態加入__del__
,它將不會調用:
>>>> class A(object):
.... pass
....
>>>> A.__del__ = lambda self: None
__main__:1: RuntimeWarning: a __del__ method added to an existing type will not be called
在pypy中,如果你將__del__
動態綁定給Python的舊式類的對象(對于CPython的新式類也是不工作的),將會得到一個RuntimeWarning
。修改這個issuse的方法是,在類中定義__del__
,僅包含pass
(或者其他實現),再在運行時動態修改;
CPython會在程序結束的時候去自動執行gc.collect()
,但pypy并不會。
內建類型(types)的子類
官方實現上,CPython對于內建類型的子類的重載方法是否會被隱式調用沒有確定任何規則。類似的,這些被重載的方法不會被同一個對象的其他內置方法調用。例如:一個在dict
子類中被重載的__getitem__()
將不會被其內置函數get()
調用。
上述的情況在CPython中和PyPy中都是正確的。不同的是,一個除self
以外的另一對象的內置方法是否會調用一個被重載的方法。這通常在CPython中是不會被調用的,而在PyPy中則會被調用。例子:
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
在字典(dictionary)中作為key的自定義類的對象
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 x
且 hash(y) == hash(x)
,同時,hash(x)
是在x
插入在一個字典中的時候計算的。如果條件滿足,這個__evil_eq__
方法將會被調用。
PyPy使用一個特殊的策略來優化,一個用戶自定義類的實例作為Keys在字典當中,且這個自定義類是沒有重載__hash__
,__eq__
,__cmp__
:當使用這個策略的時候,__eq__
和__cmp__
將不會被調用,而是通過查找id(identity)。所以在上述代碼的情況下,PyPy將能保證__eq__
不會被調用。
在其他情況下(比如,有一個自定義的__hash__
和__eq__
在y
中),PyPy將會和CPython一樣。
原始數據類型的對象標識,is
和 id
原始數據類型的對象標識是根據值來判斷想等,而不是包裝器的標識。這意味著對于一個任意值的整數(interger)x
來說x + 1 is x + 1
總是返回True
。這個規則適合如下的數據類型:
int
float
long
complex
-
str
(只對空字符串或單個字符的字符串有效) -
unicode
(只對空字符串或單個字符的字符串有效) -
tuple
(只對空元組有效) -
frozenset
(只對空的frozenset有效)
這個改變需要id()
也要做出相應的改變。id()
需要滿足如下情況:x is y <=> id(x) <=> id(y)
。因此上述類型的id()
將返回一個從參數計算而得到的值,因此可以大于sys.maxint
(所以可以是任意長)。
記住,一個長度大于等于2的字符串,可以相等(==
,equal)卻不一定相同(is
,identical)。類似的,盡管x
包含一個元組且x == (2,)
,但x is (2,)
不一定返回True
。這個規則只適應于上述的類型。str
、unicode
、tuple
和frozenset
是在PyPy5.4版本中才適應這個規則。在5.4之前,盡管x
等于?
或()
,但是if x is "?"
或者 if x is ()
將會返回False
。這個5.4中的新行為是更為接近CPython,Cpython可以精確的緩存空tuple
或firzenset
和長度小于等于1的字符串或者unicodes(對于字符串和unicodes并不總是會緩存,當大部分情況是)。
對于float,“is
”是針對一個對象的float
“位模式”。所以float('nan') is float('nan')
在PyPy上返回True
,在CPython上返回False
,因為這是兩個對象。但是0.0 is -0.0
都返回False
,因為“位模式”不同。一般的,float('nan') == float('nan')
總是返回False
。當在容器中使用時(在列表項或者集合中),判斷相等采用if x is y or x == y
(不論在Cpython中或PyPy中)。因此,因為所有的nans
在PyPy中是相同的,所以不能在一個集合中使用多次,不像CPython(Issuse #1974)
其他
-
hash()
方法的隨機bug在pypy中是忽略的,所以會有以下不同:
Python 3.4.0 (default, Apr 11 2014, 13:05:11)
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = "1"
>>>hash(a)
-7159617557763069555
>>>exit()
再次運行:
Python 3.4.0 (default, Apr 11 2014, 13:05:11)
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = "1"
>>> hash(a)
461546025534110251
>>> exit()
兩次的hash值是不同的,而在pypy中,兩次的hash值是一樣的:
Python 2.7.3 (2.2.1+dfsg-1ubuntu0.3, Sep 30 2015, 15:18:40)
[PyPy 2.2.1 with GCC 4.8.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``pypy is more stable than debian''
>>>> a = "1"
>>>> hash(a)
6272018864
>>>> exit()
Python 2.7.3 (2.2.1+dfsg-1ubuntu0.3, Sep 30 2015, 15:18:40)
[PyPy 2.2.1 with GCC 4.8.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``we still have to write software
with a metaspace bubble in it''
>>>> a = "1"
>>>> hash(a)
6272018864
>>>> exit()
- 不能在一個類型對象中存儲一個非字符串的key。例子:
class A(object):
locals()[42] = 3
# 在PyPy中是不行的
sys.setrecursionlimit(n)
僅僅是設置一個大概的值。這個通過設置一個n * 768
bytes的可用棧空間來實現的。在Linux上,這個依賴于編譯器的設置,默認值768KB大約能滿足1400次調用。因為對于字典的實現是不同的,每次調用
__hash__
和__eq__
所獲的準確數字并不相同。由于CPython也沒有一個準確的保證,所以不要依賴它。對于
__class__
的賦值使用,是基于CPython2.5上的。對于CPython2.6和CPython2.7多個特性,PyPy仍未支持(如果需要的話,是可以支持的,但在PyPy上需要比CPython2.6/2.7注意更多情況?)。-
__builtins__
的不同:
在python中有一個內建模塊,中有一些常用函數。而該模塊在Python啟動后、且沒有執行程序員所寫的任何代碼前,Python會首先加載該內建函數到內存。在pypy和python2.X中,該內建模塊是
__builtin__
,在Python3.X中,該內建模塊是builtins
。而
__builtins__
則是對于這個內建模塊的引用。無論任何地方要想使用內建模塊,都必須在該位置所處的作用域中導入
__builtin__
(builtins
)內建模塊;而對于__builtins__
卻不用導入,它在任何模塊都直接可見。在Python2/3中的主模塊
__mian__
中,__builtins__
是對內建模塊__builtin__
(builtins
)本身的引用,即__builtins__
完全等價于__builtin__
(builtins
)。在Python2/3中的非主模塊
__mian__
中,__builtins__
是對內建模塊__builtin__.__dict__
(builtins.__dict__
)的引用,此時__builtins__
的類型是字典。 對于使用無效參數直接調用內建類型的內部“魔法”函數,結果會有略微的不同。比如:
[].__add__(None)
和(2).__add__(None)
在PyPy上都將返回NotImplemented
;但在CPython 中,只有(2).__add__(None)
會返回NotImplemented
,[].__add__(None)
將引起TypeError
。(當然,[] + None
和2 + None
無論在PyPy和CPython中都會引起TypeError
)。這是內部實現引起的差異,因為PyPy沒有內部的C語言層面的實現(Slots?)。在CPython中,
[].__add__
是一個method-wrapper
,而list.__add__
是一個slot wrapper
。在PyPy中,它們都是一些普通的綁定或未綁定的方法對象。這有時會使得一些檢查內置類型的工具混淆。比如,在標準庫inspect
模塊中有一個ismethod()
方法,當參數是綁定或未綁定的方法對象,這個方法將會返回True
,當參數是method-wrapper
或slot wrapper
時,方法返回False
。但PyPy并不能區分它們,所以ismethod([].__add__) == ismethod(list.__add__) == True
。在CPython,內置類型具有各種方式實現的屬性。根據方式,如果試圖寫入(或刪除)一個只讀(或不可刪除)屬性時,將會引起一個
TypeError
或者AttributeError
。PyPy試圖在一致性和兼容性之間取舍。這意味著一些小地方將不會和CPython引起相同的異常,比如:del (lambda:None).__closure__
。在純Python中,如果寫一個類
class A(object): def f(self): pass
,且這個類有一個沒有重載f()
的子類B
,這樣B.f(x)
仍然會檢查x
是B
的一個實例。在CPython中,如果一個類型是用C語言實現的,那個這個類型將會有不同的規則。如果A
是采用C語言實現,所有A
的實例將會被B.f(x)
接受(實際上,在這個情況下B.f is A.f
是成立的)。一些代碼可以在CPython上運行,但在PyPy上不能:datetime.datetime.strftime(datetime.date.today(), ...)
(datetime.date
是datetime.datetime
的父類)。無論如何,正確修復這個方法調用應該:datetime.date.today().strftime(...)
。在PyPy中,新式類中的
__dict__
屬性將會返回一個普通的dict,而不是像CPython中那樣返回一個dict的代理。改變dict將會改變類型,反之亦然。對于內置對象,PyPy將會返回一個不可修改的dict(但行為和普通的dict類似)。gc
模塊的一些方法和屬性會和CPython中有一些略微的不同,比如:gc.enable
和gc.disable
有被支持,但并不是啟用或禁用GC,而是啟用或禁用終結器(finalizers)的執行。過去的版本中,PyPy在交互模式中啟動時打印一段來之
#pypy IRC
的隨機一行。但在release版本中,這個行為已經被抑制了。但設置一個PYPY_IRC_TOPIC
環境變量將會啟動這個行為。要注意使用到的軟件包完全禁用這個功能。PyPy的readline模塊是完全重寫的,它并不是GUN的readline。它添加了多行的支持(詳見
multiline_input()
)。但另一方面parse_and_bind()
的調用會被忽略(issue #2072)。sys.getsizeof()
總會引起TypeError
。這是因為此函數的內存分析器很有可能給出與PyPy實際上不同的結果。讓sys.getsizeof()
返回一個數字(需要足夠的工作)是可行的,但是這個數字有可能不能準確代表著這個對象使用的內存大小。在于系統其余部分(?)隔絕的情況下,這個數字甚至反應不了一個對象使用的內存大小。比如一個實例有maps,這個maps會經常在許多實例之間共享。在這種情況下,這個maps會被sys.getsizeof()
的實現忽略,但在某些情況下,如果這個maps有著很多唯一的實例,那么它的內存開銷是很重要的。相反的,即使是不同的對象,相等的字符串可以共享它們的內部數據,或者一些空的container會一直共享它們部分的內存,直到它們不再為空。更奇怪的是,一些list會在你對它進行讀操作時創建一些對象;如果嘗試估計range(10**6)
的內存大小綜合,這個操作自身會創建一百萬個整型對象,但這些對象在開始時不存在的。在CPython中也會有類似的問題,只是少一些。以上就是不實現sys.getsizeof()
的原因。timeit
模塊在PyPy中的表現會有所不同:它但打印的時平均時間和標準差,而不是CPython中的最小值,因為最小值通常是有誤差的。sysconfig
模塊中的get_config_vars
方法和distutil.sysconfig
這兩個是未完成的。在POSIX平臺,CPython在Makefile中獲取配置變量來編譯生成解析器。PyPy也需要在編譯的期間生成這些值,但還未完成。在
"%d" % d
、"%x" % x
等類似的結構中,當x
是一個long
的子類的實例時,并且這個子類重載了__str__
或__hex__
或__oct__
這幾個特殊方法時:PyPy將不會調用這些特殊方法;CPython是會的,但僅限于long
的子類,不包括int
。CPython的這些表現是很混亂的:比如,對于%x
應該調用__hex__()
,__hex__()
的功能是支持返回一個類似-0x123L
的字符串了;然后%x
會去掉0x
和最后的L
,保留其余部分。如果重載的__hex__()
返回一個格式不正確的字符串,那么將會獲得一個異常(在CPython2.7.13之前會之前crash)。在CPython2.7.13中,
sqlite
模塊有一個更新,當有一個提交時它將不再重置所有的游標(cursors)。這會使得PyPy中有一些麻煩(CPython也可能會有),所以PyPy并沒有移植這個更改:http://bugs.python.org/issue29006。