深入的知識(shí)
迭代數(shù)組中的元素
基本迭代
一種常見的算法要求是能夠遍歷多維數(shù)組中的所有元素。數(shù)組迭代器對(duì)象使這種方法易于以通用方式完成,適用于任何維度的數(shù)組。當(dāng)然,如果您知道要使用的維數(shù),那么您始終可以編寫嵌套for循環(huán)來完成迭代。但是,如果要編寫適用于任意數(shù)量維度的代碼,則可以使用數(shù)組迭代器。訪問數(shù)組的.flat屬性時(shí)返回?cái)?shù)組迭代器對(duì)象。
基本用法是調(diào)用PyArray_IterNew
(array
),其中array是ndarray對(duì)象(或其子類之一)。返回的對(duì)象是一個(gè)array-iterator對(duì)象(由ndarray的.flat屬性返回的同一對(duì)象)。此對(duì)象通常強(qiáng)制轉(zhuǎn)換為PyArrayIterObject *,以便可以訪問其成員。所需的唯一成員iter->size
包含數(shù)組的總大小iter->index
,其中包含數(shù)組的當(dāng)前1-d索引,以及iter->dataptr
指向數(shù)組當(dāng)前元素的數(shù)據(jù)的指針。有時(shí),訪問iter->ao
哪個(gè)是指向底層ndarray對(duì)象的指針也很有用。
在數(shù)組的當(dāng)前元素處理數(shù)據(jù)之后,可以使用macro PyArray_ITER_NEXT
iter
)獲取數(shù)組的下一個(gè)元素。迭代總是以C風(fēng)格的連續(xù)方式進(jìn)行(最后一個(gè)索引變化最快)。的
PyArray_ITER_GOTO
(iter
,destination
)可以用來跳到一個(gè)特定點(diǎn)的數(shù)組,其中在destination
是npy_intp數(shù)據(jù)類型與空間的數(shù)組,以處理潛在的數(shù)組中的維度中的至少數(shù)。有時(shí)使用PyArray_ITER_GOTO1D
(iter
,index
)將跳轉(zhuǎn)到由值給出的1-d索引是有用的index
。但是,最常見的用法在以下示例中給出。
PyObject *obj; /* assumed to be some ndarray object */
PyArrayIterObject *iter;
...
iter = (PyArrayIterObject *)PyArray_IterNew(obj);
if (iter == NULL) goto fail; /* Assume fail has clean-up code */
while (iter->index < iter->size) {
/* do something with the data at it->dataptr */
PyArray_ITER_NEXT(it);
}
...
您還可以使用PyArrayIter_Check
obj
)來確保您擁有迭代器對(duì)象和PyArray_ITER_RESET
(iter
)以將迭代器對(duì)象重置回?cái)?shù)組的開頭。
在這一點(diǎn)上應(yīng)該強(qiáng)調(diào)的是,如果你的數(shù)組已經(jīng)是連續(xù)的,你可能不需要數(shù)組迭代器(使用數(shù)組迭代器可以工作,但會(huì)比你寫的最快的代碼慢)。數(shù)組迭代器的主要目的是使用任意步長將迭代封裝在N維數(shù)組上。它們在NumPy源代碼本身的許多地方使用。如果您已經(jīng)知道您的數(shù)組是連續(xù)的(Fortran或C),那么只需將元素大小添加到正在運(yùn)行的指針變量就可以非常有效地引導(dǎo)您完成數(shù)組。換句話說,在連續(xù)的情況下(假設(shè)為雙精度),這樣的代碼可能會(huì)更快。
npy_intp size;
double *dptr; /* could make this any variable type */
size = PyArray_SIZE(obj);
dptr = PyArray_DATA(obj);
while(size--) {
/* do something with the data at dptr */
dptr++;
}
迭代除一個(gè)軸之外的所有軸
一種常見的算法是循環(huán)遍歷數(shù)組的所有元素,并通過發(fā)出函數(shù)調(diào)用對(duì)每個(gè)元素執(zhí)行一些函數(shù)。由于函數(shù)調(diào)用可能非常耗時(shí),因此加速此類算法的一種方法是編寫函數(shù),使其獲取數(shù)據(jù)向量,然后編寫迭代,以便一次對(duì)整個(gè)數(shù)據(jù)維度執(zhí)行函數(shù)調(diào)用。這增加了每個(gè)函數(shù)調(diào)用完成的工作量,從而將函數(shù)調(diào)用開頭減少到總時(shí)間的一小部分。即使在沒有函數(shù)調(diào)用的情況下執(zhí)行循環(huán)的內(nèi)部,在具有最大數(shù)量元素的維度上執(zhí)行內(nèi)循環(huán)也是有利的,以利用在使用流水線操作來增強(qiáng)基礎(chǔ)操作的微處理器上可用的速度增強(qiáng)。
(array
,&dim
)構(gòu)造被修改,使得它不會(huì)在由暗淡指示的尺寸迭代的迭代器對(duì)象。這個(gè)迭代器對(duì)象的唯一限制是不能使用PyArray_Iter_GOTO1D
(it
,ind
)宏(因此,如果將此對(duì)象傳遞回Python,則平面索引將不起作用
- 所以你不應(yīng)該這樣做)。請(qǐng)注意,此例程中返回的對(duì)象仍然通常轉(zhuǎn)換為PyArrayIterObject
*。所做的就是修改返回迭代器的步幅和尺寸,以模擬迭代數(shù)組[...,0,...],其中0放在
維度上。如果dim為負(fù),則找到并使用具有最大軸的尺寸。
迭代多個(gè)數(shù)組
通常,希望同時(shí)迭代幾個(gè)數(shù)組。通用函數(shù)就是這種行為的一個(gè)例子。如果您只想迭代具有相同形狀的數(shù)組,那么只需創(chuàng)建幾個(gè)迭代器對(duì)象就是標(biāo)準(zhǔn)過程。例如,以下代碼迭代兩個(gè)假定具有相同形狀和大小的數(shù)組(實(shí)際上obj1必須至少具有與obj2一樣多的總元素):
/* It is already assumed that obj1 and obj2
are ndarrays of the same shape and size.
*/
iter1 = (PyArrayIterObject *)PyArray_IterNew(obj1);
if (iter1 == NULL) goto fail;
iter2 = (PyArrayIterObject *)PyArray_IterNew(obj2);
if (iter2 == NULL) goto fail; /* assume iter1 is DECREF'd at fail */
while (iter2->index < iter2->size) {
/* process with iter1->dataptr and iter2->dataptr */
PyArray_ITER_NEXT(iter1);
PyArray_ITER_NEXT(iter2);
}
在多個(gè)數(shù)組上廣播
當(dāng)一個(gè)操作涉及多個(gè)數(shù)組時(shí),您可能希望使用數(shù)學(xué)操作(即ufuncs)使用的相同廣播規(guī)則。
這可以使用 PyArrayMultiIterObject
這是從Python命令numpy.Broadcast返回的對(duì)象,它幾乎和C一樣容易使用。
函數(shù) PyArray_MultiIterNew (
n
, ...
)。使用(n
個(gè)輸入對(duì)象代替)。輸入對(duì)象可以是數(shù)組或任何可以轉(zhuǎn)換為數(shù)組的對(duì)象。
返回指向
PyArrayMultiIterObject
的指針。廣播已經(jīng)完成,它調(diào)整迭代器,以便為每個(gè)輸入調(diào)用PyArray_ITER_NEXT,
以便前進(jìn)到每個(gè)數(shù)組中的下一個(gè)元素。
這種遞增由 PyArray_MultiIter_NEXT (
Obj
)宏自動(dòng)執(zhí)行(它可以將乘法器 obj
處理為 PyArrayMultiObject * 或 PyObject * )。輸入編號(hào)
i
中的數(shù)據(jù)可使用PyArray_MultiIter_DATA (obj
, i
)和總(廣播)大小作為PyArray_MultiIter_SIZE(Obj
)。
下面是使用此功能的示例。
mobj = PyArray_MultiIterNew(2, obj1, obj2);
size = PyArray_MultiIter_SIZE(obj);
while(size--) {
ptr1 = PyArray_MultiIter_DATA(mobj, 0);
ptr2 = PyArray_MultiIter_DATA(mobj, 1);
/* code using contents of ptr1 and ptr2 */
PyArray_MultiIter_NEXT(mobj);
}
function PyArray_RemoveSmallest
(multi
) 可用于獲取多迭代器對(duì)象并調(diào)整所有迭代器,以便迭代不會(huì)發(fā)生在最大維度上(它使得該維度的大小為1)。
循環(huán)使用指針的代碼很可能也需要每個(gè)迭代器的步幅數(shù)據(jù)。此信息存儲(chǔ)在 multi->iters[i]->strides 中。
在NumPy源代碼中使用多迭代器有幾個(gè)例子,因?yàn)樗筃維廣播代碼編寫起來非常簡單。瀏覽源代碼以獲取更多示例。
用戶定義的數(shù)據(jù)類型
NumPy帶有24種內(nèi)置數(shù)據(jù)類型。雖然這涵蓋了絕大多數(shù)可能的用例,但可以想象用戶可能需要額外的數(shù)據(jù)類型。有一些支持在NumPy系統(tǒng)中添加額外的數(shù)據(jù)類型。此附加數(shù)據(jù)類型的行為與常規(guī)數(shù)據(jù)類型非常相似,只是ufunc必須具有1-d循環(huán)才能單獨(dú)處理它。同時(shí)檢查其他數(shù)據(jù)類型是否可以“安全”地轉(zhuǎn)換到這種新類型或從這種新類型轉(zhuǎn)換為“can cast”,除非您還注冊了新數(shù)據(jù)類型可以轉(zhuǎn)換為哪種類型。添加數(shù)據(jù)類型是NumPy 1.0中經(jīng)過較少測試的領(lǐng)域之一,因此該方法可能存在漏洞。如果使用已有的OBJECT或VOID數(shù)據(jù)類型無法執(zhí)行您想要執(zhí)行的操作,則僅添加新數(shù)據(jù)類型。
添加新數(shù)據(jù)類型
要開始使用新的數(shù)據(jù)類型,您需要首先定義一個(gè)新的Python類型來保存新數(shù)據(jù)類型的標(biāo)量。如果您的新類型具有二進(jìn)制兼容布局,則可以接受從其中一個(gè)數(shù)組標(biāo)量繼承。這將允許您的新數(shù)據(jù)類型具有數(shù)組標(biāo)量的方法和屬性。新數(shù)據(jù)類型必須具有固定的內(nèi)存大?。ㄈ绻x需要靈活表示的數(shù)據(jù)類型,如變量精度數(shù),則使用指向?qū)ο蟮闹羔樧鳛閿?shù)據(jù)類型)。新Python類型的對(duì)象結(jié)構(gòu)的內(nèi)存布局必須是PyObject_HEAD,后跟數(shù)據(jù)類型所需的固定大小的內(nèi)存。例如,新Python類型的合適結(jié)構(gòu)是:
typedef struct {
PyObject_HEAD;
some_data_type obval;
/* the name can be whatever you want */
} PySomeDataTypeObject;
在定義了新的Python類型對(duì)象之后,必須定義一個(gè)新PyArray_Descr
PyArray_InitArrFuncs
(f)來實(shí)現(xiàn)。
創(chuàng)建了新結(jié)構(gòu)并填充了您調(diào)用的所需信息和有用函數(shù)PyArray_RegisterDataType
(new_descr)。此調(diào)用的返回值是一個(gè)整數(shù),為您提供指定數(shù)據(jù)類型的唯一type_number。此類型編號(hào)應(yīng)存儲(chǔ)并由您的模塊提供,以便其他模塊可以使用它來識(shí)別您的數(shù)據(jù)類型(查找用戶定義的數(shù)據(jù)類型編號(hào)的另一種機(jī)制是根據(jù)類型的名稱進(jìn)行搜索
- 與數(shù)據(jù)類型相關(guān)聯(lián)的對(duì)象PyArray_TypeNumFromName
)。
注冊投射功能
您可能希望允許內(nèi)置(和其他用戶定義的)數(shù)據(jù)類型自動(dòng)轉(zhuǎn)換為您的數(shù)據(jù)類型。為了實(shí)現(xiàn)這一點(diǎn),您必須使用您希望能夠從中投射的數(shù)據(jù)類型注冊一個(gè)轉(zhuǎn)換函數(shù)。這需要為要支持的每個(gè)轉(zhuǎn)換編寫低級(jí)轉(zhuǎn)換函數(shù),然后使用數(shù)據(jù)類型描述符注冊這些函數(shù)。低級(jí)轉(zhuǎn)換函數(shù)具有簽名。
void castfunc
( void * from ,void * to ,npy_intp
n ,void * fromarr ,void * toarr )?
鑄n
元件from
一個(gè)鍵入to
另一個(gè)。要轉(zhuǎn)換的數(shù)據(jù)位于由from指向的連續(xù),正確交換和對(duì)齊的內(nèi)存塊中。要轉(zhuǎn)換為的緩沖區(qū)也是連續(xù)的,正確交換和對(duì)齊的。fromarr和toarr參數(shù)只應(yīng)用于靈活元素大小的數(shù)組(字符串,unicode,void)。
一個(gè)示例castfunc是:
static void
double_to_float(double *from, float* to, npy_intp n,
void* ig1, void* ig2);
while (n--) {
(*to++) = (double) *(from++);
}
然后可以使用以下代碼注冊以將雙精度轉(zhuǎn)換為浮點(diǎn)數(shù):
doub = PyArray_DescrFromType(NPY_DOUBLE);
PyArray_RegisterCastFunc(doub, NPY_FLOAT,
(PyArray_VectorUnaryFunc *)double_to_float);
Py_DECREF(doub);
注冊強(qiáng)制規(guī)則
默認(rèn)情況下,不會(huì)假定所有用戶定義的數(shù)據(jù)類型都可安全地轉(zhuǎn)換為任何內(nèi)置數(shù)據(jù)類型。此外,不假定內(nèi)置數(shù)據(jù)類型可安全地轉(zhuǎn)換為用戶定義的數(shù)據(jù)類型。這種情況限制了用戶定義的數(shù)據(jù)類型參與ufuncs使用的強(qiáng)制系統(tǒng)的能力,以及在NumPy中進(jìn)行自動(dòng)強(qiáng)制時(shí)的其他情況。這可以通過將數(shù)據(jù)類型注冊為從特定數(shù)據(jù)類型對(duì)象安全地轉(zhuǎn)換來更改。函數(shù)PyArray_RegisterCanCast
(from_descr,totype_number,scalarkind)應(yīng)該用于指定數(shù)據(jù)類型對(duì)象from_descr可以轉(zhuǎn)換為類型號(hào)為totype_number的數(shù)據(jù)類型。如果您不想改變標(biāo)量強(qiáng)制規(guī)則,那么請(qǐng)使用NPY_NOSCALAR
scalarkind參數(shù)。
如果要允許新數(shù)據(jù)類型也能夠共享標(biāo)量強(qiáng)制規(guī)則,則需要在數(shù)據(jù)類型對(duì)象的“.f”成員中指定scalarkind函數(shù),以返回新數(shù)據(jù)的標(biāo)量類型-type應(yīng)該被視為(標(biāo)量的值可用于該函數(shù))。然后,您可以為可以從用戶定義的數(shù)據(jù)類型返回的每個(gè)標(biāo)量類型注冊可以單獨(dú)轉(zhuǎn)換的數(shù)據(jù)類型。如果您沒有注冊標(biāo)量強(qiáng)制處理,那么所有用戶定義的數(shù)據(jù)類型都將被視為NPY_NOSCALAR
。
注冊u(píng)func循環(huán)
您可能還希望為數(shù)據(jù)類型注冊低級(jí)ufunc循環(huán),以便數(shù)據(jù)類型的ndarray可以無縫地應(yīng)用數(shù)學(xué)。注冊具有完全相同的arg_types簽名的新循環(huán),靜默替換該數(shù)據(jù)類型的任何先前注冊的循環(huán)。
在為ufunc注冊一維循環(huán)之前,必須預(yù)先創(chuàng)建ufunc。
然后調(diào)用 PyUFunc_RegisterLoopForType
(…)。
以及循環(huán)所需的信息。
如果進(jìn)程成功,則此函數(shù)的返回值為0;
如果進(jìn)程不成功,則返回 -1
,并設(shè)置錯(cuò)誤條件。
在C中對(duì)ndarray進(jìn)行子類型化
自2.2以來一直潛伏在Python中的一個(gè)較少使用的功能是在C中子類類型的能力。這個(gè)設(shè)施是使NumPy脫離已經(jīng)在C中的數(shù)字代碼庫的重要原因之一。 C中的子類型允許在內(nèi)存管理方面具有更大的靈活性。即使您對(duì)如何為Python創(chuàng)建新類型有基本的了解,在C中進(jìn)行子類型輸入并不困難。雖然最簡單的是從單個(gè)父類型進(jìn)行子類型化,但也可以從多個(gè)父類型進(jìn)行子類型化。C中的多重繼承通常沒有Python中那么有用,因?yàn)閷?duì)Python子類型的限制是它們具有二進(jìn)制兼容的內(nèi)存布局。也許由于這個(gè)原因,從單個(gè)父類型子類型更容易一些。
與Python對(duì)象相對(duì)應(yīng)的所有C結(jié)構(gòu)必須以PyObject_HEAD
PyObject_VAR_HEAD
)開頭
。同樣,任何子類型都必須具有C結(jié)構(gòu),該結(jié)構(gòu)以與父類型完全相同的內(nèi)存布局(或多重繼承的情況下的所有父類型)開始。這樣做的原因是Python可能會(huì)嘗試訪問子類型結(jié)構(gòu)的成員,就像它具有父結(jié)構(gòu)一樣( 即 它會(huì)將指定的指針強(qiáng)制轉(zhuǎn)換為指向父結(jié)構(gòu)的指針,然后取消引用其中一個(gè)成員)。如果內(nèi)存布局不兼容,則此嘗試將導(dǎo)致不可預(yù)測的行為(最終導(dǎo)致內(nèi)存沖突和程序崩潰)。
通過創(chuàng)建一個(gè)新的類型-對(duì)象結(jié)構(gòu)并用函數(shù)和指針填充它來創(chuàng)建一個(gè)新的Python類型,
以描述該類型的所需行為。
通常,還會(huì)創(chuàng)建一個(gè)新的C結(jié)構(gòu)來包含該類型的每個(gè)對(duì)象所需的特定于實(shí)例的信息。
例如,&PyArray_Type 是指向ndarray的類型-對(duì)象表的指針,
而PyArrayObject * 變量是指向ndarray的特定實(shí)例的指針
(ndarray結(jié)構(gòu)的成員之一反過來是指向類型-對(duì)象表 &PyArray_Type 的指針)。
最后,必須為每個(gè)新的Python類型調(diào)用 PyType_Ready
(<POINTER_TO_TYPE_OBJECT>)。
創(chuàng)建子類型
要?jiǎng)?chuàng)建子類型,必須遵循類似的過程,除了只有不同的行為需要在類型 - 對(duì)象結(jié)構(gòu)中使用新條目。所有其他條目都可以為NULL,并將使用PyType_Ready
父類型中的相應(yīng)函數(shù)填充。特別是,要在C中創(chuàng)建子類型,請(qǐng)按照下列步驟操作:
-
如果需要,創(chuàng)建一個(gè)新的C結(jié)構(gòu)來處理類型的每個(gè)實(shí)例。典型的 C 的結(jié)構(gòu)是:
typedef _new_struct { PyArrayObject base; /* new things here */ } NewArrayObject;
請(qǐng)注意,完整的PyArrayObject用作第一個(gè)條目,以確保新類型的實(shí)例的二進(jìn)制布局與PyArrayObject相同。
-
使用指向新函數(shù)的指針填充新的Python類型對(duì)象結(jié)構(gòu),這些新函數(shù)將覆蓋默認(rèn)行為,同時(shí)保留任何應(yīng)該保持相同的未填充(或空)的函數(shù)。tp_name元素應(yīng)該不同。
-
用指向(Main)父類型對(duì)象的指針填充新類型對(duì)象結(jié)構(gòu)的tp_base成員。對(duì)于多重繼承,還要用一個(gè)元組填充tp_base成員,該元組包含所有父對(duì)象(按照它們用于定義繼承的順序)。請(qǐng)記住,所有父類型必須具有相同的C結(jié)構(gòu),才能使多重繼承正常工作。
-
調(diào)用PyType_Ready
(<pointer_to_new_type>)。如果此函數(shù)返回負(fù)數(shù),則表示發(fā)生故障,并且類型未初始化。否則,該類型就可以使用了。通常,將對(duì)新類型的引用放入模塊字典中,以便可以從Python訪問它,這一點(diǎn)通常很重要。
有關(guān)在 C 中創(chuàng)建子類型的更多信息,請(qǐng)參閱PEP 253(可從https://www.python.org/dev/peps/pep-0253
獲?。?/p>
ndarray子類型的特定功能
數(shù)組使用一些特殊的方法和屬性,以便于子類型與基本ndarray類型的互操作。
__array_finalize__方法
-
ndarray.
__array_finalize__
ndarray的幾個(gè)數(shù)組創(chuàng)建函數(shù)允許創(chuàng)建特定子類型的規(guī)范。這允許在許多例程中無縫地處理子類型。
但是,當(dāng)以這種方式創(chuàng)建子類型時(shí),__new__方法和__init__方法都不會(huì)被調(diào)用。
而是分配子類型并填充適當(dāng)?shù)膶?shí)例結(jié)構(gòu)成員。
最后,__array_finalize__
在對(duì)象字典中查找屬性。如果它存在而不是None,那么它可以是包含指向a的指針的CObject,PyArray_FinalizeFunc
也可以是采用單個(gè)參數(shù)的方法(可以是None)。
屬性是CObject,
則指針必須是指向具有簽名的函數(shù)的指針:
(int) (PyArrayObject *, PyObject *)
第一個(gè)參數(shù)是新創(chuàng)建的子類型。
第二個(gè)參數(shù)(如果不是NULL)是“父”數(shù)組(如果數(shù)組是使用切片或其他操作創(chuàng)建的,其中存在明顯可區(qū)分的父項(xiàng))。
這個(gè)例程可以做任何想做的事情。它應(yīng)該在錯(cuò)誤時(shí)返回-1,否則返回0。
屬性不是None也不是CObject,
那么它必須是一個(gè)Python方法,它將父數(shù)組作為參數(shù)(如果沒有父元素,則可以是None),
并且不返回任何內(nèi)容。將捕獲并處理此方法中的錯(cuò)誤。
__array_priority__屬性
-
ndarray.
__array_priority__
當(dāng)涉及兩個(gè)或更多個(gè)子類型的操作出現(xiàn)時(shí),該屬性允許簡單但靈活地確定哪個(gè)子類型應(yīng)被視為“主要”。在使用不同子類型的操作中,具有最大
__array_priority__
屬性的子類型將確定輸出的子類型。如果兩個(gè)子類型相同,
__array_priority__
則第一個(gè)參數(shù)的子類型確定輸出。__array_priority__
對(duì)于基本ndarray類型,default屬性返回值0.0,對(duì)于子類型,返回1.0。此屬性也可以由不是ndarray的子??類型的對(duì)象定義,并且可以用于確定
__array_wrap__
應(yīng)該為返回輸出調(diào)用哪個(gè)方法。
__array_wrap__方法
-
ndarray.
__array_wrap__
任何類或類型都可以定義此方法,該方法應(yīng)采用ndarray參數(shù)并返回該類型的實(shí)例。
它可以看作是該__array__
ufuncs(和其他NumPy函數(shù))使用此方法允許其他對(duì)象通過。
對(duì)于Python > 2.4,它也可以用來編寫一個(gè)裝飾器,
它將一個(gè)僅適用于ndarrays的函數(shù)轉(zhuǎn)換為一個(gè)可以使用
__array__
和__array_wrap__
方法處理任何類型的函數(shù)。
作者:柯廣的網(wǎng)絡(luò)日志 ? 深入的知識(shí)
微信公眾號(hào):Java大數(shù)據(jù)與數(shù)據(jù)倉庫