深入的知識(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)。

PyArray_IterAllButAxis

array&dim)構(gòu)造被修改,使得它不會(huì)在由暗淡指示的尺寸迭代的迭代器對(duì)象。這個(gè)迭代器對(duì)象的唯一限制是不能使用PyArray_Iter_GOTO1Ditind)宏(因此,如果將此對(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

結(jié)構(gòu),其typeobject成員將包含指向您剛剛定義的數(shù)據(jù)類型的指針。此外,必須定義“.f”成員中的必需函數(shù):nonzero,copyswap,copyswapn,setitem,getitem和cast。但是,您定義的“.f”成員中的函數(shù)越多,新數(shù)據(jù)類型就越有用。將未使用的函數(shù)初始化為NULL非常重要。這可以使用PyArray_InitArrFuncs

(f)來實(shí)現(xiàn)。

一旦PyArray_Descr

創(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_NOSCALARscalarkind參數(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)存沖突和程序崩潰)。

PyObject_HEAD

中的元素之一是指向 type-object 結(jié)構(gòu)的指針。
通過創(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)按照下列步驟操作:

  1. 如果需要,創(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相同。

  2. 使用指向新函數(shù)的指針填充新的Python類型對(duì)象結(jié)構(gòu),這些新函數(shù)將覆蓋默認(rèn)行為,同時(shí)保留任何應(yīng)該保持相同的未填充(或空)的函數(shù)。tp_name元素應(yīng)該不同。

  3. 用指向(Main)父類型對(duì)象的指針填充新類型對(duì)象結(jié)構(gòu)的tp_base成員。對(duì)于多重繼承,還要用一個(gè)元組填充tp_base成員,該元組包含所有父對(duì)象(按照它們用于定義繼承的順序)。請(qǐng)記住,所有父類型必須具有相同的C結(jié)構(gòu),才能使多重繼承正常工作。

  4. 調(diào)用PyType_Ready

  1. (<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)。

如果__array_finalize__

屬性是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。

如果__array_finalize__

  • 屬性不是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ù)倉庫