【问题标题】:Copying a generator without blowing up memory在不破坏内存的情况下复制生成器
【发布时间】:2016-04-29 16:24:47
【问题描述】:

我正在编写一个 python 类,它可以在给定整数 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

所以,我的问题是:真的没有办法在不创建这个潜在的巨大内存问题的情况下复制生成器吗?我不关心保留生成器的部分状态,我只希望它迭代与原始生成器相同的值。

编辑

看起来在 Python 3.X 中,你可以使用 copy.deepcopy 复制 itertools 对象,其依赖关系都是可挑选的。

【问题讨论】:

  • 澄清一下,对于将要处理大量itertools 迭代器的项目来说,这是一个假设性问题。上面建议的两种解决方案的实现都是微不足道的,我想知道复制 itertools.xxx 对象是否绝对不可能

标签: python generator itertools


【解决方案1】:

没有什么是不可能的……

以下内容恰好适用于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 3.X,因为 copy.copycopy.deepcopy 在 Python 2.7 中都会抛出错误。我要去测试其他itertools 对象。
  • 你没有提到 Python 的特定版本,所以我假设 CPython 3.x。
  • 为了这个好处可能值得改变,Python 2.7 并没有真正阻止我,我只是没想到行为会有所不同。
  • 看起来他们在 Python 3 中为 itertools 迭代器添加了酸洗支持,所以只要它们的依赖项是可腌制的,itertools 迭代器将是可腌制和可深度复制的。但是,当涉及到生成器时将无济于事。
  • 好的,所以我的问题的答案似乎是“生成器可能不可复制,但由于itertools 支持在 Python 3.X 中选择,这些对象可以使用内置的-在copy 模块中。”谢谢你们!
【解决方案2】:

无法复制任意迭代器。极少数特定的迭代器类型支持复制;我知道的唯一一个是itertools.tee。不过,一般来说,迭代器可能有太多不可复制的依赖项,以至于复制机制无法成为迭代器协议的一部分。

您只是遇到了这个问题,因为您编写了一个 API,它试图采用一次性迭代器并返回一个可重用的对象。如果要使用迭代器,则应将 API 设计为返回迭代器而不是 MagicSquare 对象,您可以创建一次,然后重复调用 solutions

对于您的用例,我建议将MagicSquare 设为生成器。主要的,可能仅用于此类的似乎是调用solutions 以获得解决方案的迭代器。为什么不简单地将类替换为具有 MagicSquare(size, combinations).solutions() 当前功能的函数?

【讨论】:

    【解决方案3】:

    不要传递生成器,而是传递一个函数,该函数在调用时返回一个新的生成器。这将允许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)
        ...
    

    【讨论】:

    • 在我的问题中,我注意到“我可以要求提供生成器的函数,而不是要求生成器本身,但这需要用户包装他们的生成器表达式。”似乎无法复制迭代器。
    • 啊,对不起,我错过了那部分。
    【解决方案4】:

    由于您的生成器是独立的且具有确定性,因此使用两个副本的最佳方法是创建其中两个。 (如有必要,修改MagicSquare 的签名以接受两个生成器;但您似乎想要将副本用于其他目的?)

    combs2a = permutations(range(20,30), 9)
    combs2b = permutations(range(20,30), 9)
    

    【讨论】:

    • 但是如果我想任意或未知次数地重用生成器怎么办?
    • 您每次都创建另一个?在您描述的一般情况下,您无法知道您将再次需要什么,因此无法绕过输出的完全缓存,就像tee 所做的那样。无论您是按照公认的答案制作另一个还是克隆都没有什么区别 - 如果您插入一个新的生成器,您需要参与其中。 deepcopy 很聪明,但需要您小心谨慎并(正确)测试每个新输入。
    猜你喜欢
    • 2013-08-15
    • 1970-01-01
    • 1970-01-01
    • 2016-09-01
    • 2020-06-24
    • 2011-03-11
    • 2017-12-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多