假设我有以下函数:
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 已经是列表、元组、范围或任何其他可以迭代多次的迭代器,这可能会效率低下。有更有效的解决方案吗?
一个简单的测试是看看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)
我可以在函数的开头插入一行:
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)
值得注意的是,内置函数zip、filter、map、 ... 和生成器都是迭代器。
1复制list128 个项目中的 a 与检查它是否是 a 大致一样快Collection。
| 归档时间: |
|
| 查看次数: |
410 次 |
| 最近记录: |