2010-07-05 2 views
7

Je veux convertir un générateur ou un itérateur en liste de manière récursive.
J'ai écrit un code ci-dessous, mais il semble naïf et laid, et peut être abandonné cas dans doctest.Comment convertir un générateur ou un itérateur en liste récursive

Q1. Aidez-moi bonne version.
Q2. Comment spécifier l'objet est immuable ou non?

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() 
+0

Est-ce que ce doit être une solution récursive? Pourquoi? –

+0

Essayez d'éviter les lignes de plus de 80 caractères. Il est très inconfortable de lire du code lorsque vous devez utiliser le défilement horizontal. –

+0

Merci, David Parce que je n'ai pas remarqué. Alors, que diriez-vous de la solution de boucle? – unacowa

Répondre

5

Pour éviter d'affecter mal l'objet d'origine, vous avez besoin essentiellement une variante de copy.deepcopy ... subtilement retouché parce que vous avez besoin de tourner des générateurs et itérateurs dans des listes (deepcopy ne serait pas générateurs deepcopy de toute façon). Notez que certains effet sur l'objet original est malheureusement inévitable, parce que les générateurs et les itérateurs sont "épuisés" comme un effet secondaire d'itération sur eux (que ce soit pour les transformer en listes ou à d'autres fins) - donc , il est tout simplement impossible à la fois laisser l'objet original seul et avoir ce générateur ou autre itérateur transformé en une liste dans le résultat "variant-deepcopied".

Le module copy est malheureusement pas écrit pour être personnalisé, de sorte que les autres ares, soit copier-coller-éditer, ou un singe-patch subtil (soupir) articulant sur (double soupir) la variable du module privé _deepcopy_dispatch (qui signifie que votre version corrigée pourrait ne pas survivre à une mise à niveau de version Python, disons de 2.6 à 2.7, hypothétiquement). De plus, le patch de singe devrait être désinstallé après chaque utilisation de votre eagerize (pour éviter d'affecter d'autres utilisations de deepcopy). Supposons que nous choisissions la route copy-paste-edit à la place.

Disons que nous commençons par la version la plus récente, celle en ligne here. Vous devez renommer le module, bien sûr; renommer la fonction visible de l'extérieur deepcopy en eagerize à la ligne 145; le changement est important au niveau des lignes 161-165, qui, dans ladite version annotée, sont:

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

Nous devons insérer entre la ligne 163 et 164 la logique « autrement si elle est itérables l'étendre à une liste (par exemple, . utiliser la fonction _deepcopy_list que le copieur » Ainsi, ces lignes deviennent:

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: 

C'est tout. juste là deux lignes ajoutées Notez que j'ai quitté les numéros de ligne d'origine uniquement pour le rendre parfaitement clair exactement ces deux lignes doivent être insérées, et non numérotées les deux nouvelles lignes. Vous devez également renommer d'autres instances de l'identificateur deepcopy (appels récursifs indirects) à eagerize. Vous devez également supprimer les lignes 66-144 (la fonctionnalité de copie superficielle dont vous n'avez rien à faire) et modifier les lignes 1-65 de façon appropriée (docstrings, imports, __all__, etc.).

Bien sûr, vous voulez travailler à l'extérieur une copie de la version texte brut copy.py, here, pas la version annotée j'ai parle (je la version annotée juste pour préciser exactement où les changements étaient nécessaires ! -).

+1

Wow, nous avons de la chance de vous avoir ici Alex! – fmark

+0

Merci, C'est une idée alex. C'est vraiment aidé. Je l'ai testé et deepcopy fonctionne en grille, mais le test 3.x 4.x échoue. parce que le test du générateur (r) a des effets secondaires. Désolé pour l'expression confuse en haut de docstring. J'ai besoin d'effets secondaires. dict r doit être changé. – unacowa

Questions connexes