MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python字典的序列化与反序列化

2021-02-055.0k 阅读

Python字典的序列化与反序列化

序列化与反序列化的概念

在计算机科学中,序列化(Serialization)是将数据结构或对象状态转换为可以存储或传输的格式的过程。这个过程产生的字节序列被称为“序列化形式”。而反序列化(Deserialization)则是相反的过程,即将序列化的字节序列重新恢复为原始的数据结构或对象状态。

在Python中,字典是一种非常常用的数据结构。当我们需要将字典存储到文件中,或者通过网络传输给其他程序时,就需要对字典进行序列化。反之,当从文件中读取数据或者接收到网络传输过来的数据,并要恢复为字典时,就需要进行反序列化。

Python中常用的序列化与反序列化模块

  1. json模块
    • 简介:JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于阅读和编写,同时也易于机器解析和生成。Python的json模块提供了将Python数据结构(包括字典)转换为JSON格式字符串(序列化)以及将JSON格式字符串转换回Python数据结构(反序列化)的功能。
    • 代码示例
import json

# 定义一个字典
my_dict = {
    "name": "Alice",
    "age": 30,
    "city": "New York"
}

# 序列化字典
serialized_dict = json.dumps(my_dict)
print("序列化后的结果:", serialized_dict)

# 反序列化
deserialized_dict = json.loads(serialized_dict)
print("反序列化后的结果:", deserialized_dict)
  • 注意事项json模块能处理的Python数据类型有限,主要包括字典、列表、字符串、数字、布尔值和None。字典的键必须是字符串类型,且字典的值也必须是可JSON序列化的类型。如果字典中包含自定义对象等不可序列化的类型,会抛出TypeError。例如:
import json

class MyClass:
    pass

my_dict = {
    "obj": MyClass()
}
try:
    json.dumps(my_dict)
except TypeError as e:
    print("错误:", e)
  1. pickle模块
    • 简介pickle模块是Python的标准模块,用于实现对象的序列化和反序列化。与json模块不同,pickle可以序列化几乎所有的Python对象,包括自定义类的实例、函数等。它生成的是Python特定的二进制格式,而不是像JSON那样的文本格式,因此更适合在Python程序内部使用。
    • 代码示例
import pickle

# 定义一个字典
my_dict = {
    "name": "Bob",
    "hobbies": ["reading", "swimming"]
}

# 序列化字典
with open('my_dict.pickle', 'wb') as f:
    pickle.dump(my_dict, f)

# 反序列化
with open('my_dict.pickle', 'rb') as f:
    deserialized_dict = pickle.load(f)
print("反序列化后的结果:", deserialized_dict)
  • 注意事项pickle虽然功能强大,但由于它可以反序列化任意对象,包括函数和类的实例,因此存在安全风险。如果从不可信的来源接收pickle数据并进行反序列化,可能会导致代码执行漏洞。例如,恶意攻击者可以构造一个恶意的pickle数据,当程序反序列化时,会执行攻击者预设的恶意代码。因此,在使用pickle时,务必确保数据来源可靠。
  1. yaml模块
    • 简介:YAML(YAML Ain't Markup Language)是一种人类可读的数据序列化标准,它以简洁的文本格式表示数据。Python中可以使用PyYAML库来处理YAML格式的数据。yaml模块可以将Python字典序列化为YAML格式的文本,也可以将YAML格式的文本反序列化为Python字典。
    • 安装PyYAML:在使用yaml模块之前,需要先安装PyYAML库。可以使用pip install PyYAML命令进行安装。
    • 代码示例
import yaml

# 定义一个字典
my_dict = {
    "name": "Charlie",
    "occupation": "Engineer",
    "languages": ["Python", "Java"]
}

# 序列化字典
serialized_dict = yaml.dump(my_dict)
print("序列化后的结果:\n", serialized_dict)

# 反序列化
deserialized_dict = yaml.load(serialized_dict, Loader = yaml.FullLoader)
print("反序列化后的结果:", deserialized_dict)
  • 注意事项:在使用yaml.load()进行反序列化时,从安全角度考虑,建议使用yaml.FullLoader。这是因为旧版本的yaml.load()(默认使用yaml.SafeLoader)在处理某些YAML结构时可能存在安全风险,例如可能导致任意代码执行。而yaml.FullLoader会严格按照YAML规范进行解析,减少安全隐患。

不同模块在序列化与反序列化字典时的性能比较

  1. 性能测试方法 为了比较jsonpickleyaml模块在序列化和反序列化字典时的性能,我们可以使用Python的timeit模块。timeit模块提供了一种测量小段代码执行时间的方法。我们将定义一个较大的字典,然后分别使用这三个模块进行多次序列化和反序列化操作,并记录每次操作的总时间,最后计算平均时间来比较性能。
  2. 代码示例
import json
import pickle
import yaml
import timeit


big_dict = {str(i): i for i in range(10000)}


def json_serialize_deserialize():
    serialized = json.dumps(big_dict)
    return json.loads(serialized)


def pickle_serialize_deserialize():
    with open('temp.pickle', 'wb') as f:
        pickle.dump(big_dict, f)
    with open('temp.pickle', 'rb') as f:
        return pickle.load(f)


def yaml_serialize_deserialize():
    serialized = yaml.dump(big_dict)
    return yaml.load(serialized, Loader = yaml.FullLoader)


json_time = timeit.timeit(json_serialize_deserialize, number = 100)
pickle_time = timeit.timeit(pickle_serialize_deserialize, number = 100)
yaml_time = timeit.timeit(yaml_serialize_deserialize, number = 100)

print(f"JSON序列化与反序列化100次总时间: {json_time} 秒")
print(f"Pickle序列化与反序列化100次总时间: {pickle_time} 秒")
print(f"YAML序列化与反序列化100次总时间: {yaml_time} 秒")
  1. 性能比较结果分析
    • 一般来说,json模块在处理简单字典且注重跨语言兼容性时,性能表现较好。由于JSON是一种广泛支持的格式,在网络传输和不同语言间的数据交换中使用频繁。它的序列化和反序列化速度相对较快,尤其是对于较小的字典。
    • pickle模块在处理复杂的Python对象(如包含自定义类实例的字典)时具有优势,但由于它生成的是二进制格式,并且在反序列化时需要解析Python特定的对象结构,所以在序列化和反序列化大字典时,性能可能不如json模块。同时,由于安全风险,在不可信环境中使用受限。
    • yaml模块的性能介于jsonpickle之间。它的优点是格式更易读,适合用于配置文件等场景。但在处理大数据量时,序列化和反序列化的速度相对较慢,因为YAML格式的解析相对复杂一些。

在不同应用场景下的选择

  1. 网络传输
    • 如果数据需要在不同语言编写的程序之间进行传输,json是首选。因为JSON是一种跨语言的数据格式,几乎所有现代编程语言都有对JSON的支持。例如,在一个Python后端和JavaScript前端之间进行数据交互时,将Python字典序列化为JSON格式的字符串,前端可以很方便地解析。
    • 示例:
# Python后端代码
import json
data = {
    "message": "Hello from Python",
    "status": "success"
}
serialized_data = json.dumps(data)
# 将serialized_data通过网络发送给前端

在JavaScript前端可以使用JSON.parse()来解析接收到的JSON字符串:

let receivedData = '{"message": "Hello from Python", "status": "success"}';
let parsedData = JSON.parse(receivedData);
console.log(parsedData.message);
  1. 本地数据存储
    • 如果数据仅在Python程序内部使用,并且需要存储复杂的Python对象,pickle模块是不错的选择。例如,在开发一个机器学习模型训练程序时,可能需要将训练过程中的一些中间结果(如包含自定义类实例的字典)存储到文件中,以便后续继续使用。
    • 示例:
import pickle


class ModelParams:
    def __init__(self, param1, param2):
        self.param1 = param1
        self.param2 = param2


params = ModelParams(10, 20)
model_dict = {
    "model_name": "MyModel",
    "params": params
}
with open('model_data.pickle', 'wb') as f:
    pickle.dump(model_dict, f)

后续可以通过反序列化来恢复数据:

import pickle
with open('model_data.pickle', 'rb') as f:
    loaded_dict = pickle.load(f)
print(loaded_dict["model_name"])
print(loaded_dict["params"].param1)
  1. 配置文件
    • yaml模块非常适合用于处理配置文件。YAML格式具有良好的可读性和层次性,对于编写和维护配置文件很方便。许多Python项目使用YAML来管理配置,例如Django项目中的一些配置文件可以使用YAML格式。
    • 示例:假设我们有一个配置文件config.yaml内容如下:
database:
  host: localhost
  port: 5432
  user: myuser
  password: mypassword
server:
  host: 0.0.0.0
  port: 8080

在Python中可以使用以下代码读取这个配置文件:

import yaml
with open('config.yaml', 'r') as f:
    config = yaml.load(f, Loader = yaml.FullLoader)
print(config["database"]["host"])
print(config["server"]["port"])

处理复杂字典结构的序列化与反序列化

  1. 嵌套字典
    • 对于嵌套字典,jsonpickleyaml模块都能很好地处理。例如,一个包含嵌套字典的结构:
import json
import pickle
import yaml


nested_dict = {
    "person1": {
        "name": "David",
        "age": 25,
        "address": {
            "city": "Los Angeles",
            "country": "USA"
        }
    },
    "person2": {
        "name": "Eva",
        "age": 32,
        "address": {
            "city": "London",
            "country": "UK"
        }
    }
}

# 使用json模块
json_serialized = json.dumps(nested_dict)
json_deserialized = json.loads(json_serialized)

# 使用pickle模块
with open('nested.pickle', 'wb') as f:
    pickle.dump(nested_dict, f)
with open('nested.pickle', 'rb') as f:
    pickle_deserialized = pickle.load(f)

# 使用yaml模块
yaml_serialized = yaml.dump(nested_dict)
yaml_deserialized = yaml.load(yaml_serialized, Loader = yaml.FullLoader)

print("JSON反序列化结果:", json_deserialized)
print("Pickle反序列化结果:", pickle_deserialized)
print("YAML反序列化结果:", yaml_deserialized)
  1. 包含自定义类实例的字典
    • json模块无法直接序列化包含自定义类实例的字典,因为JSON格式不支持自定义对象。但是可以通过一些方法将自定义对象转换为可JSON序列化的格式,例如定义一个方法将自定义对象转换为字典,然后再进行序列化。
    • 示例:
import json


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def to_dict(self):
        return {
            "x": self.x,
            "y": self.y
        }


point = Point(10, 20)
point_dict = {
    "point": point.to_dict()
}
serialized = json.dumps(point_dict)
print("JSON序列化结果:", serialized)
  • pickle模块可以直接序列化包含自定义类实例的字典。
import pickle


class Circle:
    def __init__(self, radius):
        self.radius = radius


circle = Circle(5)
circle_dict = {
    "circle": circle
}
with open('circle.pickle', 'wb') as f:
    pickle.dump(circle_dict, f)
with open('circle.pickle', 'rb') as f:
    deserialized_dict = pickle.load(f)
print("Pickle反序列化后圆的半径:", deserialized_dict["circle"].radius)
  • yaml模块在处理包含自定义类实例的字典时,也需要一些额外的处理。可以使用yaml.add_representer()方法来告诉yaml模块如何将自定义类实例转换为YAML格式,使用yaml.add_constructor()方法来告诉yaml模块如何从YAML格式恢复自定义类实例。
import yaml


class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height


def rectangle_representer(dumper, rect):
    return dumper.represent_mapping('!Rectangle', {
        'width': rect.width,
        'height': rect.height
    })


def rectangle_constructor(loader, node):
    fields = loader.construct_mapping(node)
    return Rectangle(fields['width'], fields['height'])


yaml.add_representer(Rectangle, rectangle_representer)
yaml.add_constructor('!Rectangle', rectangle_constructor)

rectangle = Rectangle(10, 20)
rectangle_dict = {
    "rectangle": rectangle
}
serialized = yaml.dump(rectangle_dict)
print("YAML序列化结果:\n", serialized)
deserialized = yaml.load(serialized, Loader = yaml.FullLoader)
print("YAML反序列化后矩形的宽度:", deserialized["rectangle"].width)

序列化与反序列化过程中的错误处理

  1. json模块的错误处理
    • 序列化错误:当字典中包含不可JSON序列化的对象时,json.dumps()会抛出TypeError。例如:
import json


my_dict = {
    "func": lambda x: x * 2
}
try:
    json.dumps(my_dict)
except TypeError as e:
    print("序列化错误:", e)
  • 反序列化错误:当JSON字符串格式不正确时,json.loads()会抛出JSONDecodeError。例如:
import json
bad_json = '{"name": "Alice", "age": 30,}'
try:
    json.loads(bad_json)
except json.JSONDecodeError as e:
    print("反序列化错误:", e)
  1. pickle模块的错误处理
    • 序列化错误:如果对象不可picklepickle.dump()会抛出PickleError或其子类的异常。例如,试图序列化一个打开的文件对象会导致错误:
import pickle
file = open('test.txt', 'w')
my_dict = {
    "file": file
}
try:
    pickle.dump(my_dict, open('temp.pickle', 'wb'))
except pickle.PickleError as e:
    print("序列化错误:", e)
finally:
    file.close()
  • 反序列化错误:当pickle数据损坏或者尝试反序列化一个不兼容的对象(例如,在不同Python版本中创建的pickle数据且存在不兼容)时,pickle.load()会抛出UnpicklingError或其子类的异常。
  1. yaml模块的错误处理
    • 序列化错误:如果字典中的对象无法序列化为YAML格式,yaml.dump()会抛出YAMLError。例如,当字典中包含一个没有定义yaml表示方法的自定义类实例时:
import yaml


class MyCustomClass:
    pass


my_dict = {
    "obj": MyCustomClass()
}
try:
    yaml.dump(my_dict)
except yaml.YAMLError as e:
    print("序列化错误:", e)
  • 反序列化错误:当YAML文本格式不正确时,yaml.load()会抛出YAMLError。例如:
import yaml
bad_yaml = "name: Alice\nage: 30\ncity: New York:
try:
    yaml.load(bad_yaml, Loader = yaml.FullLoader)
except yaml.YAMLError as e:
    print("反序列化错误:", e)

通过对Python字典的序列化与反序列化的深入探讨,我们了解了不同模块的特点、性能、适用场景以及错误处理方法。在实际应用中,根据具体需求选择合适的序列化与反序列化方式,能够有效地提高程序的效率和稳定性。无论是在网络传输、本地存储还是配置文件管理等场景,正确地使用这些技术都能为我们的开发工作带来便利。