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