一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

通過 cdef 進(jìn)行靜態(tài)類型聲明

 古明地覺O_o 2022-12-08 發(fā)布于北京

楔子


首先 Python 中聲明變量的方式在 Cython 里面也是可以使用的,因?yàn)?Python 代碼也是合法的 Cython 代碼。

a = [x for x in range(12)]
b = a
a[3] = 42.0
assert b[3] == 42.0
a = "xxx"
assert isinstance(b, list)

在 Cython 中,沒有類型化的動(dòng)態(tài)變量的行為和 Python 完全相同,通過賦值語句 b = a 讓 b 和 a 都指向同一個(gè)列表。在 a[3] = 42.0 之后,b[3] == 42.0 也是成立的,因此斷言成立。

即便后面將 a 修改了,也只是讓 a 指向了新的對(duì)象,調(diào)整相應(yīng)的引用計(jì)數(shù)。而對(duì) b 而言則沒有受到絲毫影響,因此 b 指向的依舊是一個(gè)列表。這是完全合法、并且有效的 Python 代碼。

而對(duì)于靜態(tài)類型變量,我們?cè)?Cython 中通過 cdef 關(guān)鍵字進(jìn)行聲明,比如:

cdef int i
cdef int j
cdef float k
# 我們看到就像使用 Python 和 C 的混合體一樣
j = 0
i = j
k = 12.0
j = 2 * i
assert i != j

上面除了變量的聲明之外,其它的使用方式和 Python 并無二致,當(dāng)然簡(jiǎn)單的賦值的話,基本上所有語言都是類似的。但是 Python 的一些內(nèi)置函數(shù)、類、關(guān)鍵字等等都是可以直接使用的,因?yàn)槲覀冊(cè)?Cython 中可以直接寫 Python 代碼,它是 Python 的超集。

但是有一點(diǎn)需要注意:我們上面創(chuàng)建的變量 i、j、k 是 C 中的類型(int、float 比較特殊,后面會(huì)解釋),其意義最終要遵循 C 的標(biāo)準(zhǔn)。

不僅如此,就連使用 cdef 聲明變量的方式也是按照 C 的標(biāo)準(zhǔn)來的。

cdef int i, j, k
cdef float x, y

# 聲明的同時(shí)并賦值
cdef int a = 1, b = 2
cdef float c = 3.0, b = 4.1

而在函數(shù)內(nèi)部,cdef 也是要進(jìn)行縮進(jìn)的,它們聲明的變量也是一個(gè)局部變量。

def foo():
    # 這里的 cdef 是縮進(jìn)在函數(shù)內(nèi)部的
    cdef int i
    cdef int N = 2000
    # a 沒有初始值,默認(rèn)是零值,即 0.0
    cdef float a, b = 2.1

并且 cdef 還可以使用類似于 Python 上下文管理器的方式。

def foo():
    # 這種聲明方式也是可以的
    # 和上面的方式是完全等價(jià)的
    cdef:
        int i
        int N = 2000
        float a, b = 2.1
    # 但是聲明變量時(shí),要注意縮進(jìn)
    # Python 對(duì)縮進(jìn)是有講究的, 它規(guī)定了作用域
    # 所以Cython在語法方面還是保留了Python的風(fēng)格

所以使用 cdef 聲明變量非常簡(jiǎn)單,格式:cdef 類型 變量名。當(dāng)然啦,同時(shí)也可以賦上初始值。

一旦使用 cdef 靜態(tài)聲明,那么后續(xù)再給變量賦值的時(shí)候,就不能那么隨心所欲了,舉個(gè)例子:

# 如果是動(dòng)態(tài)聲明,以下都是合法的
# a 可以指向任意的對(duì)像,沒有限制
a = 123
a = []

# 但如果是靜態(tài)聲明
# 那么 b 的類型必須是整型
cdef int b = 123
# 將一個(gè)列表賦值給 a 是會(huì)出現(xiàn)編譯錯(cuò)誤的
b = []  # compile error

也正是因?yàn)樵诰幾g階段就能檢測(cè)出類型,并分配好內(nèi)存,所以在執(zhí)行的時(shí)候速度才會(huì)快。


static 和 const


如果你了解 C 的話,那么思考一下:假設(shè)要在函數(shù)中返回一個(gè)局部變量的指針、并且外部在接收這個(gè)指針之后,還能訪問指針指向的值,這個(gè)時(shí)候該怎么辦呢?我們知道 C 函數(shù)中的變量是分配在棧上的(不使用 malloc 函數(shù),而是直接創(chuàng)建一個(gè)變量),函數(shù)結(jié)束之后變量對(duì)應(yīng)的值就被銷毀了,所以這個(gè)時(shí)候即使返回一個(gè)指針也是無意義的。

盡管有些時(shí)候,在返回指針之后還是能夠訪問指向的內(nèi)存,但這只是當(dāng)前使用的編譯器比較笨,在編譯時(shí)沒有檢測(cè)出來。如果是高級(jí)一點(diǎn)的編譯器,那么在訪問的時(shí)候會(huì)報(bào)出段錯(cuò)誤或者打印出一個(gè)錯(cuò)誤的值;而更高級(jí)的編譯器甚至連指針都不讓返回了,因?yàn)橹羔樦赶虻膬?nèi)存已經(jīng)被回收了,那還要這個(gè)指針做什么?因此指針都不讓返回了。

而如果想做到這一點(diǎn),那么只需要在聲明變量的同時(shí)在前面加上 static 關(guān)鍵字,比如 static int i,這樣的話 i 這個(gè)變量就不會(huì)被分配到棧區(qū),而是會(huì)被分配到數(shù)據(jù)區(qū)。數(shù)據(jù)區(qū)里變量的生命周期不會(huì)隨著函數(shù)的結(jié)束而結(jié)束,而是伴隨著整個(gè)程序。

但可惜的是,static 不是一個(gè)有效的 Cython 關(guān)鍵字,因此我們無法在 Cython 中聲明一個(gè) C 的 static 變量。

除了 static,在 C 中還有一個(gè) const,用來聲明常量。一旦使用 const聲明,比如 const int i = 3,那么這個(gè) i 在后續(xù)就不可以被修改了。而在 Cython 中,const 是支持的,但是它只能在定義函數(shù)參數(shù)的時(shí)候使用,在介紹函數(shù)的時(shí)候再聊。

所以 C 的 static 和 const 目前在 Cython 中就無需太關(guān)注了。


C 類型


我們上面聲明變量的時(shí)候,指定的類型是 int 和 float,而在 Python 和 C 里面都有 int 和 float,那么用的到底是誰的呢?其實(shí)上面已經(jīng)說了,用的是 C 的 int 和 float,至于原因,我們后面再聊。

而 Cython 可以使用的 C 類型不僅有 int 和 float,像 short, int, long, unsigned short, long long, size_t, ssize_t 等基礎(chǔ)類型都是支持的,聲明變量的方式均為 cdef 類型 變量名。聲明的時(shí)候可以賦初始值,也可以不賦初始值。

而除了基礎(chǔ)類型,還有指針、數(shù)組、定義類型別名、結(jié)構(gòu)體、共同體、函數(shù)指針等等也是支持的,我們后面細(xì)說。


Cython 的自動(dòng)類型推斷


Cython 還會(huì)對(duì)函數(shù)體中沒有進(jìn)行類型聲明的變量自動(dòng)執(zhí)行類型推斷,比如:for 循環(huán)里面全部都是浮點(diǎn)數(shù)相加,沒有涉及到其它類型的變量,那么 Cython 在自動(dòng)對(duì)變量進(jìn)行推斷的時(shí)候會(huì)發(fā)現(xiàn)這個(gè)變量可以被優(yōu)化為靜態(tài)類型的 double。

但一個(gè)程序顯然無法對(duì)一個(gè)動(dòng)態(tài)類型的語言進(jìn)行非常智能的全方位優(yōu)化,默認(rèn)情況下,Cython 只有在確認(rèn)這么做不會(huì)改變代碼塊的語義之后才會(huì)進(jìn)行類型推斷。

看一個(gè)簡(jiǎn)單的函數(shù):

def automatic_inference():
    i = 1
    d = 2.0
    c = 3 + 4j
    r = i * d + c
    return r

在這個(gè)例子中,Cython 會(huì)將賦給變量 i、c、r 的值標(biāo)記為通用的 Python 對(duì)象。盡管這些對(duì)象的類型和 C 的類型具有高度的相似性,但 Cython 會(huì)保守地推斷 i 可能無法用 C 的整型表示(C 的整數(shù)有范圍,而 Python 沒有、可以無限大),因此會(huì)將其作為符合 Python 代碼語義的 Python 對(duì)象。

而對(duì)于 d = 2.0,則可以自動(dòng)推斷為 C 的 double,因?yàn)?Python 的浮點(diǎn)數(shù)對(duì)應(yīng)的值在底層就是使用一個(gè) double 來存儲(chǔ)的。所以最終對(duì)于開發(fā)者來講,變量 d 看似是一個(gè) Python 的對(duì)象,但是 Cython 在執(zhí)行的時(shí)候會(huì)將其視為 C 的 double 以提高性能。

這就是即使我們寫純 Python 代碼,Cython 編譯器也能進(jìn)行優(yōu)化的原因,因?yàn)闀?huì)進(jìn)行推斷。但是很明顯,我們不應(yīng)該讓 Cython 編譯器去推斷,而是明確指定變量的類型。

當(dāng)然如果非要 Cython 編譯器去猜,也是可以的,而且還可以通過 infer_types 編譯器指令,在一些可能會(huì)改變 Python 代碼語義的情況下給 Cython 留有更多的余地來推斷一個(gè)變量的類型。

cimport cython

@cython.infer_types(True)
def more_inference():
    i = 1
    d = 2.0
    c = 3 + 4j
    r = i * d + c
    return r

這里出現(xiàn)了一個(gè)新的關(guān)鍵字 cimport,它的含義我們以后會(huì)說,目前只需要知道它和 import 關(guān)鍵字一樣,是用來導(dǎo)入模塊的即可。然后我們通過裝飾器 @cython.infer_types(True),啟動(dòng)了相應(yīng)的類型推斷,也就是給 Cython 留有更多的猜測(cè)空間。

當(dāng) Cython 支持更多推斷的時(shí)候,變量 i 會(huì)被類型化為 C 的整型;d 和之前一樣是 double,而 c 和 r 都是復(fù)數(shù)變量,復(fù)數(shù)則依舊使用 Python 的復(fù)數(shù)類型。

但是注意:并不代表啟用 infer_types 時(shí),就萬事大吉了;我們知道在不指定 infer_types 的時(shí)候,Cython 推斷類型顯然是采用最最保險(xiǎn)的方法、在保證程序正確執(zhí)行的情況下進(jìn)行優(yōu)化,不能為了優(yōu)化而導(dǎo)致程序出現(xiàn)錯(cuò)誤,顯然正確性和效率之間,正確性是第一位的。

而 C 的整型由于存在溢出的問題,所以 Cython 不會(huì)擅自使用。但是我們通過 infer_types 啟動(dòng)了更多的類型推斷,讓 Cython 在不改變語義的情況下使用 C 的類型。但是溢出的問題它不知道,所以在這種情況下是需要我們來負(fù)責(zé)確保不會(huì)出現(xiàn)溢出。

對(duì)于一個(gè)函數(shù)來說,如果啟動(dòng)這樣的類型推斷的話,我們可以使用 infer_types 裝飾器的方式。不過還是那句話,我們應(yīng)該手動(dòng)指定類型,而不是讓 Cython 編譯器去猜,因?yàn)槲覀兪谴a的編寫者,類型什么的我們自己最清楚。因此 infer_types 這個(gè)裝飾器,在工作中并不常用,而且想提高速度,就必須事先顯式地規(guī)定好變量的類型是什么。


小結(jié)


以上就是在 Cython 中如何靜態(tài)聲明一個(gè)變量,方法是使用 cdef 關(guān)鍵字。事先規(guī)定好類型是非常重要的,一旦類型確定了,那么生成的機(jī)器碼的數(shù)量會(huì)少很多,從而實(shí)現(xiàn)速度的提升。

而 C 類型的變量的運(yùn)算速度比 Python 要快很多,這也是為什么 int 和 float 會(huì)選擇 C 的類型。而除了 int 和 float,C 的其它類型在 Cython 中也是支持的,包括指針、結(jié)構(gòu)體、共同體這樣的復(fù)雜結(jié)構(gòu)。

但是 C 的整型有一個(gè)問題,就是它是有范圍的,在使用的時(shí)候我們要確保不會(huì)溢出。所以 Cython 在自動(dòng)進(jìn)行類型推斷的時(shí)候,只要有可能改變語義,就不會(huì)擅自使用 C 的整型,哪怕賦的整數(shù)非常小。這個(gè)時(shí)候可以通過 infer_types 裝飾器,留給 Cython 更多的猜測(cè)空間。

不過還是那句話,我們不應(yīng)該讓 Cython 編譯器去猜,是否溢出是由我們來確定的。如果能保證整數(shù)不會(huì)超過 int 所能表示的最大范圍,那么就將變量聲明為 int;如果 int 無法表示,那么就使用 long long;如果還無法表示,那就沒辦法了,只能使用 Python 的整型了。而使用 Python 整型的方式就是不使用 cdef,直接動(dòng)態(tài)聲明即可。

所以如果要將變量聲明為整型,那么直接使用 ssize_t 即可,等價(jià)于 long long。而在工作中,能超過 ssize_t 最大表示范圍的整數(shù)還是極少的。

# 需要確保賦給 a 的整數(shù)
# 不會(huì)超過 ssize_t 所能表示的最大范圍
cdef ssize_t a

# b 可能會(huì)非常非常大,也有可能是負(fù)數(shù)
# 甚至連 ssize_t 都無法表示
# 此時(shí)就需要?jiǎng)討B(tài)聲明了,但很少會(huì)遇到這么大的整數(shù)
b = ...

再次強(qiáng)調(diào),事先規(guī)定好類型對(duì)速度的提升起著非常重要的作用。因此在聲明變量的時(shí)候,一定將類型指定好,特別是涉及到數(shù)值計(jì)算的時(shí)候。只不過此時(shí)使用的是 C 的類型,需要額外考慮整數(shù)溢出的情況,但如果將類型聲明為 ssize_t 的話,還是很少會(huì)發(fā)生溢出的。

以上就是 cdef 的用法,但是還沒有結(jié)束,下一篇文章我們來介紹更多與類型相關(guān)的內(nèi)容。

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    高清在线精品一区二区| 国产精品欧美激情在线播放| 99久久成人精品国产免费| 日韩在线欧美一区二区| 内射精品欧美一区二区三区久久久| 欧美人与动牲交a精品| 日韩人妻精品免费一区二区三区| 91精品蜜臀一区二区三区| 风韵人妻丰满熟妇老熟女av| 欧美尤物在线观看西比尔| 亚洲欧美日本国产有色| 一区二区三区免费公开| 国产精品大秀视频日韩精品| 色播五月激情五月婷婷| 欧美三级不卡在线观线看| 亚洲高清中文字幕一区二区三区| 欧美日韩国产综合特黄| 欧美国产日韩在线综合| 99久久免费看国产精品| 亚洲中文字幕在线视频频道| 日韩专区欧美中文字幕| 久久精品伊人一区二区| 国产又黄又爽又粗视频在线| 少妇一区二区三区精品| 日本大学生精油按摩在线观看| 日韩人妻有码一区二区| 欧美日韩人妻中文一区二区| 99精品国产一区二区青青| 尤物天堂av一区二区| 国产精品免费自拍视频| 午夜激情视频一区二区| 国产欧美日韩视频91| 一区二区不卡免费观看免费| 日韩女优视频国产一区| 老司机精品视频在线免费看| 久久女同精品一区二区| 国产色偷丝袜麻豆亚洲| 日韩中文无线码在线视频| 蜜桃传媒在线正在播放| 亚洲av一区二区三区精品| 风韵人妻丰满熟妇老熟女av|