繁体   English   中英

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

[英]Why does Python's 'for … in' work differently on a list of values vs. a list of dictionaries?

我想知道如何在Python中工作的一些细节。

我的理解是每次迭代迭代的for var in iterable都会创建一个变量var,绑定到iterable的当前值。 所以,如果你for c in cows; c = cows[whatever]for c in cows; c = cows[whatever] 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如何处理不可变对象的基本细节。

它有助于描绘每次迭代中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

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

a = 1
b = a
b = 2 

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

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

这里a["hello]返回2,而不是1, b仍是相同的值,因为我们没有指派任何东西b ,因此b是相同的a 。我们改变了财产["hello"]b到2由于ab是相同的变量a["hello"]也是2

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

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

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

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

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

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

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

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

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

some_var = some_object

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

some_var[some_index] = some_object

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

some_var.__setitem__(some_index, some_object)

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM