Subclassing Numpy Array - атрибуты распространения
Я хотел бы знать, как можно распространять пользовательские атрибуты массивов numpy, даже когда массив проходит через такие функции, как np.fromfunction
.
Например, мой класс ExampleTensor
определяет атрибут attr
который по умолчанию установлен в 1.
import numpy as np
class ExampleTensor(np.ndarray):
def __new__(cls, input_array):
return np.asarray(input_array).view(cls)
def __array_finalize__(self, obj) -> None:
if obj is None: return
# This attribute should be maintained!
self.attr = getattr(obj, 'attr', 1)
Нарезка и основные операции между экземплярами ExampleTensor
будут поддерживать атрибуты, но использование других функций numpy не будет (вероятно, потому, что они создают обычные массивы numpy вместо ExampleTensors). Мой вопрос: существует ли решение, которое сохраняется в пользовательских атрибутах, когда обычный массив numpy создается из экземпляров массивов с подклассами numpy?
Пример для воспроизведения проблемы:
ex1 = ExampleTensor([[3, 4],[5, 6]])
ex1.attr = "some val"
print(ex1[0].attr) # correctly outputs "some val"
print((ex1+ex1).attr) # correctly outputs "some val"
np.sum([ex1, ex1], axis=0).attr # Attribute Error: 'numpy.ndarray' object has no attribute 'attr'
Ответы
Ответ 1
import numpy as np
class ExampleTensor(np.ndarray):
def __new__(cls, input_array):
return np.asarray(input_array).view(cls)
def __array_finalize__(self, obj) -> None:
if obj is None: return
# This attribute should be maintained!
default_attributes = {"attr": 1}
self.__dict__.update(default_attributes) # another way to set attributes
Реализовать метод array_ufunc, подобный этому
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): # this method is called whenever you use a ufunc
f = {
"reduce": ufunc.reduce,
"accumulate": ufunc.accumulate,
"reduceat": ufunc.reduceat,
"outer": ufunc.outer,
"at": ufunc.at,
"__call__": ufunc,
}
output = ExampleTensor(f[method](*(i.view(np.ndarray) for i in inputs), **kwargs)) # convert the inputs to np.ndarray to prevent recursion, call the function, then cast it back as ExampleTensor
output.__dict__ = self.__dict__ # carry forward attributes
return output
Тестовое задание
x = ExampleTensor(np.array([1,2,3]))
x.attr = 2
y0 = np.add(x, x)
print(y0, y0.attr)
y1 = np.add.outer(x, x)
print(y1, y1.attr) # works even if called with method
[2 4 6] 2
[[2 3 4]
[3 4 5]
[4 5 6]] 2
Объяснение в комментариях.
Ответ 2
Я думаю, что ваш пример неверен:
>>> type(ex1)
<class '__main__.ExampleTensor'>
но
>>> type([ex1, ex1])
<class 'numpy.ndarray'>
для которых ваши перегруженные __new__
и __array_finalize__
не вызываются, поскольку вы на самом деле создаете массив, а не ваш подкласс. Однако они вызываются, если вы делаете:
>>> ExampleTensor([ex1, ex1])
который устанавливает attr = 1
поскольку вы не определили, как распространять атрибут при построении ExampleTensor
из списка ExampleTensor
. Вам нужно будет определить это поведение в вашем подклассе, перегрузив соответствующие операции. Как было предложено в комментариях выше, стоит взглянуть на код для np.matrix для вдохновения.
Ответ 3
Какое значение должно "распространяться", если ex1.attr != ex2.attr
для np.sum([ex1, ex2], axis=0).attr
?
Обратите внимание, что этот вопрос является более фундаментальным, чем может показаться первым: как вообще большое количество функций numpy могло бы самостоятельно узнать ваше намерение? Вероятно, вам не удастся написать перегруженную версию для каждой из функций "attr-aware", таких как:
def sum(a, **kwargs):
sa=np.sum(a, **kwargs)
if isinstance(a[0],ExampleTensor): # or if hasattr(a[0],'attr')
sa.attr=a[0].attr
return sa
Я уверен, что этого недостаточно для обработки любого входа np.sum(), но он должен работать для вашего примера.