如何将生成器或迭代器转换为递归列表

时间:2010-07-05 06:02:42

标签: python recursion iterator generator immutability

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

Q1。帮帮我好版本。
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()

1 个答案:

答案 0 :(得分:5)

为了避免严重影响原始对象,您基本上需要copy.deepcopy的变体...巧妙地调整,因为您需要将生成器和迭代器转换为列表(深度复制不会深度复制生成器)。请注意,遗憾的是,对原始对象的某些效果是不可避免的,因为生成器和迭代器“耗尽”作为迭代它们的副作用(无论是将它们转换为列表还是其他任何其他对象)目的) - 因此,根本没有办法两者单独保留原始对象将该生成器或其他迭代器转换为“变体深度复制”中的列表“结果。

遗憾的是,copy模块并未编写为自定义,因此可选择复制粘贴编辑或微妙(叹气)猴子补丁(双重叹息)私有模块变量_deepcopy_dispatch(这意味着您的修补版本可能无法在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__等)。

当然,您想要处理copy.pyhere明文版本的副本,而不是我所指的注释版本(我使用过)带注释的版本只是为了明确需要更改的位置! - )。