2008-10-22 1 views
6

Я считаю, что меня укусил какой-то комбинацией правил вложенных правил и списков. Jeremy Hylton's blog post наводит на размышления о причинах, но я действительно не понимаю, как реализация CPython достаточно хорошо, чтобы понять, как обойти это.Неожиданное поведение понимания списка в Python

Это пример (сложный?). Если у людей будет более простой способ демонстрации, я бы хотел это услышать. Проблема: понимание списка с помощью next() заполняется результатом последней итерации.

редактировать: Проблема:

Что именно происходит с этим, и как я могу это исправить? Должен ли я использовать стандарт для цикла? Очевидно, что функция запускает правильное количество раз, но в результате списка в итоге получается итоговое значение вместо результата каждого цикла.

Некоторые гипотезы:

  • генераторы?
  • ленивое заполнение списков?

код

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

ВЫВОД

 
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

Извините, но в чем вопрос? – 2008-10-22 13:30:20

ответ

15

Проблема заключается в том, что с return self.display вы возвращаете ссылка в этот список (не копия). Итак, в итоге вы получаете список, в котором каждый элемент является ссылкой на self.display. Для иллюстрации, посмотрите на следующее:

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

Вы, вероятно, хотите использовать что-то вроде return self.display[:].

4

Ум, если я реорганизую это немного?

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

Но на самом деле вам не нужно, что один, если вы реализуете все это как простой итератора:

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

Если вы хотите избавиться от печати (отладки, это?), идите с петлей while.

Это заодно также решает вашу первоначальную проблему, потому что reversed возвращает копию списка.

О, и теперь оно основано на нуле;)

Смежные вопросы