使用带有sentinel的iter()替换while循环

cs9*_*s95 1 python generator

通常情况下,人们需要无限循环直到达到某种条件.例如,如果我想继续收集随机整数,直到我找到一个数字== n,然后我打破.我这样做:

import random

rlist = []
n = ...
low, high = ..., ...
while True:
    num = random.randint(low, high)
    if num == n:
        break
    rlist.append(num)
Run Code Online (Sandbox Code Playgroud)

这很有效,但非常笨重.有一个更多的pythonic替代品使用iter:

iter(o [,sentinel])

返回一个迭代器对象.根据第二个参数的存在,第一个参数的解释非常不同.[...]如果给出第二个参数sentinel,则o必须是可调用对象.在这种情况下创建的迭代器将为每个对其next()方法的调用调用o而不带参数; 如果返回的值等于sentinel,则会引发StopIteration,否则返回该值.

上面的循环可以替换为

import random
from functools import partial

f = partial(random.randint, low, high)
rlist = list(iter(f, 10))
Run Code Online (Sandbox Code Playgroud)

要将此原则扩展到已创建的列表,需要稍作更改.我需要定义一个像这样的部分函数:

f = partial(next, iter(x)) # where x is some list I want to keep taking items from until I hit a sentinel
Run Code Online (Sandbox Code Playgroud)

其余的保持不变,但这种方法与while循环的主要警告是我不能应用泛型布尔条件.

例如,我不能应用"生成数字,直到遇到大于1000的第一个偶数".


底线是这样的:是否有另一种替代while循环并iter支持回调sentinel?

Sne*_*tel 5

如果你想要通用布尔条件,那么iter(object, sentinel)你的需求就没有足够的表现力.itertools.takewhile()相反,它似乎或多或少是你想要的:它需要一个迭代器,并在给定的谓词停止为真时将其切断.

rlist = list(itertools.takewhile(lambda x: x >= 20, inputlist))
Run Code Online (Sandbox Code Playgroud)

顺便说一下,partial并不是非常Pythonic,也不是itertools.GvR正在记录为不喜欢高阶函数式编程(请注意reduce3.0 中从内置到模块成员的降级).像"优雅"和"可读"这样的属性在旁观者的眼中,但如果你在最纯粹的意义上寻找Pythonic,你需要while循环.