【问题标题】:While iteration over numpy array, I can't call methods of objects stored in array在对 numpy 数组进行迭代时,我无法调用存储在数组中的对象的方法
【发布时间】:2021-01-26 22:37:26
【问题描述】:

第一个问题是在 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,似乎已经过时了。

【问题讨论】:

  • 像这样使用numpy 可能会使您的性能变差。问题,print(balls.dtype) 显示什么?
  • b from nditer 是一个包含 vpy 对象的 0d 数组。 b.item().pos 可能有效。但是nditer 并没有提高对象 dtype 数组的迭代速度。并且使用对象 dtype 数组并不是对列表的改进。
  • 正如您所建议的,numpy 实际上是关于数字的集合,如果您要存储对象的集合,numpy 就会失去许多优势。通常,我最终在将系统移动到 numpy 时所做的概念上的改变是,如果我有一个代表具有 x 的 Ball 的类,它是一个浮点数,我删除 Ball 类并为 Balls 创建一个类并且具有 @ 987654331@ 这是一个包含所有球位置的 numpy 数组。这样你就可以得到大量的数字,这正是 numpy 想要的。
  • 我从所有替代方案中看到,numpy 可能不是要走的路。 @tom10 我不确定我是否理解正确。您是否建议创建一个包含球列表的类,其位置引用一个填充了该数据的 numpy 数组?
  • @juanpa.arrivillaga 返回 'object' 什么是迭代和操作/调用大量对象方法的有效方法?

标签: python arrays numpy iterator vpython


【解决方案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 文档需要更强大的性能免责声明。 nditerccython 代码中使用时有用且快速,但是当通过 Python 代码访问时,它不如更明显的替代方案。在某些情况下,额外的花里胡哨可能很有用,但大多数情况下,我将其视为正确编译代码的桥梁,而不是其本身。

性能问题的核心是Foo 是一个 Python 类。因此访问i 属性必须使用完整的Python 引用系统。它不能使用任何快速编译的numpy 数值方法。

【讨论】:

  • 非常感谢。这说明了很多。所以 item() 函数可以解决它,但是无论我使用哪种方式,性能都很差。实际上,我确实对 nditer 文档进行了深入研究,但从未遇到过 item(),因此我将寻找更好的方法。谢谢
  • item 的替代品是b[()]b[...] 就是设置这样一个0d数组的值的方式。无论如何,nditer 中的迭代比表面上看起来更棘手。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-28
  • 2019-01-30
  • 2017-10-24
  • 1970-01-01
  • 1970-01-01
  • 2012-05-20
  • 1970-01-01
相关资源
最近更新 更多