确保与装饰者之间的等待时间最短

时间:2014-07-21 10:09:01

标签: python decorator

我正在构建一个API的包装器,它要求每次调用之间至少等待1秒钟。我想我可以通过以下方式使用装饰器来解决这个问题:

import datetime, time

last_time = datetime.datetime(2014, 1, 1)

def interval_assurer(f):
    global last_time
    if (datetime.datetime.now() - last_time).seconds < 1:
        print("Too fast...")
        time.sleep(1)

    last_time = datetime.datetime.now()

    return f

@interval_assurer
def post():
    pass

由于某种原因,这不会起作用,我不知道为什么。 last_time第一次调用post时会更新,但之后不会更新。请记住,这是我第一次尝试装饰器,所以我可能遗漏了一些基本的东西。

感谢。

2 个答案:

答案 0 :(得分:4)

由于你的interval_assurer是装饰者,所以它只调用一次:定义函数时,而不是调用函数时。您需要创建一个这样的包装函数:

import time, functools

def interval_assurer(f):
    last_time = [0]
    @functools.wraps(f)  # optional, but nice to have
    def wrapper(*args, **kwargs):
        time_diff = time.time() - last_time[0]
        if time_diff < 1:
            print("Too fast...")
            time.sleep(1 - time_diff)

         last_time[0] = time.time()   
         return f(*args, **kwargs)

    return wrapper

@interval_assurer
def post(self, **kwargs):
    pass

你也不需要全局(在Python 3中,列表的技巧可以用nonlocal代替)。

答案 1 :(得分:2)

装饰器函数只返回原始函数,因此定时代码仅在调用装饰器时运行,即在评估函数定义时。相反,它应该返回包含时序代码的新函数,并在适当时调用f

尝试类似:

def interval_assurer(f):
    def func():
        global last_time
        if (datetime.datetime.now() - last_time).seconds < 1:
            print("Too fast...")
            time.sleep(1)
        last_time = datetime.datetime.now()
        return f()
    return func

如果您的装饰函数需要参数,则应在*args, **kwargs的定义中包含func并调用f;另外,请考虑依次使用functools.wraps(f)装饰func


在@bereal的答案的基础上,您可以使last_time包装函数的属性删除global(允许多个包装函数,每个函数都有自己的计时器),以及甚至让一个装饰器接受间隔的参数来强制执行:

import functools
import time

def interval_assured(interval):
    """Ensure consecutive calls are separated by a minimal interval."""
    def wrapper(f):
        @functools.wraps(f)
        def func(*args, **kwargs):
            if (time.time() - func.last_time) < interval:
                time.sleep(interval)
            result = f(*args, **kwargs)
            func.last_time = time.time()
            return result
        func.last_time = time.time()
        return func
    return wrapper

注意在调用包装函数f之后重置时间 - 如果f的运行时为相对于间隔而言很大。

使用中:

>>> def testing(x):
    print time.time()
    print x


>>> for x in range(3):
    testing(x)


1405938405.97
0
1405938406.01
1
1405938406.02
2
>>> @interval_assured(5)
def testing(x):
    print time.time()
    print x


>>> for x in range(3):
    testing(x)


1405938429.71
0
1405938434.73
1
1405938439.75
2