2008-10-22 6 views
6

Je crois que je suis mordu par une combinaison de règles de portée imbriquées et de compréhensions de listes. Jeremy Hylton's blog post est suggestive sur les causes, mais je ne comprends pas vraiment la mise en œuvre de CPython assez bien pour comprendre comment contourner cela.Comportement de compréhension de liste inattendu en Python

Voici un exemple (trop compliqué?). Si les gens en ont une version plus simple, j'aimerais l'entendre. Le problème: les compréhensions de liste utilisant next() sont remplies avec le résultat de la dernière itération.

modifier: Le problème:

ce qui se passe exactement cela, et comment puis-je résoudre ce problème? Dois-je utiliser une norme pour la boucle? Il est clair que la fonction s'exécute le nombre correct de fois, mais la compréhension de la liste se termine par la valeur finale au lieu du résultat de chaque boucle.

Quelques hypothèses:

  • générateurs?
  • remplissage paresseux de la compréhension de la liste?

Code

import itertools 
def digit(n): 
    digit_list = [ (x,False) for x in xrange(1,n+1)] 
    digit_list[0] = (1,True) 
    return itertools.cycle (digit_list) 
 
>>> D = digit(5) 
>>> [D.next() for x in range(5)] 
## This list comprehension works as expected 
[(1, True), (2, False), (3, False), (4, False), (5, False)] 
class counter(object): 
    def __init__(self): 
     self.counter = [ digit(4) for ii in range(2) ] 
     self.totalcount=0 
     self.display = [0,] * 2 
    def next(self): 
     self.totalcount += 1 
     self.display[-1] = self.counter[-1].next()[0] 
     print self.totalcount, self.display 
     return self.display 

    def next2(self,*args): 
     self._cycle(1) 
     self.totalcount += 1 
     print self.totalcount, self.display 
     return self.display 

    def _cycle(self,digit): 
     d,first = self.counter[digit].next() 
     #print digit, d, first 
     #print self._display 
     self.display[digit] = d 
     if first and digit > 0: 
      self._cycle(digit-1) 


C = counter() 
[C.next() for x in range(5)] 
[C.next2() for x in range(5)] 

SORTIE

 
In [44]: [C.next() for x in range(6)] 
1 [0, 1] 
2 [0, 2] 
3 [0, 3] 
4 [0, 4] 
5 [0, 1] 
6 [0, 2] 
Out[44]: [[0, 2], [0, 2], [0, 2], [0, 2], [0, 2], [0, 2]] 

In [45]: [C.next2() for x in range(6)] 
7 [0, 3] 
8 [0, 4] 
9 [1, 1] 
10 [1, 2] 
11 [1, 3] 
12 [1, 4] 
Out[45]: [[1, 4], [1, 4], [1, 4], [1, 4], [1, 4], [1, 4]] 

# this should be: [[0,3],[0,4]....[1,4]] or similar 
+0

Je suis désolé mais quelle est la question? –

+0

Édité, pour plus de clarté. –

Répondre

15

Le problème est que, avec return self.display vous retournez un référence à cette liste (pas une copie). Donc, vous obtenez une liste où chaque élément est une référence à self.display. Pour illustrer, regardez ce qui suit:

>>> a = [1,2] 
>>> b = [a,a] 
>>> b 
[[1, 2], [1, 2]] 
>>> a.append(3) 
>>> b 
[[1, 2, 3], [1, 2, 3]] 

Vous voulez probablement utiliser quelque chose comme return self.display[:].

4

Ça me dérange si je refais ça un peu?

def digit(n): 
    for i in itertools.count(): 
     yield (i%n+1, not i%n) 

Mais en fait, vous n'avez pas besoin que l'un, si vous implémentez la chose comme un itérateur simple:

def counter(digits, base): 
    counter = [0] * digits 

    def iterator(): 
     for total in itertools.count(1): 
      for i in range(len(counter)): 
       counter[i] = (counter[i] + 1) % base 
       if counter[i]: 
        break 
      print total, list(reversed(counter)) 
      yield list(reversed(counter)) 

    return iterator() 

c = counter(2, 4) 
print list(itertools.islice(c, 10)) 

Si vous voulez vous débarrasser de l'impression (mise au point, est-il?), allez avec un while-loop.

Ceci résout également votre problème initial, car reversed renvoie une copie de la liste.

Oh, et il est basé sur zéro maintenant;)

Questions connexes