ZetCode

Python __reduce_ex__ 方法

最后修改于 2025 年 4 月 8 日

这份综合指南探讨了 Python 的 __reduce_ex__ 方法,这是一个特殊的用于使用 pickle 进行对象序列化的方法。我们将介绍基本用法、协议版本、自定义 reduction 和实际示例。

基本定义

__reduce_ex__ 方法由 pickle 模块调用,以获取表示对象状态的元组。它允许对象进行自定义序列化行为。

主要特征:它接受协议版本号,返回指定如何重构对象的元组,并且可以用于优化序列化。它比 __reduce__ 更高级。

基本的 __reduce_ex__ 实现

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

basic_reduce_ex.py
import pickle

class SimpleObject:
    def __init__(self, value):
        self.value = value
    
    def __reduce_ex__(self, protocol):
        print(f"Using protocol {protocol}")
        return (self.__class__, (self.value,))

obj = SimpleObject(42)
data = pickle.dumps(obj)
new_obj = pickle.loads(data)
print(new_obj.value)  # 42

此示例显示了 pickling 所需的最小实现。该方法返回一个包含类和构造函数参数的元组。

protocol 参数指示正在使用的 pickle 协议版本。更高的协议支持更多功能,但可能不向后兼容。

使用状态自定义 Reduction

对于更复杂的对象,您可能需要单独处理对象的状态。此示例显示了如何在 reduction 中包含实例属性。

custom_reduction.py
import pickle

class CustomObject:
    def __init__(self, name, data):
        self.name = name
        self.data = data
    
    def __reduce_ex__(self, protocol):
        if protocol >= 2:
            return (self.__class__, (self.name,), {'data': self.data})
        else:
            return (self.__class__, (self.name, self.data))

obj = CustomObject("test", [1, 2, 3])
data = pickle.dumps(obj, protocol=4)
new_obj = pickle.loads(data)
print(new_obj.name, new_obj.data)  # test [1, 2, 3]

此实现显示了协议感知的 reduction。 对于协议 2+,它使用 3 元组格式(类,参数,状态)。 对于较旧的协议,它会回退到更简单的格式。

状态字典允许更好地控制在 unpickling 期间如何恢复实例属性,这对于复杂对象可能更有效。

针对协议版本进行优化

不同的 pickle 协议支持不同的功能。 __reduce_ex__ 可以根据协议版本优化序列化。

protocol_optimization.py
import pickle

class OptimizedObject:
    def __init__(self, items):
        self.items = items
    
    def __reduce_ex__(self, protocol):
        if protocol >= 4:
            return (self.__class__, (self.items,), None, None, iter(self.items))
        elif protocol >= 2:
            return (self.__class__, (self.items,), None)
        else:
            return (self.__class__, (self.items,))

obj = OptimizedObject([1, 2, 3])
data = pickle.dumps(obj, protocol=4)
new_obj = pickle.loads(data)
print(new_obj.items)  # [1, 2, 3]

此示例显示了特定于协议的优化。 协议 4+ 支持 5 元组返回格式,该格式可以包含用于大型序列的迭代器。

5 元组格式为(类,参数,状态,listitems,dictitems),并通过流式传输更有效地 pickling 大型数据结构。

处理外部资源

某些对象管理不应被 pickling 的资源。 __reduce_ex__ 可以通过返回替代的重构指令来处理这些情况。

external_resources.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 f"Connection to {self.connection_string}"
    
    def __reduce_ex__(self, protocol):
        return (self.__class__, (self.connection_string,))

conn = DatabaseConnection("localhost:5432")
data = pickle.dumps(conn)
new_conn = pickle.loads(data)  # Will re-establish connection
print(new_conn.connection)

此示例显示了如何处理具有外部资源的对象。 实际连接未被 pickling - 仅是重建它所需的信息。

当 unpickling 时,将仅使用连接字符串重新创建对象,并且 __init__ 方法将建立新连接。

自定义重构函数

为了完全控制 unpickling,您可以提供自定义重构函数,而不是使用类构造函数。

custom_reconstructor.py
import pickle

class ComplexObject:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self._calculate()
    
    def _calculate(self):
        self.sum = self.x + self.y
    
    def __reduce_ex__(self, protocol):
        return (reconstruct_complex, (self.x, self.y))

def reconstruct_complex(x, y):
    obj = ComplexObject.__new__(ComplexObject)
    obj.x = x
    obj.y = y
    obj._calculate()
    return obj

obj = ComplexObject(10, 20)
data = pickle.dumps(obj)
new_obj = pickle.loads(data)
print(new_obj.sum)  # 30

此示例使用独立的函数进行重构,而不是类的 __init__ 方法。 这提供了对过程的完全控制。

重构函数创建对象而不调用 __init__,然后手动设置状态并调用必要的初始化方法。

最佳实践

资料来源

作者

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

列出所有 Python 教程