ZetCode

Python __reduce__ 方法

最后修改于 2025 年 4 月 8 日

本综合指南探讨了 Python 的 __reduce__ 方法,这是一个用于对象序列化(使用 pickle)的特殊方法。我们将介绍基本用法、自定义归约、安全注意事项和实际示例。

基本定义

当一个对象被 pickle 时,会调用 __reduce__ 方法。它应该返回一个字符串或元组,描述在 unpickling 时如何重建该对象。

主要特征:它支持自定义序列化、控制对象重建,并可以提高性能。该方法是 Python pickle 协议的一部分,对于复杂的对象持久化至关重要。

基本 __reduce__ 实现

这是一个简单的实现,展示了 __reduce__ 如何与 pickle 模块一起工作。它演示了基本的元组返回格式。

basic_reduce.py
import pickle

class SimpleObject:
    def __init__(self, value):
        self.value = value
    
    def __reduce__(self):
        return (self.__class__, (self.value,))

obj = SimpleObject(42)
pickled = pickle.dumps(obj)
unpickled = pickle.loads(pickled)
print(unpickled.value)  # 42

这个例子展示了最小的 __reduce__ 实现。返回的元组包含可调用对象(类)和用于重建的参数。

在 unpickling 时,Python 调用 SimpleObject(42) 来重新创建对象。这是最简单的自定义序列化形式。

使用 __reduce__ 进行自定义重建

为了获得更多控制,__reduce__ 可以返回一个元组,其中包含可调用对象、参数以及可选的状态和迭代器项。

custom_reduce.py
import pickle

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __reduce__(self):
        return (self.__class__, (self.x, self.y), {'color': 'red'}

    def __setstate__(self, state):
        self.color = state.get('color', 'blue')

p = Point(3, 4)
pickled = pickle.dumps(p)
unpickled = pickle.loads(pickled)
print(f"Point({unpickled.x}, {unpickled.y}), Color: {unpickled.color}")

这个例子演示了使用状态的高级归约。返回的元组包括类、构造函数参数和附加状态。

__setstate__ 方法在 unpickling 期间处理状态字典。这允许恢复未在 __init__ 中设置的其他属性。

处理复杂对象

__reduce__ 可以处理具有复杂内部状态的对象,这些状态无法通过正常的初始化轻松重建。

complex_object.py
import pickle

class DatabaseConnection:
    def __init__(self, connection_string):
        self.connection_string = connection_string
        self._connection = self._connect()
    
    def _connect(self):
        print(f"Connecting to {self.connection_string}")
        return "connection_object"
    
    def __reduce__(self):
        return (self.__class__, (self.connection_string,), {'_connection': None}

db = DatabaseConnection("postgres://user:pass@localhost/db")
pickled = pickle.dumps(db)
unpickled = pickle.loads(pickled)
print(unpickled.connection_string)

这个例子展示了如何处理具有不可 pickle 属性的对象。连接对象从序列化中排除,并在需要时重新创建。

__reduce__ 方法确保只有连接字符串被 pickle。实际的连接将在需要时重新建立。

安全注意事项

如果使用不慎,__reduce__ 可能会造成安全风险,因为它允许在 unpickling 期间执行任意代码。

security.py
import pickle

class SafeObject:
    def __init__(self, data):
        self.data = data
    
    def __reduce__(self):
        if any(char in self.data for char in ";&|"):
            raise ValueError("Invalid characters in data")
        return (self.__class__, (self.data,))

# Safe usage
safe = SafeObject("normal data")
pickled = pickle.dumps(safe)

# Malicious attempt would raise ValueError
# malicious = SafeObject("dangerous; rm -rf /")
# pickle.dumps(malicious)

此示例演示了 __reduce__ 中的输入验证,以防止通过 pickle 进行代码注入攻击。

在处理不受信任的输入时,始终验证 __reduce__ 中的数据。对于安全敏感的应用程序,请考虑使用替代序列化方法。

优化性能

__reduce__ 可用于通过自定义序列化过程来优化 pickle 性能。

performance.py
import pickle
import numpy as np

class EfficientArray:
    def __init__(self, data):
        self.data = np.array(data)
    
    def __reduce__(self):
        return (
            self.__class__,
            (self.data.tolist(),),
            None,
            iter((self.data,))
    
    def __setstate__(self, state):
        if state is not None:
            self.data = np.array(state)

arr = EfficientArray([1, 2, 3, 4])
pickled = pickle.dumps(arr, protocol=4)
unpickled = pickle.loads(pickled)
print(unpickled.data)

这个例子展示了如何通过使用 reduce 元组的第四个元素进行高效的二进制数据处理,从而有效地 pickle NumPy 数组。

当使用协议 4 或更高版本时,迭代器协议(第四个元组元素)允许对大型数据结构进行更高效的二进制序列化。

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 我自 2007 年以来一直在撰写编程文章。到目前为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程