确保一个参数可以迭代两次

Ere*_*evi 8 python

假设我有以下函数:

def print_twice(x):
    for i in x: print(i)
    for i in x: print(i)
Run Code Online (Sandbox Code Playgroud)

当我跑步时:

print_twice([1,2,3])
Run Code Online (Sandbox Code Playgroud)

或者:

print_twice((1,2,3))
Run Code Online (Sandbox Code Playgroud)

我得到了预期的结果:数字 1,2,3 被打印两次。

但是当我跑步时:

print_twice(zip([1,2,3],[4,5,6]))
Run Code Online (Sandbox Code Playgroud)

(1,4),(2,5),(3,6) 对仅打印一次。可能这是因为zip返回一个在一次传递后终止的生成器。

如何修改该函数print_twice以使其正确处理所有输入?

我可以在函数的开头插入一行:x = list(x)。但如果 x 已经是列表、元组、范围或任何其他可以迭代多次的迭代器,这可能会效率低下。有更有效的解决方案吗?

kay*_*ya3 8

一个简单的测试是看看x当你迭代它时是否会被消耗iter(x) is x。这是可靠的,因为它被指定为迭代器协议的一部分(文档):

迭代器需要有一个__iter__()返回迭代器对象本身的方法

相反,如果iter(x)返回x自身,则x必须是迭代器,因为它是由iter函数返回的。

一些检查:

def is_iterator(x):
    return iter(x) is x

for obj in [
    # not iterators
    [1, 2, 3],
    (1, 2, 3),
    {1: 2, 3: 4},
    range(3),
    # iterators
    (x for x in range(3)),
    iter([1, 2, 3]),
    zip([1, 2], [3, 4]),
    filter(lambda x: x % 2 == 0, [1, 2, 3]),
    map(lambda x: 2 * x, [1, 2, 3]),
]:
    name = type(obj).__name__
    if is_iterator(obj):
        print(name, 'is an iterator')
    else:
        print(name, 'is not an iterator')
Run Code Online (Sandbox Code Playgroud)

结果:

list is not an iterator
tuple is not an iterator
dict is not an iterator
range is not an iterator
generator is an iterator
list_iterator is an iterator
zip is an iterator
filter is an iterator
map is an iterator
Run Code Online (Sandbox Code Playgroud)

因此,为了确保x可以多次迭代,而不需要进行不必要的复制(如果可以的话),您可以编写如下内容:

if iter(x) is x:
    x = list(x)
Run Code Online (Sandbox Code Playgroud)


Mis*_*agi 3

我可以在函数的开头插入一行:x = list(x)。但如果 x 已经是列表、元组、范围或任何其他可以迭代多次的迭代器,这可能会效率低下。有更有效的解决方案吗?

将一次性可迭代对象复制到 alist是完全足够的,即使对于多次使用可迭代对象来说也相当高效。

list(在某种程度上)类型tuple是 Python 中最优化的数据结构之一。list复制 a或tupleto a等常见操作list已进行内部优化;1即使对于非特殊情况的迭代,将它们复制到 a 也list比两个(或更多)循环完成的任何实际工作要快得多。

def print_twice(x):
    x = list(x)
    for i in x: print(i)
    for i in x: print(i)
Run Code Online (Sandbox Code Playgroud)

在并发上下文中,当函数运行时可迭代对象可能会被修改时,不加区别地复制也可能是有利的。常见的情况是线程和weakref集合。


如果想要避免不必要的副本,检查可迭代对象是否是 aCollection是一种合理的保护措施。

from collections.abc import Collection

x = list(x) if not isinstance(x, Collection) else x
Run Code Online (Sandbox Code Playgroud)

或者,可以检查 iterable 实际上是否是 iterat,因为这意味着有状态性,因此是一次性的。

from collections.abc import Iterator

x = list(x) if isinstance(x, Iterator) else x
x = list(x) if iter(x) is x else x
Run Code Online (Sandbox Code Playgroud)

值得注意的是,内置函数zipfiltermap、 ... 和生成器都是迭代器。


1复制list128 个项目中的 a 与检查它是否是 a 大致一样快Collection