6.2. O ndarray¶
O ndarray é o tipo que armazena dados numéricos no numpy. Ele é duas coisas em uma: um único bloco compacto de dados e um pequeno descritor à frente desse bloco que diz como lê-lo.
6.2.1. Dentro da caixa¶
O bloco de dados contém todos os elementos do array um após o outro, sem nada extra entre eles. Cada elemento ocupa o mesmo número de bytes – um para um array de valores uint8, dois para uint16, quatro para float. Um array uint8 de 256 elementos tem exatamente 256 bytes de dados; os mesmos 256 números em uma list Python ocupam um kilobyte – um slot de 32 bits por elemento, independentemente de quão poucos bits o valor realmente precisa.
O descritor registra o que o bloco significa. Cinco valores são suficientes para descrever qualquer array retangular, não importa quantas dimensões:
dtype– o tipo de elemento. Decide quantos bytes cada elemento ocupa e qual faixa de valores ele pode conter (abordado em Dtypes).itemsize– a largura em bytes de um elemento, derivada do dtype.ndim– o número de dimensões (1 para um vetor, 2 para uma matriz, 3 para um volume, até 4).shape– o tamanho ao longo de cada dimensão como uma tupla.strides– como percorrer o bloco de dados para caminhar por cada eixo. Abordado em Formato e strides.
É isso. Todo caminho rápido no numpy – aritmética, reduções, broadcasting, fatiamento – funciona diretamente a partir desses cinco valores mais o ponteiro de dados, sem overhead de Python por elemento.
6.2.2. O que o design proporciona¶
Três propriedades decorrem de “bloco compacto + descritor pequeno” e definem como o resto da seção se comporta.
A matemática elemento a elemento roda como uma única chamada. a + b entre dois arrays de forma compatível soma os dois buffers e escreve um terceiro, tudo dentro de uma única chamada de biblioteca. np.sin(a) faz o mesmo para o seno de cada elemento. Os operadores aritméticos, de comparação e bit a bit todos funcionam dessa forma.
Olhar para os mesmos dados de uma forma diferente é gratuito. Pedir uma sub-região de um array, ou os mesmos dados dispostos sob uma forma diferente, não move nenhum byte. A operação retorna um novo descritor apontando para o mesmo bloco de dados. O resultado é chamado de view – uma segunda janela para o mesmo buffer subjacente. Escrever através de uma view escreve na origem.
Formas mistas ainda funcionam. Um array mais curto contra um mais longo, uma linha contra uma matriz, uma coluna contra uma linha – o numpy os alinha por broadcasting, um pequeno conjunto de regras que decide qual eixo curto se estica para coincidir com o longo. O esticamento é virtual; nenhum dado é duplicado.
6.2.3. O que o design custa¶
Duas restrições decorrem do mesmo design.
Todo elemento tem o mesmo tipo. Uma lista pode conter um int ao lado de uma str ao lado de uma lista de mais três valores int; um ndarray não pode. O dtype é fixado no momento da construção. A página Dtypes aborda o pequeno conjunto de tipos que o numpy suporta e as regras que decorrem de fixar um.
Crescer um array não é gratuito. Uma lista mantém slots de reserva no fim e suporta .append de forma barata. Um ndarray tem exatamente o tamanho que precisa ter; anexar significaria alocar um novo buffer maior e copiar o conteúdo antigo para dentro dele. Não há método append(), de propósito. O padrão certo na câmera é pré-alocar o destino em seu tamanho final e preenchê-lo; Desempenho aborda a técnica.
Com um buffer tipado e compacto para os dados, um pequeno descritor para os metadados e três garantias comportamentais (matemática elemento a elemento rápida, views alternativas dos mesmos dados sem cópia e formas que fazem broadcasting), o ndarray é a fundação sobre a qual o resto do capítulo se apoia. Como um array realmente passa a existir – de um literal, de uma alocação pré-preenchida, de um buffer de periférico – é a próxima questão prática.