陣列迭代器 API#

陣列迭代器#

陣列迭代器封裝了 ufunc 中的許多關鍵功能,讓使用者程式碼能夠支援諸如輸出參數、記憶體佈局的保留以及對齊方式或型別錯誤的資料進行緩衝等功能,而無需進行困難的編碼。

此頁面記錄了迭代器的 API。迭代器名為 NpyIter,函數名為 NpyIter_*

有一個陣列迭代的入門指南,對於使用此 C API 的人來說可能會感興趣。在許多情況下,在編寫 C 迭代程式碼之前,先在 Python 中建立迭代器來測試想法是個好主意。

迭代範例#

熟悉迭代器的最佳方法是查看其在 NumPy 程式碼庫本身中的用法。例如,這裡是一個稍微調整過後的 PyArray_CountNonzero 程式碼版本,它計算陣列中非零元素的數量。

npy_intp PyArray_CountNonzero(PyArrayObject* self)
{
    /* Nonzero boolean function */
    PyArray_NonzeroFunc* nonzero = PyArray_DESCR(self)->f->nonzero;

    NpyIter* iter;
    NpyIter_IterNextFunc *iternext;
    char** dataptr;
    npy_intp nonzero_count;
    npy_intp* strideptr,* innersizeptr;

    /* Handle zero-sized arrays specially */
    if (PyArray_SIZE(self) == 0) {
        return 0;
    }

    /*
     * Create and use an iterator to count the nonzeros.
     *   flag NPY_ITER_READONLY
     *     - The array is never written to.
     *   flag NPY_ITER_EXTERNAL_LOOP
     *     - Inner loop is done outside the iterator for efficiency.
     *   flag NPY_ITER_NPY_ITER_REFS_OK
     *     - Reference types are acceptable.
     *   order NPY_KEEPORDER
     *     - Visit elements in memory order, regardless of strides.
     *       This is good for performance when the specific order
     *       elements are visited is unimportant.
     *   casting NPY_NO_CASTING
     *     - No casting is required for this operation.
     */
    iter = NpyIter_New(self, NPY_ITER_READONLY|
                             NPY_ITER_EXTERNAL_LOOP|
                             NPY_ITER_REFS_OK,
                        NPY_KEEPORDER, NPY_NO_CASTING,
                        NULL);
    if (iter == NULL) {
        return -1;
    }

    /*
     * The iternext function gets stored in a local variable
     * so it can be called repeatedly in an efficient manner.
     */
    iternext = NpyIter_GetIterNext(iter, NULL);
    if (iternext == NULL) {
        NpyIter_Deallocate(iter);
        return -1;
    }
    /* The location of the data pointer which the iterator may update */
    dataptr = NpyIter_GetDataPtrArray(iter);
    /* The location of the stride which the iterator may update */
    strideptr = NpyIter_GetInnerStrideArray(iter);
    /* The location of the inner loop size which the iterator may update */
    innersizeptr = NpyIter_GetInnerLoopSizePtr(iter);

    nonzero_count = 0;
    do {
        /* Get the inner loop data/stride/count values */
        char* data = *dataptr;
        npy_intp stride = *strideptr;
        npy_intp count = *innersizeptr;

        /* This is a typical inner loop for NPY_ITER_EXTERNAL_LOOP */
        while (count--) {
            if (nonzero(data, self)) {
                ++nonzero_count;
            }
            data += stride;
        }

        /* Increment the iterator to the next inner loop */
    } while(iternext(iter));

    NpyIter_Deallocate(iter);

    return nonzero_count;
}

多次迭代範例#

這是一個使用迭代器的複製函數。order 參數用於控制已分配結果的記憶體佈局,通常需要 NPY_KEEPORDER

PyObject *CopyArray(PyObject *arr, NPY_ORDER order)
{
    NpyIter *iter;
    NpyIter_IterNextFunc *iternext;
    PyObject *op[2], *ret;
    npy_uint32 flags;
    npy_uint32 op_flags[2];
    npy_intp itemsize, *innersizeptr, innerstride;
    char **dataptrarray;

    /*
     * No inner iteration - inner loop is handled by CopyArray code
     */
    flags = NPY_ITER_EXTERNAL_LOOP;
    /*
     * Tell the constructor to automatically allocate the output.
     * The data type of the output will match that of the input.
     */
    op[0] = arr;
    op[1] = NULL;
    op_flags[0] = NPY_ITER_READONLY;
    op_flags[1] = NPY_ITER_WRITEONLY | NPY_ITER_ALLOCATE;

    /* Construct the iterator */
    iter = NpyIter_MultiNew(2, op, flags, order, NPY_NO_CASTING,
                            op_flags, NULL);
    if (iter == NULL) {
        return NULL;
    }

    /*
     * Make a copy of the iternext function pointer and
     * a few other variables the inner loop needs.
     */
    iternext = NpyIter_GetIterNext(iter, NULL);
    innerstride = NpyIter_GetInnerStrideArray(iter)[0];
    itemsize = NpyIter_GetDescrArray(iter)[0]->elsize;
    /*
     * The inner loop size and data pointers may change during the
     * loop, so just cache the addresses.
     */
    innersizeptr = NpyIter_GetInnerLoopSizePtr(iter);
    dataptrarray = NpyIter_GetDataPtrArray(iter);

    /*
     * Note that because the iterator allocated the output,
     * it matches the iteration order and is packed tightly,
     * so we don't need to check it like the input.
     */
    if (innerstride == itemsize) {
        do {
            memcpy(dataptrarray[1], dataptrarray[0],
                                    itemsize * (*innersizeptr));
        } while (iternext(iter));
    } else {
        /* For efficiency, should specialize this based on item size... */
        npy_intp i;
        do {
            npy_intp size = *innersizeptr;
            char *src = dataptrarray[0], *dst = dataptrarray[1];
            for(i = 0; i < size; i++, src += innerstride, dst += itemsize) {
                memcpy(dst, src, itemsize);
            }
        } while (iternext(iter));
    }

    /* Get the result from the iterator object array */
    ret = NpyIter_GetOperandArray(iter)[1];
    Py_INCREF(ret);

    if (NpyIter_Deallocate(iter) != NPY_SUCCEED) {
        Py_DECREF(ret);
        return NULL;
    }

    return ret;
}

多索引追蹤範例#

此範例向您展示如何使用 NPY_ITER_MULTI_INDEX 旗標。為了簡化起見,我們假設參數是一個二維陣列。

int PrintMultiIndex(PyArrayObject *arr) {
    NpyIter *iter;
    NpyIter_IterNextFunc *iternext;
    npy_intp multi_index[2];

    iter = NpyIter_New(
        arr, NPY_ITER_READONLY | NPY_ITER_MULTI_INDEX | NPY_ITER_REFS_OK,
        NPY_KEEPORDER, NPY_NO_CASTING, NULL);
    if (iter == NULL) {
        return -1;
    }
    if (NpyIter_GetNDim(iter) != 2) {
        NpyIter_Deallocate(iter);
        PyErr_SetString(PyExc_ValueError, "Array must be 2-D");
        return -1;
    }
    if (NpyIter_GetIterSize(iter) != 0) {
        iternext = NpyIter_GetIterNext(iter, NULL);
        if (iternext == NULL) {
            NpyIter_Deallocate(iter);
            return -1;
        }
        NpyIter_GetMultiIndexFunc *get_multi_index =
            NpyIter_GetGetMultiIndex(iter, NULL);
        if (get_multi_index == NULL) {
            NpyIter_Deallocate(iter);
            return -1;
        }

        do {
            get_multi_index(iter, multi_index);
            printf("multi_index is [%" NPY_INTP_FMT ", %" NPY_INTP_FMT "]\n",
                   multi_index[0], multi_index[1]);
        } while (iternext(iter));
    }
    if (!NpyIter_Deallocate(iter)) {
        return -1;
    }
    return 0;
}

當以 2x3 陣列呼叫時,上述範例會印出

multi_index is [0, 0]
multi_index is [0, 1]
multi_index is [0, 2]
multi_index is [1, 0]
multi_index is [1, 1]
multi_index is [1, 2]

迭代器資料型別#

迭代器佈局是一個內部細節,使用者程式碼只能看到不完整的結構。

type NpyIter#

這是迭代器的不透明指標型別。對其內容的存取只能通過迭代器 API 完成。

type NpyIter_Type#

這是將迭代器暴露給 Python 的型別。目前,沒有公開任何 API 可以存取 Python 建立的迭代器的值。如果迭代器是在 Python 中建立的,則必須在 Python 中使用,反之亦然。此類 API 很可能會在未來版本中建立。

type NpyIter_IterNextFunc#

這是迭代迴圈的函數指標,由 NpyIter_GetIterNext 返回。

type NpyIter_GetMultiIndexFunc#

這是用於取得當前迭代器多索引的函數指標,由 NpyIter_GetGetMultiIndex 返回。

建構和解構#

NpyIter *NpyIter_New(PyArrayObject *op, npy_uint32 flags, NPY_ORDER order, NPY_CASTING casting, PyArray_Descr *dtype)#

為給定的 numpy 陣列物件 op 建立迭代器。

可以傳遞到 flags 中的旗標是 NpyIter_MultiNew 中記錄的全域和每個運算元旗標的任意組合,除了 NPY_ITER_ALLOCATE 之外。

NPY_ORDER 列舉值中的任何一個都可以傳遞給 order。為了有效率的迭代,NPY_KEEPORDER 是最佳選項,其他 order 會強制執行特定的迭代模式。

NPY_CASTING 列舉值中的任何一個都可以傳遞給 casting。這些值包括 NPY_NO_CASTINGNPY_EQUIV_CASTINGNPY_SAFE_CASTINGNPY_SAME_KIND_CASTINGNPY_UNSAFE_CASTING。為了允許轉換發生,還必須啟用複製或緩衝。

如果 dtype 不是 NULL,則需要該資料型別。如果允許複製,如果資料可轉換,它將建立一個臨時副本。如果啟用 NPY_ITER_UPDATEIFCOPY,它也會在迭代器解構時將資料複製回去並進行另一次轉換。

如果有錯誤,則返回 NULL,否則返回已分配的迭代器。

為了建立類似於舊迭代器的迭代器,這應該可行。

iter = NpyIter_New(op, NPY_ITER_READWRITE,
                    NPY_CORDER, NPY_NO_CASTING, NULL);

如果您想使用對齊的 double 程式碼編輯陣列,但順序並不重要,您可以使用這個。

dtype = PyArray_DescrFromType(NPY_DOUBLE);
iter = NpyIter_New(op, NPY_ITER_READWRITE|
                    NPY_ITER_BUFFERED|
                    NPY_ITER_NBO|
                    NPY_ITER_ALIGNED,
                    NPY_KEEPORDER,
                    NPY_SAME_KIND_CASTING,
                    dtype);
Py_DECREF(dtype);
NpyIter *NpyIter_MultiNew(npy_intp nop, PyArrayObject **op, npy_uint32 flags, NPY_ORDER order, NPY_CASTING casting, npy_uint32 *op_flags, PyArray_Descr **op_dtypes)#

為廣播在 op 中提供的 nop 陣列物件建立迭代器,使用常規 NumPy 廣播規則。

NPY_ORDER 列舉值中的任何一個都可以傳遞給 order。為了有效率的迭代,NPY_KEEPORDER 是最佳選項,其他 order 會強制執行特定的迭代模式。當使用 NPY_KEEPORDER 時,如果您還想確保迭代不會沿軸反轉,則應傳遞旗標 NPY_ITER_DONT_NEGATE_STRIDES

NPY_CASTING 列舉值中的任何一個都可以傳遞給 casting。這些值包括 NPY_NO_CASTINGNPY_EQUIV_CASTINGNPY_SAFE_CASTINGNPY_SAME_KIND_CASTINGNPY_UNSAFE_CASTING。為了允許轉換發生,還必須啟用複製或緩衝。

如果 op_dtypes 不是 NULL,則它為每個 op[i] 指定資料型別或 NULL。

如果有錯誤,則返回 NULL,否則返回已分配的迭代器。

可以傳遞到 flags 中的旗標,適用於整個迭代器,包括

NPY_ITER_C_INDEX#

使迭代器追蹤符合 C 順序的展平索引。此選項不能與 NPY_ITER_F_INDEX 一起使用。

NPY_ITER_F_INDEX#

使迭代器追蹤符合 Fortran 順序的展平索引。此選項不能與 NPY_ITER_C_INDEX 一起使用。

NPY_ITER_MULTI_INDEX#

使迭代器追蹤多索引。這可以防止迭代器合併軸以產生更大的內部迴圈。如果迴圈也沒有緩衝,並且沒有追蹤索引 (NpyIter_RemoveAxis 可以呼叫),則迭代器大小可以為 -1,表示迭代器太大。這可能是由於複雜的廣播造成的,並且在設定迭代器範圍、移除多索引或取得下一個函數時會導致錯誤。但是,如果移除後大小足夠小,則可以再次移除軸並正常使用迭代器。

NPY_ITER_EXTERNAL_LOOP#

使迭代器跳過最內層迴圈的迭代,需要迭代器的使用者來處理它。

此旗標與 NPY_ITER_C_INDEXNPY_ITER_F_INDEXNPY_ITER_MULTI_INDEX 不相容。

NPY_ITER_DONT_NEGATE_STRIDES#

這僅在為 order 參數指定 NPY_KEEPORDER 時影響迭代器。預設情況下,使用 NPY_KEEPORDER,迭代器會反轉具有負步幅的軸,以便沿正向遍歷記憶體。這會停用此步驟。如果您想使用軸的底層記憶體順序,但不希望反轉軸,請使用此旗標。例如,這是 numpy.ravel(a, order='K') 的行為。

NPY_ITER_COMMON_DTYPE#

使迭代器將所有運算元轉換為通用資料型別,該型別根據 ufunc 型別提升規則計算。必須啟用複製或緩衝。

如果事先知道通用資料型別,請勿使用此旗標。而是為所有運算元設定請求的 dtype。

NPY_ITER_REFS_OK#

表示具有參考型別的陣列 (物件陣列或包含物件型別的結構化陣列) 可以被接受並在迭代器中使用。如果啟用此旗標,則呼叫者必須確保檢查 NpyIter_IterationNeedsAPI(iter) 是否為 true,在這種情況下,它可能不會在迭代期間釋放 GIL。

NPY_ITER_ZEROSIZE_OK#

表示應允許大小為零的陣列。由於典型的迭代迴圈自然不適用於大小為零的陣列,因此您必須在進入迭代迴圈之前檢查 IterSize 是否大於零。目前僅檢查運算元,而不是強制形狀。

NPY_ITER_REDUCE_OK#

允許具有零步幅且大小大於一的維度的可寫入運算元。請注意,此類運算元必須是可讀/寫的。

當啟用緩衝時,這也會切換到特殊的緩衝模式,該模式會根據需要縮短迴圈長度,以避免踩踏正在縮減的值。

請注意,如果您想對自動分配的輸出執行縮減,則必須使用 NpyIter_GetOperandArray 來取得其參考,然後在執行迭代迴圈之前將每個值設定為縮減單位。在緩衝縮減的情況下,這表示您還必須指定旗標 NPY_ITER_DELAY_BUFALLOC,然後在初始化已分配的運算元以準備緩衝區後重置迭代器。

NPY_ITER_RANGED#

啟用對完整 iterindex 範圍 [0, NpyIter_IterSize(iter)) 的子範圍進行迭代的支援。使用函數 NpyIter_ResetToIterIndexRange 來指定迭代範圍。

僅當啟用 NPY_ITER_BUFFERED 時,此旗標才能與 NPY_ITER_EXTERNAL_LOOP 一起使用。這是因為在沒有緩衝的情況下,內部迴圈始終是最內層迭代維度的大小,並且允許將其切分將需要特殊處理,實際上使其更像緩衝版本。

NPY_ITER_BUFFERED#

使迭代器儲存緩衝資料,並使用緩衝來滿足資料型別、對齊方式和位元組順序要求。要緩衝運算元,請勿指定 NPY_ITER_COPYNPY_ITER_UPDATEIFCOPY 旗標,因為它們會覆寫緩衝。緩衝對於使用迭代器的 Python 程式碼特別有用,允許一次處理更大的資料塊,以分攤 Python 解釋器的開銷。

如果與 NPY_ITER_EXTERNAL_LOOP 一起使用,則呼叫者的內部迴圈可能會獲得比沒有緩衝時更大的資料塊,因為步幅的佈局方式。

請注意,如果運算元被賦予旗標 NPY_ITER_COPYNPY_ITER_UPDATEIFCOPY,則將優先進行複製而不是緩衝。當陣列被廣播時,緩衝仍然會發生,因此需要複製元素以獲得恆定的步幅。

在正常緩衝中,每個內部迴圈的大小等於緩衝區大小,如果指定了 NPY_ITER_GROWINNER,則可能會更大。如果啟用 NPY_ITER_REDUCE_OK 並且發生縮減,則內部迴圈可能會根據縮減的結構而變小。

NPY_ITER_GROWINNER#

當啟用緩衝時,這允許在不需要緩衝時增加內部迴圈的大小。如果您要直接遍歷所有資料,而不是對每個內部迴圈使用小的快取友善的臨時值陣列,則最好使用此選項。

NPY_ITER_DELAY_BUFALLOC#

當啟用緩衝時,這會延遲緩衝區的分配,直到呼叫 NpyIter_Reset 或另一個重置函數。此旗標的存在是為了避免在為多線程迭代建立緩衝迭代器的多個副本時浪費緩衝資料的複製。

此旗標的另一個用途是設定縮減運算。在建立迭代器後,並且迭代器自動分配縮減輸出 (請務必使用 READWRITE 存取),其值可以初始化為縮減單位。使用 NpyIter_GetOperandArray 來取得物件。然後,呼叫 NpyIter_Reset 以分配緩衝區並以其初始值填充它們。

NPY_ITER_COPY_IF_OVERLAP#

如果任何寫入運算元與任何讀取運算元重疊,則通過建立臨時副本來消除所有重疊 (必要時為寫入運算元啟用 UPDATEIFCOPY)。如果存在包含兩個陣列共用資料的記憶體位址,則一對運算元會重疊。

由於精確的重疊檢測在維度數量上具有指數級運行時間,因此決策是基於啟發式方法做出的,該方法具有誤報 (在不尋常的情況下進行不必要的複製),但沒有漏報。

如果存在任何讀/寫重疊,則此旗標可確保運算的結果與複製所有運算元時相同。在需要建立副本的情況下,如果沒有此旗標,則計算結果可能是未定義的!

可以傳遞到 op_flags[i] 中的旗標,其中 0 <= i < nop

NPY_ITER_READWRITE#
NPY_ITER_READONLY#
NPY_ITER_WRITEONLY#

指示迭代器的使用者將如何讀取或寫入 op[i]。每個運算元都必須指定這些旗標之一。對使用者提供的運算元使用 NPY_ITER_READWRITENPY_ITER_WRITEONLY 可能會觸發 WRITEBACKIFCOPY 語義。當呼叫 NpyIter_Deallocate 時,資料將寫回原始陣列。

NPY_ITER_COPY#

如果 op[i] 不符合建構函式旗標和參數指定的資料型別或對齊要求,則允許建立 op[i] 的副本。

NPY_ITER_UPDATEIFCOPY#

觸發 NPY_ITER_COPY,並且當陣列運算元被標記為寫入並被複製時,會導致副本中的資料在呼叫 NpyIter_Deallocate 時複製回 op[i]

如果運算元被標記為僅寫入且需要副本,則將建立一個未初始化的臨時陣列,然後在呼叫 NpyIter_Deallocate 時複製回 op[i],而不是執行不必要的複製操作。

NPY_ITER_NBO#
NPY_ITER_ALIGNED#
NPY_ITER_CONTIG#

使迭代器為 op[i] 提供符合原生位元組順序、根據 dtype 要求對齊、連續或任何組合的資料。

預設情況下,迭代器會產生指向所提供陣列的指標,這些指標可能是對齊或未對齊的,並且具有任何位元組順序。如果未啟用複製或緩衝,並且運算元資料不滿足約束,則會引發錯誤。

連續約束僅適用於內部迴圈,連續的內部迴圈可能具有任意指標變更。

如果請求的資料型別為非原生位元組順序,則 NBO 旗標會覆寫它,並且請求的資料型別會轉換為原生位元組順序。

NPY_ITER_ALLOCATE#

這是用於輸出陣列,並且需要設定旗標 NPY_ITER_WRITEONLYNPY_ITER_READWRITE。如果 op[i] 為 NULL,則會建立一個具有最終廣播維度的新陣列,以及與迭代器的迭代順序相符的佈局。

op[i] 為 NULL 時,請求的資料型別 op_dtypes[i] 也可能為 NULL,在這種情況下,它會從標記為可讀的陣列的 dtype 自動產生。產生 dtype 的規則與 UFuncs 相同。特別注意的是處理所選 dtype 中的位元組順序。如果只有一個輸入,則輸入的 dtype 將按原樣使用。否則,如果將多個輸入 dtype 組合在一起,則輸出將為原生位元組順序。

在使用此旗標分配後,呼叫者可以通過呼叫 NpyIter_GetOperandArray 並取得返回的 C 陣列中的第 i 個物件來檢索新陣列。呼叫者必須對其呼叫 Py_INCREF 以聲明對陣列的參考。

NPY_ITER_NO_SUBTYPE#

對於與 NPY_ITER_ALLOCATE 一起使用,此旗標禁用為輸出分配陣列子型別,強制其為直接的 ndarray。

待辦事項:也許引入函數 NpyIter_GetWrappedOutput 並移除此旗標會更好?

NPY_ITER_NO_BROADCAST#

確保輸入或輸出與迭代維度完全一致。

NPY_ITER_ARRAYMASK#

指出此運算元是作為遮罩,用於在寫入已套用 NPY_ITER_WRITEMASKED 旗標的運算元時,選取元素。只有一個運算元可以套用 NPY_ITER_ARRAYMASK 旗標。

具有此旗標的運算元的資料類型應為 NPY_BOOLNPY_MASK,或欄位皆為有效遮罩資料類型的結構化 dtype。在後者的情況下,它必須與被 WRITEMASKED 的結構化運算元匹配,因為它正在為該陣列的每個欄位指定遮罩。

此旗標僅影響從緩衝區寫回陣列。這表示如果運算元同時也是 NPY_ITER_READWRITENPY_ITER_WRITEONLY,則執行迭代的程式碼可以寫入此運算元,以控制哪些元素將保持不變,以及哪些元素將被修改。當遮罩應為輸入遮罩的組合時,這非常有用。

NPY_ITER_WRITEMASKED#

此陣列是所有 writemasked 運算元的遮罩。程式碼使用 writemasked 旗標,表示只會寫入選定的 ARRAYMASK 運算元為 True 的元素。一般來說,迭代器不會強制執行此操作,是否遵守此承諾取決於執行迭代的程式碼。

當使用 writemasked 旗標,且此運算元被緩衝時,這會改變資料從緩衝區複製到陣列的方式。將使用遮罩複製常式,該常式僅複製緩衝區中 writemasked 從 ARRAYMASK 運算元中對應元素返回 true 的元素。

NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE#

在記憶體重疊檢查中,假設啟用 NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE 的運算元僅以迭代器順序存取。

這使迭代器能夠推斷資料依賴性,並可能避免不必要的複製。

只有在迭代器上啟用 NPY_ITER_COPY_IF_OVERLAP 時,此旗標才有效。

NpyIter *NpyIter_AdvancedNew(npy_intp nop, PyArrayObject **op, npy_uint32 flags, NPY_ORDER order, NPY_CASTING casting, npy_uint32 *op_flags, PyArray_Descr **op_dtypes, int oa_ndim, int **op_axes, npy_intp const *itershape, npy_intp buffersize)#

使用多個進階選項擴展 NpyIter_MultiNew,以提供對廣播和緩衝的更多控制。

如果將 -1/NULL 值傳遞給 oa_ndimop_axesitershapebuffersize,則其等效於 NpyIter_MultiNew

參數 oa_ndim 在非零或 -1 時,指定將使用自訂廣播迭代的維度數量。如果提供此參數,則必須提供 op_axes,也可以選擇性地提供 itershapeop_axes 參數可讓您詳細控制運算元陣列的軸如何匹配在一起並進行迭代。在 op_axes 中,您必須提供一個由 nop 個指標組成的陣列,這些指標指向大小為 oa_ndim 且類型為 npy_intp 的陣列。如果 op_axes 中的項目為 NULL,則將套用正常的廣播規則。在 op_axes[j][i] 中,儲存的是 op[j] 的有效軸,或 -1(表示 newaxis)。在每個 op_axes[j] 陣列中,軸不得重複。以下範例說明正常的廣播如何應用於 3 維陣列、2 維陣列、1 維陣列和純量。

注意:在 NumPy 1.8 之前,oa_ndim == 0 用於表示 op_axesitershape 未使用。這已被棄用,應替換為 -1。為了更好的向後相容性,對於這種情況,可以使用 NpyIter_MultiNew

int oa_ndim = 3;               /* # iteration axes */
int op0_axes[] = {0, 1, 2};    /* 3-D operand */
int op1_axes[] = {-1, 0, 1};   /* 2-D operand */
int op2_axes[] = {-1, -1, 0};  /* 1-D operand */
int op3_axes[] = {-1, -1, -1}  /* 0-D (scalar) operand */
int* op_axes[] = {op0_axes, op1_axes, op2_axes, op3_axes};

itershape 參數可讓您強制迭代器具有特定的迭代形狀。它是一個長度為 oa_ndim 的陣列。當項目為負數時,其值由運算元決定。此參數允許自動分配的輸出獲得與任何輸入維度都不匹配的額外維度。

如果 buffersize 為零,則使用預設緩衝區大小,否則它會指定要使用的緩衝區大小。建議使用 2 的冪次方(例如 4096 或 8192)的緩衝區。

如果有錯誤,則返回 NULL,否則返回已分配的迭代器。

NpyIter *NpyIter_Copy(NpyIter *iter)#

建立給定迭代器的副本。提供此函數主要是為了啟用資料的多執行緒迭代。

待辦事項:將此移至關於多執行緒迭代的章節。

多執行緒迭代的建議方法是先使用旗標 NPY_ITER_EXTERNAL_LOOPNPY_ITER_RANGEDNPY_ITER_BUFFEREDNPY_ITER_DELAY_BUFALLOC 以及可能的 NPY_ITER_GROWINNER 建立迭代器。為每個執行緒建立此迭代器的副本(第一個迭代器減一)。然後,取得迭代索引範圍 [0, NpyIter_GetIterSize(iter)) 並將其劃分為多個任務,例如使用 TBB parallel_for 迴圈。當執行緒取得要執行的任務時,它會呼叫 NpyIter_ResetToIterIndexRange 並迭代完整範圍,來使用其迭代器的副本。

在多執行緒程式碼或不持有 Python GIL 的程式碼中使用迭代器時,必須注意僅呼叫在該上下文中安全的函數。在沒有 Python GIL 的情況下,不能安全地呼叫 NpyIter_Copy,因為它會增加 Python 參考計數。可以安全地呼叫 Reset* 和其他一些函數,方法是將 errmsg 參數作為非 NULL 傳遞,以便函數透過它傳回錯誤,而不是設定 Python 例外。

必須為每個副本呼叫 NpyIter_Deallocate

int NpyIter_RemoveAxis(NpyIter *iter, int axis)#

從迭代中移除軸。這需要為迭代器建立設定 NPY_ITER_MULTI_INDEX,並且如果啟用緩衝或正在追蹤索引,則此函數不起作用。此函數也會將迭代器重設為其初始狀態。

例如,這對於設定累加迴圈非常有用。可以先使用所有維度(包括累加軸)建立迭代器,以便正確建立輸出。然後,可以移除累加軸,並以巢狀方式完成計算。

警告:此函數可能會更改迭代器的內部記憶體佈局。必須再次檢索迭代器中的任何快取函數或指標!迭代器範圍也將被重設。

傳回 NPY_SUCCEEDNPY_FAIL

int NpyIter_RemoveMultiIndex(NpyIter *iter)#

如果迭代器正在追蹤多重索引,則這會移除對它們的支援,並執行在不需要多重索引時可能進行的進一步迭代器最佳化。此函數也會將迭代器重設為其初始狀態。

警告:此函數可能會更改迭代器的內部記憶體佈局。必須再次檢索迭代器中的任何快取函數或指標!

呼叫此函數後,NpyIter_HasMultiIndex(iter) 將傳回 false。

傳回 NPY_SUCCEEDNPY_FAIL

int NpyIter_EnableExternalLoop(NpyIter *iter)#

如果呼叫了 NpyIter_RemoveMultiIndex,您可能想要啟用旗標 NPY_ITER_EXTERNAL_LOOP。此旗標不允許與 NPY_ITER_MULTI_INDEX 一起使用,因此提供此函數以在呼叫 NpyIter_RemoveMultiIndex 後啟用該功能。此函數也會將迭代器重設為其初始狀態。

警告:此函數會更改迭代器的內部邏輯。必須再次檢索迭代器中的任何快取函數或指標!

傳回 NPY_SUCCEEDNPY_FAIL

int NpyIter_Deallocate(NpyIter *iter)#

解除配置迭代器物件並解析任何需要的寫回。

傳回 NPY_SUCCEEDNPY_FAIL

int NpyIter_Reset(NpyIter *iter, char **errmsg)#

將迭代器重設回其初始狀態,即迭代範圍的開頭。

傳回 NPY_SUCCEEDNPY_FAIL。如果 errmsg 為非 NULL,則在傳回 NPY_FAIL 時不會設定 Python 例外。相反,*errmsg 會設定為錯誤訊息。當 errmsg 為非 NULL 時,可以安全地呼叫此函數,而無需持有 Python GIL。

int NpyIter_ResetToIterIndexRange(NpyIter *iter, npy_intp istart, npy_intp iend, char **errmsg)#

重設迭代器並將其限制為 iterindex 範圍 [istart, iend)。請參閱 NpyIter_Copy 以了解如何將其用於多執行緒迭代的說明。這需要將旗標 NPY_ITER_RANGED 傳遞給迭代器建構函式。

如果您想要同時重設 iterindex 範圍和基礎指標,您可以執行以下操作以避免額外的緩衝區複製(複製此程式碼時,請務必新增傳回碼錯誤檢查)。

/* Set to a trivial empty range */
NpyIter_ResetToIterIndexRange(iter, 0, 0);
/* Set the base pointers */
NpyIter_ResetBasePointers(iter, baseptrs);
/* Set to the desired range */
NpyIter_ResetToIterIndexRange(iter, istart, iend);

傳回 NPY_SUCCEEDNPY_FAIL。如果 errmsg 為非 NULL,則在傳回 NPY_FAIL 時不會設定 Python 例外。相反,*errmsg 會設定為錯誤訊息。當 errmsg 為非 NULL 時,可以安全地呼叫此函數,而無需持有 Python GIL。

int NpyIter_ResetBasePointers(NpyIter *iter, char **baseptrs, char **errmsg)#

將迭代器重設回其初始狀態,但使用 baseptrs 中的值作為資料,而不是來自正在迭代的陣列的指標。此函數旨在與 op_axes 參數一起,由具有兩個或多個迭代器的巢狀迭代程式碼使用。

傳回 NPY_SUCCEEDNPY_FAIL。如果 errmsg 為非 NULL,則在傳回 NPY_FAIL 時不會設定 Python 例外。相反,*errmsg 會設定為錯誤訊息。當 errmsg 為非 NULL 時,可以安全地呼叫此函數,而無需持有 Python GIL。

待辦事項:將以下內容移至關於巢狀迭代器的特殊章節。

為巢狀迭代建立迭代器需要一些注意。所有迭代器運算元都必須完全匹配,否則對 NpyIter_ResetBasePointers 的呼叫將無效。這表示不應隨意使用自動複製和輸出分配。仍然可以使用迭代器的自動資料轉換和類型轉換功能,方法是建立其中一個啟用了所有轉換參數的迭代器,然後使用 NpyIter_GetOperandArray 函數抓取已分配的運算元,並將它們傳遞到其餘迭代器的建構函式中。

警告:為巢狀迭代建立迭代器時,程式碼不得在不同的迭代器中多次使用同一個維度。如果這樣做,巢狀迭代將在迭代期間產生超出範圍的指標。

警告:為巢狀迭代建立迭代器時,緩衝只能應用於最內層的迭代器。如果緩衝迭代器用作 baseptrs 的來源,它將指向一個小緩衝區而不是陣列,並且內部迭代將無效。

使用巢狀迭代器的模式如下。

NpyIter *iter1, *iter1;
NpyIter_IterNextFunc *iternext1, *iternext2;
char **dataptrs1;

/*
 * With the exact same operands, no copies allowed, and
 * no axis in op_axes used both in iter1 and iter2.
 * Buffering may be enabled for iter2, but not for iter1.
 */
iter1 = ...; iter2 = ...;

iternext1 = NpyIter_GetIterNext(iter1);
iternext2 = NpyIter_GetIterNext(iter2);
dataptrs1 = NpyIter_GetDataPtrArray(iter1);

do {
    NpyIter_ResetBasePointers(iter2, dataptrs1);
    do {
        /* Use the iter2 values */
    } while (iternext2(iter2));
} while (iternext1(iter1));
int NpyIter_GotoMultiIndex(NpyIter *iter, npy_intp const *multi_index)#

調整迭代器以指向 multi_index 指向的 ndim 個索引。如果未追蹤多重索引、索引超出範圍或停用內部迴圈迭代,則傳回錯誤。

傳回 NPY_SUCCEEDNPY_FAIL

int NpyIter_GotoIndex(NpyIter *iter, npy_intp index)#

調整迭代器以指向指定的 index。如果迭代器是使用旗標 NPY_ITER_C_INDEX 建構的,則 index 是 C 順序索引;如果迭代器是使用旗標 NPY_ITER_F_INDEX 建構的,則 index 是 Fortran 順序索引。如果沒有追蹤索引、索引超出範圍或停用內部迴圈迭代,則傳回錯誤。

傳回 NPY_SUCCEEDNPY_FAIL

npy_intp NpyIter_GetIterSize(NpyIter *iter)#

傳回正在迭代的元素數量。這是形狀中所有維度的乘積。當正在追蹤多重索引(且可能呼叫 NpyIter_RemoveAxis)時,大小可能為 -1,表示迭代器太大。此類迭代器無效,但在呼叫 NpyIter_RemoveAxis 後可能會變為有效。沒有必要檢查這種情況。

npy_intp NpyIter_GetIterIndex(NpyIter *iter)#

取得迭代器的 iterindex,它是與迭代器的迭代順序匹配的索引。

void NpyIter_GetIterIndexRange(NpyIter *iter, npy_intp *istart, npy_intp *iend)#

取得正在迭代的 iterindex 子範圍。如果未指定 NPY_ITER_RANGED,則這始終傳回範圍 [0, NpyIter_IterSize(iter))

int NpyIter_GotoIterIndex(NpyIter *iter, npy_intp iterindex)#

調整迭代器以指向指定的 iterindex。IterIndex 是與迭代器的迭代順序匹配的索引。如果 iterindex 超出範圍、啟用緩衝或停用內部迴圈迭代,則傳回錯誤。

傳回 NPY_SUCCEEDNPY_FAIL

npy_bool NpyIter_HasDelayedBufAlloc(NpyIter *iter)#

如果旗標 NPY_ITER_DELAY_BUFALLOC 已傳遞給迭代器建構函式,且尚未呼叫 Reset 函數之一,則傳回 1,否則傳回 0。

npy_bool NpyIter_HasExternalLoop(NpyIter *iter)#

如果呼叫者需要處理最內層的一維迴圈,則傳回 1,如果迭代器處理所有迴圈,則傳回 0。這由建構函式旗標 NPY_ITER_EXTERNAL_LOOPNpyIter_EnableExternalLoop 控制。

npy_bool NpyIter_HasMultiIndex(NpyIter *iter)#

如果迭代器是使用 NPY_ITER_MULTI_INDEX 旗標建立,則傳回 1,否則傳回 0。

npy_bool NpyIter_HasIndex(NpyIter *iter)#

如果迭代器是使用 NPY_ITER_C_INDEXNPY_ITER_F_INDEX 旗標建立,則傳回 1,否則傳回 0。

npy_bool NpyIter_RequiresBuffering(NpyIter *iter)#

如果迭代器需要緩衝,當運算元需要轉換或對齊,而無法直接使用時,就會發生這種情況,則傳回 1。

npy_bool NpyIter_IsBuffered(NpyIter *iter)#

如果迭代器是使用 NPY_ITER_BUFFERED 旗標建立,則傳回 1,否則傳回 0。

npy_bool NpyIter_IsGrowInner(NpyIter *iter)#

如果迭代器是使用 NPY_ITER_GROWINNER 旗標建立,則傳回 1,否則傳回 0。

npy_intp NpyIter_GetBufferSize(NpyIter *iter)#

如果迭代器已緩衝,則傳回正在使用的緩衝區大小,否則傳回 0。

int NpyIter_GetNDim(NpyIter *iter)#

傳回正在迭代的維度數量。如果在迭代器建構函式中未要求多重索引,則此值可能小於原始物件中的維度數量。

int NpyIter_GetNOp(NpyIter *iter)#

傳回迭代器中的運算元數量。

npy_intp *NpyIter_GetAxisStrideArray(NpyIter *iter, int axis)#

取得指定軸的步幅陣列。需要迭代器追蹤多重索引,且未啟用緩衝。

當您想要以某種方式比對運算元軸,然後使用 NpyIter_RemoveAxis 移除它們以手動處理時,可以使用此功能。透過在移除軸之前呼叫此函式,您可以取得手動處理的步幅。

錯誤時傳回 NULL

int NpyIter_GetShape(NpyIter *iter, npy_intp *outshape)#

outshape 中傳回迭代器的廣播形狀。這只能在追蹤多重索引的迭代器上呼叫。

傳回 NPY_SUCCEEDNPY_FAIL

PyArray_Descr **NpyIter_GetDescrArray(NpyIter *iter)#

這會傳回指向正在迭代的物件的 nop 資料類型 Descrs 的指標。結果指向 iter,因此呼叫端不會取得 Descrs 的任何參考。

此指標可以在迭代迴圈之前快取,呼叫 iternext 不會變更它。

PyObject **NpyIter_GetOperandArray(NpyIter *iter)#

這會傳回指向正在迭代的 nop 運算元 PyObjects 的指標。結果指向 iter,因此呼叫端不會取得 PyObjects 的任何參考。

PyObject *NpyIter_GetIterView(NpyIter *iter, npy_intp i)#

這會傳回對新 ndarray 檢視的參考,該檢視是陣列 NpyIter_GetOperandArray 中第 i 個物件的檢視,其維度和步幅與內部最佳化迭代模式相符。此檢視的 C 順序迭代等同於迭代器的迭代順序。

例如,如果迭代器是使用單一陣列作為其輸入建立的,並且可以重新排列其所有軸,然後將其摺疊成單一跨步迭代,則這將傳回一個一維陣列的檢視。

void NpyIter_GetReadFlags(NpyIter *iter, char *outreadflags)#

填入 nop 旗標。如果可以從 op[i] 讀取,則將 outreadflags[i] 設定為 1,否則設定為 0。

void NpyIter_GetWriteFlags(NpyIter *iter, char *outwriteflags)#

填入 nop 旗標。如果可以寫入 op[i],則將 outwriteflags[i] 設定為 1,否則設定為 0。

int NpyIter_CreateCompatibleStrides(NpyIter *iter, npy_intp itemsize, npy_intp *outstrides)#

建立一組步幅,這些步幅與使用 NPY_ITER_ALLOCATE 旗標建立的輸出陣列的步幅相同,其中 NULL 已傳遞給 op_axes。這適用於連續封裝但不必以 C 或 Fortran 順序排列的資料。這應該與 NpyIter_GetShapeNpyIter_GetNDim 以及傳遞至建構函式的 NPY_ITER_MULTI_INDEX 旗標一起使用。

此函式的一個用例是比對迭代器的形狀和佈局,並附加一個或多個維度。例如,為了為數值梯度產生每個輸入值的向量,您傳入 ndim*itemsize 作為 itemsize,然後在末尾新增另一個維度,大小為 ndim,步幅為 itemsize。若要執行 Hessian 矩陣,您可以執行相同的操作,但新增兩個維度,或利用對稱性並將其封裝到具有特定編碼的 1 個維度中。

只有在迭代器追蹤多重索引且使用 NPY_ITER_DONT_NEGATE_STRIDES 以防止軸以相反順序迭代時,才能呼叫此函式。

如果陣列是使用此方法建立的,則只需為每次迭代新增 'itemsize' 即可遍歷與迭代器相符的新陣列。

傳回 NPY_SUCCEEDNPY_FAIL

npy_bool NpyIter_IsFirstVisit(NpyIter *iter, int iop)#

檢查這是否是第一次看到迭代器指向的指定縮減運算元的元素。此函式會針對縮減運算元以及停用緩衝時傳回合理的答案。對於緩衝的非縮減運算元,答案可能不正確。

此函式僅適用於 EXTERNAL_LOOP 模式,並且在未啟用該模式時會產生一些錯誤的答案。

如果此函式傳回 true,呼叫端也應檢查運算元的內部迴圈步幅,因為如果該步幅為 0,則只有最內層外部迴圈的第一個元素是第一次被存取。

警告:基於效能考量,'iop' 未進行邊界檢查,未確認 'iop' 實際上是縮減運算元,也未確認已啟用 EXTERNAL_LOOP 模式。這些檢查是呼叫端的責任,應在任何內部迴圈之外完成。

用於迭代的函式#

NpyIter_IterNextFunc *NpyIter_GetIterNext(NpyIter *iter, char **errmsg)#

傳回用於迭代的函式指標。此函式可能會計算函式指標的特殊化版本,而不是儲存在迭代器結構中。因此,為了獲得良好的效能,必須將函式指標儲存在變數中,而不是在每次迴圈迭代時擷取。

如果發生錯誤,則傳回 NULL。如果 errmsg 為非 NULL,則在傳回 NPY_FAIL 時不會設定 Python 例外。而是將 *errmsg 設定為錯誤訊息。當 errmsg 為非 NULL 時,可以安全地呼叫此函式,而無需持有 Python GIL。

典型的迴圈結構如下所示。

NpyIter_IterNextFunc *iternext = NpyIter_GetIterNext(iter, NULL);
char** dataptr = NpyIter_GetDataPtrArray(iter);

do {
    /* use the addresses dataptr[0], ... dataptr[nop-1] */
} while(iternext(iter));

當指定 NPY_ITER_EXTERNAL_LOOP 時,典型的內部迴圈結構如下所示。

NpyIter_IterNextFunc *iternext = NpyIter_GetIterNext(iter, NULL);
char** dataptr = NpyIter_GetDataPtrArray(iter);
npy_intp* stride = NpyIter_GetInnerStrideArray(iter);
npy_intp* size_ptr = NpyIter_GetInnerLoopSizePtr(iter), size;
npy_intp iop, nop = NpyIter_GetNOp(iter);

do {
    size = *size_ptr;
    while (size--) {
        /* use the addresses dataptr[0], ... dataptr[nop-1] */
        for (iop = 0; iop < nop; ++iop) {
            dataptr[iop] += stride[iop];
        }
    }
} while (iternext());

請注意,我們正在使用迭代器內部的 dataptr 陣列,而不是將值複製到本機暫存變數。這是可能的,因為當呼叫 iternext() 時,這些指標將會被覆寫為新值,而不是以累加方式更新。

如果正在使用編譯時固定的緩衝區(NPY_ITER_BUFFEREDNPY_ITER_EXTERNAL_LOOP 旗標),則內部大小也可以用作訊號。當 iternext() 傳回 false 時,保證大小會變為零,從而啟用以下迴圈結構。請注意,如果您使用此結構,則不應傳遞 NPY_ITER_GROWINNER 作為旗標,因為在某些情況下,它會導致更大的大小。

/* The constructor should have buffersize passed as this value */
#define FIXED_BUFFER_SIZE 1024

NpyIter_IterNextFunc *iternext = NpyIter_GetIterNext(iter, NULL);
char **dataptr = NpyIter_GetDataPtrArray(iter);
npy_intp *stride = NpyIter_GetInnerStrideArray(iter);
npy_intp *size_ptr = NpyIter_GetInnerLoopSizePtr(iter), size;
npy_intp i, iop, nop = NpyIter_GetNOp(iter);

/* One loop with a fixed inner size */
size = *size_ptr;
while (size == FIXED_BUFFER_SIZE) {
    /*
     * This loop could be manually unrolled by a factor
     * which divides into FIXED_BUFFER_SIZE
     */
    for (i = 0; i < FIXED_BUFFER_SIZE; ++i) {
        /* use the addresses dataptr[0], ... dataptr[nop-1] */
        for (iop = 0; iop < nop; ++iop) {
            dataptr[iop] += stride[iop];
        }
    }
    iternext();
    size = *size_ptr;
}

/* Finish-up loop with variable inner size */
if (size > 0) do {
    size = *size_ptr;
    while (size--) {
        /* use the addresses dataptr[0], ... dataptr[nop-1] */
        for (iop = 0; iop < nop; ++iop) {
            dataptr[iop] += stride[iop];
        }
    }
} while (iternext());
NpyIter_GetMultiIndexFunc *NpyIter_GetGetMultiIndex(NpyIter *iter, char **errmsg)#

傳回用於取得迭代器目前多重索引的函式指標。如果迭代器未追蹤多重索引,則傳回 NULL。建議在迭代迴圈之前將此函式指標快取在本機變數中。

如果發生錯誤,則傳回 NULL。如果 errmsg 為非 NULL,則在傳回 NPY_FAIL 時不會設定 Python 例外。而是將 *errmsg 設定為錯誤訊息。當 errmsg 為非 NULL 時,可以安全地呼叫此函式,而無需持有 Python GIL。

char **NpyIter_GetDataPtrArray(NpyIter *iter)#

這會傳回指向 nop 資料指標的指標。如果未指定 NPY_ITER_EXTERNAL_LOOP,則每個資料指標都會指向迭代器的目前資料項目。如果未指定內部迭代,則它會指向內部迴圈的第一個資料項目。

此指標可以在迭代迴圈之前快取,呼叫 iternext 不會變更它。可以安全地呼叫此函式,而無需持有 Python GIL。

char **NpyIter_GetInitialDataPtrArray(NpyIter *iter)#

直接取得陣列資料指標的陣列(永遠不會進入緩衝區),對應於迭代索引 0。

這些指標與 NpyIter_ResetBasePointers 接受的指標不同,因為沿某些軸的方向可能已反轉。

可以安全地呼叫此函式,而無需持有 Python GIL。

npy_intp *NpyIter_GetIndexPtr(NpyIter *iter)#

這會傳回指向正在追蹤的索引的指標,如果未追蹤索引,則傳回 NULL。只有在建構期間指定了旗標 NPY_ITER_C_INDEXNPY_ITER_F_INDEX 之一時,才可使用。

當使用旗標 NPY_ITER_EXTERNAL_LOOP 時,程式碼需要知道執行內部迴圈的參數。這些函式提供該資訊。

npy_intp *NpyIter_GetInnerStrideArray(NpyIter *iter)#

傳回指向 nop 步幅陣列的指標,每個迭代物件一個步幅,供內部迴圈使用。

此指標可以在迭代迴圈之前快取,呼叫 iternext 不會變更它。可以安全地呼叫此函式,而無需持有 Python GIL。

警告:雖然指標可以快取,但如果迭代器已緩衝,則其值可能會變更。

npy_intp *NpyIter_GetInnerLoopSizePtr(NpyIter *iter)#

傳回指向內部迴圈應執行的迭代次數的指標。

此位址可以在迭代迴圈之前快取,呼叫 iternext 不會變更它。值本身可能會在迭代期間變更,尤其是在啟用緩衝的情況下。可以安全地呼叫此函式,而無需持有 Python GIL。

void NpyIter_GetInnerFixedStrideArray(NpyIter *iter, npy_intp *out_strides)#

取得一組步幅,這些步幅是固定的,或在整個迭代期間不會變更。對於可能會變更的步幅,值 NPY_MAX_INTP 會放在步幅中。

一旦迭代器準備好進行迭代(如果在使用了 NPY_ITER_DELAY_BUFALLOC 後進行重設),請呼叫此函式以取得可用於選取快速內部迴圈函式的步幅。例如,如果步幅為 0,則表示內部迴圈可以始終將其值載入到變數中一次,然後在整個迴圈中使用該變數,或者如果步幅等於 itemsize,則可以使用該運算元的連續版本。

可以安全地呼叫此函式,而無需持有 Python GIL。

從先前的 NumPy 迭代器轉換#

舊的迭代器 API 包括 PyArrayIter_Check、PyArray_Iter* 和 PyArray_ITER_* 等函式。多重迭代器陣列包括 PyArray_MultiIter*、PyArray_Broadcast 和 PyArray_RemoveSmallest。新的迭代器設計以單一物件和相關聯的 API 取代所有這些功能。新 API 的一個目標是,現有迭代器的所有用途都應該可以替換為新的迭代器,而無需付出太多努力。在 1.6 中,主要的例外是鄰域迭代器,此迭代器中沒有對應的功能。

以下是新迭代器要使用的函式轉換表