Учитывая, что вы добавили новое поле в 1-й фрагмент структурированного массива, почему вы не можете установить запись нового поля в список?

Название может быть немного запутанным, поэтому я надеюсь, что смогу прояснить его с помощью примера. У меня есть небольшая вспомогательная функция, которая добавляет новые поля к уже существующим структурированным массивам:

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_field , d - это совершенно новый массив, который имеет те же поля и записи, что и b . Интересно, что форма b[0] - это () , а форма d - (1,) (а также type(b) - np.void а type(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': []}

Тем не менее, доступ к кавычкам в последнем dict с помощью ключа 'test' не делает:

>>> 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 функцию 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'] = []

Всего 1 ответ


Чтобы суммировать комментарии:

Проблема в исходном вопросе выглядит как форма возвращаемого объекта - когда вы делаете, например,

c = a[0]

с имеющей форму (n,) вы берете не фрагмент из массива, а отдельный элемент. тогда c.shape is () . Передача массива shape () в add_field массив, созданный

b = np.empty(a.shape, dtype=a.dtype.descr + [*descr])

Также будет иметь форму () . Однако для структурированного массива необходимо иметь форму (n,) (хотя это не указано в документации ).

Как и при первом редактировании вопроса, правильное изменение будет

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

Возвращенный объект затем будет использовать свойства структурированного массива shape (n,) :

  1. Если вы индексируете массив в целочисленной позиции, вы получите структуру (например, d[0] )
  2. Вы можете обращаться к отдельным полям структурированного массива и изменять их, индексируя по имени поля (например, d['newer'] )

С вышеуказанной модификацией поведение d в вопросе такое же, как b например

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

действителен, как есть

b[0]['new']

Это подводит нас к реальной сути вопроса:


Почему мы не можем назначить пустой список каждому элементу поля, используя синтаксис d['newer'] = [] ?

Когда вы назначаете итератор вместо скаляра с использованием этого синтаксиса, numpy пытается поэлементное назначение (или широковещательную передачу в зависимости от итерируемого). Это отличается от назначения скаляра, в котором скаляр назначается каждому элементу этого поля. Документация не ясна по этому вопросу, но мы можем получить гораздо более полезную ошибку, используя

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([]))]

Есть идеи?

10000