Во время итерации по массиву numpy я не могу вызывать методы объектов, хранящихся в массиве

#python #arrays #numpy #iterator #vpython

#python #массивы #numpy #итератор #vpython

Вопрос:

первый вопрос, заданный в StackOverflow, поэтому советы о том, как лучше «спросить», приветствуются.

Основная цель этой части кода: количество шаров (no_balls) перемещается в случайных направлениях.

Я пытаюсь перейти от списков python к массивам numpy для повышения производительности. Вот сокращенный код.

Основная проблема: мой итератор выдает мне объекты типа ndarray, а не vpy.sphere , поэтому вызов sphere.pos для объектов, которые я перебираю, завершается неудачей. Или это невозможно, поскольку Numpy создается для чисел?? Альтернативы для повышения производительности?

 import vpython as vpy
import numpy as np

#Create and Fill numpy array with random size balls
balls = np.empty([no_ball], dtype=vpy.sphere)

with np.nditer(balls, flags=['refs_ok'], op_flags=['readwrite']) as b_it:
    debug_msg(len(b_it))
    for b in b_it:
        b[...] = (vpy.sphere( radius=random_in_range(ball_min_r,ball_max_r), 
                              opacity=0.8, 
                              color=random_RGB(), 
                              pos=vpy.vector(0,0,0),))
    debug_msg('populated balls list')

#Main Loop
debug_msg('Starting Main Loop')
while True:
    vpy.rate(30)
            
            
with np.nditer(balls, flags=['refs_ok'], op_flags=['readwrite']) as b_it:
    #Main Loop
    debug_msg('Starting Main Loop')
    while True:
        vpy.rate(30)
            
#The actual loop manipulates the position but the problem is that I can't access the   position of the sphere objects. Type returns nd.array for b
        for b in b_it:
           debug_msg(type(b[...]))
           debug_msg(b[...].pos)
  
 #Above outputs
<class 'numpy.ndarray'>
Traceback (most recent call last):
  File "path", line 93, in <module>
    debug_msg(b[...].pos)
AttributeError: 'numpy.ndarray' object has no attribute 'pos'
  

Как мне вызывать методы и элементы объектов в массиве. И в примечании, почему мне нужно вызывать b[…] вместо b, кажется устаревшим.

Комментарии:

1. numpy Подобное использование, вероятно, ухудшит вашу производительность. Вопрос, что print(balls.dtype) показывает?

2. b from nditer — это массив 0d, содержащий vpy объект. b.item().pos может сработать. Но nditer не улучшает скорость итерации по массиву объектов dtype. И использование массива объектов dtype не является дополнением к списку.

3. Как вы предполагаете, numpy на самом деле касается наборов чисел, и если вы храните коллекции объектов, numpy теряет многие из своих преимуществ. Обычно концептуальное изменение, которое я в конечном итоге вношу при переходе системы на numpy, заключается в том, что если у меня есть класс, представляющий шар x , который имеет значение с плавающей точкой, я удаляю класс Ball и создаю класс для Balls, и у него есть x массив numpy с положением всех шаров. Таким образом, вы получаете большие массивы чисел, чего и хочет numpy.

4. Я вижу из всех альтернатив, что numpy может быть неправильным. @tom10 Я не уверен, что правильно это понимаю. Вы предлагаете создать класс, который содержит список шаров, позиция которых ссылается на массив numpy, заполненный этими данными?

5. @juanpa.arrivillaga, который возвращает «объект» Каков эффективный способ итерации и управления / вызова методов большого количества объектов?

Ответ №1:

Простой класс:

 In [149]: class Foo():
     ...:     def __init__(self,i):
     ...:         self.i = i
     ...:     def __repr__(self):
     ...:         return f'<FOO {self.i}>'
     ...: 
In [150]: Foo(323)
Out[150]: <FOO 323>
  

Список таких объектов:

 In [151]: alist = [Foo(i) for i in range(10)]
  

Эквивалентный массив объектов dtype:

 In [152]: arr = np.array(alist)
In [153]: arr.dtype
Out[153]: dtype('O')
In [154]: arr
Out[154]: 
array([<FOO 0>, <FOO 1>, <FOO 2>, <FOO 3>, <FOO 4>, <FOO 5>, <FOO 6>,
       <FOO 7>, <FOO 8>, <FOO 9>], dtype=object)
  

Извлечение атрибута из списка:

 In [155]: [f.i for f in alist]
Out[155]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [156]: timeit [f.i for f in alist]
826 ns ± 8.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
  

и из массива (медленнее):

 In [157]: timeit [f.i for f in arr]
1.66 µs ± 15.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
  

Использование nditer — вы достаточно изучили документы, чтобы правильно установить флаги, но не поняли, что b это массив, а не Foo :

 In [158]: with np.nditer(arr, flags=['refs_ok'], op_flags=['readwrite']) as b_it:
     ...:     for b in b_it:
     ...:         print(b, b.dtype, b.shape, b.item())
     ...: 
<FOO 0> object () <FOO 0>
<FOO 1> object () <FOO 1>
<FOO 2> object () <FOO 2>
<FOO 3> object () <FOO 3>
<FOO 4> object () <FOO 4>
<FOO 5> object () <FOO 5>
<FOO 6> object () <FOO 6>
<FOO 7> object () <FOO 7>
<FOO 8> object () <FOO 8>
<FOO 9> object () <FOO 9>
  

Получение списка атрибута:

 In [159]: res = []
     ...: with np.nditer(arr, flags=['refs_ok'], op_flags=['readwrite']) as b_it:
     ...:     for b in b_it:
     ...:         res.append(b.item().i)
     ...: 
     ...: 
In [160]: res
Out[160]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  

И плохое время:

 In [161]: %%timeit
     ...: res = []
     ...: with np.nditer(arr, flags=['refs_ok'], op_flags=['readwrite']) as b_it:
     ...:     for b in b_it:
     ...:         res.append(b.item().i)
     ...: 

7.25 µs ± 60.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
  

Один из более чистых способов выполнения действия над элементами массива объектов — с frompyfunc :

 In [162]: f = np.frompyfunc(lambda b:b.i,1,1)
In [163]: f(arr)
Out[163]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=object)
In [164]: timeit f(arr)
2.1 µs ± 8.58 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
  

Все еще медленнее, чем итерация, хотя, если нам нужен массив, а не просто список, это лучше, чем:

 In [165]: timeit np.array([f.i for f in arr])
5.79 µs ± 21.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
  

В nditer документах требуется более строгое ограничение производительности. nditer при использовании в c or cython код полезен и быстр, но при доступе через код Python он уступает более очевидным альтернативам. В некоторых случаях могут быть полезны дополнительные подсказки, но в основном я рассматриваю это как переход к правильно скомпилированному коду, а не как самоцель.

В основе проблемы с производительностью Foo лежит класс Python. Поэтому для доступа к i атрибуту необходимо использовать полную систему ссылок Python. Он не может использовать ни один из быстро скомпилированных numpy числовых методов.

Комментарии:

1. Большое спасибо. Это многое проясняет. Таким образом, функция item () решила бы эту проблему, но производительность плохая, так как я использую ее в любом случае. На самом деле я довольно глубоко изучил документы nditer и никогда не сталкивался с item () Поэтому я буду искать лучшие подходы. Спасибо

2. Альтернативой item является b[()] . b[...] это способ установки значения такого массива 0d. В любом случае итерация nditer сложнее, чем кажется на первый взгляд.