Cython的C指針
與C一樣,盡管指針性與變量而不是類型相關(guān)聯(lián),但可以在類型或變量附近聲*號(hào)。
%%cython
cdef int *a
cdef int *b
但這樣在變量a,b寫在一行,cython編譯器會(huì)發(fā)出警告的信息,因此建議每個(gè)變量單獨(dú)聲明
%%cython
cdef int *a,*b

Cython中的指針的解引操作
在Cython中解引用指針與在C語(yǔ)言中不同。
由于Python語(yǔ)言已經(jīng)使用*args和**kwargs語(yǔ)法來(lái)允許任意位置和關(guān)鍵字參數(shù)并支持函數(shù)參數(shù)解包,因此Cython不支持*
*解引語(yǔ)法是C指針的語(yǔ)法。 取而代之的是,我們?cè)谖恢?的指針處建立索引,以解引Cython中的指針的引用。 這種語(yǔ)法也可以解引C中的指針,盡管這種情況很少見。

Cython的結(jié)構(gòu)體與指針
無(wú)論在C中使用箭頭運(yùn)算符的任何地方,在Cython中的結(jié)構(gòu)體都使用點(diǎn)運(yùn)算符訪問(wèn)其內(nèi)部的成員變量,Cython將生成正確的C級(jí)代碼
%%cython -a
cdef struct Person:
char* name
unsigned int age
#end-cdef
#初始化結(jié)構(gòu)體
cdef Person p=Person("jck308",32)
#聲明Person類型結(jié)構(gòu)體指針p_per
#并將變量p的地址賦值給指針p_per
cdef Person *p_per=&p
#訪問(wèn)結(jié)構(gòu)體的成員
print(p.age)
print(p.name)
print(p_per.age)
print(p_per.name)
混合靜態(tài)和動(dòng)態(tài)類型變量
Cython允許靜態(tài)和動(dòng)態(tài)類型變量之間的賦值。靜態(tài)和動(dòng)態(tài)的這種流體混合是一個(gè)強(qiáng)大的特性,我們將在多個(gè)實(shí)例中使用它:它允許我們對(duì)大多數(shù)代碼基使用動(dòng)態(tài)Python對(duì)象,并輕松地將它們轉(zhuǎn)換為性能關(guān)鍵部分加速、靜態(tài)類型的類比。
舉例來(lái)說(shuō),假設(shè)我們有幾個(gè)靜態(tài)int,我們想將它們組合成(動(dòng)態(tài))Python元組。使用Python/C API創(chuàng)建和初始化這個(gè)元組的C代碼很簡(jiǎn)單,但是很繁瑣,需要幾十行代碼,并且需要大量的錯(cuò)誤檢查。在Cython,顯而易見的方法就是:
%%cython
#靜態(tài)類型的變量
cdef int a=1,b=2,c=3
##動(dòng)態(tài)類型的變量
tuple_of_ints=(a,b,c)
print(tuple_of_ints)
這段代碼很無(wú)聊。 這里要強(qiáng)調(diào)的一點(diǎn)是,a,b和c是靜態(tài)類型的整數(shù),而Cython允許使用它們創(chuàng)建動(dòng)態(tài)類型的Python元組。 然后,我們可以將該元組分配給動(dòng)態(tài)鍵入的tuple_of_ints變量。 該示例的簡(jiǎn)單性是Cython強(qiáng)大之處:我們可以以顯而易見的方式創(chuàng)建一個(gè)C類型int元組,而無(wú)需進(jìn)一步思考。 我們希望像這樣的概念上簡(jiǎn)單的事情變得簡(jiǎn)單,這就是Cython所提供的。
此示例之所以有效,是因?yàn)镃類型的int與Python int之間存在明顯的對(duì)應(yīng)關(guān)系,因此Python可以為我們自動(dòng)類型轉(zhuǎn)換。 例如,如果a,b和c是C指針,則此示例無(wú)法按原樣工作。 在這種情況下,我們必須解引它們,然后再將它們放入元組或使用其他策略。
例如下面的代碼是個(gè)錯(cuò)誤的例子,因?yàn)镻ython解釋器無(wú)法識(shí)別指針類型的變量
%%cython
cdef int k=55,j=56
cdef int *a=&k
cdef int *b=&j
tuple_of_ints=(a,b)
print(tuple_of_ints)
正確的做法,如下圖所示,我們通過(guò)解引指針變量a、b

給出了內(nèi)置Python類型與C或C ++類型之間對(duì)應(yīng)關(guān)系的完整列表

Cythond的bint類型
bint布爾整數(shù)類型是C級(jí)別的int,并與Python的bool相互轉(zhuǎn)換。 它具有真實(shí)性的標(biāo)準(zhǔn)C解釋:零為False,非零為True。
整數(shù)類型轉(zhuǎn)換和溢出
Python 3中,所有int對(duì)象都是無(wú)限精度的。當(dāng)將整數(shù)類型從Python轉(zhuǎn)換為C時(shí),Cython會(huì)生成檢查溢出的代碼。 如果C類型不能表示Python整數(shù),則會(huì)引發(fā)運(yùn)行時(shí)OverflowError。

float類型轉(zhuǎn)換
Python fload存儲(chǔ)為C double。 根據(jù)IEEE 754轉(zhuǎn)換規(guī)則,將Python浮點(diǎn)數(shù)轉(zhuǎn)換為C浮點(diǎn)數(shù)可能會(huì)截?cái)酁?.0或正負(fù)無(wú)窮大。
Cython的double類型會(huì)被動(dòng)態(tài)轉(zhuǎn)換Python的float類型
%%cython
cdef double d=384848048282945060321.3835
b=d
print(b)
print(type(b))

Complex類定
The Python complex類型存儲(chǔ)為兩個(gè)double的C結(jié)構(gòu),Cython具有浮點(diǎn)復(fù)數(shù)和雙復(fù)數(shù)C級(jí)類型,它們對(duì)應(yīng)于Python復(fù)數(shù)類型。 C類型與Python復(fù)雜類型具有相同的接口,但是使用有效的C級(jí)操作。 這包括訪問(wèn)實(shí)數(shù)和虛數(shù)分量的實(shí)數(shù)和imag屬性,創(chuàng)建多個(gè)復(fù)數(shù)共軛的共軛方法,以及用于加,減,乘和除的有效運(yùn)算。C級(jí)Complex類型與C99 _Complex類型或C ++ std :: complex模板化類兼容。
bytes類型
Python字節(jié)類型會(huì)自動(dòng)在char *或std :: string之間來(lái)回轉(zhuǎn)換。下面示例就是Cython char類型指針 動(dòng)態(tài)轉(zhuǎn)換為Python的bytes
%%cython
cdef char* s="Hello World"
b=s
print(b)
print(type(b))
用Python類型靜態(tài)聲明變量
到目前為止,我們一直使用cdef靜態(tài)聲明C類型的變量。 也可以使用cdef靜態(tài)聲明Python類型的變量。 我們可以對(duì)內(nèi)置類型(例如list,tuple和dict)執(zhí)行此操作; 擴(kuò)展類型,例如NumPy數(shù)組; 還有很多其他
并非所有的Python類型都可以靜態(tài)聲明:它們必須用C實(shí)現(xiàn),并且Cython必須有權(quán)訪問(wèn)該聲明。 內(nèi)置的Python類型已經(jīng)滿足了這些要求,并且聲明它們很簡(jiǎn)單。 例如Python典型的集合類型list,dict,str,set(str就是集合類型,字符串?dāng)?shù)組):
cdef list mylist
cdef dict mydi
cdef str pname
cdef set myset
此示例中的變量是完整的Python對(duì)象。 在后臺(tái),Cython將它們聲明為指向某些內(nèi)置Python結(jié)構(gòu)類型的C指針。 它們可以像普通的Python變量一樣使用,但是受其聲明類型的約束:
%%cython
cdef list mylist=[k+1 for k in range(1,11)]
pylist=mylist
print("mylist:",mylist)
print("刪除pylist索引2的元素")
del pylist[2]
print(mylist)
在這里,通過(guò)刪除pylist第3個(gè)元素也會(huì)刪除mylist的第3個(gè)元素,因?yàn)樗鼈円玫氖峭涣斜?。mylist和pylist之間的一個(gè)區(qū)別是,mylist只能引用Python列表對(duì)象,而pylist可以引用任何Python類型。 Cython將在編譯時(shí)和運(yùn)行時(shí)對(duì)mylist施加類型約束。

備注:關(guān)于Cython更復(fù)雜的數(shù)組類型引用,可以參考此篇文章《第5篇:Cython的線性表性操作》
乍一看,Cython允許靜態(tài)聲明具有內(nèi)置Python類型的變量似乎有些奇怪。 為什么不照常使用Python的動(dòng)態(tài)類型? 答案指出了Cython的一般原理:我們提供的靜態(tài)類型信息越多,Cython就能更好地優(yōu)化結(jié)果。 像往常一樣,該規(guī)則也有例外,但這通常是正確的。 例如,以下代碼從Cython函數(shù)中返回sieveOfEratosthenes()返回一個(gè)cdef list的對(duì)象附加到動(dòng)態(tài)類型的變量中:
%%cython
#cython:language_level=3
cpdef list sieveOfEratosthenes(int n):
cdef list pr = [True for i in range(n + 1)]
cdef int p = 2
cdef list res=list()
while (p * p <= n):
if (pr[p] == True):
for i in range(p * p, n + 1, p):
pr[i] = False
#end-for
#end-if
p += 1
#end-while
cdef int k
for k in range(2,n):
if pr[k]:
res.append(k)
#end-if
#end-for
return res
#end-def
#這是Python動(dòng)態(tài)類型的list
primers=[]
primers=sieveOfEratosthenes(9)
print(primers)
print("調(diào)用append方法")
primers.append(11)
print(primers)
程序輸出

Cython編譯器將生成可處理任何Python對(duì)象的代碼,并在運(yùn)行時(shí)測(cè)試primers是否為列表。如果不是,只要它具有帶參數(shù)的append方法,該代碼就會(huì)運(yùn)行。在后臺(tái),生成的代碼首先在primers對(duì)象上查找append屬性(使用PyObject_GetAttr),然后使用完全通用的PyObject_Call Python / C API函數(shù)調(diào)用該方法。 這實(shí)質(zhì)上模擬了當(dāng)運(yùn)行等效的Python字節(jié)碼時(shí)Python解釋器將執(zhí)行的操作。
假設(shè)上面的代碼中primers變量我們使用靜態(tài)聲明
cdef list primers
現(xiàn)在,Cython可以生成專門的代碼,這些代碼可以直接從C API調(diào)用PyList_SET_ITEM或PyList_Append函數(shù)。 這就是上一示例中的PyObject_Call最終仍然要調(diào)用的內(nèi)容,但是靜態(tài)類型允許Cython繞過(guò)了Python解釋器在動(dòng)態(tài)調(diào)度(Dynamic Dispatch)一系列繁瑣沉重的類型檢測(cè)(內(nèi)部類型指針查找),這也是Cython的靜態(tài)版本list比Python動(dòng)態(tài)版本list性能高效的原因。
關(guān)于Python解釋器的動(dòng)態(tài)調(diào)度的詳細(xì)介紹,請(qǐng)查看此文《第2篇:Cython VS Python 執(zhí)行原理》
Cython當(dāng)前支持的集中內(nèi)置可靜態(tài)聲明的Python類型,我們常用靜態(tài)聲明的可能就是list,dict
- type,object
- bool
- complex
- basestring,str,unicode,bytes,bytearray
- list,tuple,dict,set,frosenset
- array
- slace
- date,time,datetime,timedelta,tzinfo
上面的列出的中不包括直接C對(duì)應(yīng)的Python類型(例如int,long和float)。 事實(shí)證明,在Cython中靜態(tài)聲明和使用PyIntObjects,PyLongObjects或PyFloatObjects并不容易。 幸運(yùn)的是,這樣做的需要很少。 我們只聲明常規(guī)的C基本數(shù)據(jù)類型int,long,float和double,然后讓Cython為我們進(jìn)行往返于Python的自動(dòng)轉(zhuǎn)換。
數(shù)字字面量的基本運(yùn)算
當(dāng)我們對(duì)數(shù)字字面量進(jìn)行加,減或乘運(yùn)算時(shí),當(dāng)操作數(shù)是動(dòng)態(tài)類型化的Python對(duì)象時(shí),這些操作具有Python語(yǔ)義(包括對(duì)于數(shù)值大的自動(dòng)Python long強(qiáng)制轉(zhuǎn)換)。當(dāng)操作數(shù)是靜態(tài)類型的C變量時(shí),它們具有C語(yǔ)義(即,對(duì)于有限精度的整數(shù)類型,結(jié)果可能會(huì)溢出)
除數(shù)和模數(shù)(即計(jì)算余數(shù))值得特別提及。使用帶符號(hào)整數(shù)操作數(shù)計(jì)算模數(shù)時(shí),C和Python具有明顯不同的行為:C舍入為零,而Python舍入為無(wú)窮。例如,使用Python語(yǔ)義時(shí),-1%5的結(jié)果為4;但是,如果使用C語(yǔ)義,它將得出-1。當(dāng)將兩個(gè)整數(shù)相除時(shí),Python始終檢查分母,并在其為零時(shí)引發(fā)ZeroDivisionError,而C沒有適當(dāng)?shù)谋Wo(hù)措施。
對(duì)于除法/取模運(yùn)算中,即便指定了C類型的靜態(tài)數(shù)字變量,Cython的行為是傾向于Python的,要獲取與C/C++完全相同的語(yǔ)義,我們可以在全局模塊級(jí)別或在指令注釋中使用cdivision編譯器指令,如下示例所示

在Python 3中,在C級(jí)別,所有整數(shù)都是PyLongObjects。Cython以與語(yǔ)言無(wú)關(guān)的方式在C整數(shù)類型和這些Python整數(shù)類型之間正確轉(zhuǎn)換,并在無(wú)法進(jìn)行轉(zhuǎn)換時(shí)引發(fā)OverflowError。
當(dāng)我們?cè)贑ython中使用Python對(duì)象時(shí),無(wú)論是靜態(tài)聲明還是動(dòng)態(tài)聲明,Cython仍將為我們管理對(duì)象的所有方面,包括繁瑣的引用計(jì)數(shù)。下一篇我們將會(huì)談到Cython中的引用計(jì)數(shù)和靜態(tài)的字符串類型。