You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让dogpile捕获pickle序列化异常并返回函数实际值?

问题场景

以下代码尝试用dogpile.cache缓存返回lambda的函数结果,会触发pickle序列化错误:

from dogpile.cache import make_region
import logging

log = logging.getLogger(__name__)
TIMEOUT_SECONDS = 10 * 60


def my_key_generator(namespace, fn):
    fname = fn.__name__

    def generate_key(*arg):
        key_template = fname + "_" + "_".join(str(s) for s in arg)
        return key_template

    return generate_key


region = make_region(function_key_generator=my_key_generator).configure(
    "dogpile.cache.redis",
    expiration_time=TIMEOUT_SECONDS,
    arguments={
        "host": "localhost",
        "port": 6379,
        "db": 0,
        "redis_expiration_time": TIMEOUT_SECONDS * 2,  # 2 hours
        "distributed_lock": True,
        "thread_local_lock": False,
    },

)


@region.cache_on_arguments()
def load_user_info(user_id):
    log.info(f"Called func {user_id}")
    return lambda: user_id

print(load_user_info(1))

对应的错误栈:

Traceback (most recent call last):
  File "/Users/user/src/python-test/dogpile-test.py", line 120, in <module>
    print(load_user_info(1))
          ^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/decorator.py", line 232, in fun
    return caller(func, *(extras + args), **kw)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/dogpile/cache/region.py", line 1577, in get_or_create_for_user_func
    return self.get_or_create(
           ^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/dogpile/cache/region.py", line 1042, in get_or_create
    with Lock(
  File "/opt/homebrew/lib/python3.11/site-packages/dogpile/lock.py", line 185, in __enter__
    return self._enter()
           ^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/dogpile/lock.py", line 94, in _enter
    generated = self._enter_create(value, createdtime)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/dogpile/lock.py", line 178, in _enter_create
    return self.creator()
           ^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/dogpile/cache/region.py", line 1012, in gen_value
    self._set_cached_value_to_backend(key, value)
  File "/opt/homebrew/lib/python3.11/site-packages/dogpile/cache/region.py", line 1288, in _set_cached_value_to_backend
    key, self._serialized_cached_value(value)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/dogpile/cache/region.py", line 1258, in _serialized_cached_value
    return self._serialize_cached_value_elements(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/dogpile/cache/region.py", line 1232, in _serialize_cached_value_elements
    serializer(payload),
    ^^^^^^^^^^^^^^^^^^^
AttributeError: Can't pickle local object 'load_user_info.<locals>.<lambda>'

用户问题:是否存在方法让dogpile捕获该pickle序列化异常,记录异常日志并返回函数的实际值,而非导致整个函数调用失败?

解决方案

方案一:自定义安全缓存装饰器

通过包装dogpile原生装饰器,在调用时捕获序列化异常,记录日志后直接执行原函数返回结果:

import logging
import pickle
from dogpile.cache import make_region

log = logging.getLogger(__name__)
TIMEOUT_SECONDS = 10 * 60


def my_key_generator(namespace, fn):
    fname = fn.__name__
    def generate_key(*arg):
        key_template = fname + "_" + "_".join(str(s) for s in arg)
        return key_template
    return generate_key


region = make_region(function_key_generator=my_key_generator).configure(
    "dogpile.cache.redis",
    expiration_time=TIMEOUT_SECONDS,
    arguments={
        "host": "localhost",
        "port": 6379,
        "db": 0,
        "redis_expiration_time": TIMEOUT_SECONDS * 2,
        "distributed_lock": True,
        "thread_local_lock": False,
    },
)

def safe_cache_on_arguments():
    def decorator(func):
        cached_func = region.cache_on_arguments()(func)
        def wrapper(*args, **kwargs):
            try:
                return cached_func(*args, **kwargs)
            except Exception as e:
                # 精准匹配序列化相关异常
                if isinstance(e, (pickle.PicklingError, AttributeError)) or "Can't pickle" in str(e):
                    log.error(f"缓存序列化失败: {str(e)}")
                    # 跳过缓存,直接返回原函数结果
                    return func(*args, **kwargs)
                # 非序列化异常正常抛出
                raise
        return wrapper
    return decorator

@safe_cache_on_arguments()
def load_user_info(user_id):
    log.info(f"Called func {user_id}")
    return lambda: user_id

print(load_user_info(1))

方案二:重写CacheRegion类处理异常

继承dogpile的CacheRegion类,重写缓存值到后端的方法,内部捕获序列化异常:

import logging
import pickle
from dogpile.cache import make_region
from dogpile.cache.region import CacheRegion

log = logging.getLogger(__name__)
TIMEOUT_SECONDS = 10 * 60


class SafeCacheRegion(CacheRegion):
    def _set_cached_value_to_backend(self, key, value):
        try:
            super()._set_cached_value_to_backend(key, value)
        except Exception as e:
            # 识别序列化异常并记录日志
            if isinstance(e, (pickle.PicklingError, AttributeError)) or "Can't pickle" in str(e):
                log.error(f"缓存存储失败,key: {key}, 错误信息: {str(e)}")
            else:
                # 其他异常继续抛出
                raise


def my_key_generator(namespace, fn):
    fname = fn.__name__
    def generate_key(*arg):
        key_template = fname + "_" + "_".join(str(s) for s in arg)
        return key_template
    return generate_key


# 使用自定义Region类
region = SafeCacheRegion(function_key_generator=my_key_generator).configure(
    "dogpile.cache.redis",
    expiration_time=TIMEOUT_SECONDS,
    arguments={
        "host": "localhost",
        "port": 6379,
        "db": 0,
        "redis_expiration_time": TIMEOUT_SECONDS * 2,
        "distributed_lock": True,
        "thread_local_lock": False,
    },
)

@region.cache_on_arguments()
def load_user_info(user_id):
    log.info(f"Called func {user_id}")
    return lambda: user_id

print(load_user_info(1))

补充方案:替换序列化器解决根本问题

如果只是想让lambda这类对象可以被序列化,可使用dill替代默认pickle,既正常缓存又不抛出异常:

import dill
from dogpile.cache import make_region
import logging

log = logging.getLogger(__name__)
TIMEOUT_SECONDS = 10 * 60

def my_key_generator(namespace, fn):
    fname = fn.__name__
    def generate_key(*arg):
        key_template = fname + "_" + "_".join(str(s) for s in arg)
        return key_template
    return generate_key

region = make_region(function_key_generator=my_key_generator).configure(
    "dogpile.cache.redis",
    expiration_time=TIMEOUT_SECONDS,
    arguments={
        "host": "localhost",
        "port": 6379,
        "db": 0,
        "redis_expiration_time": TIMEOUT_SECONDS * 2,
        "distributed_lock": True,
        "thread_local_lock": False,
        # 指定dill作为序列化器
        "serializer": dill.dumps,
        "deserializer": dill.loads,
    },
)

@region.cache_on_arguments()
def load_user_info(user_id):
    log.info(f"Called func {user_id}")
    return lambda: user_id

print(load_user_info(1))

注意:使用dill需先安装pip install dill。

内容的提问来源于stack exchange,提问作者user

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 20:57:11