Dentro de la biblioteca estándar de Python dispones de auténticas joyas, muchas veces ignoradas u olvidadas. Es por ello que voy a empezar un breve pero intenso recorrido por algunas piezas de arte disponibles de serie.
collections
Con la ayuda de este módulo puedes aumentar las estructuras de datos típicas disponibles en Python (listas, tuplas, diccionarios,...). Veamos algunas utilidades disponibles:
ChainMap
Solo Python 3. Actualízate!!
Dicho en bruto, es un conglomerado de diccionarios (también conocidos como mappings o hash tables).
Para que puede ser útil:
Ejemplo, imaginemos que tenemos un diccionario de configuración dict_a
, que posee las claves a
y b
, y queremos actualizar sus valores con otros pares clave:valor que están en el diccionario dict_b
, que posee las claves b
y c
. Podemos hacer:
In [1]:
from collections import ChainMap
dict_a = {'a': 1, 'b': 10}
dict_b = {'b': 100, 'c': 1000}
cm = ChainMap(dict_a, dict_b)
for key, value in cm.items():
print(key, value)
Hemos añadido el valor de la clave c
de dict_b
sin necesidad de modificar nuestro diccionario original de configuración dict_a
, es decir, hemos hecho un 'cambio' reversible. También podemos 'sobreescribir' las claves que están en nuestro diccionario original de configuración, dict_b
variando los parámetros del constructor:
In [2]:
cm = ChainMap(dict_b, dict_a)
for key, value in cm.items():
print(key, value)
Vemos que, además de añadir la clave c
, hemos sobreescrito la clave b
.
Los diccionarios originales están disponibles haciendo uso del atributo maps
:
In [3]:
cm.maps
Out[3]:
Ejercicio: haced un dir
de cm
y un dir
de dict_a
y veréis que los atributos y métodos disponibles son parecidos.
Más información en este hilo de stackoverflow en el que me he basado para el ejemplo anterior (¿basar y copiar no son sinónimos?).
Counter
Permite contar ocurrencias de forma simple. En realidad, su funcionalidad se podría conseguir sin problemas con algunas líneas extra de código pero ya que lo tenemos, está testeado e implementado por gente experta vamos a aprovecharnos de ello.
En la documentación oficial hay algunos ejemplos interesantes y en github podéis encontrar unos cuantos más. Veamos un ejemplo simple pero potente, yo trabajo mucho con datos meteorológicos y uno de los problemas recurrentes es tener fechas repetidas que no deberían existir (pero pasa demasiado a menudo). Una forma rápida de buscar problemas de estos en ficheros y lanzar una alarma cuando ocurra lo que buscamos, sería:
In [4]:
from io import StringIO
from collections import Counter
virtual_file = StringIO("""2010/01/01 2.7
2010/01/02 2.2
2010/01/03 2.1
2010/01/04 2.3
2010/01/05 2.4
2010/01/06 2.2
2010/01/02 2.2
2010/01/03 2.1
2010/01/04 2.3
""")
if Counter(virtual_file.readlines()).most_common(1)[0][1] > 1:
print('fichero con fecha repetida')
In [5]:
import numpy as np
import datetime as dt
from pprint import pprint
datos = {
'valores': np.random.randn(100),
'frecuencia': dt.timedelta(minutes = 10),
'fecha_inicial': dt.datetime(2016, 1, 1, 0, 0),
'parametro': 'wind_speed',
'unidades': 'm/s'
}
pprint(datos)
Lo anterior es simple y rápido pero usando una namedtuple
dispongo de algo parecido con algunas cosas extra. Veamos un ejemplo similar usando namedtuple
:
In [6]:
from collections import namedtuple
Datos = namedtuple('Datos', 'valores frecuencia fecha_inicial parametro unidades')
datos = Datos(np.random.randn(100),
dt.timedelta(minutes = 10),
dt.datetime(2016, 1, 1, 0, 0),
'wind_speed',
'm/s')
print(datos)
Ventajas que le veo con respecto a lo anterior:
In [7]:
print(datos.valores)
verbose = True
. Usa exec
entre bambalinas (o_O). Puedo ver que todas las claves se transforman en property
's. Puedo ver que se crea documentación... MAGIA en estado puro!!!(Si no quieres usar la keyword verbose = True
puedes seguir teniendo acceso en un objeto usando obj._source
)
In [10]:
Datos = namedtuple('Datos', 'valores frecuencia fecha_inicial parametro unidades', verbose = True)
In [11]:
# Lo mismo de antes
print(datos._source)
OrderedDict
, también incluido en el módulo collections
) si así lo deseo:
In [13]:
datos._asdict()['valores']
Out[13]:
valores
:
In [14]:
class DatosExtendidos(Datos):
def media(self):
"Calcula la media de los valores."
return self.valores.mean()
datos_ext = DatosExtendidos(**datos._asdict())
print(datos_ext.media())
WOW!!!!!
Los ejemplos en la documentación oficial son muy potentes y dan nuevas ideas de potenciales usos.
deque
Otra joyita que quizá debería usar más a menudo sería deque
. Es una secuencia mutable (parecido a una lista), pero con una serie de ventajas. Es una cola/lista cuyo principio y fin es 'indistinguible', es thread-safe y está diseñada para poder insertar y eliminar de forma rápida en ambos extremos de la cola (ahora veremos qué significa todo esto). Un uso evidente es el de usar, por ejemplo, una secuencia como stream de datos con un número de elementos fijo y/o rápidamente actualizable:
Veamos un ejemplo:
In [15]:
from collections import deque
dq = deque(range(10), maxlen = 10)
lst = list(range(10))
print(dq)
print(lst)
In [16]:
# los tres últimos elementos los anexa nuevamente al principio de la secuencia.
dq.rotate(3)
print(dq)
lst = lst[-3:] + lst[:-3]
print(lst)
Veamos la eficiencia de esta operación:
In [17]:
tmp = deque(range(100000), maxlen = 100000)
%timeit dq.rotate(30000)
tmp = list(range(100000))
%timeit tmp[-30000:] + tmp[:-30000]
Con una queue
podemos anexar de forma eficiente a ambos lados:
In [18]:
dq.append(100)
print(dq)
dq.appendleft(10000)
print(dq)
In [19]:
dq.extend(range(10))
print(dq)
dq.extendleft([10, 100])
print(dq)
Etc.
Puedes hacer cosas similares a las hechas con listas pero de forma más eficiente y práctica en determinados casos!!
Recordad que, además, disponemos del módulo queue
en la librería estándar.