Python中列表推导的意外行为

6
我认为我被嵌套作用域规则和列表解析的某种组合所困扰。Jeremy Hylton's blog post 对问题的原因有所暗示,但我对CPython的实现并不了解得足够好,无法找出解决方法。
这是一个(过于复杂的?)例子。如果有更简单的演示案例,我想听听。问题是:使用next()的列表解析会充满上一次迭代的结果。
编辑:问题:
这到底是怎么回事,我该如何解决?我必须使用标准for循环吗?显然函数运行了正确的次数,但列表解析的结果最终变成了 final 值,而不是每次循环的结果。
一些假设:
生成器?
列表解析的延迟填充?
代码:
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)]
## 这个列表推导式按预期工作
[(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)]

输出

在 [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]]
在 [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]]
# 应该是:[[0,3],[0,4]....[1,4]] 或类似的结果

对不起,但您的问题是什么? - Florian Bösch
2个回答

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 返回列表的副本。

哦,现在它是从零开始的 ;)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接