При создании подклассов ndarray почему транспонирование происходит после __array_finalize__, а не до? - PullRequest
4 голосов
/ 17 марта 2020

Давайте для простоты просто скопируем диагноз c ndarray подкласс из numpy документов:

import numpy as np

class MySubClass(np.ndarray):

    def __new__(cls, input_array, info=None):
        obj = np.asarray(input_array).view(cls)
        obj.info = info
        return obj

    def __array_finalize__(self, obj):
        print('In __array_finalize__:')
        print('   self is %s' % repr(self))
        print('   obj is %s' % repr(obj))
        if obj is None: return
        self.info = getattr(obj, 'info', None)

Теперь давайте сделаем простой пример:

>>> x = MySubClass(np.ones((1,5)))
In __array_finalize__:
   self is MySubClass([[1., 1., 1., 1., 1.]])
   obj is array([[1., 1., 1., 1., 1.]])
>>> y = x.T
In __array_finalize__:
   self is MySubClass([[1., 1., 1., 1., 1.]])
   obj is MySubClass([[1., 1., 1., 1., 1.]])

Как мы может видеть то, что явно не является транспонированием и передается __array_finalize__. Помимо распространения значения слова «финализировать» на целые новые области, какова цель этого поведения?

Не имеет ли больше смысла отправлять фактический результат, то есть транспонировать через этот хук для что будет завершено?

Каков рекомендуемый способ подправить базовую транспозицию с любой постобработкой, которая может потребоваться моему подклассу?

1 Ответ

1 голос
/ 20 марта 2020

Это связано с тем, что для создания нового объекта они используют уже доступную (общую) функцию PyArray_NewFromDescrAndBase для управления выделением памяти. Исходный код PyArray_Transpose показывает, что сначала создается новый объект из существующего массива с аналогичной формой и шагами, а затем они исправляются путем доступа к ранее выделенной памяти:

/*
 * this allocates memory for dimensions and strides (but fills them
 * incorrectly), sets up descr, and points data at PyArray_DATA(ap).
 */
Py_INCREF(PyArray_DESCR(ap));
ret = (PyArrayObject *) PyArray_NewFromDescrAndBase(
        Py_TYPE(ap), PyArray_DESCR(ap),
        n, PyArray_DIMS(ap), NULL, PyArray_DATA(ap),
        flags, (PyObject *)ap, (PyObject *)ap);
if (ret == NULL) {
    return NULL;
}

/* fix the dimensions and strides of the return-array */
for (i = 0; i < n; i++) {
    PyArray_DIMS(ret)[i] = PyArray_DIMS(ap)[permutation[i]];
    PyArray_STRIDES(ret)[i] = PyArray_STRIDES(ap)[permutation[i]];
}

Здесь PyArray_NewFromDescrAndBase отвечает за , вызывая __array_finalize__, и, следовательно, этот метод получает версию с неправильной формой и шагами (т.е. нетранспонированными). Можно было бы поступить иначе, но для PyArray_NewFromDescrAndBase потребовался бы дополнительный параметр, чтобы отложить вызов до __array_finalize__, а затем это можно было бы сделать вручную после того, как форма и шаги были скорректированы.

...