在不破坏内存的情况下复制生成器
Copying a generator without blowing up memory
我正在写一个 python class,在给定整数 size
和可能的 combinations
的生成器的情况下,找到所有可能的 magic squares。这些组合是长度为 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
并重复 combinations
.
所以,我的问题是:是否真的没有办法在不产生这个潜在的巨大内存问题的情况下复制生成器?我不关心保留生成器的部分状态,我只希望它迭代与原始生成器相同的值。
编辑
好像在Python3.X中,你可以使用copy.deepcopy
复制itertools
个依赖项都是pickable的对象
由于您的生成器是 self-contained 并且是确定性的,因此使用两个副本的最佳方法是创建其中两个。 (如有必要,修改 MagicSquare
的签名以接受两个生成器;但看起来您想要副本用于其他目的?)
combs2a = permutations(range(20,30), 9)
combs2b = permutations(range(20,30), 9)
无法复制任意迭代器。极少数特定迭代器类型支持复制;我唯一知道的是 itertools.tee
。但是,一般来说,迭代器可能有太多不可复制的依赖关系,以至于复制机制无法成为迭代器协议的一部分。
您只是 运行 陷入这个问题,因为您编写了一个 API 试图采用 one-shot 迭代器和 return 一个可重用对象。如果您要使用迭代器,则应将 API 设计为 return 迭代器,而不是可以创建一次然后重复调用 solutions
的 MagicSquare
对象.
对于您的用例,我建议制作 MagicSquare
生成器。主要的,可能仅用于此 class 似乎是调用 solutions
作为解决方案的迭代器。为什么不简单地将 class 替换为可以执行 MagicSquare(size, combinations).solutions()
当前功能的函数?
不是传递生成器,而是传递一个函数,该函数在调用时 returns 一个新的生成器。这将允许 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
。不要假设它适用于任何可迭代对象,因为它不会!
>>> 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)
我正在写一个 python class,在给定整数 size
和可能的 combinations
的生成器的情况下,找到所有可能的 magic squares。这些组合是长度为 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
并重复 combinations
.
所以,我的问题是:是否真的没有办法在不产生这个潜在的巨大内存问题的情况下复制生成器?我不关心保留生成器的部分状态,我只希望它迭代与原始生成器相同的值。
编辑
好像在Python3.X中,你可以使用copy.deepcopy
复制itertools
个依赖项都是pickable的对象
由于您的生成器是 self-contained 并且是确定性的,因此使用两个副本的最佳方法是创建其中两个。 (如有必要,修改 MagicSquare
的签名以接受两个生成器;但看起来您想要副本用于其他目的?)
combs2a = permutations(range(20,30), 9)
combs2b = permutations(range(20,30), 9)
无法复制任意迭代器。极少数特定迭代器类型支持复制;我唯一知道的是 itertools.tee
。但是,一般来说,迭代器可能有太多不可复制的依赖关系,以至于复制机制无法成为迭代器协议的一部分。
您只是 运行 陷入这个问题,因为您编写了一个 API 试图采用 one-shot 迭代器和 return 一个可重用对象。如果您要使用迭代器,则应将 API 设计为 return 迭代器,而不是可以创建一次然后重复调用 solutions
的 MagicSquare
对象.
对于您的用例,我建议制作 MagicSquare
生成器。主要的,可能仅用于此 class 似乎是调用 solutions
作为解决方案的迭代器。为什么不简单地将 class 替换为可以执行 MagicSquare(size, combinations).solutions()
当前功能的函数?
不是传递生成器,而是传递一个函数,该函数在调用时 returns 一个新的生成器。这将允许 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
。不要假设它适用于任何可迭代对象,因为它不会!
>>> 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)