Определить простой класс:
class Cell():
def __init__(self,x,y):
self.x=x
self.y=y
def setX(self,x):
self.x=x
def __repr__(self):
return f'Cell({self.x},{self.y})'
Способ создания массива этих объектов:
In [653]: f = np.frompyfunc(Cell, 2, 1)
In [654]: arr = f(np.arange(3)[:,None], np.arange(4))
In [655]: arr
Out[655]:
array([[Cell(0,0), Cell(0,1), Cell(0,2), Cell(0,3)],
[Cell(1,0), Cell(1,1), Cell(1,2), Cell(1,3)],
[Cell(2,0), Cell(2,1), Cell(2,2), Cell(2,3)]], dtype=object)
In [656]: arr.shape
Out[656]: (3, 4)
Список способов создания одинаковых объектов:
In [658]: [[Cell(i,j) for i in range(3)] for j in range(4)]
Out[658]:
[[Cell(0,0), Cell(1,0), Cell(2,0)],
[Cell(0,1), Cell(1,1), Cell(2,1)],
[Cell(0,2), Cell(1,2), Cell(2,2)],
[Cell(0,3), Cell(1,3), Cell(2,3)]]
Некоторые сравнительные тайминги:
In [659]: timeit arr = f(np.arange(3)[:,None], np.arange(4))
13.5 µs ± 73.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [660]: timeit [[Cell(i,j) for i in range(3)] for j in range(4)]
8.3 µs ± 115 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [661]: timeit arr = f(np.arange(300)[:,None], np.arange(400))
64.9 ms ± 293 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [662]: timeit [[Cell(i,j) for i in range(300)] for j in range(400)]
78 ms ± 2.51 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Для больших наборов подход frompyfunc
имеет умеренное преимущество в скорости.
Извлечение значений из всех ячеек:
In [664]: np.frompyfunc(lambda c: c.x, 1, 1)(arr)
Out[664]:
array([[0, 0, 0, 0],
[1, 1, 1, 1],
[2, 2, 2, 2]], dtype=object)
Использование метода SetX
:
In [665]: np.frompyfunc(Cell.setX, 2, 1)(arr, np.arange(12).reshape(3,4))
Out[665]:
array([[None, None, None, None],
[None, None, None, None],
[None, None, None, None]], dtype=object)
In [666]: arr
Out[666]:
array([[Cell(0,0), Cell(1,1), Cell(2,2), Cell(3,3)],
[Cell(4,0), Cell(5,1), Cell(6,2), Cell(7,3)],
[Cell(8,0), Cell(9,1), Cell(10,2), Cell(11,3)]], dtype=object)
SetX
ничего не возвращает, поэтому массив, полученный при вызове функции, равен None
.Но он изменил все элементы arr
.Как и списки, мы обычно не используем frompyfunc
вызовы для побочных эффектов, но это возможно.
np.vectorize
, в его стандартной (и оригинальной) форме, просто используется frompyfunc
и настраиваетсяd-тип возврата.frompyfunc
всегда возвращает объект dtype.Более новые версии vectorize
имеют параметр signature
, что позволяет нам передавать массивы (в отличие от скаляров) в функцию и возвращать массивы.Но эта обработка еще медленнее.
Определение таких массивов объектов может сделать ваш код более чистым и лучше организованным, но они никогда не смогут соответствовать числовым массивам с точки зрения скорости.
Учитывая определение Cell
, я могу установить атрибуты для массивов, например,
Cell(np.arange(3), np.zeros((3,4)))
Но чтобы установить значения массива Cell, я должен создатьСначала массив объектов:
In [676]: X = np.zeros(3, object)
In [677]: for i,row in enumerate(np.arange(6).reshape(3,2)): X[i]=row
In [678]: X
Out[678]: array([array([0, 1]), array([2, 3]), array([4, 5])], dtype=object)
In [679]: np.frompyfunc(Cell.setX, 2, 1)(arr, X[:,None])
Out[679]:
array([[None, None, None, None],
[None, None, None, None],
[None, None, None, None]], dtype=object)
In [680]: arr
Out[680]:
array([[Cell([0 1],0), Cell([0 1],1), Cell([0 1],2), Cell([0 1],3)],
[Cell([2 3],0), Cell([2 3],1), Cell([2 3],2), Cell([2 3],3)],
[Cell([4 5],0), Cell([4 5],1), Cell([4 5],2), Cell([4 5],3)]],
dtype=object)
Я не смог передать массив (3,2):
In [681]: np.frompyfunc(Cell.setX, 2, 1)(arr, np.arange(6).reshape(3,2))
ValueError: operands could not be broadcast together with shapes (3,4) (3,2)
numpy
преимущественно работает с многомерными (числовыми) массивами.Создание и использование массива dtype объекта требует специальных приемов.