6.2. De ndarray

De ndarray is het type dat numerieke data bevat in numpy. Het is twee dingen in één: een enkel ingepakt datablok en een kleine descriptor vóór dat blok die zegt hoe het te lezen.

6.2.1. Binnen de doos

Het datablok bevat elk element van de array van begin tot eind, met niets ertussen. Elk element neemt hetzelfde aantal bytes in – één voor een array van uint8-waarden, twee voor uint16, vier voor float. Een array van 256 elementen uint8 is precies 256 bytes data; dezelfde 256 getallen in een Python-list nemen een kilobyte in – één 32-bits slot per element, ongeacht hoe weinig bits de waarde daadwerkelijk nodig heeft.

De descriptor legt vast wat het blok betekent. Vijf waarden zijn genoeg om elke rechthoekige array te beschrijven, ongeacht hoeveel dimensies:

  • dtype – het elementtype. Bepaalt hoeveel bytes elk element inneemt en welk waardenbereik het kan bevatten (behandeld op Dtypes).

  • itemsize – de bytebreedte van één element, afgeleid van de dtype.

  • ndim – het aantal dimensies (1 voor een vector, 2 voor een matrix, 3 voor een volume, tot 4).

  • shape – de grootte langs elke dimensie als een tuple.

  • strides – hoe door het datablok te stappen om elke as te doorlopen. Behandeld op Vorm en strides.

Dat is het. Elk snel pad in numpy – rekenkunde, reducties, broadcasting, slicing – werkt rechtstreeks vanuit die vijf waarden plus de datapointer, zonder Python-overhead per element.

6.2.2. Wat het ontwerp oplevert

Drie eigenschappen vloeien voort uit “ingepakt blok + kleine descriptor” en bepalen hoe de rest van de sectie zich gedraagt.

Elementsgewijze wiskunde verloopt als een enkele aanroep. a + b tussen twee arrays met overeenkomende vorm telt de twee buffers op en schrijft een derde, allemaal binnen één bibliotheekaanroep. np.sin(a) doet hetzelfde voor de sinus van elk element. De rekenkundige, vergelijkings- en bitsgewijze operatoren werken allemaal op deze manier.

Naar dezelfde data op een andere manier kijken is gratis. Vragen om een deelgebied van een array, of om dezelfde data uitgelegd onder een andere vorm, verplaatst geen enkele byte. De bewerking geeft een nieuwe descriptor terug die naar hetzelfde datablok wijst. Het resultaat wordt een view genoemd – een tweede venster op dezelfde onderliggende buffer. Schrijven via een view schrijft naar de bron.

Gemengde vormen werken nog steeds. Een kortere array tegen een langere, een rij tegen een matrix, een kolom tegen een rij – numpy lijnt ze uit via broadcasting, een kleine set regels die bepalen welke korte as zich uitrekt om bij de lange te passen. De uitrekking is virtueel; geen data wordt gedupliceerd.

6.2.3. Wat het ontwerp kost

Twee beperkingen volgen uit hetzelfde ontwerp.

Elk element heeft hetzelfde type. Een lijst kan een int naast een str naast een lijst van drie nog int-waarden bevatten; een ndarray kan dat niet. De dtype ligt vast op het moment van constructie. De pagina Dtypes behandelt de kleine set types die numpy ondersteunt en de regels die voortkomen uit het vastleggen van één type.

Een array laten groeien is niet gratis. Een lijst houdt reserveslots aan het eind en ondersteunt .append goedkoop. Een ndarray is precies de grootte die hij nodig heeft; toevoegen zou betekenen dat er een nieuwe, grotere buffer wordt gealloceerd en de oude inhoud erin wordt gekopieerd. Er is met opzet geen append()-methode. Het juiste patroon op de camera is om de bestemming vooraf op zijn uiteindelijke grootte te alloceren en hem te vullen; Prestaties behandelt de techniek.

Met een ingepakte getypeerde buffer voor de data, een kleine descriptor voor de metadata, en drie gedragsgaranties (snelle elementsgewijze wiskunde, kopievrije alternatieve views van dezelfde data, en vormen die broadcasten), is de ndarray de fundering waarop de rest van het hoofdstuk rust. Hoe een array daadwerkelijk tot stand komt – vanuit een literal, vanuit een vooraf gevulde allocatie, vanuit een randapparaatbuffer – is de volgende praktische vraag.