【问题标题】:How to convert generator or iterator to list recursively如何将生成器或迭代器以递归方式转换为列表
【发布时间】:2011-03-11 18:49:22
【问题描述】:

我想将生成器或迭代器转换为递归列表。
我在下面写了一段代码,但它看起来很幼稚和丑陋,在doctest中可能会被丢弃。

第一季度。帮我看看好版本。
Q2。如何指定对象是不可变的?

import itertools

def isiterable(datum):
    return hasattr(datum, '__iter__')

def issubscriptable(datum):
    return hasattr(datum, "__getitem__")

def eagerlize(obj):
    """ Convert generator or iterator to list recursively.
    return a eagalized object of given obj.
    This works but, whether it return a new object, break given one.

    test 1.0 iterator

    >>> q = itertools.permutations('AB',  2)
    >>> eagerlize(q)
    [('A', 'B'), ('B', 'A')]
    >>>

    test 2.0 generator in list

    >>> q = [(2**x for x in range(3))]
    >>> eagerlize(q)
    [[1, 2, 4]]
    >>>

    test 2.1 generator in tuple

    >>> q = ((2**x for x in range(3)),)
    >>> eagerlize(q)
    ([1, 2, 4],)
    >>>

    test 2.2 generator in tuple in generator

    >>> q = (((x, (y for y in range(x, x+1))) for x in range(3)),)
    >>> eagerlize(q)
    ([(0, [0]), (1, [1]), (2, [2])],)
    >>>

    test 3.0 complex test

    >>> def test(r):
    ...     for x in range(3):
    ...         r.update({'k%s'%x:x})
    ...         yield (n for n in range(1))
    >>>
    >>> def creator():
    ...     r = {}
    ...     t = test(r)
    ...     return r, t
    >>>
    >>> a, b = creator()
    >>> q = {'b' : a, 'a' : b}
    >>> eagerlize(q)
    {'a': [[0], [0], [0]], 'b': {'k2': 2, 'k1': 1, 'k0': 0}}
    >>>

    test 3.1 complex test (other dict order)

    >>> a, b = creator()
    >>> q = {'b' : b, 'a' : a}
    >>> eagerlize(q)
    {'a': {'k2': 2, 'k1': 1, 'k0': 0}, 'b': [[0], [0], [0]]}
    >>>

    test 4.0 complex test with tuple

    >>> a, b = creator()
    >>> q = {'b' : (b, 10), 'a' : (a, 10)}
    >>> eagerlize(q)
    {'a': ({'k2': 2, 'k1': 1, 'k0': 0}, 10), 'b': ([[0], [0], [0]], 10)}
    >>>

    test 4.1 complex test with tuple (other dict order)

    >>> a, b = creator()
    >>> q = {'b' : (b, 10), 'a' : (a, 10)}
    >>> eagerlize(q)
    {'a': ({'k2': 2, 'k1': 1, 'k0': 0}, 10), 'b': ([[0], [0], [0]], 10)}
    >>>

    """
    def loop(obj):
        if isiterable(obj):
            for k, v in obj.iteritems() if isinstance(obj, dict) \
                         else enumerate(obj):
                if isinstance(v, tuple):
                    # immutable and iterable object must be recreate, 
                    # but realy only tuple?
                    obj[k] = tuple(eagerlize(list(obj[k])))
                elif issubscriptable(v):
                    loop(v)
                elif isiterable(v):
                    obj[k] = list(v)
                    loop(obj[k])

    b = [obj]
    loop(b)
    return b[0]

def _test():
    import doctest
    doctest.testmod()

if __name__=="__main__":
    _test()

【问题讨论】:

  • 必须是递归解决方案吗?为什么?
  • 尽量避免超过 80 个字符的行。当你必须使用水平滚动时,阅读代码非常不舒服。
  • 谢谢,大卫 因为我没有注意到。那么,循环解决方案呢?

标签: python recursion iterator generator immutability


【解决方案1】:

为了避免严重影响原始对象,您基本上需要copy.deepcopy... 的变体,因为您需要将生成器和迭代器转换为列表(深度复制无论如何都不会深度复制生成器)。请注意,不幸的是,对原始对象的 一些 影响是不可避免的,因为生成器和迭代器被“耗尽”作为一直迭代它们的副作用(无论是将它们变成列表还是任何其他目的)——因此,您根本不可能单独留下原始对象并且将该生成器或其他迭代器变成“变体深度复制”中的列表" 结果。

不幸的是,copy 模块不是为定制而编写的,因此替代方案是复制粘贴编辑,或者一个微妙的(叹气)猴子补丁(双叹)私人模块变量@987654325 @ (这意味着您的修补版本可能无法在 Python 版本升级后继续存在,例如从 2.6 到 2.7,假设)。另外,每次使用eagerize 后都必须卸载猴子补丁(以避免影响deepcopy 的其他用途)。所以,假设我们选择了复制粘贴编辑路线。

假设我们从最新版本开始,即在线here 的版本。当然,您需要重命名模块;在第 145 行将外部可见函数 deepcopy 重命名为 eagerize;实质性变化在第 161-165 行,在所述版本中,注释为:

161 :               copier = _deepcopy_dispatch.get(cls)
162 :               if copier:
163 :                   y = copier(x, memo)
164 :               else:
165 :   tim_one 18729           try:

我们需要在第 163 行和第 164 行之间插入逻辑“否则,如果它是可迭代的,则将其扩展为列表(即,使用函数 _deepcopy_list 作为复制器”。所以这些行变为:

161 :               copier = _deepcopy_dispatch.get(cls)
162 :               if copier:
163 :                   y = copier(x, memo)
                     elif hasattr(cls, '__iter__'):
                         y = _deepcopy_list(x, memo)
164 :               else:
165 :   tim_one 18729           try:

就是这样:只添加了两行。请注意,我已经单独留下了原始行号,以便完全清楚 在哪里 需要插入这两行,而不是为这两个新行编号。您还需要将标识符deepcopy(间接递归调用)的其他实例重命名为eagerize

您还应该删除第 66-144 行(您不关心的浅拷贝功能)并适当调整第 1-65 行(文档字符串、导入、__all__ 等)。

当然,你想复制一份 plaintext 版本的copy.pyhere,而不是我一直提到的带注释版本(我只使用了带注释的版本明确需要更改的确切位置!-)。

【讨论】:

  • 哇,我们很幸运有你在这里亚历克斯!
  • 谢谢,这是 alex 的好主意。这真的很有帮助。我对其进行了测试,并且 deepcopy 可以正常工作,但测试 3.x 4.x 失败。因为生成器 test(r) 对 dict r 有副作用。非常抱歉混淆了文档字符串顶部的表达。我需要副作用。 dict r 必须更改。
猜你喜欢
  • 2011-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-12
  • 1970-01-01
  • 2016-04-28
  • 2012-04-24
  • 2016-01-26
相关资源
最近更新 更多