如何对协程进行速率限制,并在限制后重新调用协程?



我一直在阅读这里的回复:什么是好的速率限制算法?

Carlos A. Ibarra 的回复在没有异步功能的情况下效果很好,但是有什么方法可以修改它以异步工作吗?

import time
def RateLimited(maxPerSecond):
minInterval = 1.0 / float(maxPerSecond)
def decorate(func):
lastTimeCalled = [0.0]
def rateLimitedFunction(*args,**kargs):
elapsed = time.clock() - lastTimeCalled[0]
leftToWait = minInterval - elapsed
if leftToWait>0:
time.sleep(leftToWait)
ret = func(*args,**kargs)
lastTimeCalled[0] = time.clock()
return ret
return rateLimitedFunction
return decorate
@RateLimited(2)  # 2 per second at most
def PrintNumber(num):
print num
if __name__ == "__main__":
print "This should print 1,2,3... at about 2 per second."
for i in range(1,100):
PrintNumber(i)

time.sleep(leftToWait)更改为await asyncio.sleep(leftToWait)并等待PrintNumber(i)首先有效,但此后就不起作用。我是Python的新手,并尽力遵守API的速率限制。

我的实现:

def rate_limited(max_per_second):
min_interval = 1.0 / float(max_per_second)
def decorate(func):
last_time_called = [0.0]
async def rate_limited_function(*args, **kargs):
elapsed = time.clock() - last_time_called[0]
left_to_wait = min_interval - elapsed
if left_to_wait > 0:
await asyncio.sleep(left_to_wait)
ret = func(*args, **kargs)
last_time_called[0] = time.clock()
return ret
return rate_limited_function
return decorate

class Test:
def __init__(self, bot):
self.bot = bot
@commands.command(hidden=True, pass_context=True)
@checks.serverowner()
async def test1(self, ctx):
await self.print_number()
@rate_limited(0.1)
def print_number(self):
print("TEST")

这是一个简单的 discord.py 解决方案。这使用on_command_error事件来保留命令并永久运行它,直到冷却时间得到解决,基本上是通过等待冷却时间asyncio.sleep

bot = commands.Bot('?')
@bot.command(hidden=True, pass_context=True)
@commands.cooldown(1, 5, commands.BucketType.user)  # means "allow to be called 1 time every 5 seconds for this user, anywhere"
async def test(ctx):
print("TEST")
@bot.event
async def on_command_error(exc, context: commands.Context):
if isinstance(exc, commands.errors.CommandOnCooldown):
while True:
await asyncio.sleep(exc.retry_after)
try:
return await context.command.invoke(context)
except commands.errors.CommandOnCooldown as e:
exc = e

在不和谐中(假设前缀为?(:

0s> ?test
1s> ?test
2s> ?test

在控制台中:

0s> TEST
5s> TEST
10s> TEST

在这里,您可以做的最简单的事情之一是使代码重新轮询共享变量,从而循环,而不是假设此当前实例将是单个睡眠后的下一个实例:

import time, asyncio
def rate_limited(max_per_second):
min_interval = 1.0 / float(max_per_second)
def decorate(func):
last_time_called = [0.0]
async def rate_limited_function(*args, **kargs):
elapsed = time.time() - last_time_called[0]
left_to_wait = min_interval - elapsed
while left_to_wait > 0:
await asyncio.sleep(left_to_wait)
elapsed = time.time() - last_time_called[0]
left_to_wait = min_interval - elapsed
ret = func(*args, **kargs)
last_time_called[0] = time.time()
return ret
return rate_limited_function
return decorate
@rate_limited(0.2)
def print_number():
print("Actually called at time: %r" % (time.time(),))
loop = asyncio.get_event_loop()
asyncio.ensure_future(print_number())
asyncio.ensure_future(print_number())
asyncio.ensure_future(print_number())
asyncio.ensure_future(print_number())
loop.run_forever()

。正确发出:

Actually called at time: 1530570623.868958
Actually called at time: 1530570628.873996
Actually called at time: 1530570633.876241
Actually called at time: 1530570638.879455

。显示两次呼叫之间的 5 秒(每秒 0.2 秒(。

最新更新