至今學習 Cython 語言的筆記。
此篇文章可能需要一些 C 語言基礎。
前言
Cython 是 Python 的超集 (superset) 語言,直接將 Python 腳本交給 Cython 進行編譯是可行的;正如 Type script 與 Java script 的關係。
Cython 中能夠使用 Python 全部的語法,並且支援額外模組的匯入。不過須注意的是,Cython 會不斷參照 PEP (Python Enhancement Proposals) 規則進行強化,以兼顧 Python 的撰寫風格,因此推薦升級模組時跟進 Python 版本。
Python 語法概要
之前提過的 記憶體管理 方法,為方便再簡要介紹。
建立與刪除
Python 使用「名稱綁定」的方式指派變數。
a = 10 #將記憶體中儲存數值 10,並給予名稱 "a"。 b = 10 #找到已儲存數值 10,再給予名稱 "b"。 print(a is b) #True,"a" 與 "b" 共用記憶體。 del a #尋找並移除名稱 "a",10 仍存於記憶體。 del b #尋找並移除名稱 "b",10 失去所有名稱,被刪除。
修改
使用重新綁定以使名稱中的值變化。
a = 10 b = 10 b += 1 #針對 "b" 名稱取值並進行加法運算,得 2。 """此時 "b" 名稱重新綁定到新結果 2。""" print(a is b) #False,"a" 與 "b" 使用不同記憶體。
複製與參照
作用域更改時(導入模組、進入類型、進出函式等),物件將會改名,前方編上作用域名稱。
Python 遵循以下方法:
-
字面類型 (literal) 包含 bool、Numeric (int, float, complex)、str 共五種,使用複製。
-
其餘類型如容器 (container)、函式 (function)、自訂類型等,則為參照。
-
複製行為是創造副本,對複本修改只能在當前作用域有效(例如函式中),離開後此改動值會失去名稱,也不會存回本體。
-
參照相當於取別名,此名稱執行任何行為可以影響本體。
由於多了一層「名稱綁定」,Python 的執行速度就不會如靜態語言如此高效。
Cython 語法概要
Python 的超高效開發速度就是因為簡單有效的名稱運用,而不必注意瑣碎的運算子操作。
當需要對 Python 程式提速時,將名稱轉為靜態類型就可以了,剩下的就如 C 語言一樣交由編譯器規劃。
直接對 Python 腳本編譯,會稍微進行提速,這得感謝你安裝的 C 語言編譯器。通常編譯器會試著找到 Python 對於名稱嘮嘮叨叨之處,試圖簡化語法。
但是越大的腳本,名稱網路就會越複雜,效果將會更不明顯。
延伸語法:C 語言名稱
接下來介紹全新的 Cython 關鍵字 cdef
:
cdef int a = 0 #宣告靜態整數類型 "a",規劃記憶體存入 0 值。
cdef a = 0 #建立靜態 "a" 指派 0?Cython 會自動推斷 "a" 的型別。
此變數會完全參照 C 語言的行為,而非靜態變數則會參照 Python 的「名稱綁定」。
同一作用域下,名稱更改時,若沒有指標或參照運算子操作,一律為複製。
不同作用域時,則同於 Python。
cdef int a = 10
b = a #將整數綁定名稱 "b"。
cdef int c = b #將名稱 "b" 的值轉型為整數,複製到名稱 "c"。
print(a is b) #True,兩者共用記憶體。
print(a is c) #False,兩者不共用記憶體。
跟 C 語言一樣,使用此關鍵字的名稱「宣告」與「定義」是可以分開的。
cdef int a, b, c #宣告三個整數類型的名稱 "a"、"b"、"c"。
a = 20 #對 "a" 指派 20 完成定義。
for b in [5, 4, 6, 8]: #名稱 "b" 將用於取出迭代值。
if b > 5:
c = func(b*b) #名稱 "c" 將用於判斷式區塊中。
即使是函式或類型也可以,當然參數 (arguments) 的簽章 (signature) 必須一致。
cdef int add(int, int b = *) #宣告新函式
cdef inline int add(int a, int b = 10): #完成定義函式(可再加上簡單的修飾符)
return a + b
cdef class NewClass(OldClass) #宣告新類型
cdef class NewClass(OldClass): #完成定義類型
pass
經過靜態定義的類型就像有一部份的 C 語言血統,部分功能也能加速或改變性能,可以參照這裡:
- http://cython.readthedocs.io/en/latest/src/userguide/extension_types.html
- http://cython.readthedocs.io/en/latest/src/userguide/special_methods.html
混和 C 和 C++
而 C 語言的類型則可以完全支援,如使用標頭檔引入 C 類型:
enum class Color {red, green = 20, blue};
接著透過 extern from ... :
語句建立型別檢查器來引入物件:
cdef extern from "colors.h": #引入類型作為檢查器。
cdef cppclass Color:
pass
cdef extern from "colors.h" namespace "Color": #引入數值。
cdef Color red
cdef Color green
cdef Color blue
cdef class PyColor: #給 Python 透過類型方法取值。
cdef Color thisobj
def __cinit__(self, int val):
self.thisobj = <Color> val
def get_color_type(self):
cdef dict c = {<int>red : "red", <int> green : "green", <int> blue : "blue"}
return c[<int>self.thisobj]
當然這樣可以直接在 Cython 中寫 C 或 C++ 大部分的語法,但是如果這樣的話效能會減損很多,C 語言的東西還是先自己包好再引導給 Python 吧。
端口
必須注意的是,Python 無法從程式庫中取出靜態類型的名稱(除了類型的名稱),想要取得必須透過幾種方式:
- 函式回傳。
- 使用修飾符。
兩種函式可以給 Python 看到:
cpdef void func_cpy(int a): #使用 C 語言製作的函式接口。
print("P{}".format(a))
def func_py(a: int) -> None: #使用 C 物件的純 Python 函式。
print("P{}".format(a))
cdef
、cpdef
、def
三種函式中,速度最快自然是使用 cdef
生成的函式,Python 的 def
函式是最慢的。
不過兩種有端口的函式使用上自然有限制,例如完全不支援修飾符。
不過,Cython 可以支援使用 def
生成 Python 生產器 (generator),並且能很好的使用靜態類型的物件,至於效率自然是會因為同步迭代而提升,就視情況自行取捨。
順帶一提,Cython 編譯時也能簡單檢查 Python 的 typing 語法,不過不支援從 typing 模組匯入的類型。
使用 readonly
和 public
修飾符可以使物件被 Python 可視,不過仍有些許限制,只能給予 Python 支援的類型,指標、陣列或 vector 容器自然是不可能暴露給 Python。
標頭檔 pxd
由於宣告與定義可以分開,假如使用 from ... import ...
語句,只能以 Python 的方式引入。不但得經過語言端口,C 語言的名稱會完全看不到。因此在 Cython 程式庫之間設計了新的語句 from ... cimport ...
,達成兩個 pyx 共用程式的效果。
鑒於 C 和 C++ 的原理相仿,相同宣告的名稱使用相同的定義,這邊就不多作介紹了,基本上這句 cimport 可以當作有 namespace 遮罩的 include 語法。
Cython 比較不一樣的是,為了避免混淆名稱,擁有該 pxd 定義的 pyx 程式庫,兩者必須同名且定義不可分割,而此 pyx 程式庫不用任何 cimport 自該 pxd 的語句。
這樣,其他 pyx 程式庫就可以藉由 pxd 的宣告項目進行 cimport。
自訂檢查器
使用 ctypedef fused ... :
語句建立客製化的檢查器,讓多種類型以供選擇。
ctypedef fused sequence:
list
tuple
此表現類似於 C++ 的 template 功能。
Comments
comments powered by Disqus