小能豆

如何使用 dill 库和 shelve 库进行对象序列化

py

我正在使用PyMemoize库来缓存协程。我修饰了协程,但当 Python 调用它时,我得到:

TypeError: can't pickle coroutine objects

发生这种情况是因为PyMemoize内部尝试 pickle 协程并将其存储在 Redis 中。为此,它使用shelve.Shelf,而后者又使用pickle。问题是,由于未知原因,pickle不支持 pickle 协程。

我尝试使用 pickle 来处理协程,dill并且成功了。如何告知shelve将其用作dill序列化后端?

我尝试过 monkey-patch shelve,但是没有用(我不知道为什么):

import shelve
from dill import Pickler, Unpickler
shelve.Pickler = Pickler
shelve.Unpickler = Unpickler

阅读 9

收藏
2024-11-19

共1个答案

小能豆

在 Python 的标准库中,shelve 模块是基于 pickle 的,因此默认情况下它只支持 pickle。但是,shelve 可以通过自定义 pickle 类来更改其序列化行为。这意味着你可以替换 picklePicklerUnpickler,例如使用 dill,从而支持协程等不受 pickle 支持的对象。

以下是解决方案步骤:

替换 shelve 的默认 pickle 行为

你需要定义一个自定义的 Shelf 子类,明确使用 dillPicklerUnpickler

import shelve
import dill

class DillShelf(shelve.Shelf):
    def __init__(self, filename, flag='c', protocol=None, writeback=False):
        super().__init__(dill.open(filename, flag), protocol=protocol, writeback=writeback)

# 用法示例
with DillShelf('cache.db') as db:
    async def my_coroutine():
        return "Hello, coroutine!"

    db['my_coroutine'] = my_coroutine  # DillShelf 支持存储协程
    print(db['my_coroutine'])  # 输出协程对象

PyMemoize 添加支持

如果你希望在 PyMemoize 中直接替换默认的 shelve 序列化,可以通过配置来完成。以下是一个示例实现:

import dill
from pymemoize.decorators import memoize

# 自定义序列化与反序列化方法
def dill_serializer(obj):
    return dill.dumps(obj)

def dill_deserializer(data):
    return dill.loads(data)

# 在 `memoize` 中注册
@memoize(serializer=dill_serializer, deserializer=dill_deserializer)
async def my_coroutine(x):
    return x * 2

# 测试
import asyncio

async def main():
    result = await my_coroutine(5)  # 缓存调用
    print(result)  # 输出 10

asyncio.run(main())

原理说明

  1. 自定义 shelve: 通过继承 shelve.Shelf 并将 dill 替换为默认的序列化工具,你可以确保所有对象(包括协程)都可以存储。
  2. 修改 PyMemoize 配置: PyMemoize 支持自定义的序列化器和反序列化器,你只需传入 dill 的序列化函数即可。

这种方法可以避免直接 monkey-patch 标准库,同时保持代码的灵活性和可维护性。

2024-11-19