iter(callable, sentinel)的用途是什么?

35

所以,我在观看Raymond Hettinger的演讲“将代码转换为美丽,惯用的Python”时,他提到了这种我从未意识到的iter形式。他的例子如下:

改为:

blocks = []
while True:
    block = f.read(32)
    if block == '':
        break
    blocks.append(block)

使用:

blocks = []
read_block = partial(f.read, 32)
for block in iter(read_block, ''):
    blocks.append(block)

在查看iter文档后,我找到了一个相似的示例:

with open('mydata.txt') as fp:
    for line in iter(fp.readline, ''):
        process_line(line)

这对我来说看起来非常有用,但我想知道你们中的 Pythonista 是否知道任何不涉及 I/O 读取循环的此结构的示例?或许在标准库中能找到?

我可以想到一些非常牵强的例子,比如下面这个:

>>> def f():
...     f.count += 1
...     return f.count
... 
>>> f.count = 0
>>> list(iter(f,20))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> 

但显然,这并不比内置的可迭代对象更有用。而且,当您将状态分配给函数时,这似乎对我来说是一种代码异味。此时,我可能应该使用类,但如果我要编写一个类,我也可以为我想要完成的任何事情实现迭代器协议。


相关问题 https://stackoverflow.com/questions/52446415/line-in-iterfp-readline-rather-than-line-in-fp/ 和错误报告 https://bugs.python.org/issue34764 - Chris_Rands
3个回答

19

这是我想到的一个愚蠢的例子:

from functools import partial
from random import randint

pull_trigger = partial(randint, 1, 6)

print('Starting a game of Russian Roulette...')
print('--------------------------------------')

for i in iter(pull_trigger, 6):
    print('I am still alive, selected', i)

print('Oops, game over, I am dead! :(')

样例输出:

$ python3 roulette.py 
Starting a game of Russian Roulette...
--------------------------------------
I am still alive, selected 2
I am still alive, selected 4
I am still alive, selected 2
I am still alive, selected 5
Oops, game over, I am dead! :(

这个想法是有一个生成器,它会产生随机值,你想在选择特定的值后进行一项处理。例如,在每次运行试图确定随机过程平均结果的模拟中使用此模式。

当然,你正在建模的过程在内部可能比简单的掷骰子要复杂得多...

我能想到的另一个例子是反复执行操作,直到成功为止,并且没有错误消息指示(在这里,让我们假设某些第三方函数是按照这种方式设计而不是使用异常):

from foo_lib import guess_password

for msg in iter(guess_password, ''):
    print('Incorrect attempt, details:', msg)

# protection cracked, continue...

11

通常,我见过的两个参数迭代器的主要用途是将类似于C API(隐式状态,没有迭代概念)的函数转换为迭代器。文件对象是一个常见的例子,但它也出现在其他库中,这些库不太好地包装了C API。你期望看到的模式就像FindFirstFile/FindNextFile这样的API中一样,打开一个资源,每次调用都会推进内部状态并返回一个新值或标记变量(例如C中的NULL)。将其包装在实现迭代器协议的类中通常是最佳选择,但如果必须自己完成,那么在API是C级内置的情况下,实现为两个参数的迭代器同样是在C中实现的,可以避免额外的字节码执行开销。

其他例子涉及可变对象,在循环本身中进行更改,例如,在bytearray中反向循环行,在处理完成后才删除该行:

>>> from functools import partial
>>> ba = bytearray(b'aaaa\n'*5)
>>> for i in iter(partial(ba.rfind, b'\n'), -1):
...     print(i)
...     del ba[:i]
...
24
19
14
9
4

另一种情况是在逐步切片时使用,例如,一种高效(虽然可能不太好看)的方法是将可迭代对象分组为n个元素的组,同时允许最后一组元素小于n个,如果输入的可迭代对象长度不是n的整数倍(我实际上用过这种方法,尽管我通常使用itertools.takewhile(bool)而不是两个参数的iter):

# from future_builtins import map  # Python 2 only
from itertools import starmap, islice, repeat

def grouper(n, iterable):
    '''Returns a generator yielding n sized tuples from iterable

    For iterables not evenly divisible by n, the final group will be undersized.
    '''
    # Keep islicing n items and converting to groups until we hit an empty slice
    return iter(map(tuple, starmap(islice, repeat((iter(iterable), n)))).__next__, ())  # Use .next instead of .__next__ on Py2

另一种用法:将多个 pickled 对象写入单个文件,然后跟随一个标记值(例如 None),以便在反序列化时,您可以使用此惯用语,而无需记住 pickled 的项目数量,或需要一遍又一遍地调用 load 直到 EOFError

with open('picklefile', 'rb') as f:
    for obj in iter(pickle.Unpickler(f).load, None):
        ... process an object ...

1
谢谢您提供有关将函数转换为类似于 C API 的背景信息,这正是我在寻找的。 - juanpa.arrivillaga

6

在多进程/多线程代码中,你会经常找到这个构造用于轮询队列或管道。在标准库中,你也会在 multiprocessing.Pool 中找到它:

@staticmethod
def _handle_tasks(taskqueue, put, outqueue, pool, cache):
    thread = threading.current_thread()

    for taskseq, set_length in iter(taskqueue.get, None):
        task = None
        try:
            # iterating taskseq cannot fail
            for task in taskseq:
        ...
    else:
        util.debug('task handler got sentinel')

之前我看到了这篇博客文章,其中讲述了用iter(callable, sentinel)来取代while True ... break的优点,我认为很好地概括了:

通常,当我们迭代对象或直到某个条件发生时,我们在第一行就可以理解循环的范围。例如,当我们阅读以for book in books开始的循环时,我们意识到我们正在遍历所有的书籍。当我们看到以while not battery.empty()开始的循环时,我们意识到循环的范围是在电池不为空时。

当我们说“永久执行”(即while True)时,显然这个范围是一个谎言。因此,它要求我们保持这个想法在脑海中,并搜索代码的其余部分,以找到一个将我们推出循环的语句。我们进入循环时掌握的信息更少,因此可读性更差。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接