【问题标题】:Cache decorator for numpy arraysnumpy 数组的缓存装饰器
【发布时间】:2019-02-19 06:22:02
【问题描述】:

我正在尝试为具有 numpy 数组输入参数的函数制作缓存装饰器

from functools import lru_cache
import numpy as np
from time import sleep

a = np.array([1,2,3,4])

@lru_cache()
def square(array):
    sleep(1)
    return array * array

square(a)

但是 numpy 数组是不可散列的,

TypeError                                 Traceback (most recent call last)
<ipython-input-13-559f69d0dec3> in <module>()
----> 1 square(a)

TypeError: unhashable type: 'numpy.ndarray'

因此需要将它们转换为元组。我有这个工作和缓存正确:

@lru_cache()
def square(array_hashable):
    sleep(1)
    array = np.array(array_hashable)
    return array * array

square(tuple(a))

但我想把它全部包在一个装饰器中,到目前为止我已经尝试过:

def np_cache(function):
    def outter(array):
        array_hashable = tuple(array)

        @lru_cache()
        def inner(array_hashable_inner):
            array_inner = np.array(array_hashable_inner)
            return function(array_inner)

        return inner(array_hashable)

    return outter

@np_cache
def square(array):
    sleep(1)
    return array * array

但是缓存不起作用。计算已执行但未正确缓存,因为我总是等待 1 秒。

我在这里缺少什么?我猜lru_cache 没有得到正确的上下文并且它在每次调用中都被实例化,但我不知道如何解决它。

我试过盲目地到处乱扔functools.wraps装饰器,但没有运气。

【问题讨论】:

    标签: python python-3.x numpy caching decorator


    【解决方案1】:

    您的包装函数会在您每次调用时创建一个新的inner() 函数。并且那个新的函数对象在那个时候被修饰了,所以最终的结果是每次调用outter(),都会创建一个新的lru_cache(),并且它会是空的。空缓存总是需要重新计算值。

    您需要创建一个装饰器,将缓存附加到为每个装饰目标创建一次的函数。如果要在调用缓存之前转换为元组,则必须创建 两个 函数:

    from functools import lru_cache, wraps
    
    def np_cache(function):
        @lru_cache()
        def cached_wrapper(hashable_array):
            array = np.array(hashable_array)
            return function(array)
    
        @wraps(function)
        def wrapper(array):
            return cached_wrapper(tuple(array))
    
        # copy lru_cache attributes over too
        wrapper.cache_info = cached_wrapper.cache_info
        wrapper.cache_clear = cached_wrapper.cache_clear
    
        return wrapper
    

    cached_wrapper() 函数在每次调用 np_cache() 时只创建一次,并且可作为闭包供 wrapper() 函数使用。所以wrapper() 调用cached_wrapper(),它附加了一个@lru_cache(),缓存你的元组。

    我还复制了 lru_cache 放在修饰函数上的两个函数引用,因此它们也可以通过返回的包装器访问。

    此外,我还使用@functools.wraps() decorator 将元数据从原始函数对象复制到包装器,例如名称、注释和文档字符串。这总是一个好主意,因为这意味着您的修饰函数将在回溯中、调试时以及您需要访问文档或注释时清楚地标识。装饰器还添加了一个__wrapped__ 属性指向原始函数,即let you unwrap the decorator again if need be

    【讨论】:

    • 我想补充一点,您可以通过将数组分配给 np_cache() 中定义的非局部变量来在 wrapper() 和 cashed_wrapper() 之间共享数组,以便行 'array = np.array( hashable_array)' 可以被删除。
    • @Pulsar:不,你不能。它不会是线程安全的。您无法确定传递给 cached_wrapper() 的任何内容都是同一数组的元组值版本。两者之间共享的任何闭包都应像对待每个被装饰的 function 范围内的全局一样对待。
    猜你喜欢
    • 2015-07-29
    • 1970-01-01
    • 2022-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-03
    • 1970-01-01
    • 2018-09-13
    相关资源
    最近更新 更多