【问题标题】:What are the uses of iter(callable, sentinel)?iter(callable, sentinel) 的用途是什么?
【发布时间】:2016-06-28 22:41:23
【问题描述】:

所以,我正在观看 Raymond Hettinger 的演讲 Transforming Code into Beautiful, Idiomatic 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)

查看iterdocumentation后,发现了一个类似的例子:

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

这对我来说看起来很有用,但我想知道你们中的 Pythonistas 是否知道任何不涉及 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]
>>> 

但显然这并不比内置的迭代器有用。此外,当您为函数分配状态时,我觉得代码有异味。那时,我可能应该使用一个类,但如果我要编写一个类,我不妨实现迭代器协议来完成我想要完成的任何事情。

【问题讨论】:

标签: python


【解决方案1】:

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

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! :(

我们的想法是有一个生成随机值的生成器,并且您希望在选择特定值后进行处理。你可以例如在每次尝试确定随机过程的平均结果的模拟运行中使用此模式。

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

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

from foo_lib import guess_password

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

# protection cracked, continue...

【讨论】:

    【解决方案2】:

    通常,我看到的两个 arg iter 的主要用途涉及将类似于 C API(隐式状态,无迭代概念)的函数转换为迭代器。类似文件的对象是一个常见的例子,但它出现在其他对 C API 封装不佳的库中。您期望的模式将出现在像 FindFirstFile/FindNextFile 这样的 API 中,其中打开了一个资源,每次调用都会推进内部状态并返回一个新值或一个标记变量(如 C 中的 NULL) .将它包装在实现迭代器协议的类中通常是最好的,但如果你必须自己做,而 API 是 C 级内置的,包装最终会减慢使用速度,其中两个 arg iter 在 C 中实现为好吧,可以避免额外执行字节码的开销。

    其他示例涉及在循环本身期间更改的可变对象,例如,在字节数组中的行上以相反的顺序循环,仅在处理完成后删除该行:

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

    另一种情况是以渐进方式使用切片时,例如,一种有效(如果公认丑陋)的方式将可迭代分组为 n 项的组,同时允许最终组小于 n 项,如果输入可迭代的长度不是n 项目的偶数倍(这个我实际使用过,虽然我通常使用itertools.takewhile(bool 而不是两个arg 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
    

    另一种用途:将多个腌制对象写入单个文件,后跟一个标记值(例如None),因此在取消腌制时,您可以使用此成语而不需要以某种方式记住腌制项目的数量,或者需要一遍又一遍地打电话给load,直到EOFError

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

    【讨论】:

    • 感谢有关转换类似于 C API 的函数的背景,这正是我所寻找的。​​span>
    【解决方案3】:

    在多处理/多线程代码中,您会(希望)经常发现这种构造用于轮询队列或管道。在标准库中,您还可以在 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')
    

    不久前,我遇到了this 博客条目,IMO 很好地总结了 iter(callable, sentinel) 相对于 while True ... break 的优势:

    通常,当我们迭代一个对象或直到某个条件发生时,我们会在第一行中了解循环的范围。例如,当阅读以 for book in books 开头的循环时,我们意识到我们正在迭代所有的书。当我们看到一个以 while not battery.empty() 开头的循环时,我们意识到循环的范围是只要我们还有电池。 当我们说“永远做”(即当 True)时,很明显这个作用域是一个谎言。因此,它要求我们将这个想法牢记在心,并在代码的其余部分中寻找可以让我们摆脱困境的语句。我们正在以较少的信息进入循环,因此可读性较差。

    【讨论】:

      猜你喜欢
      • 2020-05-12
      • 1970-01-01
      • 1970-01-01
      • 2011-11-08
      • 2012-06-03
      • 1970-01-01
      • 2010-09-27
      • 2011-12-17
      • 2010-11-21
      相关资源
      最近更新 更多