How to clone a Python generator object?
本方案考虑:P></
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | #!/usr/bin/env python# -*- coding: utf-8 -*-
 import os
 
 walk = os.walk('/home')
 
 for root, dirs, files in walk:
 for pathname in dirs+files:
 print os.path.join(root, pathname)
 
 for root, dirs, files in walk:
 for pathname in dirs+files:
 print os.path.join(root, pathname)
 | 
我知道这一点,但你能闭嘴example is that we should,考虑使用walkneed to the same日期超过一次。我在基准情景和使用日期是强制性的walkof same to get帮助的结果。P></
我想walk2 = walkto the second迭代的克隆和使用,但它不挤压。The question is…如何可以复制吗?它曾经是可能的吗?P></
谢谢你提前。P></
		
		
- 两次使用os.walk('/home')有什么问题?这是怎么回事?
- @S.lott好吧,那种任务在每次运行中都有很大的不同。另一个问题是,在第一次运行之后,系统可能会缓存结果,因此在下一次运行中,我们将得到不精确的结果。其思想是先走一步,然后测量两个作为参数传递给它的场景。:)
- 缓存不会导致错误的结果。
- @pf.me:使用os.walk('/home')两次与您试图在"克隆"生成器的地方编写的代码有什么不同?两次写代码有什么问题?
- @S.lott在我正在测量的方法中运行os.walk()时,我注意到在随后的运行中,我得到的结果是随机的,有几秒钟的差异。然后,我的目标是测量步行之后,作为论点传递数据的情况。
- @pf.me:如果您正在对以下操作进行分析,那么您一定要将生成器展开到一个列表中,以消除目录爬行中的变化(请参阅下面的答案)。但是,如果您正在遍历的目录结构非常大,那么由于内存分页,您可能仍然会得到变化。
- @pf.me:"我注意到,在随后的运行中,我会得到几秒钟差异的随机结果。""克隆"os.walk('/home')生成器是如何修复这个问题的?
 
	 
您可以使用itertools.tee():
| 1
 | walk, walk2 = itertools.tee(walk) | 
请注意,正如文档所指出的,这可能"需要大量的额外存储"。
		
		
- 此外,文档中还提到:"一般来说,如果一个迭代器在另一个迭代器启动之前使用了大部分或全部数据,那么使用list()而不是tee()会更快。"考虑到OP的原始代码片段重复了一次,然后又一次,不建议他使用list()吗?
- 使用缓存的生成器,例如使用lambda: a_new_generator,如下所述。
- 另请参阅此答案的注释。
- 为什么我看到这么多人说没有办法在Python中克隆生成器??
- @ishansrivastava这实际上并不克隆生成器对象。它只是创建一个新的迭代器,生成相同的值,但新对象不再是生成器。
- 不,伙计,这不会复制生成器,它会将其转换为迭代器…不是发电机。假设我有一个生成器,它从一个包含60亿行的SQL表中部分地、顺序地获取数据…如果我使用itertools.tee,我会爆炸内存
 
	 
如果知道每次使用时都要遍历整个生成器,那么将生成器展开到一个列表并多次使用该列表,可能会获得最佳性能。
walk = list(os.walk('/home'))
		
		
- 出于好奇,为什么在一个生成器中迭代每个对象的必要性使得在迭代之前在内存中保存一个值映射更有效?
 
	 
定义函数
| 12
 3
 
 |  def walk_home():for r in os.walk('/home'):
 yield r
 | 
甚至这个
| 12
 
 | def walk_home():return os.walk('/home')
 | 
两者都是这样使用的:
| 12
 3
 
 | for root, dirs, files in walk_home():for pathname in dirs+files:
 print os.path.join(root, pathname)
 | 
		
		
- 虽然这不是OP所问的确切问题的答案,但这是一种在不将完整目录树存储在内存中的情况下完成此操作的好方法。+ 1
- 循环是不必要的。def walk_home(): return os.walk('/home')也做同样的事情。
- @斯文·马纳奇:"准确"的问题毫无意义。
- @尚:说得好!
 
	 
这是functools.partial()的一个很好的使用案例。制造快速发电机工厂:
| 12
 3
 4
 5
 6
 
 | from functools import partialimport os
 
 walk_factory = partial(os.walk, '/home')
 
 walk1, walk2, walk3 = walk_factory(), walk_factory(), walk_factory()
 | 
functools.partial()所做的很难用人类语言来描述,但这^正是它的目的。
它部分地填充函数参数而不执行该函数。因此,它充当一个功能/发电机工厂。
此答案旨在扩展/详细说明其他答案所表达的内容。解决方案必然会有所不同,具体取决于您的目标是实现什么。
如果您要多次重复os.walk的完全相同的结果,则需要从os.walkiterable的项(即walk = list(os.walk(path)))初始化一个列表。
如果您必须保证数据保持不变,那可能是您唯一的选择。然而,有几种情况下这是不可能或不可取的。
如果输出的大小足够大(即尝试使用list()文件系统,整个文件系统可能会冻结您的计算机),则不可能使用list()一个iterable。
如果您希望在每次使用之前获得"新"数据,那么list()是不可取的。
如果list()不合适,您需要按需运行发电机。请注意,发电机每次使用后都会熄灭,因此这会造成一个小问题。要多次"重新运行"生成器,可以使用以下模式:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | #!/usr/bin/env python# -*- coding: utf-8 -*-
 import os
 
 class WalkMaker:
 def __init__(self, path):
 self.path = path
 def __iter__(self):
 for root, dirs, files in os.walk(self.path):
 for pathname in dirs + files:
 yield os.path.join(root, pathname)
 
 walk = WalkMaker('/home')
 
 for path in walk:
 pass
 
 # do something...
 
 for path in walk:
 pass
 | 
上述设计模式将允许您保持代码干燥。