【问题标题】:Python decorator to limit number of calls限制调用次数的 Python 装饰器
【发布时间】:2014-01-04 16:47:03
【问题描述】:

我想编写一个装饰器来限制对包装函数的调用次数。比如说,如果我希望包装函数最多被调用 10 次,那么装饰器应该在前 10 次执行该函数,然后它应该返回 None

这是我想出的:

from functools import wraps


def max_calls(num):
     """Decorator which allows its wrapped function to be called `num` times"""

     def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            calls = getattr(wrapper, 'calls', 0)
            calls += 1

            if calls == num:
                return None

            setattr(wrapper, 'calls', calls)
            return func(*args, **kwargs)
        setattr(wrapper, 'calls', 0)
        return wrapper
    return decorator

然而,这个计数正确调用,当达到限制时返回None,但是......它不会在程序运行之间重置。也就是说,如果我执行一次程序,计数器达到5,然后重新执行程序,它从5继续。我需要更改什么才能使装饰器正常工作?

【问题讨论】:

  • 在我的机器上,当我第二次运行脚本时,计数器会按预期重置。你是如何执行你的脚本的?我猜你没有从命令行执行python myscript.py
  • That is, if I execute the program once, the counter reaches 5, and then re-execute the program, it continues from 5 .. 你能解释一下你是如何执行程序的吗?
  • 你在 python 解释器中运行吗?
  • 我有一个 Flask 应用程序,在视图中我开始执行一个用上述装饰器装饰的函数。在这个函数内部,还有对同一函数的其他调用;我想实现的想法是只说,10次,要调用的包装函数,然后返回None

标签: python decorator


【解决方案1】:

问题在于您只维护一组呼叫计数。但这意味着每个 Flask 请求与所有其他请求共享调用计数。您需要做的是为每个 Flask 请求维护一组单独的调用计数。

从阅读API documentation 看来,完成这项工作的方法似乎是执行以下三个步骤:

  1. 创建一个flask.Request 的子类,可以存储您的函数调用计数:

    import collections
    import flask
    
    class MyRequest(flask.Request):
        """A subclass of Request that maintains function call counts."""
        def __init__(self, *args, **kwargs):
            super(MyRequest, self).__init__(*args, **kwargs)
            self.call_counts = collections.defaultdict(int)
    
  2. 在初始化应用程序时将request_class 设置为您的子类:

    app = flask.Flask(__name__, request_class=MyRequest, ...)
    
  3. 重写您的装饰器以将其计数存储在全局 flask.request 对象中:

    import functools
    
    def max_calls(num, default=None):
        """Decorator which allows its wrapped function to be called at most `num`
        times per Flask request, but which then returns `default`.
    
        """
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                if flask.request.call_counts[func] == num:
                    return default
                flask.request.call_counts[func] += 1
                return func(*args, **kwargs)
            return wrapper
        return decorator
    

但是写完之后,如果我不指出你的问题似乎很奇怪,那就太失职了。为什么要限制一个函数可以被 Flask 请求调用的次数?您是否尝试进行某种速率限制?使用其他方法似乎可以更好地完成您想做的任何事情。

【讨论】:

  • 听起来很奇怪,但它是解决我的问题的最佳解决方案:包装函数使 Soundcloud API 调用以通过给定的搜索词检索歌曲(搜索词从一个调用更改为另一个)。如果搜索失败超过 10 次,我的函数应该返回 None,而不是无限尝试使用不同的搜索词搜索 API。实际上,它并不是无限的,因为在某些时候它最终会返回一些东西,但是这个限制更像是一个超时,将请求时间保持在一个合理的限制。
猜你喜欢
  • 2015-02-18
  • 2019-08-02
  • 1970-01-01
  • 2015-09-04
  • 2019-11-07
  • 2016-02-26
  • 1970-01-01
  • 2017-12-11
  • 2020-11-25
相关资源
最近更新 更多