日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

《Cython系列》5. 组织你的Cython代码

發(fā)布時間:2023/12/15 综合教程 26 生活家
生活随笔 收集整理的這篇文章主要介紹了 《Cython系列》5. 组织你的Cython代码 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

楔子

我們之前在介紹Cython語法的時候,一直永遠都是一個pyx文件,如果是多個pyx文件改怎么辦?怎么像Python那樣進行導入呢?

Python提供了modules和packages來幫助我們組織項目,這允許我們將函數(shù)、類、變量等等,按照各自的功能或者實現(xiàn)的業(yè)務(wù),分組到各自的邏輯單元中,從而使項目更容易理解和定位。并且模塊和包也使得代碼重用變得容易,在Python中我們使用import語句訪問其它module和package中的函數(shù)。

而Cython也支持我們將項目分成多個模塊,首先它完全支持import語句,并且含義與Python中的含義完全相同。這就允許我們在運行時訪問外部純Python模塊中定義的Python對象,或者其它擴展模塊中定義的可以被訪問的Python對象。

但故事顯然沒有到此為止,因為只有import的話,Cython是不允許兩個模塊訪問彼此的cdef、cpdef定義的函數(shù)、ctypedef、struct等等,也不允許訪問對其它的擴展類型進行C一級的訪問。

而為了解決這一問題,Cython提供了三種類型的文件來組織Cython文件一級C文件。到目前為止,我們一直使用擴展名為pyx的Cython源文件,它是包含代碼的邏輯的實現(xiàn)文件。但是還有兩種類型的文件,分別是擴展名為pxd的文件和擴展名為pxi的文件。

pxd文件你可以想象成類似于C中的頭文件,用于存放一些聲明之類的,而Cython的cimport就是從pxd文件中進行屬性導入。

這一節(jié)我們就來介紹cimport語句的詳細信息,以及pyx、pxd、pxi文件之間的相互聯(lián)系,我們?nèi)绾问褂盟鼈儊順?gòu)建更大的Cython項目。有了cimport和這三種類型的文件,我們就可以有效地組織Cython項目,而不會影響性能。

Cython的實現(xiàn)文件(pxd文件)和聲明文件(pxi文件)

我們目前一直在處理pyx文件,它是我們編寫具體Cython代碼的文件,當然它和py文件是等價的。如果的Cython項目非常小,并且沒有其它代碼需要訪問里面的C級結(jié)構(gòu),那么一個pyx文件足夠了。但如果我們想要共享pyx文件中的C級結(jié)構(gòu),那么就需要pxd文件了。

假設(shè)我們有這樣一個Cython文件:lover.pyx

# lover.pyx
from libc.stdlib cimport malloc, free

ctypedef double real

cdef class Girl:

    cdef public :
        str name  # 姓名
        long age  # 年齡
        str gender  # 性別
    cdef real *scores  # 分數(shù),這里我們的double數(shù)組長度為3,但是real *不能被訪問,所以它不可以使用public

    def __cinit__(self, *args, **kwargs):
        self.scores = <real *> malloc(3 * sizeof(real))

    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

    def __dealloc__(self):
        if self.scores != NULL:
            free(self.scores)

    cpdef str get_info(self):
        return f"name: {self.name}, age: {self.age}, gender: {self.gender}"
	
    cpdef set_score(self, list scores): 
        # 雖然not None也可以寫在參數(shù)后面,但是它只適用于Python函數(shù)
        assert scores is not None and len(scores) == 3
        cdef real score
        cdef long idx
        for idx, score in enumerate(scores):
            self.scores[idx] = score

    cpdef list get_score(self):
        cpdef list res = [self.scores[0], self.scores[1], self.scores[2]]
        return res

目前來講,由于所有內(nèi)容都在一個pyx文件里面,因此任何C級屬性都可以自由訪問。

>>> import cython_test
>>> cython_test.Girl('古明地覺', 16, 'female')
<cython_test.Girl object at 0x7f9716ee3308>
>>> g = cython_test.Girl('古明地覺', 16, 'female')
>>> 
>>> g.get_info()
'name: 古明地覺, age: 16, gender: female'
>>> g.set_score([90.4, 97.3, 97.6])
>>> g.get_score()
[90.4, 97.3, 97.6]
>>> 

訪問非常地自由,沒有任何限制,但是隨著我們Girl這個類的功能越來越多的話,該怎么辦呢?

所以我們需要創(chuàng)建一個pxd文件,就叫l(wèi)over.pxd吧,然后把我們希望暴露給外界訪問的C級結(jié)構(gòu)放在里面。

# lover.pxd
ctypedef double real

cdef class Girl:

    cdef public :
        str name  
        long age  
        str gender  
    cdef real *scores  
    
    cpdef str get_info(self)
    cpdef set_score(self, list scores)
    cpdef list get_score(self)

我們看到在pxd文件中,我們只存放了C級結(jié)構(gòu)的聲明,像ctypedef、cdef、cpdef等等,并且函數(shù)的話我們只是存放了定義,函數(shù)體并沒有寫在里面,同理后面也不可以有冒號。另外,pxd文件是在編譯時訪問的,而且我們不可以在里面放類似于def這樣的純Python聲明,否則會發(fā)生編譯錯誤。

所以pxd文件只放相應(yīng)的聲明,而它們的具體實現(xiàn)是在pyx文件中,因此有人發(fā)現(xiàn)了,這個pxd文件不就是C中的頭文件嗎?答案確實如此。

然后我們對應(yīng)的lover.pyx文件也需要修改,lover.pyx和lover.pxd具有相同的基名稱,Cython會將它們視為一個命名空間。另外,如果我們在pxd文件中聲明了一個函數(shù),那么在pyx文件中不可以再次聲明,否則會發(fā)生編譯錯誤。怎么理解呢?

我們說類似于cpdef func(): pass這種形式,它是一個函數(shù)(有定義);但是cpdef?func()這種形式,它只是一個函數(shù)聲明。所以函數(shù)聲明和C中的函數(shù)聲明也是類似的,在Cython中沒有冒號、以及函數(shù)體的話,那么就是函數(shù)聲明。而在Cython的pyx文件中也可以進行函數(shù)聲明,比如C源文件中也是可以聲明函數(shù)的,但是一般都會把聲明寫在h頭文件中,在Cython中也是如此,會把C級結(jié)構(gòu)、一些聲明寫在pxd文件中。

而一旦聲明,那么只能聲明一次,不可以重復(fù)聲明,具有相同基名稱的pyx、pxd文件中,函數(shù)聲明只能出現(xiàn)一次,如果在pxd文件中出現(xiàn)了,那么在pyx中就不可以重復(fù)聲明了。并且對于函數(shù),如果聲明了,那么則需要有具體的實現(xiàn)。

注意:聲明針對的是C一級的結(jié)構(gòu),所以對于def定義的Python函數(shù)是不可以進行聲明的。

重新修改我們的pyx文件

# lover.pyx
from libc.stdlib cimport malloc, free

cdef class Girl:

    def __cinit__(self, *args, **kwargs):
        self.scores = <real *> malloc(3 * sizeof(real))

    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

    def __dealloc__(self):
        if self.scores != NULL:
            free(self.scores)

    cpdef str get_info(self):
        return f"name: {self.name}, age: {self.age}, gender: {self.gender}"

    cpdef set_score(self, list scores):
        assert scores is not None and len(scores) == 3
        cdef real score
        cdef long idx
        for idx, score in enumerate(scores):
            self.scores[idx] = score

    cpdef list get_score(self):
        cpdef list res = [self.scores[0], self.scores[1], self.scores[2]]
        return res

雖然結(jié)構(gòu)沒有什么變化,但是我們把一些C級數(shù)據(jù)拿到pxd文件中了,所以pyx文件中的可以直接刪掉了,會自動到對應(yīng)的pyd文件中找,因為它們有相同的基名稱,Cython會將其整體看成一個命名空間。所以:這里pyx文件和pxd文件一定要有相同的基名稱,只有這樣才能夠找得到,否則你會發(fā)現(xiàn)代碼中real是沒有被定義的,當然還有self的一些屬性,因為它們必須要使用cdef在類里面進行聲明。

但是哪些東西我們才應(yīng)該寫在pxd文件中呢?本質(zhì)上講,任何在C級別上,需要對其它模塊公開的,我們才需要寫在pxd文件中,比如:

C類型聲明--ctypedef、結(jié)構(gòu)體、共同體、枚舉(后續(xù)系列中介紹)
外部的C、C++庫的聲明(后續(xù)系列中介紹)
cdef、cpdef模塊級函數(shù)的聲明
cdef class擴展類的聲明
擴展類的cdef屬性
使用cdef、cpdef方法的聲明
C級內(nèi)聯(lián)函數(shù)或者方法的實現(xiàn)

但是,一個pxd文件不可以包含如下內(nèi)容:

Python函數(shù)和非內(nèi)聯(lián)C級函數(shù)、方法的實現(xiàn)
Python類的定義
IF或者DEF宏的外部Python可執(zhí)行代碼

那么我們的pxd文件都帶來了哪些功能呢?那就是lover.pyx文件可以被其它的pyx文件導入了,這幾個pyx文件作為一個整體為Python提供更強大的功能,否則的話其它的pyx文件是無法導入的,導入方式是使用cimport。

cimport語句

然后我們在另一個pyx文件中導入這個lover.pyx,當然導入的話其實尋找的是pxd,然后調(diào)用的是pyx里面的函數(shù)。

# cython_test.pyx
from lover cimport Girl

cdef class NewGirl(Girl):
    pass

然后進行編譯,不過此時由于涉及到多個pyx文件,因此這些pyx都要進行編譯才行。

from distutils.core import Extension, setup
from Cython.Build import cythonize


ext = [Extension("lover", ["lover.pyx"]),  # 不用管pxd,會自動包含
       Extension("cython_test", ["cython_test.pyx"])]

setup(ext_modules=cythonize(ext, language_level=3))

編譯的命令和之前一樣,編譯之后會發(fā)現(xiàn)原來的目錄中有兩個pyd文件了。

將這兩個文件拷貝出來,首先在cython_test.pyx中,是直接導入的lover,因此這兩個pyd文件要在一個目錄中。

>>> from cython_test import NewGirl
>>> 
>>> g = NewGirl('古明地覺', 17, 'female')
>>> g.get_info()
'name: 古明地覺, age: 17, gender: female'
>>> 
>>> g.set_score([90.1, 90.3, 93.5])
>>> g.get_score()
[90.1, 90.3, 93.5]
>>> 

此時兩個pyd文件就實現(xiàn)了導入,我們可以將這個cython_test.pyx寫的更復(fù)雜一些。

from lover cimport Girl


cdef class NewGirl(Girl):

    cdef public str where

    def __init__(self, name, age, gender, where):
        self.where = where
        super().__init__(name, age, gender)

    def new_get_info(self):
        return super(NewGirl, self).get_info() + f", where: {self.where}"
>>> from cython_test import NewGirl
>>> # 自己定義了__init__,接收4個參數(shù)
>>> g = NewGirl('古明地覺', 17, 'female')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "cython_test.pyx", line 8, in cython_test.NewGirl.__init__
    def __init__(self, name, age, gender, where):
TypeError: __init__() takes exactly 4 positional arguments (3 given)
>>> 
>>> # 傳遞4個參數(shù),前面3個會交給父類處理
>>> g = NewGirl('古明地覺', 17, 'female', '東方地靈殿')
>>> g.get_info()  # 父類的方法
'name: 古明地覺, age: 17, gender: female'
>>> 
>>> g.new_get_info()  # 在父類的方法返回的結(jié)果之上,進行添加
'name: 古明地覺, age: 17, gender: female, where: 東方地靈殿'
>>> 

因此我們看到使用起來基本上和Python之間是沒有區(qū)別,主要就是如果涉及到多個pyx,那么這些pyx都要進行編譯。并且想被導入,那么該pyx文件一定要有相同基名稱的pxd文件。導入的時候使用cimport,會去pxd文件中找,然后具體實現(xiàn)則是去調(diào)用pyx文件。但即便pyx文件能被導入,但也未必就能使用其所有的C級結(jié)構(gòu),想使用的話必須要在對應(yīng)的pxd文件中進行聲明。

另外,可能有人發(fā)現(xiàn)了,我們這是絕對導入,對于目前演示來說使用絕對導入、相對導入是沒有什么區(qū)別的。但實際上,pyd應(yīng)該采用相對導入,因為它無法作為啟動文件,只能被導入。所以我們在pyx文件中使用相對導入即可。

我們舉個栗子,之前的lover.pyx和lover.pxd不變,在cython_test.pyx進行相對導入。

from .lover import Girl

但是:上面三個文件在一個單獨的目錄中,假設(shè)就叫l(wèi)over吧。1.py就是我們執(zhí)行編譯的,它和lover目錄是同級的。

然后編譯擴展模塊的時候可以用之前的方式編譯,只不過Extension中文件路徑要指定對。但是這里我們換一種方式吧,我們之前說支持通配符。

from distutils.core import setup
from Cython.Build import cythonize

# 將lover目錄下的所有pyx文件進行編譯,名字和pyx文件名是一致的。
# 如果是Extension的話,我們可以自己指定
setup(ext_modules=cythonize("lover/*.pyx", language_level=3))

當編譯成功之后,對應(yīng)的目錄就會出現(xiàn)兩個擴展模塊,我們將其移動過來。

但是注意:由于涉及到相對導入,所以我們不可以在和cython_test.so同目錄下導入,否則會報錯。

>>> import cython_test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "lover/cython_test.pyx", line 1, in init cython_test
    from .lover import Girl
ImportError: attempted relative import with no known parent package
>>> 

我們可以將這兩個so文件移動到一個單獨的目錄中,就移動到lover中吧。

其實我們還可以在里面寫入__init__文件,這樣才更符合Python中的包,但這里我們就不寫了,只要可以相對導入即可。

>>> from lover.cython_test import Girl
>>> 
>>> g = Girl("古明地覺", 17, "female")
>>> g.get_info()
'name: 古明地覺, age: 17, gender: female'
>>> 

預(yù)定義的pxd文件

還記得我們之前的from?libc.stdlib?cimport?malloc, free這行代碼嗎?顯然這是Cython提供的,沒錯它就在Cython模塊主目錄下的Includes目錄中,libc這個包下面除了stdlib之外,還有stdio、math、string等pxd文件。除此之外,它還有一個libcpp包對應(yīng)的pxd文件,里面包含了C++標準模板庫(STL)容器,如:string、vector、list、map、pair、set等等。

而CPython解釋器的Include目錄中定義了大量的C頭文件,而Cython也提供了對應(yīng)的pxd文件,可以方便地訪問Python/C api。當然還有一個最重要的包就是numpy,Cython也是支持的,當然這些我們會在后面系列中介紹了。

from ... cimport...和from ... import ...的用法是一致的,區(qū)別就是前者多了一個c

下面我們就來演示一下常見的功能。

from libc cimport math
print(math.sin(math.pi / 2))

from libc.math cimport cos, pi
print(cos(pi / 2))

# 使用C++模板
from libcpp.vector cimport vector
cdef vector[int] *vi = new vector[int](10)

# 注意:如果使用import和cimport導入了相同的函數(shù),那么會編譯錯誤
"""
from math import sin
from lib.math cimport sin
這種方式是報錯的
解決辦法可以通過as
from math import sin as py_sin
from lib.math cimport sin as c_sin
"""

我們說pxd文件類似于C的頭文件(h文件),它們有以下相似之處:

都通過使用外部代碼聲明C級結(jié)構(gòu)
都允許我們將一個大文件分成不同的多個組件
都負責聲明用于實現(xiàn)的C級接口

除此之外,還有一個pxi文件,但是個人覺得用處不是很大,這里就不說了。

總結(jié)

以上是生活随笔為你收集整理的《Cython系列》5. 组织你的Cython代码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。