2008-10-22 11 views
6

Creo que me está mordiendo una combinación de reglas anidadas de scoping y listas de comprensión. Jeremy Hylton's blog post es sugerente sobre las causas, pero realmente no entiendo la implementación de CPython lo suficiente como para encontrar la manera de evitar esto.Comportamiento de comprensión de lista inesperada en Python

Aquí hay un ejemplo (¿complicado?). Si la gente tiene uno más simple que lo demuestre, me gustaría escucharlo. El problema: las listas de comprensiones que usan next() se llenan con el resultado de la última iteración.

edición: El Problema:

exactamente lo que está pasando con esto, y cómo puedo solucionar esto? ¿Tengo que usar un bucle estándar para? Claramente, la función se está ejecutando la cantidad correcta de veces, pero las listas de comprensión terminan con el valor final en lugar del resultado de cada ciclo.

Algunas hipótesis:

  • generadores?
  • relleno perezoso de la lista de comprensiones?

código

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)] 

SALIDA

 
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

Lo siento pero ¿cuál es la pregunta? –

+0

Editado, para mayor claridad. –

Respuesta

15

El problema es que con return self.display devuelve un referencia a esta lista (no una copia). Entonces, con lo que terminas es con una lista donde cada elemento es una referencia a self.display. Para ilustrar esto, mira el siguiente:

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

es probable que desee usar algo como return self.display[:].

4

¿Te importaría si refactoré esto un poco?

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

Pero en realidad no es necesario que uno, si se implementa todo el asunto como un simple repetidor:

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 desea deshacerse de la impresión (depuración, ¿verdad?), vaya con un ciclo while.

Esto incindentally también resuelve su problema inicial, porque reversed devuelve una copia de la lista.

Oh, y está basado en cero ahora;)

Cuestiones relacionadas