【问题标题】:Data structure of memoization in dbdb中memoization的数据结构
【发布时间】:2016-01-31 16:47:27
【问题描述】:

在数据库中缓存(保存/存储/记忆)这么多函数结果的最佳数据结构是什么。 假设函数 calc_regress 在 python 中具有流动定义:

def calc_regress(ind_key, dep_key, count=30):
    independent_list = sql_select_recent_values(count, ind_key)
    dependant_list = sql_select_recent_values(count, dep_key)
    import scipy.stats as st
    return st.linregress(independent_list, dependant_list)

我看到了What kind of table structure should be used to store memoized function parameters and results in a relational database? 的答案,但它似乎只解决了一个函数的问题,而我有大约 500 个函数。

【问题讨论】:

    标签: python database-design data-structures memoization


    【解决方案1】:

    选项 A

    您可以使用链接答案中的结构,未标准化,列数 = 500 个函数中的最大参数数。还需要为函数名添加一列。

    然后你可以做一个SELECT * FROM expensive_func_results WHERE func_name = 'calc_regress' AND arg1 = ind_key AND arg2 = dep_key and arg3 = count等。

    当然,这不是一个很好的设计。对于使用较少参数调用的相同函数,需要忽略具有空值/不匹配的列;否则你会得到多个结果行。

    选项 B

    将表/结构创建为func_nameargumentsresult,其中“参数”始终是 kwargs 字典或位置参数,但每个条目不混合。即使将 kwargs 字典存储为字符串,其中的键-> 值的顺序也是不可预测/一致的,即使它是相同的参数。因此,您需要先订购它,然后再转换为字符串并存储它。当您想要查询时,您将使用SELECT * FROM expensive_func_results WHERE func_name = 'calc_regress' AND arguments = 'str(kwargs_dict)',其中str(kwargs_dict) 是您将以编程方式设置的内容。它也可以设置为inspect.getargspec(或inspect.getcallargs)的结果,但您必须检查一致性。

    除非您为查询提供所有参数或与LIKE 部分匹配,否则您将无法对参数组合进行查询。

    选项 C

    一直标准化:一张表func_callsfunc_nameargs_combo_idarg_name_idxarg_value。表的每一行都将为该函数的调用参数的一个组合存储一个参数。另一个表func_resultsfunc_nameargs_combo_idresult。您还可以进一步规范化 func_name 以映射到 func_id

    在这一个中,关键字 args 的顺序无关紧要,因为您将执行内部联接来选择每个参数。该查询必须以编程方式构建或通过存储过程完成,因为获取所有参数所需的连接数由参数数决定。您上面的函数有 3 个参数,但您可能有另一个有 10 个参数。arg_name_idx 是“参数名称或索引”,因此它也适用于混合 kwargs + args。在calc_regress(ind_key=1, dep_key=2, count=30)calc_regress(1, 2, 30) 等情况下可能会出现一些重复(以及calc_regress(1, 2),默认值为count args_combo_id 会有所不同,但结果显然是相同的。同样,inspect 模块可能会在这方面有所帮助。


    [编辑] PS:此外,对于func_name,您可能需要使用完全限定名称以避免包中的模块之间发生冲突。装饰器也可能会干扰它;没有deco.__name__ = func.__name__ 等。

    PPS:如果将对象传递给正在数据库中记忆的函数,请确保它们的 __str__ 是有用且可重复/一致的,可以存储为 arg 值。

    这种特殊情况不需要您从数据库中的 arg 值重新创建对象,否则,您需要像 __repr__ was intended to be 那样制作 __str____repr__(但不是一般完成):

    这应该看起来像一个有效的 Python 表达式,可用于重新创建具有相同值的对象(给定适当的环境)。

    【讨论】:

    • 我更喜欢 C 部分,但我对此有些担心:如果我重构代码的某些部分会怎样?似乎保存结果就像在数据库中冻结您的代码!背诵是正确的做法吗? (根据软件工程原则)如果发生任何重构,我应该重新计算那些函数。
    【解决方案2】:

    我在这里使用键值存储,其中键可以是函数对象的id 的串联(以保证键的唯一性)及其参数,而值将是函数返回值。

    所以calc_regress(1, 5, 30) 调用会生成一个示例键139694472779248_1_5_30,其中第一部分是id(calc_regress)。密钥生成函数示例:

    >>> def produce_cache_key(fun, *args, **kwargs):
    ...     args_key = '_'.join(str(a) for a in args)
    ...     kwargs_key = '_'.join('%s%s' % (k, v) for k, v in kwargs.items())
    ...     return '%s_%s_%s' % (id(fun), args_key, kwargs_key)
    

    您可以使用字典和装饰器将结果保存在内存中:

    >>> def cache_result(cache):
    ...     def decorator(fun):
    ...         def wrapper(*args, **kwargs):
    ...             key = produce_cache_key(fun, *args, **kwargs)
    ...             if key not in cache:
    ...                 cache[key] = fun(*args, **kwargs)
    ...             return cache[key]
    ...         return wrapper
    ...     return decorator
    ... 
    >>> 
    >>> @cache_result(cache_dict)
    ... def fx(x, y, z=0):
    ...     print 'Doing some expensive job...'
    ... 
    >>> cache = {}
    >>> fx(1, 2, z=1)
    Doing some expensive job...
    >>> fx(1, 2, z=1)
    >>> 
    

    【讨论】:

    • 您的解决方案是否像 aneroid 答案的 B 部分,具有 func_namearguments 的联合主键?
    • 请看我的编辑,我提供了一个使用python字典的示例实现。
    猜你喜欢
    • 2019-12-06
    • 2018-07-31
    • 1970-01-01
    • 2010-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多