Я просто возился с массивами numpy, когда понял менее известное поведение параметра dtypes.
Кажется, что он меняется при изменении ввода. Например,
t = np.array([2, 2])
t.dtype
дает dtype('int32')
Однако,
t = np.array([2, 22222222222])
t.dtype
дает dtype('int64')
Итак, мой первый вопрос: как это рассчитывается? Делает ли это тип данных подходящим для максимального элемента в качестве типа данных для всех элементов? Если это так, не думаете ли вы, что для этого требуется больше места, потому что он излишне хранит избыточную память для хранения 2 во втором массиве как 64-битное целое число?
Теперь снова, если я хочу изменить нулевой элемент array([2, 2]) как
t = np.array([2, 2])
t[0] = 222222222222222
Я получаю OverflowError: Python int too large to convert to C long.
Мой второй вопрос: почему он не поддерживает ту же логику, что и при создании массива, если вы измените определенное значение? Почему он не пересчитывает и не переоценивает?
Любая помощь приветствуется. Заранее спасибо.
После создания dtype массива фиксируется. Установленные значения изменяются, чтобы соответствовать этому dtype, если это возможно. view и astype создают новые массивы. np.array — сложная функция, способная обрабатывать самые разные входные данные. Он оценивает все входные значения и выбирает тип dtype, который подходит для всех. Большинство из нас принимает этот выбор как операцию черного ящика — результаты обычно логичны.
@hpaulj «Большинство из нас принимают этот выбор как операцию черного ящика». Вы только что сделали мой день :-)






Давайте попробуем найти соответствующие биты в документах.
из строки документа np.array:
array(...)
[...]
Parameters
[...]
dtype : data-type, optional The desired data-type for the array. If not given, then the type will be determined as the minimum type required to hold the objects in the sequence. This argument can only be used to 'upcast' the array. For downcasting, use the .astype(t) method.
[...]
(мой акцент)
Следует отметить, что это не совсем точно, например, для целочисленных массивов целое число системы (C) по умолчанию предпочтительнее, чем меньшие целочисленные типы, как видно из вашего примера.
Обратите внимание, что для быстрой работы numpy важно, чтобы все элементы массива были одного размера. Иначе как бы вы быстро нашли, скажем, 1000-й элемент? Кроме того, смешивание типов не сэкономит столько места, поскольку вам придется хранить типы каждого отдельного элемента поверх необработанных данных.
Повторяю ваш второй вопрос. Прежде всего. В numpy есть правила продвижения типов. Лучший документ, который я смог найти для этого, — это строка документа np.result_type:
result_type(...) result_type(*arrays_and_dtypes)
Returns the type that results from applying the NumPy type promotion rules to the arguments.
Type promotion in NumPy works similarly to the rules in languages like C++, with some slight differences. When both scalars and arrays are used, the array's type takes precedence and the actual value of the scalar is taken into account.
For example, calculating 3*a, where a is an array of 32-bit floats, intuitively should result in a 32-bit float output. If the 3 is a 32-bit integer, the NumPy rules indicate it can't convert losslessly into a 32-bit float, so a 64-bit float should be the result type. By examining the value of the constant, '3', we see that it fits in an 8-bit integer, which can be cast losslessly into the 32-bit float.
[...]
Я не цитирую здесь все целиком, обратитесь к строке документа для получения более подробной информации.
Точный способ применения этих правил сложен и, по-видимому, представляет собой компромисс между интуитивностью и эффективностью.
Например, выбор основан на входных данных, а не на результате.
>>> A = np.full((2, 2), 30000, 'i2')
>>>
>>> A
array([[30000, 30000],
[30000, 30000]], dtype=int16)
# 1
>>> A + 30000
array([[-5536, -5536],
[-5536, -5536]], dtype=int16)
# 2
>>> A + 60000
array([[90000, 90000],
[90000, 90000]], dtype=int32)
Здесь побеждает эффективность. Возможно, было бы более интуитивно понятно, если бы №1 вел себя как №2. Но это будет дорого.
Кроме того, и это более непосредственно связано с вашим вопросом, продвижение типа применяется только вне места, а не на месте:
# out-of-place
>>> A_new = A + 60000
>>> A_new
array([[90000, 90000],
[90000, 90000]], dtype=int32)
# in-place
>>> A += 60000
>>> A
array([[24464, 24464],
[24464, 24464]], dtype=int16)
или
# out-of-place
>>> A_new = np.where([[0, 0], [0, 1]], 60000, A)
>>> A_new
array([[30000, 30000],
[30000, 60000]], dtype=int32)
# in-place
>>> A[1, 1] = 60000
>>> A
array([[30000, 30000],
[30000, -5536]], dtype=int16)
Опять же, это может показаться довольно неинтуитивным. Однако для такого выбора есть веские причины.
И они должны ответить на ваш второй вопрос:
Переход на больший тип dtype потребует выделения большего буфера и копирования всех данных. Мало того, что это было бы дорого для больших массивов.
Многие идиомы в numpy полагаются на представления и тот факт, что запись в представление напрямую изменяет базовый массив (и другие перекрывающиеся представления). Следовательно, массив не может свободно изменять свой буфер данных, когда захочет. Чтобы не разорвать связь между представлениями, массиву необходимо знать обо всех представлениях в своем буфере данных, что добавит много административной нагрузки, и все эти представления также должны будут изменить свои указатели данных и метаданные. И если первый массив сам является представлением (скажем, фрагментом) в другой массив, все становится еще хуже.
Я полагаю, мы можем согласиться с тем, что это того не стоит, и поэтому типы не продвигаются на месте.
Я считаю, что
int32является значением по умолчаниюdtypeдля целых чисел в numpy. Однако22222222222больше, чем2**32//2-1(максимальное значение дляint32), поэтому вместо него используетсяint64.