Arrays en python

De ChuWiki

Python viene con un módulo array que nos permite manejar arrays de diversos tipos de forma eficiente. Soporta todo tipo de variantes de caracteres, enteros y flotantes: unsigned, long, short, double, etc. Veamos algunos ejemplos.

Creación del array[editar]

Lo primero es importar el módulo

>>> import array

El array se crea con una llamada a array.array(typecode[, initializer]). typecode es una letra que indica qué tipo de array queremos. initializer puede ser un string o una lista de elementos que será con los que se inicialice el array.

Los posibles tipos que podemos poner en typecode son los siguientes

Type code Tipo en C Tipo en Python Tamaño mínimo en bytes Comentarios
'b' signed char int 1
'B' unsigned char int 1
'u' wchar_t Unicode character 2 Dependiendo de la plataforma puede ser uno o dos bytes
'h' signed short int 2
'H' unsigned short int 2
'i' signed int int 2
'I' unsigned int int 2
'l' signed long int 4
'L' unsigned long int 4
'q' signed long long int 8
'Q' unsigned long long int 8
'f' float float 4
'd' double float 8

El typecode es la letra de la primera columna. Es interesante la columna "Tamaño minimo de bytes" que nos indica cuántos bytes va a tener cada elemento según el tipo que elijamos.

Un ejemplo sencillo con enteros

>>> numeros = array.array('h',[1,2,3])
>>> numeros
array('h', [1, 2, 3])


Hemos creado el array de tipo h, es decir, signed short, dos bytes cada elemento. Lo hemos inicializado con una lista de tres números.

Veamos qué pasa si ponemos un número que exceda el rango de signed short que va de -32,768 a 32,767

>>> numeros = array.array('h',[1,2,100000])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: signed short integer is greater than maximum

Obtenemos un error de que uno de los números es mayor que el rango que admite signed short.

Veamos ahora que pasa si metemos un string como inicializador.

>>> letras = array.array('u',"juan")
>>> letras
array('u', 'juan')

El tipo debe ser u que es el que corresponde a caracters.

Añadir elementos al array: append(), extend() e insert()[editar]

El método append() permite añadir un elemento nuevo al final del array

>>> numeros = array.array('h',[1,2,3])
>>> numeros
array('h', [1, 2, 3])
>>> numeros.append(4)
>>> numeros
array('h', [1, 2, 3, 4])

El método extend() permite añadir una coleción iterable al final del array. Puede ser otro array o cualquier objeto python iterable. Si se añade un array, debe ser del mismo tipo. Veamos el ejemplo con un array

>>> numeros = array.array('h',[1,2,3])
>>> numeros2 = array.array('h',[4,5,6])
>>> numeros.extend(numeros2)
>>> numeros
array('h', [1, 2, 3, 4, 4, 5, 6])

Y ahora un ejemplo con una lista

>>> numeros = array.array('h',[1,2,3])
>>> numeros.extend([4,5,6])
>>> numeros
array('h', [1, 2, 3, 4, 5, 6])

Al método insert() se le pasa una posición y un elemento. Inerta el elemento justo antes del elemento que está en la posición indicada. Si se indica una posición negativa, las posiciones empiezan a contarse desde el último elemento del array. Veamos ejemplos de ambos, con posición positiva y negativa.

>>> numeros = array.array('h',[1,2,3])
>>> numeros.insert(2,10)
>>> numeros
array('h', [1, 2, 10, 3])

>>> numeros = array.array('h',[1,2,3])
>>> numeros.insert(-2,10)
>>> numeros
array('h', [1, 10, 2, 3])

En el primer caso, el elemento con posición 2 es el 3. Recuerda que la posición 0 es el primer elemento de la lista. Así que numeros.insert(2,10) inserta un 10 delante del 3. En el segundo caso, al ser -2 negativo, se empieza a contar por el final. El 3 ocupa la posición 0, el 2 la -1 y el 1 la posición -2. Se inserta el 10 delante del 1, pero como estamos leyendo desde el final, en realidad se inserta detrás del 1.

Eliminar elementos del array: pop() y remove()[editar]

Al método pop() se le pasa una posición, nos devuelve el elemento en dicha posición y elimina el elemento del array.

>>> numeros = array.array('h',[1,2,3])
>>> numeros.pop(1)
2
>>> numeros
array('h', [1, 3])

Eliminamos el elemento en la posición 1, que es el 2.

Al método remove() se le pasa un elemento y elimina la primera aparición de dicho elemento en el array.

>>> numeros = array.array('h',[10,20,30,10,20,30])
>>> numeros.remove(20)
>>> numeros
array('h', [10, 30, 10, 20, 30])

Modificar el array: byteswap() y reverse()[editar]

El método reverse() poner el array en orden inverso

>>> numeros = array.array('h',[1,2,3])
>>> numeros.reverse()
>>> numeros
array('h', [3, 2, 1])

El método byteswap() da la vuelta a los bytes de cada elemento. Este método es útil si queremos convertir de big endian a little endian o viceversa. Hay números que ocupan varios bytes en memoria, por ejemplo, los short ocupan 2 bytes. Según la marca del microprocesador HW los bytes de un número pueden guardarse en un orden o en otro. Por ejemplo, el número 65 expresado como dos bytes sería 0,65. Algunos micros, los big endian, guardarían los bytes en ese orden: 0,65. Otros micros, los little endian, los guardan en sentido inverso: 65,0.

Si escribimos uno de estos arrays en un fichero o lo mandamos por red para que lo lea otro ordeandor, es posible que uno de los micros sean big endian y el otro little endian, por lo que los bytes no se interpretarían correctamente. El método byteswap() nos ayuda en este sentido, da la vuelta a los bytes procedentes de otro micro con un endian distinto al nuestro para que podamos interpretar correctamente los valores.

Veamos un ejemplo


>>> numeros = array.array('h',[1,2,3])
>>> numeros.byteswap()
>>> numeros
array('h', [256, 512, 768])
  • El 1, en dos bytes, es 0,1. Si les damos la vuelta, es 1,0. Y eso es un 1*256+0 = 256.
  • En 2, en dos bytes, es 0,2. Si les damos la vuelta, es 2,0. Y eso es un 2*256+0 = 512.
  • En 3, en dos bytes, es 0,3. Si les damos la vuelta, es 3,0. Y eso es un 3*256+0 = 768.

Consultar el array: count() e index()[editar]

El método count() nos indica cuántas veces aparece un elemento repetido en el array

>>> numeros = array.array('h',[10,20,20,30,30,30])
>>> numeros.count(20)
2

El método index() nos indica en qué posición aparece un elemento en el array por primera vez. Admite como parámetros opcionales en índica de inicio y fin de búsqueda. Lanza un ValueError si no encuentra el elemento en el array.

>>> numeros = array.array('h',[10,20,30,10,20,30,10,20,30])
>>> numeros.index(20)
1

>>> numeros.index(20,2)
4

>>> numeros.index(20,2,4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: array.index(x): x not in array2

En el ejemplo hemos creado un array con elementso repetidos y hemos hecho tres pruebas con el método index():

  • En el primer caso, sin parámetros opcionales. Buscamos el 20 y su primera aparición es en la posición 1. Recuerda que el primer elemento tiene posición 0.
  • En el segundo caso, le indicamos con el segundo parámetro que comience a buscar a partir de la posicón 2. Encuentra el 20 en la posición 4, es decir, el quito elemento.
  • En el tercer caso, le indicamos que busque entre la posición 2 y la 4. La 4 está excluida. Los elementos de las posiciones 2 y 3 son [30,10]. Como ahí no hay un 20, salta un ValueError.

Leer metadatos del array: typecodes, typecode, itemsize, buffer_info()[editar]

El atributo array.typecodes de la clase array nos da los posibles tipos soportados. Si la versión de python es la misma con la que obtuvimos la tabla del principio, deberían coincidir con la primera columna.

>>> array.typecodes
'bBuhHiIlLqQfd'

El atributo typecode de nuestra instancia concreta de array nos dice de qué type code es el array

>>> numeros = array.array('h',[1,2,3])
>>> numeros.typecode
'h'

El atributo itemsize nos dice el tamaño en bytes de un elemento del array

>>> numeros = array.array('h',[1,2,3])
>>> numeros.itemsize
2

El método buffer_info() nos da dos valores: la dirección de memoria donde está guardado el array y el número de elementos del array.

>>> numeros = array.array('h',[1,2,3])
>>> numeros.buffer_info()
(2285706757648, 3)

La dirección de memoria no es útil salvo que nuestro programa se vaya a meter a bajo nivel con direcciones de memoria.

Si queremos saber la longitud total en bytes que ocupa nuestro array debemos multiplicar itemsize por el número de elementos del array

>>> numeros = array.array('h',[1,2,3])
>>> numeros.buffer_info()[1] * numeros.itemsize
6

Convertir el array a y de otros formatos[editar]

tobytes() y frombytes()[editar]

El array puede convertirse en bytes con el método tobytes() y puede volver a obtenerse a partir de los bytes con frombytes()

>>> numeros = array.array('h',[1,2,3])
>>> numeros.tobytes()
b'\x01\x00\x02\x00\x03\x00'

>>> numeros2 = array.array('h')
>>> numeros2.frombytes(b'\x01\x00\x02\x00\x03\x00')
>>> numeros2
array('h', [1, 2, 3])

Hemos exportado el array a bytes usando tobytes(). Luego hemos creado otro array vacío del mismo code type h y hemos importado los bytes con frombytes().

Si te fijas en la explicación que dimos sobre big endian, little endian y el método byteswap(), verás en estos bytes que el 1 queda en bytes como \x01\x00, es decir, little endian. El micro de mi ordenador es un intel que sí, usa little endian.

tofile() y fromfile()[editar]

Los métodos tofile() y fromfile() sirven para escribir el array en un fichero y recuperarlo despues.

>>> numeros = array.array('h',[1,2,3])
>>> f = open("array.bin", "wb")
>>> numeros.tofile(f)
>>> f.close()
>>> import os
>>> os.listdir()
[... , 'array.bin', ...]

Hemos creado el array. El fichero se abre con el nombre "array.bin" y debe ser binario y de escritura "wb". Con tofile() escribimos el fichero y lo cerramos. Usamos el módulo os que viene con python para ver que el fichero efectivamente se ha creado. os.listdir() nos da el listado de ficheros en el directorio actual. Saldrá una lista con todos los ficheros que tengamos y entre ellos debe estar "array.bin". Vamos ahora a leerlo.

>>> numeros2 = array.array('h')
>>> g = open("array.bin","rb")
>>> numeros2.fromfile(g,10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
EOFError: read() didn't return enough bytes
>>> g.close()
>>> numeros2
array('h', [1, 2, 3])

Hemos creado un array del mismo tipo. Abrimos el fichero "array.bin" de lectura y binario "rb". Con fromfile() leemos el fichero. El segundo parámetro es el número de elementos que queremos leer del fichero. Es obligatorio poner dicho parámetro. Si sabemos cuántos elementos queremos leer no hay problema. Si no sabemos cuántos elementos hay el fichero y queremos leerlos todos podemos

  • Calcular el número de elementos. Para ello podemos ver el tamaño del fichero en bytes con os.path.getsize("array.bin") y dividr entre numeros2.itemsize que es el tamaño en bytes de un elemento.
  • Poner un número muy grande. Se iran leyendo y añadiendo elementos hasta llegar al final de fichero. Ahí nos dará una excepción EOFError pero los elementos se abrán leído. Esto es lo que hemos hecho en el ejemplo anterior.

Veamos el mismo ejemplo calculando el tamaño del fichero

>>> numeros2 = array.array('h')
>>> numero_elementos = os.path.getsize("array.bin") / numeros2.itemsize
>>> numero_elementos
3.0
>>> g = open("array.bin","rb")
>>> numeros2.fromfile(g,int(numero_elementos))
>>> numeros2
array('h', [1, 2, 3])
>>> g.close()

En cualquier caso no suele ser buena idea leer todos los elementos a ciegas. Si el fichero es grande y los leemos todos podemos quedarnos escasos de memoria. Si calculamos el tamaño del fichero podemos tener una idea si va a caber todo en memoria o tenemos que leerlo y tratarlo en trozos. Si ponemos un número grande de elementos en fromfile() podemos poner un número grande que consideremos que cabe en memoria y estar pendintes si salta o no la excpeción EOFError para saber si quedan o no más elementos por leer en el fichero.

tolist() y fromlist()[editar]

tolist() convierte el array en una lista normal de python. fromlist() añade los elementos al array al final

>>> numeros = array.array('h',[1,2,3])
>>> lista = numeros.tolist()
>>> lista
[1, 2, 3]

>>> numeros2 = array.array('h')
>>> numeros2.fromlist(lista)
>>> numeros2
array('h', [1, 2, 3])

tounicode() y fromunicode()[editar]

tounicode() convierte el array a un string en codificación unicode. fromunicode() lee un string en formato unicode y lo añade al array. Los arrays deben ser de tipo u, correspondiente a tipo caracter unicode. Si no lo es, vemos más abajo como apañarlo.

>>> letras = array.array('u','hola mundo')
>>> letras.tounicode()
'hola mundo'

>>> letras2 = array.array('u')
>>> letras2.fromunicode('hola mundo')
>>> letras2
array('u', 'hola mundo')

Lo hemos convertido a string unicode con tounicode(). Creamos un segundo array de tipo "u" y usando fromunicode() volvemos a convertir el string a array.

Si el array no es de tipo "u", podemos usar los métodos tobytes() para converirlo en bytes y el método decode() de los byts para convertirlo en string. Para recuperarlo, el proceso es el inverso, encode() del string para obtener los bytes y frombytes() para volver a obtener el array.

>>> numeros = array.array('h',[1,2,3])
>>> numeros.tobytes().decode('utf-8')
'\x01\x00\x02\x00\x03\x00'

>>> numeros2 = array.array('h')
>>> numeros2.frombytes('\x01\x00\x02\x00\x03\x00'.encode('utf-8'))
>>> numeros2
array('h', [1, 2, 3])

En decode() y encode() hemos pasado como parámetro la codificación "utf-8" para asegurar que la cadena que se obtiene/lee es unicode.

Enlaces[editar]