命名的dtype数组:a[0]['name']和a['name'][0]有什么区别?

16

我在numpy中遇到了以下奇怪的现象,可能是bug:

import numpy as np
dt = np.dtype([('tuple', (int, 2))])
a = np.zeros(3, dt)
type(a['tuple'][0])  # ndarray
type(a[0]['tuple'])  # ndarray

a['tuple'][0] = (1,2)  # ok
a[0]['tuple'] = (1,2)  # ValueError: shape-mismatch on array construction

我本来期望以下两个选项都能够生效。 您有什么看法吗?


1
提示:在 SO 上发布代码时,请发布我们可以复制和粘贴的代码片段;对于 Python,这意味着使用“#”进行插入注释,而不是“%”。 - DSM
1
有趣的事情,我也发现使用1.6.1无论哪种方式都会得到相同的结果... - Brian Larsen
有点奇怪,但是 a[0]['tuple'][:] = (1,2) 可以工作,也许那里有一些线索... - Ricardo Cárdenes
显式切片也可以使用 a[0]['tuple'][0:2] = (1,2)。 - Paul Seeb
3个回答

9

我在numpy-discussion列表上提出了这个问题,Travis Oliphant在这里回答了。

他的回答如下:

简短的答案是,这不是一个“正常”的错误,但可以被视为一个“设计”错误(尽管问题可能不容易解决)。这意味着它在短期内可能不会改变,您应该只使用第一种拼写方式。

结构化数组可能是NumPy中令人困惑的领域,原因有几个。您构建了一个数据类型为“结构”数组的示例,其中包含一个成员(“元组”)。该成员包含一个整数的2向量。

首先,重要的是记住,在Python中执行

a['tuple'][0] = (1,2)

等同于

b = a['tuple']; b[0] = (1,2)

同样,

a[0]['tuple'] = (1,2)

等同于

b = a[0]; b['tuple'] = (1,2)

为了理解这种行为,我们需要分析两个代码路径以及发生的事情。您在'a'中构建了一个(3,)数组。当您写入b = a['tuple']时,您可能应该得到一个(2,)-整数的(3,)数组,但由于目前在NumPy中没有正式支持(n,)-整数作为一般数据类型,因此您会得到一个(3,2)的整数数组,这是NumPy可以给您提供的最接近的东西。通过

a['tuple'][0] = (1,2)

设置此对象的[0]行可以正常工作并执行您所期望的操作。

另一方面,当您键入:

b = a[0]

您将获得一个数组标量,它是一种特别有趣的数组标量,可以保存记录。这个新对象正式是numpy.void类型,并且它保存了任何适合“VOID”基本dtype下的“标量表示”。

出于某种原因:

b['tuple'] = [1,2]

不起作用。在我的系统上,我得到了一个不同的错误:TypeError: object of type 'int' has no len()

我认为这应该被归档为问题跟踪器上的错误,暂时在这里:http://projects.scipy.org/numpy

问题最终是在voidtype_setfields中调用void->copyswap函数,如果有人想要调查的话。我认为这种行为应该起作用。

这个的解释可以在numpy bug report中找到。


8

我遇到的错误不同于你的(使用numpy 1.7.0.dev):

ValueError: setting an array element with a sequence.

因此,下面的解释可能不适用于您的系统(或者甚至可能是我所看到的错误解释)。
首先,请注意索引一个结构数组的行将给您一个numpy.void对象(请参见数据类型文档)。
import numpy as np
dt = np.dtype([('tuple', (int, 2))])
a = np.zeros(3, dt)
print type(a[0]) # = numpy.void

据我所了解,void 类似于 Python 中的列表,因为它可以保存不同数据类型的对象。这很有道理,因为结构化数组中的列可以是不同的数据类型。
如果您不使用索引而是切片获取第一行,你会得到一个 ndarray
print type(a[:1]) # = numpy.ndarray

这类似于Python列表的工作方式:
b = [1, 2, 3]
print b[0] # 1
print b[:1] # [1]

切片可以返回原始序列的缩短版本,而索引则返回一个元素(此处为int类型;上面是void类型)。 因此,当你切片结构化数组的行时,你应该期望它的行为与原始数组相同(只是少了几行)。按照你的例子继续进行,现在你可以对第一行的'tuple'列进行赋值。
a[:1]['tuple'] = (1, 2)

所以,为什么 a[0]['tuple'] = (1, 2) 不起作用呢?

好的,回想一下 a[0] 返回一个 void 对象。因此,当你调用

a[0]['tuple'] = (1, 2) # this line fails

你正在将一个tuple分配给该void对象的'tuple'元素。 注意: 尽管你将此索引称为“tuple”,但它存储为ndarray

print type(a[0]['tuple']) # = numpy.ndarray

因此,这意味着元组需要转换为一个ndarray。然而,void对象不能进行转换赋值(这只是一个猜测),因为它可以包含任意数据类型,所以它不知道要转换成什么类型。为了解决这个问题,您可以自己进行转换:

a[0]['tuple'] = np.array((1, 2))

我们收到不同的错误提示,这意味着上述行可能对您无效,因为强制转换只解决了我收到的错误,而没有解决您收到的错误。

补充:

那么为什么以下代码可以工作呢?

a[0]['tuple'][:] = (1, 2)

在这里,当你添加[:]时,你正在数组中进行索引,但是没有添加它时,你正在void对象中进行索引。换句话说,a[0]['tuple'][:]表示“替换存储的数组元素”(由数组处理),a[0]['tuple']表示“替换存储的数组”(由void处理)。
结语:
奇怪的是,访问行(即使用0进行索引)似乎会丢失基本数组,但仍然允许您对基本数组进行赋值。
print a['tuple'].base is a # = True
print a[0].base is a # = False
a[0] = ((1, 2),) # `a` is changed

也许 void 不是真正的数组,所以它没有基本数组,... 但为什么它有一个 base 属性呢?

2
这是一个上游错误,在NumPy PR#5947中已经修复,并在1.9.3中进行了修复。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接