# 擴展類型
> 原文: [http://docs.cython.org/en/latest/src/userguide/extension_types.html](http://docs.cython.org/en/latest/src/userguide/extension_types.html)
## 介紹
除了使用 Python 類語句創建普通的用戶定義類之外,Cython 還允許您創建新的內置 Python 類型,稱為擴展類型。使用 [`cdef`](language_basics.html#cdef) 類語句定義擴展類型。這是一個例子:
```py
from __future__ import print_function
cdef class Shrubbery:
cdef int width, height
def __init__(self, w, h):
self.width = w
self.height = h
def describe(self):
print("This shrubbery is", self.width,
"by", self.height, "cubits.")
```
如您所見,Cython 擴展類型定義看起來很像 Python 類定義。在其中,您使用 def 語句來定義可以從 Python 代碼調用的方法。您甚至可以像在 Python 中一樣定義許多特殊方法,如`__init__()`。
主要區別在于您可以使用 [`cdef`](language_basics.html#cdef) 語句來定義屬性。屬性可以是 Python 對象(通用或特定擴展類型),或者它們可以是任何 C 數據類型。因此,您可以使用擴展類型來包裝任意 C 數據結構,并為它們提供類似 Python 的接口。
## 靜態屬性
擴展類型的屬性直接存儲在對象的 C 結構中。這組屬性在編譯時是固定的;您無法在運行時向擴展類型實例添加屬性,只需分配給它們,就像使用 Python 類實例一樣。但是,您可以顯式啟用對動態分配的屬性的支持,或者使用普通的 Python 類將擴展類型子類化,然后支持任意屬性分配。參見 [動態屬性](#dynamic-attributes) 。
有兩種方法可以訪問擴展類型的屬性:通過 Python 屬性查找,或通過從 Cython 代碼直接訪問 C 結構。 Python 代碼只能通過第一種方法訪問擴展類型的屬性,但 Cython 代碼可以使用任一方法。
默認情況下,擴展類型屬性只能通過直接訪問訪問,而不能通過 Python 訪問訪問,這意味著無法從 Python 代碼訪問它們。要使它們可以從 Python 代碼訪問,您需要將它們聲明為 [`public`](#public) 或 [`readonly`](#readonly) 。例如:
```py
cdef class Shrubbery:
cdef public int width, height
cdef readonly float depth
```
使得寬度和高度屬性可以從 Python 代碼中讀取和寫入,深度屬性可讀但不可寫。
注意
您只能為 Python 訪問公開簡單的 C 類型,例如整數,浮點數和字符串。您還可以公開 Python 值屬性。
Note
此外, [`public`](#public) 和 [`readonly`](#readonly) 選項僅適用于 Python 訪問,而不適用于直接訪問。擴展類型的所有屬性始終可通過 C 級訪問進行讀寫。
## 動態屬性
默認情況下,無法在運行時向擴展類型添加屬性。您有兩種方法可以避免這種限制,當從 Python 代碼調用方法時,這兩種方法都會增加開銷。特別是在調用`cpdef`方法時。
第一種方法是創建一個 Python 子類:
```py
cdef class Animal:
cdef int number_of_legs
def __cinit__(self, int number_of_legs):
self.number_of_legs = number_of_legs
class ExtendableAnimal(Animal): # Note that we use class, not cdef class
pass
dog = ExtendableAnimal(4)
dog.has_tail = True
```
聲明`__dict__`屬性是啟用動態屬性的第二種方式:
```py
cdef class Animal:
cdef int number_of_legs
cdef dict __dict__
def __cinit__(self, int number_of_legs):
self.number_of_legs = number_of_legs
dog = Animal(4)
dog.has_tail = True
```
## 類型聲明
在您可以直接訪問擴展類型的屬性之前,Cython 編譯器必須知道您擁有該類型的實例,而不僅僅是通用 Python 對象。它已經知道這種類型的方法的`self`參數,但在其他情況下,您將不得不使用類型聲明。
例如,在以下功能中:
```py
cdef widen_shrubbery(sh, extra_width): # BAD
sh.width = sh.width + extra_width
```
因為`sh`參數沒有給出類型,所以將通過 Python 屬性查找訪問 width 屬性。如果屬性已被聲明為 [`public`](#public) 或 [`readonly`](#readonly) ,那么這將起作用,但效率非常低。如果屬性是私有的,它根本不起作用 - 代碼將編譯,但是在運行時會引發屬性錯誤。
解決方案是將`sh`聲明為`Shrubbery`類型,如下所示:
```py
from my_module cimport Shrubbery
cdef widen_shrubbery(Shrubbery sh, extra_width):
sh.width = sh.width + extra_width
```
現在,Cython 編譯器知道`sh`有一個名為`width`的 C 屬性,并將生成代碼以直接有效地訪問它。同樣的考慮適用于局部變量,例如:
```py
from my_module cimport Shrubbery
cdef Shrubbery another_shrubbery(Shrubbery sh1):
cdef Shrubbery sh2
sh2 = Shrubbery()
sh2.width = sh1.width
sh2.height = sh1.height
return sh2
```
Note
我們這里`cimport`類`Shrubbery`,這是在編譯時聲明類型所必需的。為了能夠`cimport`擴展類型,我們將類定義分為兩部分,一部分在定義文件中,另一部分在相應的實現文件中。你應該閱讀 [共享擴展類型](sharing_declarations.html#sharing-extension-types) 來學習這樣做。
### 類型測試和鑄造
假設我有一個方法`quest()`,它返回`Shrubbery`類型的對象。要訪問它的寬度我可以寫:
```py
cdef Shrubbery sh = quest()
print(sh.width)
```
這需要使用局部變量并在賦值時執行類型測試。如果 _ 知道 _,`quest()`的返回值將是`Shrubbery`類型,你可以使用強制轉換來寫:
```py
print( (<Shrubbery>quest()).width )
```
如果`quest()`實際上不是`Shrubbery`,這可能是危險的,因為它將嘗試訪問寬度作為可能不存在的 C 結構成員。在 C 級別,不是提出 [`AttributeError`](https://docs.python.org/3/library/exceptions.html#AttributeError "(in Python v3.7)") ,而是返回一個無意義的結果(將該地址處的任何數據解釋為 int)或者嘗試訪問無效內存可能導致段錯誤。相反,人們可以寫:
```py
print( (<Shrubbery?>quest()).width )
```
在進行強制轉換并允許代碼繼續之前執行類型檢查(可能引發 [`TypeError`](https://docs.python.org/3/library/exceptions.html#TypeError "(in Python v3.7)") )。
要顯式測試對象的類型,請使用`isinstance()`內置函數。對于已知的內置或擴展類型,Cython 將這些轉換為快速且安全的類型檢查,忽略對象的`__class__`屬性等的更改,以便在成功進行`isinstance()`測試后,代碼可以依賴于預期的 C 結構擴展類型及其 [`cdef`](language_basics.html#cdef) 屬性和方法。
## 擴展類型和無
當您將參數或 C 變量聲明為擴展類型時,Cython 將允許它采用值`None`以及其聲明類型的值。這類似于 C 指針可以采用值`NULL`的方式,因此需要謹慎行事。只要您對它執行 Python 操作就沒有問題,因為將應用完整的動態類型檢查。但是,當您訪問擴展類型的 C 屬性時(如上面的 widen_shrubbery 函數),由您來確保您使用的引用不是`None` - 為了提高效率,Cython 不會檢查這個。
在公開將擴展類型作為參數的 Python 函數時,您需要特別小心。如果我們想讓`widen_shrubbery()`成為 Python 函數,例如,如果我們只是寫道:
```py
def widen_shrubbery(Shrubbery sh, extra_width): # This is
sh.width = sh.width + extra_width # dangerous!
```
那么我們模塊的用戶可以通過傳遞`sh`參數的`None`來使其崩潰。
解決這個問題的一種方法是:
```py
def widen_shrubbery(Shrubbery sh, extra_width):
if sh is None:
raise TypeError
sh.width = sh.width + extra_width
```
但由于預計這是一個如此頻繁的要求,Cython 提供了一種更方便的方式。聲明為擴展類型的 Python 函數的參數可以具有`not None`子句:
```py
def widen_shrubbery(Shrubbery sh not None, extra_width):
sh.width = sh.width + extra_width
```
現在該函數將自動檢查`sh`是否為`not None`并檢查它是否具有正確的類型。
Note
`not None`子句只能用于 Python 函數(用 [`def`](https://docs.python.org/3/reference/compound_stmts.html#def "(in Python v3.7)") 定義)而不能用于 C 函數(用 [`cdef`](language_basics.html#cdef) 定義)。如果需要檢查 C 函數的參數是否為 None,則需要自己進行。
Note
還有一些事情:
* 保證擴展類型方法的 self 參數永遠不會是`None`。
* 將值與`None`進行比較時,請記住,如果`x`是 Python 對象,`x is None`和`x is not None`非常有效,因為它們直接轉換為 C 指針比較,而`x == None`和`x != None`或者簡單地使用`x`作為布爾值(如在`if x: ...`中)將調用 Python 操作,因此速度要慢得多。
## 特殊方法
盡管原理類似,但許多`__xxx__()`擴展類型的特殊方法和它們的 Python 對應方之間存在很大差異。有一個 [單獨的頁面](special_methods.html#special-methods) 致力于這個主題,你應該在嘗試使用擴展類型中的任何特殊方法之前仔細閱讀它。
## 屬性
您可以使用與普通 Python 代碼中相同的語法在擴展類中聲明屬性:
```py
cdef class Spam:
@property
def cheese(self):
# This is called when the property is read.
...
@cheese.setter
def cheese(self, value):
# This is called when the property is written.
...
@cheese.deleter
def cheese(self):
# This is called when the property is deleted.
```
還有一種特殊的(已棄用的)遺留語法,用于定義擴展類中的屬性:
```py
cdef class Spam:
property cheese:
"A doc string can go here."
def __get__(self):
# This is called when the property is read.
...
def __set__(self, value):
# This is called when the property is written.
...
def __del__(self):
# This is called when the property is deleted.
```
`__get__()`,`__set__()`和`__del__()`方法都是可選的;如果省略它們,則在嘗試相應操作時將引發異常。
這是一個完整的例子。它定義了一個屬性,每次寫入時都會添加到列表中,在讀取列表時返回列表,并在刪除列表時清空列表:
```py
# cheesy.pyx
cdef class CheeseShop:
cdef object cheeses
def __cinit__(self):
self.cheeses = []
@property
def cheese(self):
return "We don't have: %s" % self.cheeses
@cheese.setter
def cheese(self, value):
self.cheeses.append(value)
@cheese.deleter
def cheese(self):
del self.cheeses[:]
# Test input
from cheesy import CheeseShop
shop = CheeseShop()
print(shop.cheese)
shop.cheese = "camembert"
print(shop.cheese)
shop.cheese = "cheddar"
print(shop.cheese)
del shop.cheese
print(shop.cheese)
```
```py
# Test output
We don't have: []
We don't have: ['camembert']
We don't have: ['camembert', 'cheddar']
We don't have: []
```
## 子類化
擴展類型可以從內置類型或其他擴展類型繼承:
```py
cdef class Parrot:
...
cdef class Norwegian(Parrot):
...
```
基本類型的完整定義必須可用于 Cython,因此如果基類型是內置類型,則它必須先前已聲明為 extern 擴展類型。如果基類型在另一個 Cython 模塊中定義,則必須將其聲明為 extern 擴展類型或使用 [`cimport`](sharing_declarations.html#cimport) 語句導入。
擴展類型只能有一個基類(沒有多重繼承)。
Cython 擴展類型也可以在 Python 中進行子類化。 Python 類可以從多個擴展類型繼承,前提是遵循通常的多重繼承的 Python 規則(即所有基類的 C 布局必須兼容)。
有一種方法可以防止擴展類型在 Python 中被子類型化。這是通過`final`指令完成的,通常使用裝飾器在擴展類型上設置:
```py
cimport cython
@cython.final
cdef class Parrot:
def done(self): pass
```
嘗試從此類型創建 Python 子類將在運行時引發 [`TypeError`](https://docs.python.org/3/library/exceptions.html#TypeError "(in Python v3.7)") 。 Cython 還將阻止在同一模塊內部對最終類型進行子類型化,即創建使用最終類型的擴展類型,因為其基類型將在編譯時失敗。但請注意,此限制目前不會傳播到其他擴展模塊,因此即使是最終擴展類型仍可以通過外部代碼在 C 級進行子類型化。
## C 方法
擴展類型可以有 C 方法和 Python 方法。與 C 函數一樣,使用 [`cdef`](language_basics.html#cdef) 或 [`cpdef`](language_basics.html#cpdef) 而不是 [`def`](https://docs.python.org/3/reference/compound_stmts.html#def "(in Python v3.7)") 聲明 C 方法。 C 方法是“虛擬的”,可以在派生的擴展類型中重寫。另外,當調用 C 方法時, [`cpdef`](language_basics.html#cpdef) 方法甚至可以被 python 方法覆蓋。與 [`cdef`](language_basics.html#cdef) 方法相比,這增加了一些他們的調用開銷:
```py
# pets.pyx
cdef class Parrot:
cdef void describe(self):
print("This parrot is resting.")
cdef class Norwegian(Parrot):
cdef void describe(self):
Parrot.describe(self)
print("Lovely plumage!")
cdef Parrot p1, p2
p1 = Parrot()
p2 = Norwegian()
print("p1:")
p1.describe()
print("p2:")
p2.describe()
```
```py
# Output
p1:
This parrot is resting.
p2:
This parrot is resting.
Lovely plumage!
```
上面的例子還說明了一個 C 方法可以使用通常的 Python 技術調用一個繼承的 C 方法,即:
```py
Parrot.describe(self)
```
可以使用@staticmethod 裝飾器將 <cite>cdef</cite> 方法聲明為靜態。這對于構造采用非 Python 兼容類型的類特別有用:
```py
cdef class OwnedPointer:
cdef void* ptr
def __dealloc__(self):
if self.ptr is not NULL:
free(self.ptr)
@staticmethod
cdef create(void* ptr):
p = OwnedPointer()
p.ptr = ptr
return p
```
## 前向聲明擴展類型
擴展類型可以是前向聲明的,如 [`struct`](language_basics.html#struct) 和 [`union`](language_basics.html#union) 類型。這通常不是必要的,違反了 DRY 原則(不要重復自己)。
如果要向前聲明具有基類的擴展類型,則必須在前向聲明及其后續定義中指定基類,例如:
```py
cdef class A(B)
...
cdef class A(B):
# attributes and methods
```
## 快速實例化
Cython 提供了兩種加速擴展類型實例化的方法。第一個是直接調用`__new__()`特殊靜態方法,如 Python 所知。對于擴展類型`Penguin`,您可以使用以下代碼:
```py
cdef class Penguin:
cdef object food
def __cinit__(self, food):
self.food = food
def __init__(self, food):
print("eating!")
normal_penguin = Penguin('fish')
fast_penguin = Penguin.__new__(Penguin, 'wheat') # note: not calling __init__() !
```
請注意,通過`__new__()`的路徑將 _ 而非 _ 調用類型的`__init__()`方法(再次,如 Python 所知)。因此,在上面的示例中,第一個實例化將打印`eating!`,但第二個實例化不會打印`eating!`。這只是`__cinit__()`方法比擴展類型的正常`__init__()`方法更安全和更可取的原因之一。
第二個性能改進適用于經常連續創建和刪除的類型,以便它們可以從空閑列表中受益。 Cython 為此提供了裝飾器`@cython.freelist(N)`,它為給定類型創建了一個靜態大小的`N`實例空閑列表。例:
```py
cimport cython
@cython.freelist(8)
cdef class Penguin:
cdef object food
def __cinit__(self, food):
self.food = food
penguin = Penguin('fish 1')
penguin = None
penguin = Penguin('fish 2') # does not need to allocate memory!
```
## 從現有的 C / C ++指針實例化
想要從現有(指向 a)數據結構實例化擴展類是很常見的,通常由外部 C / C ++函數返回。
由于擴展類只能在其構造函數中接受 Python 對象作為參數,因此必須使用工廠函數。例如,
```py
from libc.stdlib cimport malloc, free
# Example C struct
ctypedef struct my_c_struct:
int a
int b
cdef class WrapperClass:
"""A wrapper class for a C/C++ data structure"""
cdef my_c_struct *_ptr
cdef bint ptr_owner
def __cinit__(self):
self.ptr_owner = False
def __dealloc__(self):
# De-allocate if not null and flag is set
if self._ptr is not NULL and self.ptr_owner is True:
free(self._ptr)
self._ptr = NULL
# Extension class properties
@property
def a(self):
return self._ptr.a if self._ptr is not NULL else None
@property
def b(self):
return self._ptr.b if self._ptr is not NULL else None
@staticmethod
cdef WrapperClass from_ptr(my_c_struct *_ptr, bint owner=False):
"""Factory function to create WrapperClass objects from
given my_c_struct pointer.
Setting ``owner`` flag to ``True`` causes
the extension type to ``free`` the structure pointed to by ``_ptr``
when the wrapper object is deallocated."""
# Call to __new__ bypasses __init__ constructor
cdef WrapperClass wrapper = WrapperClass.__new__(WrapperClass)
wrapper._ptr = _ptr
wrapper.ptr_owner = owner
return wrapper
@staticmethod
cdef WrapperClass new_struct():
"""Factory function to create WrapperClass objects with
newly allocated my_c_struct"""
cdef my_c_struct *_ptr = <my_c_struct *>malloc(sizeof(my_c_struct))
if _ptr is NULL:
raise MemoryError
_ptr.a = 0
_ptr.b = 0
return WrapperClass.from_ptr(_ptr, owner=True)
```
然后,從現有的`my_c_struct`指針創建`WrapperClass`對象,可以在 Cython 代碼中使用`WrapperClass.from_ptr(ptr)`。要分配新結構并同時包裝它,可以使用`WrapperClass.new_struct`代替。
如果需要,可以從同一指針創建多個 Python 對象,這些指針指向相同的內存中數據,盡管在解除分配時必須小心,如上所示。此外,`ptr_owner`標志可用于控制哪個`WrapperClass`對象擁有指針并負責解除分配 - 在示例中默認設置為`False`,可以通過調用`from_ptr(ptr, owner=True)`來啟用。
GIL 必須 _ 而不是 _ 在`__dealloc__`中釋放,或者如果是,則使用另一個鎖定,在這種情況下,或者在多次解除分配時可能發生競爭條件。
作為對象構造函數的一部分,`__cinit__`方法具有 Python 簽名,這使得它無法接受`my_c_struct`指針作為參數。
嘗試在 Python 簽名中使用指針將導致以下錯誤:
```py
Cannot convert 'my_c_struct *' to Python object
```
這是因為 Cython 不能自動將指針轉換為 Python 對象,這與`int`等本機類型不同。
請注意,對于本機類型,Cython 將復制該值并創建新的 Python 對象,而在上述情況下,不會復制數據,并且取消分配內存是擴展類的責任。
## 使擴展類型弱引用
默認情況下,擴展類型不支持對它們進行弱引用。您可以通過聲明名為`__weakref__`的對象類型的 C 屬性來啟用弱引用。例如,:
```py
cdef class ExplodingAnimal:
"""This animal will self-destruct when it is
no longer strongly referenced."""
cdef object __weakref__
```
## 控制 CPython 中的釋放和垃圾收集
Note
本節僅適用于 Python 的常用 CPython 實現。 PyPy 等其他實現的工作方式不同。
### 介紹
首先,很好理解在 CPython 中有兩種方法可以觸發 Python 對象的釋放:CPython 對所有對象使用引用計數,并且任何引用計數為零的對象都會被立即釋放。這是解除分配對象的最常用方法。例如,考慮一下
```py
>>> x = "foo"
>>> x = "bar"
```
執行第二行后,不再引用字符串`"foo"`,因此將其取消分配。這是使用`tp_dealloc`插槽完成的,可以通過實現`__dealloc__`在 Cython 中自定義。
第二種機制是循環垃圾收集器。這是為了解決諸如的循環參考循環
```py
>>> class Object:
... pass
>>> def make_cycle():
... x = Object()
... y = [x]
... x.attr = y
```
調用`make_cycle`時,會創建一個參考循環,因為`x`引用`y`,反之亦然。即使在`make_cycle`返回后無法訪問`x`或`y`,兩者的引用計數均為 1,因此不會立即取消分配。在常規時間,垃圾收集器運行,它會注意到參考周期(使用`tp_traverse`插槽)并將其中斷。打破引用循環意味著在循環中獲取一個對象并將其中的所有引用移除到其他 Python 對象(我??們稱之為 _ 清除 _ 一個對象)。清除與解除分配幾乎相同,只是實際對象尚未釋放。對于上例中的`x`,`x`的屬性將從`x`中刪除。
請注意,在參考周期中只清除一個對象就足夠了,因為在清除一個對象后不再有一個周期。一旦循環中斷,通常基于 refcount 的釋放將實際從內存中刪除對象。清除在`tp_clear`插槽中實現。正如我們剛剛解釋的那樣,循環中的一個對象實現`tp_clear`就足夠了。
### 啟用釋放垃圾箱
在 CPython 中,可以創建深度遞歸對象。例如:
```py
>>> L = None
>>> for i in range(2**20):
... L = [L]
```
現在假設我們刪除了最后的`L`。然后`L`解除分配`L[0]`,解除分配`L[0][0]`,依此類推,直到達到`2**20`的遞歸深度。這種解除分配是在 C 中完成的,這種深度遞歸可能會溢出 C 調用堆棧,從而導致 Python 崩潰。
CPython 發明了一種稱為 _ 垃圾桶 _ 的機制。它通過延遲一些解除分配來限制解除分配的遞歸深度。
默認情況下,Cython 擴展類型不使用垃圾桶,但可以通過將`trashcan`指令設置為`True`來啟用它。例如:
```py
cimport cython
@cython.trashcan(True)
cdef class Object:
cdef dict __dict__
```
Trashcan 用法由子類繼承(除非`@cython.trashcan(False)`明確禁用)。像`list`這樣的內置類型使用垃圾桶,因此它的子類默認使用垃圾桶。
### 禁用循環中斷(`tp_clear`)
默認情況下,每種擴展類型都支持 CPython 的循環垃圾收集器。如果可以引用任何 Python 對象,Cython 將自動生成`tp_traverse`和`tp_clear`插槽。這通常是你想要的。
至少有一個原因可能不是你想要的:如果你需要清理`__dealloc__`特殊功能中的一些外部資源而你的對象恰好處于參考周期,垃圾收集器可能已經觸發了一個`tp_clear`清除對象(參見 [簡介](#dealloc-intro) )。
在這種情況下,調用`__dealloc__`時,任何對象引用都會消失。現在,您的清理代碼無法訪問它必須清理的對象。要解決此問題,您可以使用`no_gc_clear`指令禁用特定類的清除實例:
```py
@cython.no_gc_clear
cdef class DBCursor:
cdef DBConnection conn
cdef DBAPI_Cursor *raw_cursor
# ...
def __dealloc__(self):
DBAPI_close_cursor(self.conn.raw_conn, self.raw_cursor)
```
此示例嘗試在銷毀 Python 對象時通過數據庫連接關閉游標。 `DBConnection`對象通過`DBCursor`的引用保持活動狀態。但是如果游標恰好在引用循環中,則垃圾收集器可能會刪除數據庫連接引用,這使得無法清理游標。
如果使用`no_gc_clear`,則任何給定的參考循環必須包含至少一個沒有 `no_gc_clear`的對象 _。否則,循環不能被破壞,這是內存泄漏。_
### 禁用循環垃圾收集
在極少數情況下,可以保證擴展類型不參與循環,但編譯器將無法證明這一點。如果類永遠不能引用自身,甚至間接引用它,情況就是這樣。在這種情況下,您可以使用`no_gc`指令手動禁用循環收集,但要注意這樣做實際上擴展類型可以參與循環可能會導致內存泄漏
```py
@cython.no_gc
cdef class UserInfo:
cdef str name
cdef tuple addresses
```
如果您可以確定地址僅包含對字符串的引用,則上述內容將是安全的,并且可能會產生顯著的加速,具體取決于您的使用模式。
## 控制酸洗
默認情況下,Cython 將生成一個`__reduce__()`方法,以便當且僅當其每個成員都可以轉換為 Python 且沒有`__cinit__`方法時才允許修改擴展類型。要求此行為(即,如果無法對類進行 pickle,則在編譯時拋出錯誤)使用`@cython.auto_pickle(True)`修飾類。也可以用`@cython.auto_pickle(False)`注釋以獲得在任何情況下都不生成`__reduce__`方法的舊行為。
手動實現`__reduce__`或 <cite>__reduce_ex__`</cite> 方法也將禁用此自動生成,并可用于支持更復雜類型的酸洗。
## 公共和外部擴展類型
擴展類型可以聲明為 extern 或 public。外部擴展類型聲明使外部 C 代碼中定義的擴展類型可用于 Cython 模塊。公共擴展類型聲明使得在 Cython 模塊中定義的擴展類型可用于外部 C 代碼。
### 外部擴展類型
外部擴展類型允許您訪問 Python 核心或非 Cython 擴展模塊中定義的 Python 對象的內部。
Note
在以前版本的 Pyrex 中,extern 擴展類型也用于引用另一個 Pyrex 模塊中定義的擴展類型。雖然你仍然可以做到這一點,但 Cython 為此提供了更好的機制。參見 [在 Cython 模塊](sharing_declarations.html#sharing-declarations) 之間共享聲明。
下面是一個示例,它將讓您了解內置復雜對象的 C 級成員:
```py
from __future__ import print_function
cdef extern from "complexobject.h":
struct Py_complex:
double real
double imag
ctypedef class __builtin__.complex [object PyComplexObject]:
cdef Py_complex cval
# A function which uses the above type
def spam(complex c):
print("Real:", c.cval.real)
print("Imag:", c.cval.imag)
```
Note
一些重要的事情:
1. 在此示例中,已使用 [`ctypedef`](language_basics.html#ctypedef) 類。這是因為,在 Python 頭文件中,`PyComplexObject`結構聲明為:
```py
typedef struct {
...
} PyComplexObject;
```
在運行時,將導入`__builtin__.complex`的`tp_basicsize`與`sizeof(`PyComplexObject)`匹配的 Cython c 擴展模塊時執行檢查。如果使用一個版本的`complexobject.h`標頭編譯 Cython c-extension 模塊但導入到具有更改標頭的 Python 中,則此檢查可能會失敗。可以使用名稱規范子句中的`check_size`來調整此檢查。
2. 除了擴展類型的名稱外,還指定了可以在其中找到類型對象的模塊。請參閱下面的隱式導入部分。
3. 聲明外部擴展類型時,不要聲明任何方法。為了調用它們,不需要聲明方法,因為調用是 Python 方法調用。另外,與 [`struct`](language_basics.html#struct) 和 [`union`](language_basics.html#union) 一樣,如果你的擴展類聲明在塊中的 [`cdef`](language_basics.html#cdef) extern 內,你只需要聲明您希望訪問的 C 成員。
### 名稱規范條款
方括號中的類聲明部分是僅適用于外部或公共擴展類型的特殊功能。該條款的完整形式是:
```py
[object object_struct_name, type type_object_name, check_size cs_option]
```
哪里:
* `object_struct_name`是為類型的 C 結構假設的名稱。
* `type_object_name`是為類型的靜態聲明的類型對象假定的名稱。
* `cs_option`是`warn`(默認值),`error`或`ignore`,僅用于外部擴展類型。如果`error`,在編譯時找到的`sizeof(object_struct)`必須與類型的運行時`tp_basicsize`完全匹配,否則模塊導入將失敗并顯示錯誤。如果`warn`或`ignore`,允許`object_struct`小于類型的`tp_basicsize`,這表示運行時類型可能是更新模塊的一部分,并且外部模塊的開發人員向后擴展了對象兼容的方式(僅在對象的末尾添加新字段)。如果`warn`,在這種情況下將發出警告。
條款可以按任何順序書寫。
如果擴展類型聲明在塊中的 [`cdef`](language_basics.html#cdef) extern 內,則需要 object 子句,因為 Cython 必須能夠生成與頭文件中的聲明兼容的代碼。否則,對于 extern 擴展類型,object 子句是可選的。
對于公共擴展類型,object 和 type 子句都是必需的,因為 Cython 必須能夠生成與外部 C 代碼兼容的代碼。
### 屬性名稱匹配和別名
有時,`object_struct_name`中指定的類型的 C 結構可能會使用不同的字段標簽,而不是`PyTypeObject`中的標簽。這在手工編碼的 C 擴展中很容易發生,其中`PyTypeObject_Foo`具有 getter 方法,但名稱與`PyFooObject`中的名稱不匹配。例如,在 NumPy 中,python-level `dtype.itemsize`是 C struct 字段`elsize`的 getter。 Cython 支持別名字段名稱,以便可以在 Cython 代碼中編寫`dtype.itemsize`,這些代碼將被編譯為 C struct 字段的直接訪問,而無需通過相當于`dtype.__getattr__('itemsize')`的 C-API。
例如,我們可能有一個擴展模塊`foo_extension`:
```py
cdef class Foo:
cdef public int field0, field1, field2;
def __init__(self, f0, f1, f2):
self.field0 = f0
self.field1 = f1
self.field2 = f2
```
但是文件`foo_nominal.h`中的 C 結構:
```py
typedef struct {
PyObject_HEAD
int f0;
int f1;
int f2;
} FooStructNominal;
```
請注意,結構使用`f0`,`f1`,`f2`,但它們是`Foo`中的`field0`,`field1`和`field2`。我們得到了這種情況,包括帶有該結構的頭文件,我們希望編寫一個函數來對值進行求和。如果我們編寫擴展模塊`wrapper`:
```py
cdef extern from "foo_nominal.h":
ctypedef class foo_extension.Foo [object FooStructNominal]:
cdef:
int field0
int field1
int feild2
def sum(Foo f):
return f.field0 + f.field1 + f.field2
```
那么`wrapper.sum(f)`(其中`f = foo_extension.Foo(1, 2, 3)`)仍將使用相當于的 C-API:
```py
return f.__getattr__('field0') +
f.__getattr__('field1') +
f.__getattr__('field1')
```
而不是所需的 C 當量`return f->f0 + f->f1 + f->f2`。我們可以使用以下字段對字段進行別名:
```py
cdef extern from "foo_nominal.h":
ctypedef class foo_extension.Foo [object FooStructNominal]:
cdef:
int field0 "f0"
int field1 "f1"
int field2 "f2"
def sum(Foo f) except -1:
return f.field0 + f.field1 + f.field2
```
現在 Cython 將用對 CooStructNominal 字段的直接 C 訪問來替換慢`__getattr__`。這在直接處理 Python 代碼時很有用。即使 Python 和 C 中的字段名稱不同,也不需要對 Python 進行任何更改以實現顯著的加速。當然,應該確保字段是等價的。
### 隱式導入
Cython 要求您在 extern 擴展類聲明中包含模塊名稱,例如:
```py
cdef extern class MyModule.Spam:
...
```
類型對象將從指定模塊隱式導入,并綁定到此模塊中的相應名稱。換句話說,在這個例子中隱含:
```py
from MyModule import Spam
```
語句將在模塊加載時執行。
模塊名稱可以是帶點名稱,用于引用包層次結構內的模塊,例如:
```py
cdef extern class My.Nested.Package.Spam:
...
```
您還可以使用 as 子句指定用于導入類型的備用名稱,例如:
```py
cdef extern class My.Nested.Package.Spam as Yummy:
...
```
它對應于隱式 import 語句:
```py
from My.Nested.Package import Spam as Yummy
```
### 類型名稱與構造函數名稱
在 Cython 模塊中,擴展類型的名稱有兩個不同的用途。在表達式中使用時,它指的是包含類型構造函數(即其類型對象)的模塊級全局變量。但是,它也可以用作 C 類型名稱來聲明該類型的變量,參數和返回值。
當你聲明:
```py
cdef extern class MyModule.Spam:
...
```
Spam 這個名字兼具這兩個角色。可能有其他名稱可以引用構造函數,但只有 Spam 可以用作類型名稱。例如,如果要顯式導入 MyModule,則可以使用`MyModule.Spam()`創建 Spam 實例,但不能將`MyModule.Spam`用作類型名稱。
使用 as 子句時,as 子句中指定的名稱也將接管這兩個角色。所以如果你宣布:
```py
cdef extern class MyModule.Spam as Yummy:
...
```
然后 Yummy 成為類型名稱和構造函數的名稱。同樣,您可以通過其他方式獲取構造函數,但只有 Yummy 可用作類型名稱。
## 公共擴展類型
擴展類型可以聲明為 public,在這種情況下會生成一個包含其對象 struct 和 type 對象聲明的`.h`文件。通過將`.h`文件包含在您編寫的外部 C 代碼中,該代碼可以訪問擴展類型的屬性。
- 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 后端