【问题标题】:Unexpected list comprehension behaviour in PythonPython中的意外列表理解行为
【发布时间】:2010-09-18 13:09:10
【问题描述】:

我相信我被嵌套范围规则和列表理解的某种组合所困扰。 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 = 数字(5) >>> [D.next() for x in range(5)] ## 这个列表理解按预期工作 [(1,真),(2,假),(3,假),(4,假),(5,假)]
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] 输出[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] 输出[45]:[[1, 4], [1, 4], [1, 4], [1, 4], [1, 4], [1, 4]] # 这应该是:[[0,3],[0,4]....[1,4]] 或类似的

【问题讨论】:

  • 对不起,请问有什么问题?

标签: python list-comprehension language-implementation


【解决方案1】:

问题在于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[:] 之类的东西。

【讨论】:

    【解决方案2】:

    介意我重构一下吗?

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

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

    【讨论】:

      最近更新 更多