鉴于您将新字段添加到结构化数组的一维切片中,为什么不能将新字段的条目设置为列表?

Given that you added a new field to a 1-d slice of a structured array, why can you not set the entry of the new field to a list?

标题可能有点混乱,所以我希望我能借助一个例子来解释清楚。图片 我有一个小辅助函数,可以将新字段添加到现有的结构化数组中:

import numpy as np


def add_field(a, *descr):
    b = np.empty(a.shape, dtype=a.dtype.descr + [*descr])
    for name in a.dtype.names:
        b[name] = a[name]
    return b

给定一个结构化数组,我可以简单地使用它来添加新字段:

a = np.array(
    [(1, False), (2, False), (3, False), (4, True)],
    dtype=[('id', 'i4'), ('used', '?')]
)
print(a)
b = add_field(a, ('new', 'O'))
print(b)

然后我可以毫无问题地将新创建字段的条目设置为(空)列表:

b[0]['new'] = []

我也可以创建一个新数组,它只是原始数组的一部分,然后向这个新数组添加一个新字段:

c = a[0]
print(c)
d = add_field(c, ('newer', 'O'))
print(d)

但是如果我现在尝试将新字段设置为一个(空)列表,它不起作用:

d['newer'] = []

ValueError: assignment to 0-d array

这是为什么?根据 add_fieldd 是一个全新的数组,恰好与 b 共享相同的字段和条目。有趣的是,b[0]的形状是(),而d的形状是(1,)(还有type(b)np.voidtype(d)np.array)。也许这与它有关?同样有趣的是,所有这些都有效:

d['newer'] = 1.34
d['newer'] = False
d['newer'] = None
d['newer'] = add_field
d['newer'] = set()
d['newer'] = {}
d['newer'] = {'test': []}

但是,使用键 'test' 访问最后 dict 中的值不会:

>>> d['newer'] = {'test': []}
>>> d['newer']
>>> array({'test': []}, dtype=object)
>>> d['newer']['test']
>>> IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
>>> d['newer'][0]
>>> IndexError: too many indices for array

这很令人困惑。

编辑

好吧,我只是试着像这样修改 add_field 函数:

def add_field(a, *descr):
    shape = a.shape if len(a.shape) else (1,)
    b = np.empty(shape, dtype=a.dtype.descr + [*descr])
    for name in a.dtype.names:
        b[name] = a[name]
    return b

但这没有帮助:

>>> d = add_field(a[0], ('newer', 'O'))
>>> d
>>> array([(1, False, None)], dtype=[('id', '<i4'), ('used', '?'), ('test', 'O')])
>>> d.shape
>>> (1,)
>>> d['newer'] = []
>>> ValueError: cannot copy sequence with size 0 to array axis with dimension 1

所以我猜不是这样。然而,这现在有效:

>>> d['newer'][0] = []

但我不喜欢这种解决方法。我希望它能像 b[0].

一样工作

编辑 2

如果我进一步修改 add_field 函数,我可以强制执行想要的行为,尽管我不是 100% 喜欢它:

def add_field(a, *descr):
    shape = a.shape if len(a.shape) else (1,)
    b = np.empty(shape, dtype=a.dtype.descr + [*descr])
    for name in a.dtype.names:
        b[name] = a[name]
    return b if len(a.shape) else b[0]

d = add_field(a[0], ('newer', 'O'))
d['newer'] = []

总结评论:

原始问题中的问题似乎是返回对象的形状 - 当您这样做时

c = a[0]

a 的形状为 (n,) 你不是从数组中取出一个切片而是一个元素。 c.shape那么就是()。当您将形状数组 () 传递给 add_field 时,

创建的新数组
b = np.empty(a.shape, dtype=a.dtype.descr + [*descr])

也将具有形状 ()。但是,结构化数组必须具有 (n,) 的形状(尽管 documentation 中未概述)。

与问题的第一次编辑一样,正确的修改是

def add_field(a, *descr):
    shape = a.shape if len(a.shape) else (1,)
    b = np.empty(shape, dtype=a.dtype.descr + [*descr])
    b[list(a.dtype.names)] = a
    return b

返回的对象将共享形状 (n,) 结构化数组的属性,其中:

  1. 如果您在整数位置索引数组,您将得到一个结构(例如 d[0]
  2. 您可以通过使用字段名称进行索引(例如 d['newer'])来访问和修改结构化数组的各个字段

通过上述修改,问题中 d 的行为与 b 相同,例如

d[0]['newer'] = []

有效,

也是如此
b[0]['new'] = []

这就把我们带到了问题的真正症结所在:


为什么我们不能使用 d['newer']=[] 语法为字段的每个元素分配一个空列表?

当您使用此语法分配可迭代对象而不是标量时,numpy 会尝试按元素分配(或根据可迭代对象进行广播)。这不同于标量的分配,其中标量分配给该字段的每个元素。 documentation 在这一点上并不清楚,但我们可以通过使用

获得更有帮助的错误
b['new'] = np.array([])
Traceback (most recent call last):
  File "structuredArray.py", line 20, in <module>
    b['new'] = np.array([])
ValueError: could not broadcast input array from shape (0) into shape (4)

所以这里的问题不是如何添加字段,而是您如何尝试为该字段的每个元素分配一个空列表。正确的做法应该是

b['new'] = [[]*b.shape[0]]

对于 (1,)(4,) 形状的结构化数组按预期工作:

import numpy as np

def add_field(a, *descr):
    shape = a.shape if len(a.shape) else (1,)
    b = np.empty(shape, dtype=a.dtype.descr + [*descr])
    for name in a.dtype.names:
        b[name] = a[name]
    return b

a = np.array(
    [(1, False), (2, False), (3, False), (4, True)],
    dtype=[('id', 'i4'), ('used', '?')]
)

b = add_field(a, ('new', 'O'))
b['new'] = [[]*b.shape[0]]
print(b)

c = a[0]
d = add_field(c, ('newer', 'O'))
d['newer'] = [[]*d.shape[0]]
print(d)
[(1, False, list([])) (2, False, list([])) (3, False, list([])) (4,  True, list([]))]
[(1, False, list([]))]