为什么Python的'for ... in'在值列表和字典列表上的工作方式不同?

时间:2010-05-28 04:03:52

标签: python iteration

我想知道如何在Python中使用的一些细节。

我的理解是for var in iterable每次迭代都会创建一个变量var,绑定到iterable的当前值。因此,如果您执行for c in cows; c = cows[whatever],但在循环内更改c不会影响原始值。但是,如果要为字典键分配值,它似乎会有所不同。

cows=[0,1,2,3,4,5]
for c in cows:
  c+=2

#cows is now the same - [0,1,2,3,4,5]

cows=[{'cow':0},{'cow':1},{'cow':2},{'cow':3},{'cow':4},{'cow':5}]
for c in cows:
  c['cow']+=2

# cows is now [{'cow': 2}, {'cow': 3}, {'cow': 4}, {'cow': 5}, {'cow': 6}, {'cow': 7}
#so, it's changed the original, unlike the previous example

我看到人们可以使用枚举来使第一个例子工作,但我想这是一个不同的故事。

cows=[0,1,2,3,4,5]
for i,c in enumerate(cows):
  cows[i]+=1

# cows is now [1, 2, 3, 4, 5, 6]

为什么它会影响第二个示例中的原始列表值,而不影响第一个示例中的原始列表值?

[编辑]

感谢您的回答。我从PHP的角度来看这个,你可以使用& foreach中的符号,用于指定您是对可迭代的引用还是副本进行操作。我现在看到真正的区别是python如何处理不可变对象的基本细节。

7 个答案:

答案 0 :(得分:17)

它有助于描绘每次迭代中c所持有的引用会发生什么:

[ 0, 1, 2, 3, 4, 5 ]
  ^
  |
  c

c包含指向列表中第一个元素的引用。执行c += 2(即c = c + 2时,临时变量c会重新分配一个新值。此新值为2c将反弹为这个新值。原始列表是单独的。

[ 0, 1, 2, 3, 4, 5 ]

  c -> 2

现在,在字典案例中,这是c在第一次迭代中绑定的内容:

[ {'cow':0}, {'cow':1}, {'cow':2}, {'cow':3}, {'cow':4}, {'cow':5} ]
     ^
     |
     c

此处,c指向字典对象{'cow':0}。当您执行c['cow'] += 2(即c['cow'] = c['cow'] + 2)时,字典对象本身会发生变化,因为c不会反弹到不相关的对象。也就是说,c仍然指向第一个字典对象。

[ {'cow':2}, {'cow':1}, {'cow':2}, {'cow':3}, {'cow':4}, {'cow':5} ]
     ^
     |
     c

答案 1 :(得分:5)

实际上并没有采取不同的行动。更改变量与更改变量的属性不同。您将在以下示例中看到相同的内容:

a = 1
b = a
b = 2 

这里a仍然是1. b被赋予了不同的值,并且不再与

相同
a = {"hello": 1}
b = a
b["hello"] = 2 

此处a["hello]返回2而不是1. b仍然是相同的值,因为我们没有为b分配任何内容,因此ba。我们将["hello"]的属性b更改为2,因为ab是相同的变量a["hello"]也是2

答案 2 :(得分:4)

在这两种情况下,

c都是临时的一次性变量。 (请记住,在Python中,所有变量都只是引用,绑定到它们所代表的对象,并且能够反弹到不同的对象。在这方面,Python比某些其他语言更加一致。)

在列表示例中,每次迭代都会将c从一个整数重新绑定到另一个整数,而原始列表保持不变。

在你的dict示例中,每次迭代都会访问c临时绑定的dict,将其中一个dict的成员重新绑定到另一个整数。

在这两种情况下,c在循环结束时被忽略,但由于您在第二种情况下更改了c以外的数据结构,因此您会注意到循环时的更改完成。

答案 3 :(得分:4)

这与for ... in ...无关。在每个示例中将代码从for c in cows:更改为c = cows[3](并在下一行中显示)并查看效果。

在第一个示例中,list元素是int对象;他们是不变的。在第二个例子中,它们是dict对象,它们是可变的。

答案 4 :(得分:3)

在第一个循环中执行名称分配仅重新绑定名称。在第二个循环中执行项目分配会修改现有对象。

答案 5 :(得分:1)

在第二个示例中,您有一个列表 字典对象。 c引用在循环范围内修改的字典对象。

答案 6 :(得分:1)

无论循环如何,您都必须注意:

some_var = some_object

将名称some_var绑定到对象some_objectsome_var引用的上一个对象(如果有)是未绑定的。

some_var[some_index] = some_object

不绑定/取消绑定some_var;它只是以下的语法糖:

some_var.__setitem__(some_index, some_object)

显然,some_var仍指向与之前相同的可索引(序列或映射)对象,该对象刚被修改。