Как оценить ограничение сопрограммы и повторно вызвать сопрограмму после лимита? - PullRequest
0 голосов
/ 03 июля 2018

Я прочитал ответы здесь: Что такое хороший алгоритм ограничения скорости?

Ответ от Карлоса А. Ибарры прекрасно работает без асинхронных возможностей, но есть ли способ изменить его, чтобы он работал асинхронно?

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")

Ответы [ 2 ]

0 голосов
/ 03 июля 2018

Вот простое решение 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
0 голосов
/ 03 июля 2018

Одна из самых простых вещей, которые вы можете сделать здесь, - это заставить ваш код повторно опрашивать общие переменные и, таким образом, выполнять цикл, а не , предполагая , что этот текущий экземпляр будет следующим после одного сна :

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 в секунду).

...