Saltar al contenido principal

Tutorial de Pickle en Python: Serialización de objetos

Descubre el módulo pickle de Python: aprende sobre serialización, cuándo (no) usarla, cómo comprimir objetos pickle, multiprocesamiento ¡y mucho más!
22 feb 2024  · 16 min de lectura

Introducción a la serialización de objetos

Practica la carga de un archivo Pickled de Python con este ejercicio práctico.

¿Estás cansado de volver a ejecutar tu código Python cada vez que tienes que acceder a un marco de datos, una variable o un modelo de machine learning creados previamente?

La serialización de objetos puede ser la solución que buscas.

Es el proceso de almacenar una estructura de datos en memoria para poder cargarla o transmitirla cuando sea necesario sin perder su estado actual.

Aquí tienes un sencillo diagrama que explica cómo funciona la serialización:

Serialización de objetos Pickle Python

Imagen del autor

En Python, trabajamos con estructuras de datos de alto nivel, como listas, tuplas y conjuntos. Sin embargo, cuando queremos almacenar estos objetos en la memoria, hay que convertirlos en una secuencia de bytes que el ordenador pueda entender. Este proceso se llama serialización.

La próxima vez que queramos acceder a la misma estructura de datos, esta secuencia de bytes debe convertirse de nuevo en el objeto de alto nivel en un proceso conocido como deserialización.

Podemos utilizar formatos como JSON, XML, HDF5 y Pickle para la serialización. En este tutorial, aprenderemos sobre la biblioteca Pickle de Python para la serialización. Cubriremos sus usos y comprenderemos cuándo debes elegir Pickle en lugar de otros formatos de serialización. 

Por último, aprenderemos a utilizar la biblioteca Pickle de Python para serializar listas, diccionarios, marcos de datos Pandas, modelos de machine learning, etc.

¿Por qué necesitamos la serialización de objetos?

Antes de empezar con Python Pickle, entendamos por qué es tan importante la serialización de objetos.

Quizá te preguntes por qué no podemos simplemente guardar las estructuras de datos en un archivo de texto y volver a acceder a ellas cuando sea necesario, en lugar de tener que serializarlas.

Veamos un ejemplo sencillo para comprender las ventajas de la serialización.

Aquí tienes un diccionario anidado que contiene información sobre el alumno, como el nombre, la edad y el sexo:

students = {


  'Student 1': {
        'Name': "Alice", 'Age' :10, 'Grade':4,
    },
   
    'Student 2': {
        'Name':'Bob', 'Age':11, 'Grade':5
    },
   
    'Student 3': {
        'Name':'Elena', 'Age':14, 'Grade':8
    }
   
}

Examinemos el tipo de datos del objeto "students":

type(students)
dict

Ahora que hemos confirmado que el objeto alumno es de tipo diccionario, procedamos a escribirlo en un archivo de texto sin serialización:

with open('student_info.txt','w') as data:
      data.write(str(students))

Observa que, como solo podemos escribir objetos de cadena en archivos de texto, hemos convertido el diccionario en una cadena utilizando la función str(). Esto significa que se pierde el estado original de nuestro diccionario.

Ahora, leamos el diccionario, imprimámoslo y comprobemos de nuevo su tipo:

with open("student_info.txt", 'r') as f:
    for students in f:
        print(students)


type(students)
str

El diccionario anidado se imprime ahora como una cadena, y devolverá un error cuando intentemos acceder a sus claves o valores.

Aquí es donde entra en juego la serialización.

Cuando se trata con tipos de datos más complejos, como diccionarios, marcos de datos y listas anidadas, la serialización permite al usuario preservar el estado original del objeto sin perder ninguna información relevante.

Más adelante en este artículo, aprenderemos a almacenar este mismo diccionario en un archivo utilizando Pickle y a deserializarlo utilizando la biblioteca. Puedes leer más sobre la comprensión de diccionario en Python en otro tutorial. 

Introducción a Pickle en Python

El módulo Pickle de Python es un popular formato utilizado para serializar y deserializar tipos de datos. Este formato es nativo de Python, lo que significa que los objetos Pickle no pueden cargarse utilizando ningún otro lenguaje de programación.

Pickle tiene sus propias ventajas e inconvenientes en comparación con otros formatos de serialización.

Ventajas de utilizar Pickle para serializar objetos

  • A diferencia de formatos de serialización como JSON, que no pueden gestionar tuplas y objetos datetime, Pickle puede serializar casi todos los tipos de datos integrados de Python de uso común. También conserva el estado exacto del objeto, cosa que JSON no puede hacer.
  • Pickle también es una buena opción para almacenar estructuras recursivas, ya que solo escribe un objeto una vez.
  • Pickle ofrece flexibilidad a la hora de deserializar objetos. Puedes guardar fácilmente distintas variables en un archivo Pickle y volver a cargarlas en otra sesión de Python, recuperando tus datos exactamente como estaban sin tener que editar tu código.

Desventajas de utilizar Pickle

  • Pickle no es seguro porque puede ejecutar callables Python maliciosos para construir objetos. Al deserializar un objeto, Pickle no puede distinguir entre un callable malicioso y uno no malicioso. Debido a esto, los usuarios pueden acabar ejecutando código arbitrario durante la deserialización.
  • Como ya se ha mencionado, Pickle es un módulo específico de Python, y puede que te cueste deserializar objetos pickled cuando utilices un lenguaje diferente.
  • Según varias pruebas comparativas, Pickle parece ser más lento y produce valores serializados mayores que formatos como JSON y ApacheThrift.

Guardar y cargar objetos con la función Pickle Dump Python y la función Load

Pickle utiliza las siguientes funciones para serializar y deserializar objetos Python:

pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)




pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None)




pickle.load(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)




pickle.loads(data, /, *, fix_imports=True, encoding=”ASCII”, errors=”strict”, buffers=None)

Las funciones Pickle dump() y dumps() se utilizan para serializar un objeto. La única diferencia entre ellas es que dump() escribe los datos en un archivo, mientras que dumps() los representa como objeto byte. 

Del mismo modo, load() lee objetos pickled de un archivo, mientras que loads() los deserializa de un objeto tipo bytes.

En este tutorial, utilizaremos las funciones dump() y load() para pickle objetos Python a un archivo y unplickle esos objetos.

Serializar estructuras de datos Python con Pickle

Listas

En primer lugar, vamos a crear una lista Python sencilla:

import pickle 

student_names = ['Alice','Bob','Elena','Jane','Kyle']

Ahora, abramos un archivo de texto, escribamos la lista en él utilizando la función dumps() y cerremos el archivo:

with open('student_file.pkl', 'wb') as f:  # open a text file
    pickle.dump(student_names, f) # serialize the list

Primero creamos un archivo llamado "student_file.pkl". La extensión no tiene por qué ser .pkl. Puedes ponerle el nombre que quieras, y el archivo se creará igualmente. Sin embargo, es una práctica recomendada utilizar la extensión .pkl para que te recuerde que se trata de un archivo Pickle.

Además, fíjate en que hemos abierto el archivo en modo "wb". Esto significa que estás escribiendo el archivo en modo binario, de modo que los datos se devuelven en un objeto bytes.

A continuación, utilizamos la función dump() para almacenar la lista "student_names" en el archivo.

Por último, puedes cerrar el archivo con la siguiente línea de código:

f.close()

Ahora, vamos a deserializar el archivo e imprimir la lista:

with open('student_file.pkl', 'rb') as f:

    student_names_loaded = pickle.load(f) # deserialize using load()
    print(student_names_loaded) # print student names

El resultado del código anterior debería ser el siguiente:

['Alice', 'Bob', 'Elena', 'Jane', 'Kyle']

Observa que, para deserializar el archivo, tenemos que utilizar el modo "rb", que significa read binary. A continuación, unpickle el objeto mediante la función load(), tras lo cual podemos almacenar los datos en una variable diferente y utilizarlos como creamos conveniente.

Comprobemos ahora el tipo de datos de la lista que acabamos de unpickle:

type(student_names_loaded)
list

¡Estupendo! Hemos preservado el estado original y el tipo de datos de esta lista.

Matrices Numpy

Ahora vamos a intentar serializar y deserializar un tipo de datos algo más complejo: las matrices Numpy.

Vamos a crear primero una matriz de unos de 10 por 10:

import numpy as np

numpy_array = np.ones((10,10)) # 10x10 array

A continuación, igual que hicimos antes, vamos a invocar la función dump() para serializar esta matriz en un archivo:

with open('my_array.pkl','wb') as f:
    pickle.dump(numpy_array, f)

Por último, unpickle esta matriz y comprobemos su forma y tipo de datos para asegurarnos de que ha conservado su estado original:

with open('my_array.pkl','rb') as f:
    unpickled_array = pickle.load(f)
    print('Array shape: '+str(unpickled_array.shape))
    print('Data type: '+str(type(unpickled_array)))

Deberías obtener la siguiente salida:

Array shape: (10, 10)

Data type: <class 'numpy.ndarray'>

El objeto unpickled es una matriz Numpy de 10 × 10, que tiene la misma forma y tipo de datos que el objeto que acabamos de serializar.

DataFrames pandas

Un dataframe es un objeto con el que trabajan a diario los científicos de datos. La forma más popular de cargar y guardar un dataframe Pandas es leerlo y escribirlo como archivo .csv. Aprende más sobre la importación de datos en nuestro tutorial de pandas read_csv()

Sin embargo, este proceso es más lento que la serialización y puede llegar a consumir mucho tiempo si el dataframe es grande.

Vamos a contrastar la eficiencia de guardar y cargar un dataframe pandas utilizando Pickle frente a .csv comparando el tiempo que tarda cada uno.

En primer lugar, vamos a crear un dataframe pandas con 100 000 filas de datos falsos:

import pandas as pd
import numpy as np

# Set random seed
np.random.seed(123)

data = {'Column1': np.random.randint(0, 10, size=100000),
        'Column2': np.random.choice(['A', 'B', 'C'], size=100000),
        'Column3': np.random.rand(100000)}


# Create Pandas dataframe
df = pd.DataFrame(data)

Ahora, vamos a calcular el tiempo que se tarda en guardar este dataframe como archivo .csv:

import time

start = time.time()

df.to_csv('pandas_dataframe.csv')

end = time.time()
print(end - start)
0.19349145889282227

Tardamos 0,19 segundos en guardar un dataframe Pandas con tres filas y 100 000 columnas en un archivo .csv.

Veamos si el uso de Pickle puede ayudar a mejorar el rendimiento. La biblioteca pandas tiene un método llamado to_pickle() que nos permite serializar dataframes a archivos pickle en una sola línea de código:

start = time.time()


df.to_pickle("my_pandas_dataframe.pkl")


end = time.time()
print(end - start)
0.0059659481048583984

Solo tardamos 5 milisegundos en guardar el mismo dataframe Pandas en un archivo Pickle, lo que supone una mejora significativa del rendimiento si lo comparamos con guardarlo como .csv.

Ahora, volvamos a leer el archivo en Pandas y veamos si cargar un archivo Pickle ofrece alguna ventaja de rendimiento frente a la simple lectura de un archivo .csv:

# Reading the csv file into Pandas:

start1 = time.time()
df_csv = pd.read_csv("my_pandas_dataframe.csv")
end1 = time.time()
print("Time taken to read the csv file: " + str(end1 - start1) + "\n")

# Reading the Pickle file into Pandas:

start2 = time.time()
df_pkl = pd.read_pickle("my_pandas_dataframe.pkl")
end2 = time.time()
print("Time taken to read the Pickle file: " + str(end2 - start2))

El código anterior debería dar el siguiente resultado:

Time taken to read the csv file: 0.00677490234375




Time taken to read the Pickle file: 0.0009608268737792969

Tardamos 6 milisegundos en leer el archivo .csv en Pandas, y solo 0,9 milisegundos en leerlo en Pickle. 

Aunque esta diferencia pueda parecer menor, serializar grandes dataframes Pandas con Pickle puede suponer un ahorro de tiempo considerable. Pickle también nos ayudará a preservar el tipo de datos de cada columna en todos los casos y ocupa menos espacio en disco que un archivo .csv.

Diccionarios

Por último, vamos a serializar el diccionario que escribimos en un archivo de texto en la primera sección del tutorial:

students = {
  'Student 1': {
        'Name': "Alice", 'Age' :10, 'Grade':4,
    },
   
    'Student 2': {
        'Name':'Bob', 'Age':11, 'Grade':5
    },
   
    'Student 3': {
        'Name':'Elena', 'Age':14, 'Grade':8
    }
}

Recuerda que, cuando guardamos este diccionario como archivo de texto, tuvimos que convertirlo en una cadena y perdió su estado original.

Ahora vamos a serializarlo utilizando Pickle y a volver a leerlo para asegurarnos de que sigue conteniendo todas las propiedades de un diccionario Python:

# serialize the dictionary to a pickle file

with open("student_dict.pkl", "wb") as f:
    pickle.dump(students, f)
   
# deserialize the dictionary and print it out

with open("student_dict.pkl", "rb") as f:
    deserialized_dict = pickle.load(f)
    print(deserialized_dict)

Deberías obtener la siguiente salida:

{'Student 1': {'Name': 'Alice', 'Age': 10, 'Grade': 4}, 'Student 2': {'Name': 'Bob', 'Age': 11, 'Grade': 5}, 'Student 3': {'Name': 'Elena', 'Age': 14, 'Grade': 8}}

Comprobemos ahora el tipo de esta variable:

type(deserialized_dict)
dict

Intentemos acceder a alguna información sobre el primer alumno de este diccionario:

print(
    "The first student's name is "
    + deserialized_dict["Student 1"]["Name"]
    + " and she is "
    + (str(deserialized_dict["Student 1"]["Age"]))
    + " years old."
)
The first student's name is Alice and she is 10 years old.

¡Estupendo! El diccionario conserva todas sus propiedades originales y se puede acceder a él igual que antes de la serialización. Consulta nuestro tutorial de comprensión de diccionario en Python para saber más. 

Serializar modelos de machine learning con Pickle

Entrenar un modelo de machine learning es un proceso que requiere mucho tiempo: puede llevar horas, y a veces, incluso muchos días. Sencillamente, no es factible volver a entrenar un algoritmo desde cero cuando necesitas reutilizarlo o transferirlo a un entorno diferente.

Si quieres aprender las prácticas recomendadas para construir algoritmos de machine learning, puedes seguir Diseño de flujos de trabajo de machine learning en Python.

Pickle te permite serializar modelos de machine learning en su estado existente, lo que permite volver a utilizarlos cuando sea necesario.

En esta sección, aprenderemos a serializar un modelo de machine learning con Pickle.

Para ello, generemos primero unos datos falsos y construyamos un modelo de regresión lineal con la biblioteca Scikit-Learn:

from sklearn.linear_model import LinearRegression
from sklearn.datasets import make_regression

# generate regression dataset
X, y = make_regression(n_samples=100, n_features=3, noise=0.1, random_state=1)

# train regression model
linear_model = LinearRegression()
linear_model.fit(X, y)

Ahora, vamos a imprimir algunos parámetros resumidos del modelo:

# summary of the model
print('Model intercept :', linear_model.intercept_)
print('Model coefficients : ', linear_model.coef_)
print('Model score : ', linear_model.score(X, y))
Model intercept : -0.010109549594702116

Model coefficients :  [44.18793068 98.97389468 58.17121618]

Model score :  0.9999993081899219

A continuación, podemos serializar este modelo utilizando la función dump() de Pickle:

with open("linear_regression.pkl", "wb") as f:
    pickle.dump(linear_model, f)

El modelo se guarda ahora como archivo Pickle. Vamos a deserializarlo utilizando la función load():

with open("linear_regression.pkl", "rb") as f:
    unpickled_linear_model = pickle.load(f)

El modelo serializado se carga y guarda en la variable "unpickled_linear_model". Comprobemos los parámetros de este modelo para asegurarnos de que sea el mismo que creamos inicialmente:

# summary of the model
print('Model intercept :', unpickled_linear_model.intercept_)
print('Model coefficients : ', unpickled_linear_model.coef_)
print('Model score : ', unpickled_linear_model.score(X, y))

Deberías obtener la siguiente salida:

Model intercept : -0.010109549594702116

Model coefficients :  [44.18793068 98.97389468 58.17121618]

Model score :  0.9999993081899219

¡Estupendo! Los parámetros del modelo que acabamos de unpickle son los mismos que los del que creamos inicialmente. 

Ahora podemos utilizar este modelo para hacer previsiones sobre un conjunto de datos de prueba, entrenar con él o transferirlo a un entorno diferente.

Aumentar el rendimiento de Python Pickle para objetos grandes

Pickle es un formato de serialización eficiente que, a menudo, ha demostrado ser más rápido que JSON, XML y HDF5 en diversas pruebas comparativas

Sin embargo, cuando trata con estructuras de datos extremadamente grandes o con enormes modelos de machine learning, Pickle puede ralentizarse mucho, y la serialización puede convertirse en un cuello de botella en tu flujo de trabajo.

Aquí tienes algunas formas de reducir el tiempo que se tarda en guardar y cargar archivos Pickle:

Utilizar el argumento ‘PROTOCOL’

El protocolo por defecto utilizado al guardar y cargar archivos Pickle es actualmente el 4, que es el más compatible con las distintas versiones de Python. 

Sin embargo, si quieres acelerar tu flujo de trabajo, puedes utilizar el argumento HIGHEST_PROTOCOL, que es el protocolo más rápido disponible en Pickle.

Para comparar el rendimiento del protocolo más compatible de Pickle y del protocolo por defecto, serialicemos primero un dataframe Pandas utilizando el protocolo por defecto. Ten en cuenta que esta es la versión del protocolo que utiliza Pickle si no se indica explícitamente ningún protocolo específico:

import pickle
import time
import numpy as np

# Set random seed
np.random.seed(100)

data = {'Column1': np.random.randint(0, 10, size=100000),
        'Column2': np.random.choice(['A', 'B', 'C'], size=100000),
        'Column3': np.random.rand(100000)}

# serialize to a file

start = time.time()

with open("df1.pkl", "wb") as f:
    pickle.dump(data, f)

end = time.time()
print(end - start)
0.006001710891723633

Tardamos unos 6 milisegundos en serializar el dataframe utilizando el protocolo por defecto de Pickle.

Ahora, vamos a pickle el dataframe utilizando el protocolo más alto:

start = time.time()

with open("df2.pkl", "wb") as f:
    pickle.dump(data, f, protocol=pickle.HIGHEST_PROTOCOL)

end = time.time()
print(end - start)
0.0030384063720703125

Con el protocolo más alto, conseguimos serializar el dataframe en la mitad de tiempo.

Utilizar 'cPickle' en lugar de 'Pickle'

El módulo 'cPickle' es una versión más rápida de 'Pickle' que está escrita en C. Esto la hace más rápida que la biblioteca 'Pickle', que está implementada puramente en Python. 

Ten en cuenta que, en Python3, 'cPickle' ha pasado a llamarse '_pickle', que es la biblioteca que vamos a importar.

import _pickle as cPickle

start = time.time()

with open("df3.pkl", "wb") as f:
    cPickle.dump(data, f)

end = time.time()

print(end-start)
0.004027366638183594

La serialización con 'cPickle' tardó aproximadamente 4 milisegundos, lo que representa una mejora sustancial respecto al módulo 'Pickle' de Python.

Serializar solo lo que necesites

Incluso con soluciones para acelerar la serialización, el proceso puede seguir siendo muy lento para objetos grandes.

Para mejorar el rendimiento, puedes descomponer la estructura de datos y serializar solo los subconjuntos necesarios.

Cuando trabajes con diccionarios, por ejemplo, puedes especificar pares clave-valor a los que quieras volver a acceder. Reduce el tamaño del diccionario antes de serializarlo, ya que esto reducirá la complejidad del objeto y acelerará el proceso considerablemente.

Serialización con Pickle en Python: Próximos pasos

¡Enhorabuena! Has aprendido una amplia gama de temas relacionados con la serialización en Python y el uso de la biblioteca Pickle.

Ahora deberías tener una sólida comprensión de lo que es la serialización, cómo utilizar Pickle para serializar estructuras de datos de Python y cómo optimizar el rendimiento de Pickle utilizando diferentes argumentos y módulos.

He aquí algunos pasos que puedes dar para mejorar tu comprensión de la serialización y aprovecharla para mejorar tus flujos de trabajo de ciencia de datos:

  1. Conoce los nuevos formatos de serialización. Aunque en este tutorial solo hemos tratado el módulo Pickle, no es necesariamente el mejor formato de serialización para todos los casos. Merece la pena conocer otros formatos, como JSON, XML y HDF5, para que sepas cuál elegir para los distintos casos de uso.
  2. Pruébalo tú mismo. Aplica los conceptos enseñados en este tutorial a tus flujos de trabajo de ciencia de datos. La próxima vez que crees una nueva estructura de datos o almacenes la salida de un cálculo en una variable, serialízalas para utilizarlas más tarde en lugar de ejecutar todo tu código una y otra vez.
  3. Haz un curso online. Para optimizar tus flujos de trabajo de ciencia de datos y realizar tareas como la serialización de forma eficiente, es imprescindible tener un sólido conocimiento de los objetos y las estructuras de datos de Python. Para conseguirlo, puedes seguir nuestro curso Tipos de datos para la ciencia de datos en Python.
Temas

Más información sobre Python y machine learning

curso

Supervised Learning with scikit-learn

4 hr
135.7K
Grow your machine learning skills with scikit-learn in Python. Use real-world datasets in this interactive course and learn how to make powerful predictions!
Ver detallesRight Arrow
Comienza El Curso
Ver másRight Arrow
Relacionado

tutorial

Tutorial sobre cómo trabajar con módulos en Python

Los módulos te permiten dividir partes de tu programa en archivos diferentes para facilitar el mantenimiento y mejorar el rendimiento.

Nishant Kumar

8 min

tutorial

Tutorial de multiprocesamiento en Python

Descubra los fundamentos del multiprocesamiento en Python y las ventajas que puede aportar a sus flujos de trabajo.
Kurtis Pykes 's photo

Kurtis Pykes

6 min

tutorial

Programación orientada a objetos (POO) en Python: Tutorial

Aborda los fundamentos de la Programación Orientada a Objetos (POO) en Python: explora las clases, los objetos, los métodos de instancia, los atributos y ¡mucho más!
Théo Vanderheyden's photo

Théo Vanderheyden

12 min

tutorial

Datos JSON en Python

Trabajar con JSON en Python: Una guía paso a paso para principiantes
Moez Ali's photo

Moez Ali

6 min

tutorial

Aprendizaje automático de datos categóricos con el tutorial de Python

Aprenda los trucos más comunes para manejar datos categóricos y preprocesarlos para construir modelos de aprendizaje automático.
Moez Ali's photo

Moez Ali

28 min

tutorial

Tutorial de Excel en Python: La guía definitiva

Aprende a leer e importar archivos Excel en Python, a escribir datos en estas hojas de cálculo y a encontrar los mejores paquetes para hacerlo.
Natassha Selvaraj's photo

Natassha Selvaraj

30 min

See MoreSee More