【问题标题】:Flask cache memoize not working with flask restful resourcesFlask 缓存 memoize 不能使用 Flask Restful 资源
【发布时间】:2017-08-01 00:10:36
【问题描述】:

flask_cache.Cache.memoize 不能与 flask_restful.Resource 一起使用

这里是示例代码:

from flask import Flask, request, jsonify
from flask_restful import Resource, Api
from flask_cache import Cache

app = Flask(__name__)
api = Api(app)
cache = Cache(app, config={'CACHE_TYPE': 'simple'})


class MyResource(Resource):
    JSONIFY = True
    PATH = None
    ENDPOINT = None

    def dispatch_request(self, *args, **kwargs):
        kw = dict(**kwargs)
        kw.update(request.args.items())
        r = super().dispatch_request(*args, **kw)
        if self.JSONIFY:
            return jsonify(r)
        else:
            return r


class DebugResource(MyResource):
    PATH = '/debug'
    ENDPOINT = 'debug'

    @cache.memoize(timeout=30)
    def get(self, **kwargs):
        print('cache is not used!')
        return kwargs

for r in [DebugResource]:
    api.add_resource(r, r.PATH, endpoint=r.ENDPOINT)


print('running!')
app.run()

请注意,在get() 中,我添加了 print 以便我可以查看代码何时被实际调用以及何时使用缓存值。

我启动服务器然后在浏览器中我转到http://localhost:5000/debug?a=1 并重复按f5。我希望我的函数get 被调用一次,然后使用缓存值。但是在服务器控制台中,每次按f5 时,我都会看到我的打印内容。所以memoize 不起作用。我做错了什么?

编辑:

我将缓存函数从Resource 类移到了外面

@cache.memoize(timeout=30)
def my_foo(a):
    print('cache is not used!')
    return dict(kw=a, id=id(a))

class DebugResource(MyResource):
    PATH = '/debug'
    ENDPOINT = 'debug'

    def get(self, a):
        return my_foo(a)

这很奏效。据我所知,问题是self 参数在每个调用中实际上都是唯一的。 问题仍然是,如何在不为我要缓存的每个方法提取附加功能的情况下使其工作?当前的解决方案看起来像是一种解决方法。

【问题讨论】:

  • 我不知道是谁点击了downvote 来回答你的问题,但我不明白你为什么点击downvote 来回答我的问题。我想帮忙,我确信我的解决方案是有效的。你能解释一下你为什么这样做吗?
  • 抱歉,我点击了downvote,因为这不是我所期望的,尽管您的解决方案会起作用。我想改回我的投票,但在你编辑你的答案之前我不会被允许这样做。我的意思是我希望我的缓存考虑函数参数并且只考虑它们。所以我可以用同样的方式装饰资源方法和任何其他功能。因此,当缓存使用 request.path 作为键而不是 args 时,它不适合我。问题是 arg self影响缓存键计算并使其在每个请求中都是唯一的。
  • 可以回滚downvote吗?我更新了我的答案。
  • 谢谢。祝你发展顺利;)

标签: python caching flask flask-restful flask-cache


【解决方案1】:

缓存不起作用,因为您使用了memoize 方法。在这种情况下,它将缓存函数的结果。装饰器对路由(视图,路径)一无所知。

要修复它,您应该使用cached 方法。 @cached 装饰器有参数 key_prefix,默认值 = view/request.path

所以,只需将 @cache.memoize(timeout=30) 更改为 @cache.cached(timeout=30)

【讨论】:

    【解决方案2】:

    感谢@Rugnar,这个决定派上了用场。

    solution

    唯一的一点,我不得不稍微改变一下,这样我就不会排除第一个元素(self),而是使用它,以便在缓存方法定义在基类,并在子类中自定义。

    方法_extract_self_arg已更新。

    class ResourceCache(Cache):
    """ When the class method is being memoized,
        cache key uses the class name from self or cls."""
    
    def _memoize_make_cache_key(self, make_name=None, timeout=None):
        def make_cache_key(f, *args, **kwargs):
            fname, _ = function_namespace(f)
            if callable(make_name):
                altfname = make_name(fname)
            else:
                altfname = fname
            updated = altfname + json.dumps(dict(
                args=self._extract_self_arg(f, args),
                kwargs=kwargs), sort_keys=True)
            return b64encode(
                md5(updated.encode('utf-8')).digest()
            )[:16].decode('utf-8')
    
        return make_cache_key
    
    @staticmethod
    def _extract_self_arg(f, args):
        argspec_args = inspect.getargspec(f).args
    
        if argspec_args and argspec_args[0] in ('self', 'cls'):
            if hasattr(args[0], '__name__'):
                return (args[0].__name__,) + args[1:]
            return (args[0].__class__.__name__,) + args[1:]
        return args
    

    也许它对某人也有用。

    【讨论】:

    • 如果我使用 @staticmethod 并在 get() 方法中删除 self 怎么办?这破坏了代码,有解决方法吗?
    【解决方案3】:

    它不起作用,因为 memoize takes function's arguments into account in the cache key 并且每个新请求都会获得唯一的 kwargsid function 的唯一结果)。

    要查看,只需修改代码

    @cache.memoize(timeout=30)
    def get(self, **kwargs):
        print('cache is not used!')
        return id(kwargs)
    

    每一个新的请求你都会得到另一个结果。所以每个新的请求缓存键都不一样,这就是你在控制台输出看到cache is not used!的原因。

    【讨论】:

    • 使用**kwargs 不是好的解决方案,因为其他开发人员不会看到函数的签名。输入数据就像一个黑盒子。
    • @DanilaGanchar 我并不是说这是一个好习惯,这只是一个 OP 的代码,我用它来描述为什么缓存“不起作用”。
    • 这个我试过了,id没变,但是问题还是@cache.memoize(timeout=30)def get(self, a):print('cache is not used!')return dict(kw=a, id=id(a))
    • @Rugnar 只需使用 @cache.cached,正如 Danila Ganchar 所建议的并定义可调用的 key_prefixstackoverflow.com/a/14264116/3866610
    【解决方案4】:

    通过子类化Cache 并重载为memoize 创建缓存键的逻辑找到了解决方案。所以它工作正常。

    import json
    import inspect
    from base64 import b64encode
    from hashlib import md5
    from flask_cache import Cache, function_namespace
    
    class ResourceCache(Cache):
        def _memoize_make_cache_key(self, make_name=None, timeout=None):
            def make_cache_key(f, *args, **kwargs):
                fname, _ = function_namespace(f)
                if callable(make_name):
                    altfname = make_name(fname)
                else:
                    altfname = fname
    
                updated = altfname + json.dumps(dict(
                    args=self._extract_self_arg(f, args),
                    kwargs=kwargs), sort_keys=True)
    
                return b64encode(
                    md5(updated.encode('utf-8')).digest()
                )[:16].decode('utf-8')
    
            return make_cache_key
    
        @staticmethod
        def _extract_self_arg(f, args):
            argspec_args = inspect.getargspec(f).args
            if argspec_args and argspec_args[0] in ('self', 'cls'):
                return args[1:]
            return args
    

    换句话说,当类方法被记忆时,缓存会忽略第一个参数selfcls

    【讨论】:

      猜你喜欢
      • 2013-12-08
      • 2014-09-25
      • 2017-11-23
      • 1970-01-01
      • 2021-04-22
      • 2019-11-01
      • 2020-03-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多