将while循环转换为可重用的形式
我经常会用到这样的代码模式:
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循环,所有的 break
、continue
和 else
的用法都保持不变。
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 ...