【问题标题】:Throttle a function call in python在python中限制函数调用
【发布时间】:2015-11-02 13:31:10
【问题描述】:

我有以下类型的代码,但速度很慢,因为report() 经常被调用。

import time
import random

def report(values):
    open('report.html', 'w').write(str(values))

values = []

for i in range(10000):
    # some computation
    r = random.random() / 100.
    values.append(r)
    time.sleep(r)
    # report on the current status, but this should not slow things down
    report(values)

在这个说明性代码示例中,我希望报告是最新的(最多 10 年代旧),所以我想限制该功能。

我可以在报告中分叉,写入当前时间戳,然后等待该时间段,然后使用共享内存时间戳检查是否同时调用了报告。如果是,终止,如果不是,写报告。

有没有更优雅的方式在 Python 中做到这一点?

【问题讨论】:

  • 对共享队列使用线程?
  • 我想它很慢,因为你每次都打开文件(也应该关闭)。如果您保持文件打开(将其传递到报告函数或创建reporter 类),则可能不会花费很长时间。
  • 您打算每次都覆盖该文件?
  • @Trengot 说了什么。您应该在for 循环之外打开文件,然后将打开的文件作为第二个参数传递给report() 函数。然后在for 循环结束后关闭文件。虽然你的整个report() 函数变成了def report(f, values): f.write(str(values)),你可能会考虑内联它。无需重新创建file.write() 方法:)
  • 由于您只是向报告文件添加值并且从未更改,您可以通过以附加模式打开文件来将值附加到文件中吗?例如。您不需要每次都写入所有值,您可以每隔 N 个值附加到文件中。

标签: python throttling


【解决方案1】:

这是一个装饰器,它将接受一个参数来说明保护内部函数多长时间,如果调用过早引发异常。

import time
from functools import partial, wraps

class TooSoon(Exception):
  """Can't be called so soon"""
  pass

class CoolDownDecorator(object):
  def __init__(self,func,interval):
    self.func = func
    self.interval = interval
    self.last_run = 0
  def __get__(self,obj,objtype=None):
    if obj is None:
      return self.func
    return partial(self,obj)
  def __call__(self,*args,**kwargs):
    now = time.time()
    if now - self.last_run < self.interval:
      raise TooSoon("Call after {0} seconds".format(self.last_run + self.interval - now))
    else:
      self.last_run = now
      return self.func(*args,**kwargs)

def CoolDown(interval):
  def applyDecorator(func):
    decorator = CoolDownDecorator(func=func,interval=interval)
    return wraps(func)(decorator)
  return applyDecorator

然后:

>>> @CoolDown(10)
... def demo():
...   print "demo called"
...
>>>
>>> for i in range(12):
...   try:
...     demo()
...   except TooSoon, exc:
...     print exc
...   time.sleep(1)
...
demo called
Call after 8.99891519547 seconds
Call after 7.99776816368 seconds
Call after 6.99661898613 seconds
Call after 5.99548196793 seconds
Call after 4.9943420887 seconds
Call after 3.99319410324 seconds
Call after 2.99203896523 seconds
Call after 1.99091005325 seconds
Call after 0.990563154221 seconds
demo called
Call after 8.99888515472 seconds

【讨论】:

  • 在 OP 的上下文中这种方法的问题是最后 N 秒的数据永远不会写入文件。
  • 好点。但是,您可以保留该函数的未修饰版本并将其称为最终写入。
  • 这个解决方案的问题是,如果计算的持续时间(这里是time.sleep)是随机的(例如在0.1s和100s之间),那么demo()可能很长一段时间都不会被调用超过 10 秒。
  • @j13r。我已经理解要问的关于节流的问题。我认为在指定时间内防止多次执行是一种合理的限制尝试。您所描述的此解决方案的问题,我认为是提供解决方案的功能。如果这项工作需要 100 秒,那么报告中就没有什么新内容了。一旦该工作迭代完成,将编写报告。如果下一个工作需要 0.01 秒,则不会更新报告。
  • 困难在于相反的情况,如果你有 .01s、.01s 和 100s。
【解决方案2】:

这是一个在 Python3 中使用闭包限制函数的示例。

import time

def get_current_time_milli():
    return int(round(time.time() * 1000))

def mycallbackfunction():
    time.sleep(0.1)  #mocking some work
    print ("in callback function...")


'''
Throttle a function call using closures.
Don't call the callback function until last invokation is more than 100ms ago.
Only works with python 3. 
Caveat: python 2 we cannot rebind nonlocal variable inside the closure. 

'''

def debouncer(callback, throttle_time_limit=100):

    last_millis = get_current_time_milli()  

    def throttle():
        nonlocal last_millis
        curr_millis = get_current_time_milli()
        if (curr_millis - last_millis) > throttle_time_limit:
            last_millis = get_current_time_milli()
            callback()

return throttle    

# 
myclosure_function = debouncer(mycallbackfunction, 100)

# we are calling myclosure_function 20 times, but only few times, the callback is getting executed. 

# some event triggers this call repeatedly. 
for i in range(20):
    print('calling my closure', myclosure_function(), get_current_time_milli())

【讨论】:

  • 你为什么用毫秒
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-03
  • 2016-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多