我用generator生成一些列表。但是,它不像我预期的那样工作。我首先使用numgen1生成列表,但它无法正常工作。然后我切换到numgen2,这可以给我正确的想法。但是numgen1和numgen2基本相同(至少我认为),为什么它们表现得如此不同?愿任何人给我一些解释吗?
def numgen1(start, end, delta):
curr=start
while curr[1] < end[1] or curr[2]<end[2]:
yield curr
curr[2] += delta
print 'Output1: ', [ i for i in numgen1([1,1,1],[1,1,5],1)]
def numgen2(start, end, delta):
curr=start
while curr[1] < end[1] or curr[2]<end[2]:
yield [curr[0], curr[1], curr[2]]
curr[2] += delta
print 'Output2: ', [ i for i in numgen2([1,1,1],[1,1,5],1)]
以下是输出。
Output1: [[1, 1, 5], [1, 1, 5], [1, 1, 5], [1, 1, 5]]
Output2: [[1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 1, 4]]
跟进问题:
在读完unutbu的答案后,我又写了一个用于测试unutbu所说的生成器。但是发电机不像unutbu那样表现。我对生成器是否正在产生指针或值的副本感到困惑。
def numgen3(start, end, delta):
curr=start
while curr<end:
yield curr
curr += delta
print list(numgen3(1,10,1))
这是输出。 [1,2,3,4,5,6,7,8,9]
这次我尝试生成一些数字而不是一些列表。但为什么不是列表9中的所有元素?我没有创建一个新的数字,我只是产生相同的数字(curr)。我希望numgen3的结果应该类似于numgen1的结果。
答案 0 :(得分:3)
curr
是一个列表。 curr[2] += delta
正在修改列表就地。
当你屈服curr
时,你会一遍又一遍地产生相同的列表。
当您打印Output1
时,您会看到同一列表被多次打印。
当您获得[curr[0], curr[1], curr[2]]
时,您将生成一个新列表。因此,当您打印Output2
时,您会看到不同的值。
请注意修改curr
如何影响result
中的所有项,因为result
是一个包含3个项目的列表,每个项目都是相同的列表curr
:
curr = [0,0,0]
result = [curr for i in range(3)]
print(result)
# [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
curr[2] = 100
print(result)
# [[0, 0, 100], [0, 0, 100], [0, 0, 100]]
您可以通过产生numgen1
来“修复”list(curr)
,因为list(curr)
返回的新列表与curr
中的元素相同(即“浅版”):
def numgen1(start, end, delta):
curr=start
while curr[1] < end[1] or curr[2]<end[2]:
yield list(curr)
curr[2] += delta
print 'Output1: ', [ i for i in numgen1([1,1,1],[1,1,5],1)]
产量
Output1: [[1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 1, 4]]
关于numgen3
:
def numgen3(start, end, delta):
curr=start
while curr<end:
yield curr
curr += delta
print list(numgen3(1,10,1))
有助于在可变值和不可变值之间进行精神区分。列表是可变的,ints
之类的数字是不可变的。
列表是容器。您可以在不更改对容器的引用的情况下改变其内容。因此curr
是一个列表,curr[2] += delta
会在索引2处改变内容,但yield curr
会生成相同的列表。
在numgen3
中,curr
是不可变的int
。 curr += delta
将curr
分配给新的不可变int
。它不再引用同一个对象。 yield curr
产生该值。这些不同的值在列表推导中累积,因此您可以看到包含不同值的结果。
以下是关于修改列表就地的含义的另一个视角:如果列表内容发生更改,则修改将在就地完成,而列表本身的内存地址不会改变。
id(obj)
返回对象obj
的内存地址。请注意,修改curr[2]
不会更改id
的{{1}}:
curr
将此与增加分配给In [162]: curr = [0,0,0]
In [163]: id(curr)
Out[163]: 196192940
In [164]: curr[2] += 1
In [165]: curr
Out[165]: [0, 0, 1]
In [166]: id(curr)
Out[166]: 196192940
的变量时发生的情况进行比较:
int
此处,In [191]: curr = 1
In [192]: id(curr)
Out[192]: 150597808
In [193]: curr += 1
In [194]: id(curr)
Out[194]: 150597796
未就地修改。只需重定向curr
以在新的内存地址引用新值。