Python 在不破坏内存的情况下复制生成器
我正在编写一个python类,在给定一个整数Python 在不破坏内存的情况下复制生成器,python,generator,itertools,Python,Generator,Itertools,我正在编写一个python类,在给定一个整数size和一个用于可能的组合的生成器的情况下,找到所有可能的方法。这些组合是长度为size**2的元组,并被拆分为size×size网格。代码本身工作正常,但重用生成器似乎需要itertools.tee。在下面所示的示例中,这会导致线程使用的内存跳转到300MB,因为迭代器中的每个值都存储在一个列表中 from itertools import permutations, tee class MagicSquare: def __init__
size
和一个用于可能的组合的生成器的情况下,找到所有可能的方法。这些组合是长度为size**2
的元组,并被拆分为size
×size
网格。代码本身工作正常,但重用生成器似乎需要itertools.tee
。在下面所示的示例中,这会导致线程使用的内存跳转到300MB,因为迭代器中的每个值都存储在一个列表中
from itertools import permutations, tee
class MagicSquare:
def __init__(self, size, combinations):
self.size = size
self.range = range(self.size)
self.combinations = combinations
def getGrid(self, entries):
return [ entries[self.size*i:self.size*(i+1)] for i in self.range ]
def checkGrid(self, grid):
check_sum = sum(grid[0])
if any( sum(row) != check_sum for row in grid ):
return False
if any( sum(row[col] for row in grid) != check_sum for col in self.range ):
return False
if sum(grid[diag][diag] for diag in self.range) != check_sum:
return False
if sum(grid[diag][self.size-diag-1] for diag in self.range) != check_sum:
return False
return True
def solutions(self):
combinations, self.combinations = tee(self.combinations)
for entries in combinations:
grid = self.getGrid(entries)
if self.checkGrid(grid):
yield grid
if __name__ == '__main__':
combs = permutations(range(20,30), 9)
ms = MagicSquare(3, combs)
for solution in ms.solutions():
for row in solution:
print row
print
对于这个问题,我想到了两个显而易见的解决方案。首先,我可以要求提供生成器的函数,而不是要求生成器本身,但这需要用户包装其生成器表达式。其次,我可以缓存解决方案。为了便于讨论,假设如果没有足够数量的解,我不想再检查对角线,因此我需要更新checkGrid
,并重复组合
所以,我的问题是:真的没有办法复制一个生成器而不产生这个潜在的巨大内存问题吗?我不关心是否保留生成器的部分状态,我只希望它在与原始生成器相同的值上迭代
编辑
在Python3.X中,您可以使用copy.deepcopy
来复制依赖项都是可拾取的itertools
对象。由于生成器是自包含的和确定性的,因此使用两个副本的最佳方法是创建两个副本。(如有必要,请修改MagicSquare
的签名以接受两个生成器;但您似乎希望该副本用于其他目的?)
无法复制任意迭代器。极少数特定迭代器类型支持复制;我只知道一个是itertools.tee
。不过,一般来说,迭代器可能有太多不可复制的依赖项,使得复制机制无法成为迭代器协议的一部分
您之所以会遇到这个问题,是因为您编写了一个API,试图使用一次性迭代器并返回一个可重用对象。如果要使用迭代器,则应将API设计为返回迭代器,而不是可以创建一次然后反复调用solutions
的MagicSquare
对象
对于您的用例,我建议您制作MagicSquare
生成器。该类的主要用途(可能仅限于此)似乎是调用solutions
作为解决方案的迭代器。为什么不简单地用一个函数替换该类,该函数执行MagicSquare(大小、组合)。solutions()
当前的功能?传递一个函数,而不是传递一个生成器,该函数在调用时返回一个新的生成器。这将允许MagicSquare
根据需要多次迭代组合,而无需将它们保存在内存中
要解释代码,请执行以下操作:
class MagicSquare:
def __init__(self, size, get_combinations):
...
self.get_combinations = get_combinations
...
def solutions(self):
for entries in self.get_combinations():
...
if __name__ == '__main__':
combs2 = lambda: permutations(range(20,30), 9) #
ms2 = MagicSquare(3, combs2)
...
没有什么是不可能的
以下内容恰好适用于itertools.permutations
。不要假设它能与任何iterable一起工作,因为它不会
>>> from itertools import permutations
>>> combs = permutations(range(20,30), 9)
>>> from copy import deepcopy
>>> combs2 = deepcopy(combs)
>>> next(combs)
(20, 21, 22, 23, 24, 25, 26, 27, 28)
>>> next(combs2)
(20, 21, 22, 23, 24, 25, 26, 27, 28)
澄清一下,这是一个假设问题,对于一个将要处理大量itertools
迭代器的项目来说。上面建议的两种解决方案的实现都很简单,我想知道是否绝对不可能复制itertools.xxx
对象。但是,如果我想重复使用生成器任意或未知次数,该怎么办?您每次都会创建另一个?在您描述的一般情况下,您无法再次知道需要什么,因此无法绕过输出的完全缓存,就像tee
所做的那样。无论您是按照公认的答案制作另一个还是克隆,都没有什么区别——如果插入新的生成器,您需要参与其中deepcopy
很聪明,但它依赖于你的细心和(正确地)测试每一个新输入。在我的问题中,我注意到“我可以要求提供生成器的函数,而不是要求生成器本身,但这需要用户包装其生成器表达式。”似乎不可能复制迭代器。啊,抱歉,我错过了这一部分。这似乎只适用于Python 3.X,因为copy.copy
和copy.deepcopy
都在Python 2.7中抛出错误。我将使用其他itertools
对象进行测试。您没有提到Python的特定版本,所以我假设是CPython 3.x。为了这个好处,可能值得更改,没有什么真正让我坚持使用Python 2.7,我只是没想到会有不同的行为。看起来他们在Python 3中为itertools
迭代器添加了pickle支持,所以只要它们的依赖项是可pickle的,itertools
迭代器将是可pickle和可深度复制的。但是,当涉及到生成器时,这不会有帮助。好吧,我的问题的答案似乎是“生成器可能不可复制,但由于itertools
支持在Python 3.X中拾取,因此可以使用内置的copy
模块复制这些对象。”谢谢你们!
>>> from itertools import permutations
>>> combs = permutations(range(20,30), 9)
>>> from copy import deepcopy
>>> combs2 = deepcopy(combs)
>>> next(combs)
(20, 21, 22, 23, 24, 25, 26, 27, 28)
>>> next(combs2)
(20, 21, 22, 23, 24, 25, 26, 27, 28)