将while循环转换为可重用的形式

1 投票
3 回答
668 浏览
提问于 2025-04-18 02:40

我经常会用到这样的代码模式:

num_repeats = 123
interval = 12

for _ in xrange(num_repeats):
    result = ...
    if result meets condition:
         break
    time.sleep(interval)

else:
    raise Failed despite multiple attempts

基本上,它会重复执行代码,直到得到正确的结果,或者计数器到期。

虽然这样做有效,但我觉得代码写得有点啰嗦。有没有办法把这个循环“参数化”,变成一个可以重复使用的函数或者上下文管理器,比如说:

with repeat(num_repeats, interval):
    code

或者,也许标准库里有什么工具可以解决这个问题?

3 个回答

1

你肯定不能使用 with 语句,因为 Python 只在代码运行前后提供一些钩子,而没有提供在运行时调用的钩子,也就是说,你不能把一个循环藏在 with 语句里面。

一个不错的办法是使用一个 lambda 函数:

def repeat(repeats, interval, func):
    for i in xrange(repeats):
        if func(i):
            break
        time.sleep(interval)

然后你可以很简单地使用它:

repeat(123, 12, lambda i: condition(i))

或者类似的其他方法。

1

一种方法是给你想要重复的函数加上装饰器:

def repeats_until(num_repeats, interval, condition):
    def deco(f):
        def func(*args, **kwargs):
            for _ in xrange(num_repeats):
                result = f(*args, **kwargs)
                if condition(result):
                    return result
                time.sleep(interval)
        return func
    return deco

然后你可以这样使用它:

@repeats_until(3, 5, lambda s: s == "hello")
def take_input():
    return raw_input("Say hello: ")

举个例子(虽然我不能展示等待的过程!)

>>> take_input()
Say hello: foo
Say hello: bar
Say hello: baz
>>> take_input()
Say hello: hello
'hello'

另外,为了保持条件和被调用的函数一起,可以这样做:

def repeats(num_repeats, interval):
    def deco(f):
        def func(*args, **kwargs):
            for _ in xrange(num_repeats):
                result = f(*args, **kwargs)
                if result is not None: # or e.g. False if None is valid return
                    return result
                time.sleep(interval)
        return func
    return deco    

@repeats(3, 5)
def take_input(condition):
    s = raw_input("Say hello: ")
    if condition(s):
        return s

ui = take_input(lambda s: s == "hello")

这依赖于被装饰的函数返回一个值(在这个例子中是隐含的None),这个值告诉装饰器它还没有完成。

1

你可以使用一个生成器,在返回重复结果之前先“睡一觉”。这样做的好处是,调用这个生成器的地方依然是一个真正的for循环,所有的 breakcontinueelse 的用法都保持不变。

def trickle_range(num_repeats, interval):
    yield 0
    for k in xrange(1, num_repeats):
          time.sleep(interval)
          yield k


for k in trickle_range(num_repeats, interval):
    ... do stuff, iterate or break as you like ...

撰写回答