Как преобразовать генератор или итератор в список рекурсивно - PullRequest
7 голосов
/ 05 июля 2010

Я хочу преобразовать генератор или итератор в список рекурсивно.
Я написал код ниже, но он выглядит наивно и некрасиво, и в 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 Ответ

5 голосов
/ 05 июля 2010

Чтобы избежать плохого влияния на исходный объект, вам в основном нужен вариант copy.deepcopy ... слегка подправленный, потому что вам нужно превратить генераторы и итераторы в списки (глубокое копирование в любом случае не приведет к глубокому копированию генераторов). Обратите внимание, что некоторый эффект на исходный объект, к сожалению, неизбежен, потому что генераторы и итераторы «исчерпаны» как побочный эффект итерации по ним (будь то превращение их в списки или для любых других целей). ) - следовательно, просто невозможно и оставить исходный объект в покое и , превратив этот генератор или другой итератор в список в результате "варианта с глубокой копией".

Модуль copy, к сожалению, не написан для настройки, поэтому альтернативные варианты: копирование-вставка-редактирование или тонкое (вздох) обезьяна-патч, основанное на (двойном вздохе) переменной приватного модуля _deepcopy_dispatch (что означает, что ваша исправленная версия может не выдержать обновления версии Python, скажем, с 2.6 до 2.7, гипотетически). Кроме того, обезьяна-патч должна быть удалена после каждого использования eagerize (во избежание влияния на другие варианты использования deepcopy). Итак, давайте предположим, что мы выбрали вместо этого маршрут копирования-вставки-редактирования.

Скажем, мы начинаем с самой последней версии, той, которая онлайн здесь . Конечно, вам нужно переименовать модуль; переименуйте внешне видимую функцию от deepcopy до eagerize в строке 145; существенное изменение в строках 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.py, здесь , а не аннотированную версию, на которую я ссылался (я использовал Аннотированная версия просто для того, чтобы уточнить, где именно были необходимы изменения! -).

...