如何擴(kuò)展NumPy
編寫擴(kuò)展模塊
雖然ndarray對象旨在允許在Python中進(jìn)行快速計算,但它也被設(shè)計為通用的并且滿足各種各樣的計算需求。因此,如果絕對速度是必不可少的,那么就不能替換特定于您的應(yīng)用程序和硬件的精心編制的循環(huán)。這是numpy包含f2py的原因之一,因此可以使用易于使用的機(jī)制將(簡單的)C / C ++和(任意)Fortran代碼直接鏈接到Python中。我們鼓勵您使用和改進(jìn)此機(jī)制。本節(jié)的目的不是記錄此工具,而是記錄編寫此工具所依賴的擴(kuò)展模塊的更基本步驟。
當(dāng)擴(kuò)展模塊被編寫,編譯并安裝到Python路徑(sys.path)中的某個位置時,可以將代碼導(dǎo)入到Python中,就好像它是標(biāo)準(zhǔn)的python文件一樣。它將包含已在C代碼中定義和編譯的對象和方法。在Python中執(zhí)行此操作的基本步驟已有詳細(xì)記錄,您可以在www.python.org
上的在線文檔中找到更多信息。
除了Python C-API之外,NumPy還有一個完整而豐富的C-API,允許在C級上進(jìn)行復(fù)雜的操作。但是,對于大多數(shù)應(yīng)用程序,通常只使用少量API調(diào)用。如果你需要做的就是提取一個指向內(nèi)存的指針以及一些形狀信息以傳遞給另一個計算例程,那么你將使用非常不同的調(diào)用,然后如果你試圖創(chuàng)建一個類似于數(shù)組的新類型或添加一個新數(shù)據(jù)ndarrays的類型。本章介紹了最常用的API調(diào)用和宏。
必需的子程序
必須在C代碼中定義一個函數(shù)才能使Python將其用作擴(kuò)展模塊。該函數(shù)必須被稱為init {name},其中{name}是Python中模塊的名稱。必須聲明此函數(shù),以便對例程外部的代碼可見。除了添加您想要的方法和常量之外,此子例程還必須包含調(diào)用import_array()
和/或import_ufunc()
取決于需要哪個C-API。只要實際調(diào)用任何C-API子例程,忘記放置這些命令就會將自身顯示為一個丑陋的分段錯誤(崩潰)。實際上,在單個文件中可以有多個init
{name}函數(shù),在這種情況下,該文件將定義多個模塊。但是,有一些技巧可以讓它正常工作,這里沒有涉及。
一個最小的init{name}
方法看起來像:
PyMODINIT_FUNC
init{name}(void)
{
(void)Py_InitModule({name}, mymethods);
import_array();
}
mymethods必須是PyMethodDef結(jié)構(gòu)的數(shù)組(通常是靜態(tài)聲明的),它包含方法名,實際的C函數(shù),指示方法是否使用關(guān)鍵字參數(shù)的變量,以及docstrings。這些將在下一節(jié)中介紹。如果要向模塊添加常量,則存儲Py_InitModule的返回值,Py_InitModule是一個模塊對象。向模塊添加項目的最常用方法是使用PyModule_GetDict(模塊)獲取模塊字典。使用模塊字典,您可以手動將任何您喜歡的內(nèi)容添加到模塊中。向模塊添加對象的更簡單方法是使用三個額外的Python C-API調(diào)用之一,這些調(diào)用不需要單獨提取模塊字典。這些內(nèi)容記錄在Python文檔中,但為方便起見,在此處重復(fù):
int PyModule_AddObject
(PyObject
* value )?
int PyModule_AddIntConstant
(PyObject
* module ,char * name ,long value )?
int PyModule_AddStringConstant
(PyObject
* module ,char * name ,char * value )?
所有這三個函數(shù)都需要 模塊 對象(Py_InitModule的返回值)。該 名稱 是標(biāo)簽?zāi)K中的值的字符串。根據(jù)調(diào)用的函數(shù), value 參數(shù)是一般對象(PyModule_AddObject
竊取對它的引用),整數(shù)常量或字符串常量。
定義函數(shù)
傳遞給Py_InitModule函數(shù)的第二個參數(shù)是一個結(jié)構(gòu),可以很容易地在模塊中定義函數(shù)。在上面給出的示例中,mymethods結(jié)構(gòu)將在文件的早期(通常在init {name}子例程之前)定義為:
static PyMethodDef mymethods[] = {
{ nokeywordfunc,nokeyword_cfunc,
METH_VARARGS,
Doc string},
{ keywordfunc, keyword_cfunc,
METH_VARARGS|METH_KEYWORDS,
Doc string},
{NULL, NULL, 0, NULL} /* Sentinel */
}
mymethods數(shù)組中的每個條目都是一個PyMethodDef
結(jié)構(gòu),包含1)Python名稱,2)實現(xiàn)函數(shù)的C函數(shù),3)指示是否接受此函數(shù)的關(guān)鍵字的標(biāo)志,以及4)函數(shù)的文檔字符串。通過向該表添加更多條目,可以為單個模塊定義任意數(shù)量的功能。最后一個條目必須全部為NULL,如圖所示充當(dāng)哨兵。Python查找此條目以了解已定義模塊的所有函數(shù)。
完成擴(kuò)展模塊必須做的最后一件事是實際編寫執(zhí)行所需功能的代碼。有兩種函數(shù):不接受關(guān)鍵字參數(shù)的函數(shù)和那些函數(shù)。
沒有關(guān)鍵字參數(shù)的函數(shù)
不接受關(guān)鍵字參數(shù)的函數(shù)應(yīng)寫為:
static PyObject*
nokeyword_cfunc (PyObject *dummy, PyObject *args)
{
/* convert Python arguments */
/* do function */
/* return something */
}
偽參數(shù)不在此上下文中使用,可以安全地忽略。該 ARGS 參數(shù)包含所有的傳遞給函數(shù)作為一個元組的參數(shù)。此時您可以執(zhí)行任何操作,但通常管理輸入?yún)?shù)的最簡單方法是調(diào)用PyArg_ParseTuple
PyArg_UnpackTuple
(元組,“名稱”,分鐘,最大,......)。有關(guān)如何使用第一個函數(shù)的詳細(xì)說明,請參見Python
C-API參考手冊第5.5節(jié)(解析參數(shù)和構(gòu)建值)。您應(yīng)該特別注意使用轉(zhuǎn)換器函數(shù)在Python對象和C對象之間進(jìn)行的“O&”格式。所有其他格式函數(shù)都可以(大部分)被認(rèn)為是這個一般規(guī)則的特殊情況。NumPy
C-API中定義了幾種可能有用的轉(zhuǎn)換器功能。特別是,該PyArray_DescrConverter
函數(shù)對于支持任意數(shù)據(jù)類型規(guī)范非常有用。此函數(shù)將任何有效的數(shù)據(jù)類型Python對象轉(zhuǎn)換為
對象。請記住傳入應(yīng)填寫的C變量的地址。
PyArray_Descr *
有很多關(guān)于如何在PyArg_ParseTuple
整個NumPy源代碼中使用的示例。標(biāo)準(zhǔn)用法是這樣的:
PyObject *input;
PyArray_Descr *dtype;
if (!PyArg_ParseTuple(args, "OO&", &input,
PyArray_DescrConverter,
&dtype)) return NULL;
請務(wù)必記住,在使用“O”格式字符串時,您會獲得對該對象的 借用 引用。但是,轉(zhuǎn)換器功能通常需要某種形式的內(nèi)存處理。在此示例中,如果轉(zhuǎn)換成功,則 dtype 將保持對對象的新引用,而 輸入 將保留借用的引用。因此,如果此轉(zhuǎn)換與另一個轉(zhuǎn)換(比如整數(shù))混合并且數(shù)據(jù)類型轉(zhuǎn)換成功但整數(shù)轉(zhuǎn)換失敗,那么您需要在返回之前將引用計數(shù)釋放到數(shù)據(jù)類型對象。一種典型的方法是
在調(diào)用之前將 dtype 設(shè)置為,然后
在 dtype上 使用PyArray_Descr *
NULL
PyArg_ParseTuple
Py_XDECREF
** 回來之前。
處理完輸入?yún)?shù)后,將編寫實際完成工作的代碼(可能會根據(jù)需要調(diào)用其他函數(shù))。C函數(shù)的最后一步是返回一些東西。如果遇到錯誤,NULL
則應(yīng)返回(確保實際設(shè)置了錯誤)。如果不應(yīng)返回任何內(nèi)容,則遞增
Py_None
Py_BuildValue
(format_string,c_variables
...)函數(shù)可以很容易地從C變量構(gòu)建Python對象的元組。請?zhí)貏e注意格式字符串中“N”和“O”之間的區(qū)別,否則您可能很容易造成內(nèi)存泄漏。'O'格式字符串增加它對應(yīng)的C變量的引用計數(shù),而'N'格式字符串竊取對相應(yīng)C變量的引用。如果已經(jīng)為對象創(chuàng)建了引用并且只想對元組進(jìn)行引用,則應(yīng)使用“N”。如果您只有一個對象的借用引用并且需要創(chuàng)建一個來提供元組,則應(yīng)該使用“O”。PyObject *
PyObject *
帶關(guān)鍵字參數(shù)的函數(shù)
這些函數(shù)與沒有關(guān)鍵字參數(shù)的函數(shù)非常相似。唯一的區(qū)別是函數(shù)簽名是:
static PyObject*
keyword_cfunc (PyObject *dummy, PyObject *args, PyObject *kwds)
{
...
}
kwds參數(shù)包含一個Python字典,其鍵是關(guān)鍵字參數(shù)的名稱,其值是相應(yīng)的關(guān)鍵字參數(shù)值。無論你認(rèn)為合適,都可以處理這本字典。然而,處理它的最簡單方法是PyArg_ParseTuple
PyArg_ParseTupleAndKeywords
(args,kwds,format_string,char * kwlist [],地址......)調(diào)用替換
(args,format_string,addresses ...)函數(shù)。此函數(shù)的kwlist參數(shù)是一個NULL
字符串?dāng)?shù)組,提供了預(yù)期的關(guān)鍵字參數(shù)。format_string中的每個條目都應(yīng)該有一個字符串。如果傳入無效的關(guān)鍵字參數(shù),則使用此函數(shù)將引發(fā)TypeError。
有關(guān)此功能的更多幫助,請參閱Python文檔中的擴(kuò)展和嵌入教程的第1.8節(jié)(擴(kuò)展函數(shù)的關(guān)鍵字參數(shù))。
引用計數(shù)
編寫擴(kuò)展模塊時最大的困難是引用計數(shù)。這是f2py,weave,Cython,ctypes等受歡迎的重要原因。如果您錯誤處理引用計數(shù),則可能會出現(xiàn)從內(nèi)存泄漏到分段錯誤的問題。我知道處理參考計數(shù)的唯一策略是血液,汗水和眼淚。首先,你強(qiáng)迫每個Python變量都有一個引用計數(shù)。然后,您可以準(zhǔn)確了解每個函數(shù)對對象的引用計數(shù)的作用,以便在需要時可以正確使用DECREF和INCREF。引用計數(shù)可以真正測試您對編程工藝的耐心和勤奮程度。盡管形象嚴(yán)峻,大多數(shù)引用計數(shù)的情況非常簡單,最常見的困難是由于某些錯誤而在從例程退出之前不在對象上使用DECREF。第二,是不會在傳遞給將要竊取引用的函數(shù)或宏的對象上擁有引用的常見錯誤(
例如 PyTuple_SET_ITEM
PyArray_Descr
對象的功能)。
通常,在創(chuàng)建變量時會獲得對變量的新引用,或者是某個函數(shù)的返回值(但是有一些突出的例外 - 例如從元組或字典中獲取項目)。當(dāng)您擁有引用時,您有責(zé)任確保Py_DECREF
Py_INCREF
獲取自己的引用)。您還將遇到借用參考的概念。借用引用的函數(shù)不會改變對象的引用計數(shù),也不會期望“保持”引用。它只是暫時使用該對象。當(dāng)你使用PyArg_ParseTuple
或者PyArg_UnpackTuple
您收到對元組中對象的借用引用,不應(yīng)更改其函數(shù)內(nèi)的引用計數(shù)。通過練習(xí),您可以學(xué)會正確引用計數(shù),但一開始可能會令人沮喪。
引用計數(shù)錯誤的一個常見來源是Py_BuildValue
函數(shù)。請?zhí)貏e注意'N'格式字符和'O'格式字符之間的區(qū)別。如果在子例程中創(chuàng)建一個新對象(例如輸出數(shù)組),并且在返回值的元組中將其傳回,則最應(yīng)該使用“N”格式字符。
Py_BuildValue
。“O”字符將引用計數(shù)增加1。這將為調(diào)用者提供一個全新數(shù)組的兩個引用計數(shù)。刪除變量并且引用計數(shù)減1時,仍會有額外的引用計數(shù),并且永遠(yuǎn)不會釋放該數(shù)組。您將有一個引用計數(shù)引起的內(nèi)存泄漏。使用'N'字符將避免這種情況,因為它將使用單個引用計數(shù)返回給調(diào)用者一個對象(在元組內(nèi))。
處理數(shù)組對象
NumPy的大多數(shù)擴(kuò)展模塊都需要訪問ndarray對象(或其中一個子類)的內(nèi)存。最簡單的方法不需要您了解NumPy的內(nèi)部結(jié)構(gòu)。方法是
- 確保處理的是行為良好的數(shù)組(按機(jī)器字節(jié)順序和單段對齊),具有正確的維數(shù)類型和數(shù)量。
- 通過使用PyArray_FromAny或在其上構(gòu)建的宏將其從某個Python對象轉(zhuǎn)換。
- 通過使用PyArray_NewFromDescr或基于它的更簡單的宏或函數(shù)構(gòu)建所需形狀和類型的新ndarray。
- 獲取數(shù)組的形狀和指向其實際數(shù)據(jù)的指針。
- 將數(shù)據(jù)和形狀信息傳遞給子例程或?qū)嶋H執(zhí)行計算的其他代碼部分。
- 如果您正在編寫算法,那么我建議您使用數(shù)組中包含的步幅信息來訪問數(shù)組的元素(PyArray_GetPtr宏使這一過程變得輕松)。然后,您可以放松您的要求,這樣就不會強(qiáng)制使用單段數(shù)組和可能導(dǎo)致的數(shù)據(jù)復(fù)制。
這些子主題中的每一個都會在下面的小節(jié)中介紹。
轉(zhuǎn)換任意序列對象
從任何可以轉(zhuǎn)換為數(shù)組的Python對象獲取數(shù)組的主程序是PyArray_FromAny
PyArray_FROM_OTF
可以說是最常用的這些宏中最有用的。它允許您將任意Python對象轉(zhuǎn)換為特定內(nèi)置數(shù)據(jù)類型( 例如 float)的數(shù)組,同時指定一組特定的需求( 例如, 連續(xù),對齊和可寫)。語法是
從任何可以轉(zhuǎn)換為數(shù)組的 Python 對象 obj 返回 ndarray。返回數(shù)組中的維數(shù)由對象確定。
返回數(shù)組的所需數(shù)據(jù)類型在 typenum 中提供,它應(yīng)該是枚舉類型之一。
對返回數(shù)組的*要求(requirements)*可以是標(biāo)準(zhǔn)數(shù)組標(biāo)志的任意組合。下面將更詳細(xì)地解釋這些論點中的每一個。
成功后,您將收到對數(shù)組的新引用。如果失敗,則返回 NULL
并設(shè)置異常。
-
obj
該對象可以是任何可轉(zhuǎn)換為ndarray的Python對象。
如果對象已經(jīng)是滿足要求的ndarray的子類,則返回一個新的引用。否則,構(gòu)造一個新的數(shù)組。
除非使用數(shù)組接口,否則將obj的內(nèi)容復(fù)制到新數(shù)組,以便不必復(fù)制數(shù)據(jù)。
可以轉(zhuǎn)換為數(shù)組的對象包括:
1)任何嵌套的Sequence對象,
2)暴露數(shù)組接口的任何對象,
3)具有數(shù)組方法的任何對象(應(yīng)該返回ndarray),
以及4)任何標(biāo)量對象(變成零維數(shù)組)。
否則符合要求的ndarray的子類將被傳遞。如果要確?;恘darray,
則在Requirements標(biāo)志中使用 NPY_ARRAY_ENSUREARRAY
只有在必要時才會制作副本。
如果要保證復(fù)制,則將 NPY_ARRAY_ENSURECOPY
(如果數(shù)據(jù)類型應(yīng)從對象本身確定)??梢允褂没贑的名稱:
, NPY_BYTE , NPY_UBYTE , NPY_SHORT , NPY_USHORT , NPY_INT , NPY_UINT , NPY_LONG , NPY_ULONG , NPY_LONGLONG , NPY_ULONGLONG , NPY_DOUBLE , NPY_LONGDOUBLE , NPY_CFLOAT , NPY_CDOUBLE , NPY_CLONGDOUBLE , NPY_OBJECT.
或者,可以使用平臺上支持的位寬名稱。例如:
, NPY_INT16 , NPY_INT32 , NPY_INT64 , NPY_UINT8 , NPY_UINT16 , NPY_UINT32 , NPY_UINT64 , NPY_FLOAT32 , NPY_FLOAT64 , NPY_COMPLEX64 , NPY_COMPLEX128.
僅當(dāng)可以在不丟失精度的情況下完成時,對象才會轉(zhuǎn)換為所需的類型。
否則將返回 NULL
并引發(fā)錯誤。
在Requirements標(biāo)志中使用 NPY_ARRAY_FORCECAST
NPY_OUT_ARRAY
、and NPY_ARRAY_INOUT_ARRAY
:
此標(biāo)志對于必須按C連續(xù)順序和對齊的數(shù)組非常有用。這些類型的數(shù)組通常是某些算法的輸入數(shù)組。
此標(biāo)志用于指定C連續(xù)順序的數(shù)組,該數(shù)組是對齊的,并且也可以寫入。這樣的數(shù)組通常作為輸出返回(盡管通常這樣的輸出數(shù)組是從頭開始創(chuàng)建的)。
此標(biāo)志用于指定將用于輸入和輸出的數(shù)組。必須在接口例程末尾的 Py_DECREF 之前調(diào)用 PyArray_ResolveWritebackIfCopy ,
以將臨時數(shù)據(jù)寫回傳入的原始數(shù)組。
使用 NPY_ARRAY_WRITEBACKIFCOPY 或
NPY_ARRAY_UPDATEIFCOPY 標(biāo)志要求輸入對象已經(jīng)是數(shù)組(因為其他對象不能以這種方式自動更新)。
如果發(fā)生錯誤,請在設(shè)置了這些標(biāo)志的數(shù)組上使用 PyArray_DiscardWritebackIfCopy
(obj)。
這將設(shè)置底層基本數(shù)組可寫,而不會導(dǎo)致內(nèi)容復(fù)制回原始數(shù)組。
可以作為附加要求進(jìn)行OR運算的其他有用標(biāo)志包括:
強(qiáng)制轉(zhuǎn)換為所需的類型,即使在不丟失信息的情況下也是如此。
確保生成的數(shù)組是原始數(shù)組的副本。
確保生成的對象是實際的ndarray,而不是子類。
注意
數(shù)組是否進(jìn)行字節(jié)交換取決于數(shù)組的數(shù)據(jù)類型。始終請求本機(jī)字節(jié)順序數(shù)組PyArray_FROM_OTF
NPY_ARRAY_NOTSWAPPED
require參數(shù)中不需要標(biāo)志。也無法從此例程中獲取字節(jié)交換數(shù)組。
創(chuàng)建一個全新的ndarray
通常,必須在擴(kuò)展模塊代碼中創(chuàng)建新數(shù)組。也許需要輸出數(shù)組,
并且您不希望調(diào)用者必須提供它。
也許只需要一個臨時數(shù)組來進(jìn)行中間計算。
無論需要什么,都需要簡單的方法來獲得任何數(shù)據(jù)類型的ndarray對象。
這樣做最常見的功能是PyArray_NewFromDescr
所有數(shù)組創(chuàng)建函數(shù)都經(jīng)過這個重復(fù)使用的代碼。由于其靈活性,使用起來可能有點混亂。
結(jié)果,存在更易于使用的更簡單的形式。這些表單是
PyArray_SimpleNew
函數(shù)族的一部分
,它通過為常見用例提供默認(rèn)值來簡化界面。
獲取ndarray內(nèi)存并訪問ndarray的元素
如果obj是一個 ndarray(),那么ndarray 的數(shù)據(jù)區(qū)域由void 指針(obj)或char 指針(obj)指向。請記?。ㄍǔ#┐藬?shù)據(jù)區(qū)域可能未根據(jù)數(shù)據(jù)類型對齊,它可能表示字節(jié)交換數(shù)據(jù),和/或可能無法寫入。如果數(shù)據(jù)區(qū)域是對齊的并且是以本機(jī)字節(jié)順序排列的,那么如何獲取數(shù)組的特定元素只能由npy_intp變量數(shù)組(obj)確定。特別是,這個整數(shù)的c數(shù)組顯示了必須向當(dāng)前元素指針添加多少字節(jié)才能到達(dá)每個維度中的下一個元素。對于小于4維的數(shù)組,有PyArrayObject *
PyArray_DATA
PyArray_BYTES
PyArray_STRIDES
**PyArray_GETPTR{k}
(obj,...)宏,其中{k}是整數(shù)1,2,3或4,這使得使用數(shù)組步幅更容易。爭論...... 將{k}非負(fù)整數(shù)索引表示到數(shù)組中。例如,假設(shè)
E
是一個三維的ndarray。E[i,j,k]
獲得元素的(void *)指針作為
PyArray_GETPTR3
(E,i,j,k)。
如前所述,C風(fēng)格的連續(xù)數(shù)組和Fortran風(fēng)格的連續(xù)數(shù)組具有特定的跨步模式。兩個數(shù)組標(biāo)志(NPY_ARRAY_C_CONTIGUOUS
NPY_ARRAY_F_CONTIGUOUS
)表示特定數(shù)組的跨步模式是否與C風(fēng)格的連續(xù)或Fortran風(fēng)格的連續(xù)匹配或兩者都不匹配??梢允褂?a rel="noopener noreferrer" target="_blank" data-original-title="" title="">PyArray_IS_C_CONTIGUOUS
(obj)和()來測試跨步模式是否匹配標(biāo)準(zhǔn)C或Fortran
PyArray_ISFORTRAN
(obj)分別。大多數(shù)第三方庫都期望連續(xù)的數(shù)組。但是,通常支持通用跨越并不困難。我鼓勵您盡可能在自己的代碼中使用跨步信息,并保留包裹第三方代碼的單段要求。使用與ndarray一起提供的跨步信息而不是需要連續(xù)的跨步減少了必須進(jìn)行的復(fù)制。
示例
以下示例顯示了如何編寫一個包含兩個輸入?yún)?shù)(將轉(zhuǎn)換為數(shù)組)和輸出參數(shù)(必須是數(shù)組)的包裝器。該函數(shù)返回None并更新輸出數(shù)組。請注意NumPy v1.14及更高版本的WRITEBACKIFCOPY語義的更新使用
static PyObject *
example_wrapper(PyObject *dummy, PyObject *args)
{
PyObject *arg1=NULL, *arg2=NULL, *out=NULL;
PyObject *arr1=NULL, *arr2=NULL, *oarr=NULL;
if (!PyArg_ParseTuple(args, "OOO!", &arg1, &arg2,
&PyArray_Type, &out)) return NULL;
arr1 = PyArray_FROM_OTF(arg1, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY);
if (arr1 == NULL) return NULL;
arr2 = PyArray_FROM_OTF(arg2, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY);
if (arr2 == NULL) goto fail;
#if NPY_API_VERSION >= 0x0000000c
oarr = PyArray_FROM_OTF(out, NPY_DOUBLE, NPY_ARRAY_INOUT_ARRAY2);
#else
oarr = PyArray_FROM_OTF(out, NPY_DOUBLE, NPY_ARRAY_INOUT_ARRAY);
#endif
if (oarr == NULL) goto fail;
/* code that makes use of arguments */
/* You will probably need at least
nd = PyArray_NDIM(<..>) -- number of dimensions
dims = PyArray_DIMS(<..>) -- npy_intp array of length nd
showing length in each dim.
dptr = (double *)PyArray_DATA(<..>) -- pointer to data.
If an error occurs goto fail.
*/
Py_DECREF(arr1);
Py_DECREF(arr2);
#if NPY_API_VERSION >= 0x0000000c
PyArray_ResolveWritebackIfCopy(oarr);
#endif
Py_DECREF(oarr);
Py_INCREF(Py_None);
return Py_None;
fail:
Py_XDECREF(arr1);
Py_XDECREF(arr2);
#if NPY_API_VERSION >= 0x0000000c
PyArray_DiscardWritebackIfCopy(oarr);
#endif
Py_XDECREF(oarr);
return NULL;
}
作者:柯廣的網(wǎng)絡(luò)日志 ? 如何擴(kuò)展NumPy
微信公眾號:Java大數(shù)據(jù)與數(shù)據(jù)倉庫