Python iter(可呼叫、哨兵)有什么用途?

Python iter(可呼叫、哨兵)有什么用途?,python,Python,所以,我在看雷蒙德·赫廷格的演讲,他提出了一种我从未意识到的iter。他的例子如下: >>> 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] >>>

所以,我在看雷蒙德·赫廷格的演讲,他提出了一种我从未意识到的
iter
。他的例子如下:

>>> 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]
>>> 
而不是:

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

在检查了国际热核聚变实验堆,我发现了一个类似的例子:

with open('mydata.txt') as fp:
    for line in iter(fp.readline, ''):
        process_line(line)
这对我来说似乎很有用,但我想知道你们这些Pythonistas是否知道这种构造的任何例子不涉及I/O-read循环?也许在标准图书馆

我可以想出一些非常做作的例子,例如:

>>> 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]
>>> 

但显然,这并没有内置的iterables更有用。另外,当您将状态分配给函数时,我觉得代码有味道。在这一点上,我可能应该使用一个类,但如果我要编写一个类,我可能会为我想要完成的任何事情实现迭代器协议

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! :(
这个想法是要有一个生成随机值的生成器,一旦选择了一个特定的值,您就可以创建一个进程。例如,您可以在模拟的每次运行中使用此模式,以确定随机过程的平均结果

当然,你建模的过程可能比简单的掷骰子复杂得多

我能想到的另一个例子是重复执行一个操作,直到成功为止,由一条空错误消息指示(这里假设某些第三方函数是这样设计的,而不是使用异常):


通常,我看到的两个arg iter的主要用途是将类似于capis(隐式状态,没有迭代概念)的函数转换为迭代器。类似文件的对象是一个常见的例子,但它出现在包装C API不好的其他库中。您期望的模式是在API中看到的模式,如
FindFirstFile
/
FindNextFile
,其中打开了一个资源,每个调用进入内部状态并返回一个新值或一个标记变量(如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)
...     ba[i:] = b''
...
24
19
14
9
4
另一种情况是,以渐进方式使用切片时,例如,如果输入iterable的长度不是
n
项的偶数倍,则允许最后一组小于
n
项,而将iterable分组为
n
项组的有效方法(如果公认丑陋的话)(虽然我通常使用的是
itertools.takewhile(bool
而不是两个arg
iter
),但我实际使用的是这个:

另一个用法:将多个pickle对象写入一个文件,后跟一个sentinel值(例如,
None
),因此在取消pickle时,您可以使用此习惯用法,而无需记住pickle的项目数,或者需要反复调用
load
,直到
eoferor

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

在多处理/多线程代码中,您(希望)经常可以在轮询队列或管道时找到此结构。在标准库中,您也可以在
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')
不久前,我看到了一篇博客文章,IMO将其完美地概括为
iter(callable,sentinel)
优于
while True…break

通常,当我们迭代某个对象或直到某个条件发生时,我们了解循环第一行的范围。例如,当在books中读取以for book开头的循环时,我们意识到我们正在迭代所有的书籍。当我们看到以while而非battery.empty()开头的循环时我们意识到,只要我们还有电池,循环的范围就一直存在。 当我们说“永远做”(即,虽然是真的),很明显,这个范围是一个谎言。因此,它要求我们在头脑中保持这种想法,并搜索代码的其余部分,以找到一条语句,使我们摆脱它。我们进入循环的信息较少,因此可读性较低


感谢您提供有关转换类似于C API的函数的背景知识,这正是我想要的。相关问题和错误报告
with open('picklefile', 'rb') as f:
    for obj in iter(pickle.Unpickler(f).load, None):
        ... process an object ...
@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')