# 使用 C 庫
> 原文: [http://docs.cython.org/en/latest/src/tutorial/clibraries.html](http://docs.cython.org/en/latest/src/tutorial/clibraries.html)
除了編寫快速代碼之外,Cython 的一個主要用例是從 Python 代碼調用外部 C 庫。由于 Cython 代碼編譯為 C 代碼本身,因此直接在代碼中調用 C 函數實際上是微不足道的。下面給出了在 Cython 代碼中使用(和包裝)外部 C 庫的完整示例,包括適當的錯誤處理和有關為 Python 和 Cython 代碼設計合適 API 的注意事項。
想象一下,您需要一種有效的方法來將整數值存儲在 FIFO 隊列中。由于內存非常重要,并且值實際上來自 C 代碼,因此您無法在列表或雙端隊列中創建和存儲 Python `int`對象。所以你要注意 C 中的隊列實現。
經過一些網絡搜索,你會發現 C 算法庫 [[CAlg]](#calg) 并決定使用它的雙端隊列實現。但是,為了使處理更容易,您決定將其包裝在可以封裝所有內存管理的 Python 擴展類型中。
<colgroup><col class="label"><col></colgroup>
| [[CAlg]](#id2) | Simon Howard,C 算法庫, [http://c-algorithms.sourceforge.net/](http://c-algorithms.sourceforge.net/) |
## 定義外部聲明
你可以在這里下載 CAlg [。](https://codeload.github.com/fragglet/c-algorithms/zip/master)
隊列實現的 C API,在頭文件`c-algorithms/src/queue.h`中定義,基本上如下所示:
```py
/* queue.h */
typedef struct _Queue Queue;
typedef void *QueueValue;
Queue *queue_new(void);
void queue_free(Queue *queue);
int queue_push_head(Queue *queue, QueueValue data);
QueueValue queue_pop_head(Queue *queue);
QueueValue queue_peek_head(Queue *queue);
int queue_push_tail(Queue *queue, QueueValue data);
QueueValue queue_pop_tail(Queue *queue);
QueueValue queue_peek_tail(Queue *queue);
int queue_is_empty(Queue *queue);
```
首先,第一步是在`.pxd`文件中重新定義 C API,例如`cqueue.pxd`:
```py
# cqueue.pxd
cdef extern from "c-algorithms/src/queue.h":
ctypedef struct Queue:
pass
ctypedef void* QueueValue
Queue* queue_new()
void queue_free(Queue* queue)
int queue_push_head(Queue* queue, QueueValue data)
QueueValue queue_pop_head(Queue* queue)
QueueValue queue_peek_head(Queue* queue)
int queue_push_tail(Queue* queue, QueueValue data)
QueueValue queue_pop_tail(Queue* queue)
QueueValue queue_peek_tail(Queue* queue)
bint queue_is_empty(Queue* queue)
```
請注意這些聲明與頭文件聲明幾乎完全相同,因此您通常可以將它們復制過來。但是,您不需要像上面那樣提供 _ 所有 _ 聲明,只需要在代碼或其他聲明中使用那些聲明,這樣 Cython 就可以看到它們的足夠和一致的子集。然后,考慮對它們進行一些調整,以使它們在 Cython 中更舒適。
具體來說,您應該注意為 C 函數選擇好的參數名稱,因為 Cython 允許您將它們作為關鍵字參數傳遞。稍后更改它們是向后不兼容的 API 修改。立即選擇好的名稱將使這些函數更適合使用 Cython 代碼。
我們上面使用的頭文件的一個值得注意的差異是第一行中`Queue`結構的聲明。在這種情況下,`Queue`用作 _ 不透明手柄 _;只有被調用的庫才知道里面是什么。由于沒有 Cython 代碼需要知道結構的內容,我們不需要聲明它的內容,所以我們只提供一個空的定義(因為我們不想聲明 C 頭中引用的`_Queue`類型) [[1]](#id4) 。
<colgroup><col class="label"><col></colgroup>
| [[1]](#id3) | `cdef struct Queue: pass`和`ctypedef struct Queue: pass`之間存在細微差別。前者聲明一種在 C 代碼中引用為`struct Queue`的類型,而后者在 C 中引用為`Queue`。這是 Cython 無法隱藏的 C 語言怪癖。大多數現代 C 庫使用`ctypedef`類型的結構。 |
另一個例外是最后一行。 `queue_is_empty()`函數的整數返回值實際上是一個 C 布爾值,即關于它的唯一有趣的事情是它是非零還是零,表明隊列是否為空。這最好用 Cython 的`bint`類型表示,它在 C 中使用時是普通的`int`類型,但在轉換為 Python 對象時映射到 Python 的布爾值`True`和`False`。這種在`.pxd`文件中收緊聲明的方法通常可以簡化使用它們的代碼。
最好為您使用的每個庫定義一個`.pxd`文件,如果 API 很大,有時甚至為每個頭文件(或功能組)定義。這簡化了它們在其他項目中的重用。有時,您可能需要使用標準 C 庫中的 C 函數,或者想直接從 CPython 調用 C-API 函數。對于像這樣的常見需求,Cython 附帶了一組標準的`.pxd`文件,這些文件以易于使用的方式提供這些聲明,以適應它們在 Cython 中的使用。主要包裝是`cpython`,`libc`和`libcpp`。 NumPy 庫還有一個標準的`.pxd`文件`numpy`,因為它經常在 Cython 代碼中使用。有關提供的`.pxd`文件的完整列表,請參閱 Cython 的`Cython/Includes/`源包。
## 編寫包裝類
在聲明我們的 C 庫的 API 之后,我們可以開始設計應該包裝 C 隊列的 Queue 類。它將存在于名為`queue.pyx`的文件中。 [[2]](#id6)
<colgroup><col class="label"><col></colgroup>
| [[2]](#id5) | 請注意,`.pyx`文件的名稱必須與帶有 C 庫聲明的`cqueue.pxd`文件不同,因為兩者都沒有描述相同的代碼。具有相同名稱的`.pyx`文件旁邊的`.pxd`文件定義`.pyx`文件中代碼的導出聲明。由于`cqueue.pxd`文件包含常規 C 庫的聲明,因此不得有與 Cython 關聯的`.pyx`文件具有相同名稱的文件。 |
這是 Queue 類的第一個開始:
```py
# queue.pyx
cimport cqueue
cdef class Queue:
cdef cqueue.Queue* _c_queue
def __cinit__(self):
self._c_queue = cqueue.queue_new()
```
請注意,它表示`__cinit__`而不是`__init__`。雖然`__init__`也可用,但不保證可以運行(例如,可以創建子類并忘記調用祖先的構造函數)。因為沒有初始化 C 指針經常導致 Python 解釋器的硬崩潰,所以在 CPython 甚至考慮調用`__init__`之前,Cython 提供`__cinit__`,_ 始終 _ 在構造時立即被調用,因此它是正確的位置初始化新實例的`cdef`字段。但是,在對象構造期間調用`__cinit__`時,`self`尚未完全構造,并且必須避免對`self`執行任何操作,而是分配給`cdef`字段。
另請注意,上述方法不帶參數,但子類型可能需要接受一些參數。無參數`__cinit__()`方法是一種特殊情況,它只是不接收傳遞給構造函數的任何參數,因此它不會阻止子類添加參數。如果參數在`__cinit__()`的簽名中使用,則它們必須與用于實例化類型的類層次結構中任何聲明的`__init__`類方法相匹配。
## 內存管理
在我們繼續實施其他方法之前,重要的是要了解上述實現并不安全。如果在`queue_new()`調用中出現任何問題,此代碼將簡單地吞下錯誤,因此我們稍后可能會遇到崩潰。根據`queue_new()`功能的文檔,上述可能失敗的唯一原因是內存不足。在這種情況下,它將返回`NULL`,而它通常會返回指向新隊列的指針。
Python 的方法是提出`MemoryError` [[3]](#id8) 。因此我們可以更改 init 函數,如下所示:
```py
# queue.pyx
cimport cqueue
cdef class Queue:
cdef cqueue.Queue* _c_queue
def __cinit__(self):
self._c_queue = cqueue.queue_new()
if self._c_queue is NULL:
raise MemoryError()
```
<colgroup><col class="label"><col></colgroup>
| [[3]](#id7) | 在`MemoryError`的特定情況下,為了引發它而創建一個新的異常實例實際上可能會失敗,因為我們的內存不足。幸運的是,CPython 提供了一個 C-API 函數`PyErr_NoMemory()`,可以安全地為我們提出正確的例外。只要您編寫`raise MemoryError`或`raise MemoryError()`,Cython 就會自動替換此 C-API 調用。如果您使用的是舊版本,則必須從標準軟件包`cpython.exc`中導入 C-API 函數并直接調用它。 |
接下來要做的是在不再使用 Queue 實例時清理(即刪除了對它的所有引用)。為此,CPython 提供了 Cython 作為特殊方法`__dealloc__()`提供的回調。在我們的例子中,我們所要做的就是釋放 C Queue,但前提是我們在 init 方法中成功初始化它:
```py
def __dealloc__(self):
if self._c_queue is not NULL:
cqueue.queue_free(self._c_queue)
```
## 編譯和鏈接
在這一點上,我們有一個可以測試的工作 Cython 模塊。要編譯它,我們需要為 distutils 配置一個`setup.py`腳本。這是編譯 Cython 模塊的最基本腳本:
```py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
setup(
ext_modules = cythonize([Extension("queue", ["queue.pyx"])])
)
```
要構建針對外部 C 庫,我們需要確保 Cython 找到必要的庫。存檔有兩種方法。首先,我們可以告訴 distutils 在哪里找到 c-source 來自動編譯`queue.c`實現。或者,我們可以構建和安裝 C-Alg 作為系統庫并動態鏈接它。如果其他應用也使用 C-Alg,后者是有用的。
### 靜態鏈接
要自動構建 c 代碼,我們需要在 <cite>queue.pyx</cite> 中包含編譯器指令:
```py
# distutils: sources = c-algorithms/src/queue.c
# distutils: include_dirs = c-algorithms/src/
cimport cqueue
cdef class Queue:
cdef cqueue.Queue* _c_queue
def __cinit__(self):
self._c_queue = cqueue.queue_new()
if self._c_queue is NULL:
raise MemoryError()
def __dealloc__(self):
if self._c_queue is not NULL:
cqueue.queue_free(self._c_queue)
```
`sources`編譯器指令給出了 distutils 將編譯和鏈接(靜態)到生成的擴展模塊的 C 文件的路徑。通常,所有相關的頭文件都應該在`include_dirs`中找到。現在我們可以使用以下方法構建項目
```py
$ python setup.py build_ext -i
```
并測試我們的構建是否成功:
```py
$ python -c 'import queue; Q = queue.Queue()'
```
### 動態鏈接
如果我們要打包的庫已經安裝在系統上,則動態鏈接很有用。要執行動態鏈接,我們首先需要構建和安裝 c-alg。
要在您的系統上構建 c 算法:
```py
$ cd c-algorithms
$ sh autogen.sh
$ ./configure
$ make
```
安裝 CAlg 運行:
```py
$ make install
```
之后文件`/usr/local/lib/libcalg.so`應該存在。
注意
此路徑適用于 Linux 系統,在其他平臺上可能有所不同,因此您需要根據系統中`libcalg.so`或`libcalg.dll`的路徑調整本教程的其余部分。
在這種方法中,我們需要告訴安裝腳本與外部庫鏈接。為此,我們需要擴展安裝腳本以安裝更改擴展設置
```py
ext_modules = cythonize([Extension("queue", ["queue.pyx"])])
```
至
```py
ext_modules = cythonize([
Extension("queue", ["queue.pyx"],
libraries=["calg"])
])
```
現在我們應該能夠使用以下方法構建項目:
```py
$ python setup.py build_ext -i
```
如果 <cite>libcalg</cite> 未安裝在“普通”位置,用戶可以通過傳遞適當的 C 編譯器標志在外部提供所需的參數,例如:
```py
CFLAGS="-I/usr/local/otherdir/calg/include" \
LDFLAGS="-L/usr/local/otherdir/calg/lib" \
python setup.py build_ext -i
```
在運行模塊之前,我們還需要確保 <cite>libcalg</cite> 在 <cite>LD_LIBRARY_PATH</cite> 環境變量中,例如通過設置:
```py
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
```
一旦我們第一次編譯模塊,我們現在可以導入它并實例化一個新的隊列:
```py
$ export PYTHONPATH=.
$ python -c 'import queue; Q = queue.Queue()'
```
但是,這是我們所有 Queue 類到目前為止所能做到的,所以讓它更有用。
### 映射功能
在實現此類的公共接口之前,最好先查看 Python 提供的接口,例如在`list`或`collections.deque`類中。由于我們只需要 FIFO 隊列,因此足以提供方法`append()`,`peek()`和`pop()`,另外還有`extend()`方法一次添加多個值。此外,由于我們已經知道所有值都來自 C,因此最好現在只提供`cdef`方法,并為它們提供直接的 C 接口。
在 C 中,數據結構通常將數據作為`void*`存儲到任何數據項類型。由于我們只想存儲通常適合指針類型大小的`int`值,我們可以通過一個技巧避免額外的內存分配:我們將`int`值轉換為`void*`,反之亦然,并存儲值直接作為指針值。
這是`append()`方法的簡單實現:
```py
cdef append(self, int value):
cqueue.queue_push_tail(self._c_queue, <void*>value)
```
同樣,適用與`__cinit__()`方法相同的錯誤處理注意事項,因此我們最終會使用此實現:
```py
cdef append(self, int value):
if not cqueue.queue_push_tail(self._c_queue,
<void*>value):
raise MemoryError()
```
現在添加`extend()`方法應該是直截了當的:
```py
cdef extend(self, int* values, size_t count):
"""Append all ints to the queue.
"""
cdef int value
for value in values[:count]: # Slicing pointer to limit the iteration boundaries.
self.append(value)
```
例如,當從 C 數組讀取值時,這變得很方便。
到目前為止,我們只能將數據添加到隊列中。下一步是編寫兩個方法來獲取第一個元素:`peek()`和`pop()`,它們分別提供只讀和破壞性讀訪問。為了避免在直接將`void*`轉換為`int`時出現編譯器警告,我們使用的中間數據類型足以容納`void*`。在這里,`Py_ssize_t`:
```py
cdef int peek(self):
return <Py_ssize_t>cqueue.queue_peek_head(self._c_queue)
cdef int pop(self):
return <Py_ssize_t>cqueue.queue_pop_head(self._c_queue)
```
通常,在 C 中,當我們將較大的整數類型轉換為較小的整數類型而不檢查邊界時,我們冒著丟失數據的風險,并且`Py_ssize_t`可能是比`int`更大的類型。但由于我們控制了如何將值添加到隊列中,我們已經知道隊列中的所有值都適合`int`,因此上面的轉換從`void*`到`Py_ssize_t`到`int`(返回類型) )設計安全。
### 處理錯誤
現在,當隊列為空時會發生什么?根據文檔,函數返回`NULL`指針,該指針通常不是有效值。但由于我們只是簡單地向內和外輸入,我們無法區分返回值是否為`NULL`,因為隊列為空或者隊列中存儲的值為`0`。在 Cython 代碼中,我們希望第一種情況引發異常,而第二種情況應該只返回`0`。為了解決這個問題,我們需要特殊情況下這個值,并檢查隊列是否真的是空的:
```py
cdef int peek(self) except? -1:
cdef int value = <Py_ssize_t>cqueue.queue_peek_head(self._c_queue)
if value == 0:
# this may mean that the queue is empty, or
# that it happens to contain a 0 value
if cqueue.queue_is_empty(self._c_queue):
raise IndexError("Queue is empty")
return value
```
請注意我們如何在希望常見的情況下有效地創建了通過該方法的快速路徑,返回值不是`0`。如果隊列為空,則只有該特定情況需要額外檢查。
方法簽名中的`except? -1`聲明屬于同一類別。如果函數是返回 Python 對象值的 Python 函數,CPython 將在內部返回`NULL`而不是 Python 對象來指示異常,該異常將立即由周圍的代碼傳播。問題是返回類型是`int`并且任何`int`值都是有效的隊列項值,因此無法向調用代碼顯式地發出錯誤信號。實際上,如果沒有這樣的聲明,Cython 就沒有明顯的方法可以知道在異常上返回什么以及調用代碼甚至知道這個方法 _ 可能 _ 以異常退出。
調用代碼可以處理這種情況的唯一方法是在從函數返回時調用`PyErr_Occurred()`以檢查是否引發了異常,如果是,則傳播異常。這顯然有性能損失。因此,Cython 允許您聲明在異常的情況下它應隱式返回的值,以便周圍的代碼只需在接收到此精確值時檢查異常。
我們選擇使用`-1`作為異常返回值,因為我們期望它是一個不太可能被放入隊列的值。 `except? -1`聲明中的問號表示返回值不明確(畢竟,_ 可能 _ 可能是隊列中的`-1`值)并且需要使用`PyErr_Occurred()`進行額外的異常檢查在調用代碼。沒有它,調用此方法并接收異常返回值的 Cython 代碼將默默地(有時不正確地)假定已引發異常。在任何情況下,所有其他返回值將幾乎沒有懲罰地通過,因此再次為“正常”值創建快速路徑。
既然實現了`peek()`方法,`pop()`方法也需要適應。但是,由于它從隊列中刪除了一個值,因此僅在刪除后測試隊列是否為空 _ 是不夠的。相反,我們必須在進入時測試它:_
```py
cdef int pop(self) except? -1:
if cqueue.queue_is_empty(self._c_queue):
raise IndexError("Queue is empty")
return <Py_ssize_t>cqueue.queue_pop_head(self._c_queue)
```
異常傳播的返回值與`peek()`完全相同。
最后,我們可以通過實現`__bool__()`特殊方法以正常的 Python 方式為 Queue 提供空白指示符(注意 Python 2 調用此方法`__nonzero__`,而 Cython 代碼可以使用任一名稱):
```py
def __bool__(self):
return not cqueue.queue_is_empty(self._c_queue)
```
請注意,此方法返回`True`或`False`,因為我們在`cqueue.pxd`中將`queue_is_empty()`函數的返回類型聲明為`bint`。
### 測試結果
既然實現已經完成,您可能需要為它編寫一些測試以確保它正常工作。特別是 doctests 非常適合這個目的,因為它們同時提供了一些文檔。但是,要啟用 doctests,您需要一個可以調用的 Python API。從 Python 代碼中看不到 C 方法,因此無法從 doctests 中調用。
為類提供 Python API 的一種快速方法是將方法從`cdef`更改為`cpdef`。這將讓 Cython 生成兩個入口點,一個可以使用 Python 調用語義和 Python 對象作為參數從普通 Python 代碼調用,另一個可以使用快速 C 語義從 C 代碼調用,不需要從 Python 進行中間參數轉換。類型。請注意,`cpdef`方法確保 Python 方法可以適當地覆蓋它們,即使從 Cython 調用它們也是如此。與`cdef`方法相比,這增加了很小的開銷。
現在我們已經為我們的類提供了 C 接口和 Python 接口,我們應該確保兩個接口都是一致的。 Python 用戶期望`extend()`方法接受任意迭代,而 C 用戶希望有一個允許傳遞 C 數組和 C 內存的方法。兩個簽名都不兼容。
我們將通過考慮在 C 中,API 也可能想要支持其他輸入類型來解決此問題,例如, `long`或`char`的數組,通常支持不同名稱的 C API 函數,如`extend_ints()`,`extend_longs()`,extend_chars()``等。這允許我們釋放方法名`extend()` duck typed Python 方法,可以接受任意迭代。
以下清單顯示了盡可能使用`cpdef`方法的完整實現:
```py
# queue.pyx
cimport cqueue
cdef class Queue:
"""A queue class for C integer values.
>>> q = Queue()
>>> q.append(5)
>>> q.peek()
5
>>> q.pop()
5
"""
cdef cqueue.Queue* _c_queue
def __cinit__(self):
self._c_queue = cqueue.queue_new()
if self._c_queue is NULL:
raise MemoryError()
def __dealloc__(self):
if self._c_queue is not NULL:
cqueue.queue_free(self._c_queue)
cpdef append(self, int value):
if not cqueue.queue_push_tail(self._c_queue,
<void*> <Py_ssize_t> value):
raise MemoryError()
# The `cpdef` feature is obviously not available for the original "extend()"
# method, as the method signature is incompatible with Python argument
# types (Python does not have pointers). However, we can rename
# the C-ish "extend()" method to e.g. "extend_ints()", and write
# a new "extend()" method that provides a suitable Python interface by
# accepting an arbitrary Python iterable.
cpdef extend(self, values):
for value in values:
self.append(value)
cdef extend_ints(self, int* values, size_t count):
cdef int value
for value in values[:count]: # Slicing pointer to limit the iteration boundaries.
self.append(value)
cpdef int peek(self) except? -1:
cdef int value = <Py_ssize_t> cqueue.queue_peek_head(self._c_queue)
if value == 0:
# this may mean that the queue is empty,
# or that it happens to contain a 0 value
if cqueue.queue_is_empty(self._c_queue):
raise IndexError("Queue is empty")
return value
cpdef int pop(self) except? -1:
if cqueue.queue_is_empty(self._c_queue):
raise IndexError("Queue is empty")
return <Py_ssize_t> cqueue.queue_pop_head(self._c_queue)
def __bool__(self):
return not cqueue.queue_is_empty(self._c_queue)
```
現在我們可以使用 python 腳本測試我們的 Queue 實現,例如這里`test_queue.py`:
```py
from __future__ import print_function
import time
import queue
Q = queue.Queue()
Q.append(10)
Q.append(20)
print(Q.peek())
print(Q.pop())
print(Q.pop())
try:
print(Q.pop())
except IndexError as e:
print("Error message:", e) # Prints "Queue is empty"
i = 10000
values = range(i)
start_time = time.time()
Q.extend(values)
end_time = time.time() - start_time
print("Adding {} items took {:1.3f} msecs.".format(i, 1000 * end_time))
for i in range(41):
Q.pop()
Q.pop()
print("The answer is:")
print(Q.pop())
```
作為在作者的機器上使用 10000 個數字的快速測試表明,使用來自 Cython 代碼的 C `int`值的這個隊列大約是使用 Cython 代碼和 Python 對象值的速度的五倍,幾乎是使用它的速度的八倍。 Python 循環中的 Python 代碼,仍然比使用 Python 整數的 Cython 代碼中的 Python 高度優化的`collections.deque`類型快兩倍。
### 回調
假設您希望為用戶提供一種方法,可以將隊列中的值彈出到某個用戶定義的事件。為此,您希望允許它們傳遞一個判斷何時停止的謂詞函數,例如:
```py
def pop_until(self, predicate):
while not predicate(self.peek()):
self.pop()
```
現在,讓我們假設為了參數,C 隊列提供了一個將 C 回調函數作為謂詞的函數。 API 可能如下所示:
```py
/* C type of a predicate function that takes a queue value and returns
* -1 for errors
* 0 for reject
* 1 for accept
*/
typedef int (*predicate_func)(void* user_context, QueueValue data);
/* Pop values as long as the predicate evaluates to true for them,
* returns -1 if the predicate failed with an error and 0 otherwise.
*/
int queue_pop_head_until(Queue *queue, predicate_func predicate,
void* user_context);
```
C 回調函數具有通用`void*`參數是正常的,該參數允許通過 C-API 將任何類型的上下文或狀態傳遞到回調函數中。我們將使用它來傳遞我們的 Python 謂詞函數。
首先,我們必須定義一個帶有預期簽名的回調函數,我們可以將其傳遞給 C-API 函數:
```py
cdef int evaluate_predicate(void* context, cqueue.QueueValue value):
"Callback function that can be passed as predicate_func"
try:
# recover Python function object from void* argument
func = <object>context
# call function, convert result into 0/1 for True/False
return bool(func(<int>value))
except:
# catch any Python errors and return error indicator
return -1
```
主要思想是將指針(a.k.a。借用的引用)作為用戶上下文參數傳遞給函數對象。我們將調用 C-API 函數如下:
```py
def pop_until(self, python_predicate_function):
result = cqueue.queue_pop_head_until(
self._c_queue, evaluate_predicate,
<void*>python_predicate_function)
if result == -1:
raise RuntimeError("an error occurred")
```
通常的模式是首先將 Python 對象引用轉換為`void*`以將其傳遞給 C-API 函數,然后將其轉換回 C 謂詞回調函數中的 Python 對象。對`void*`的強制轉換創建了借用的引用。在轉換為`<object>`時,Cython 遞增對象的引用計數,從而將借用的引用轉換回擁有的引用。在謂詞函數結束時,擁有的引用再次超出范圍,Cython 丟棄它。
上面代碼中的錯誤處理有點簡單。具體而言,謂詞函數引發的任何異常將基本上被丟棄,并且只會導致在事實之后引發普通`RuntimeError()`。這可以通過將異常存儲在通過 context 參數傳遞的對象中并在 C-API 函數返回`-1`以指示錯誤之后重新引發它來改進。
- Cython 3.0 中文文檔
- 入門
- Cython - 概述
- 安裝 Cython
- 構建 Cython 代碼
- 通過靜態類型更快的代碼
- Tutorials
- 基礎教程
- 調用 C 函數
- 使用 C 庫
- 擴展類型(又名.cdef 類)
- pxd 文件
- Caveats
- Profiling
- Unicode 和傳遞字符串
- 內存分配
- 純 Python 模式
- 使用 NumPy
- 使用 Python 數組
- 進一步閱讀
- 相關工作
- 附錄:在 Windows 上安裝 MinGW
- 用戶指南
- 語言基礎
- 擴展類型
- 擴展類型的特殊方法
- 在 Cython 模塊之間共享聲明
- 與外部 C 代碼連接
- 源文件和編譯
- 早期綁定速度
- 在 Cython 中使用 C ++
- 融合類型(模板)
- 將 Cython 代碼移植到 PyPy
- Limitations
- Cython 和 Pyrex 之間的區別
- 鍵入的內存視圖
- 實現緩沖協議
- 使用并行性
- 調試你的 Cython 程序
- 用于 NumPy 用戶的 Cython
- Pythran 作為 Numpy 后端