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

为何Python中用dict.setdefault实现的缓存装饰器重复调用函数?

为什么使用字典setdefault的装饰器无法正确缓存函数结果?

我尝试编写一个Python装饰器用于缓存函数执行结果。当cacheDecorator返回wrapper函数时,重复调用参数相同的f1(1,2,3)不会再次执行f1;但当返回使用字典setdefault方法的wrapper2时,即使传入相同参数,每次调用都会执行f1,不清楚原因。

复现代码

#!/bin/python

import logging
import sys

logging.basicConfig(
    format="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
    level=logging.INFO,
    datefmt="%Y-%m-%d %H:%M:%S",
    stream=sys.stdout,
)
logger = logging.getLogger("mylogger")

cache = {}

# Relevant stuff begins here.
def cacheDecorator(func):

    """
    Goal is to decorate a function so that successive calls to it with the same
    arguments return the cached value, rather than invoking the function again
    unnecessarily.
    :param func:
    :return:  cached value when possible
    """

    def wrapper(*args, **kwargs):
        # This works
        global cache

        if args in cache:
            return cache.get(args)
        else:
            retVal = func(*args, **kwargs)
            cache[args] = retVal
            return  retVal

    def wrapper2(*args, **kwargs):
        # This doesn't work - it always invokes func even on successive
        # calls with the same input arguments passed to f1.
        global cache
        return cache.setdefault(args,func(*args, **kwargs) )

    #return wrapper # Causes f1 to be executed only once, as desired
    return wrapper2 # Causes f1 to be executed again even with same arguments. Why??

@cacheDecorator
def f1 (a, b, c, /):
    logger.info('Inside f1')
    result = a * b * c
    return result

logger.info (f'Result from executing f1(1,2,3) = {f1(1,2,3)}')
logger.info (f'Result from executing f1(1,2,3) again = {f1(1,2,3)}')

sys.exit(0)

问题原因

核心问题是Python的参数求值顺序:
调用cache.setdefault(args, func(*args, **kwargs))时,Python会先计算所有传入的参数值,再执行setdefault方法。也就是说,不管args是否已经在缓存里,func(*args, **kwargs)都会先被执行,把结果计算出来后才传给setdefault。这就导致每次调用wrapper2都会无条件执行原函数,完全起不到缓存的作用。

而wrapper的逻辑是先判断缓存是否存在:只有当args不在缓存中时,才会执行原函数并缓存结果,否则直接返回缓存值,这才符合缓存的预期行为。

修复方案

如果想用setdefault实现缓存,必须避免提前执行函数,但setdefault本身不支持懒加载参数,所以最直接的方式还是回到wrapper的判断逻辑:

def wrapper2(*args, **kwargs):
    global cache
    if args not in cache:
        cache[args] = func(*args, **kwargs)
    return cache[args]

或者直接使用Python标准库自带的functools.lru_cache装饰器,它已经实现了成熟的缓存逻辑,无需自己重复造轮子:

from functools import lru_cache

@lru_cache(maxsize=None)
def f1(a, b, c, /):
    logger.info('Inside f1')
    result = a * b * c
    return result

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 12:12:52