python append 为何如此奇怪?

2016-11-20 09:33:25 +08:00
 woniu127

这是一段测试代码

li1 = []
li2 = []
ki = [0]
j = 0
for i in range(10):
    ki[0] = ki[0]+1
    j = j+1
    li1.append(ki)
    li2.append(j)
print(li1)
print(li2)

这是输出:

[[10], [10], [10], [10], [10], [10], [10], [10], [10], [10]]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

为何 append()参数是列表类型时,每次循环都会改变 li1 中的值???

4574 次点击
所在节点    Python
18 条回复
laoyur
2016-11-20 09:37:55 +08:00
li1 = []
li2 = []
ki = [0]
j = 0
for i in range(10):
ki[0] = ki[0]+1
j = j+1
li1.append(list(ki))
li2.append(j)
print(li1)
print(li2)
kindjeff
2016-11-20 09:38:39 +08:00
因为你只是 append 了 ki 对象的引用,而 list 是可变的,内容改变了,你 append 的进去的仍然是 ki 的引用。
woniu127
2016-11-20 09:48:26 +08:00
@kindjeff 也就是说只要 append 的是不可变的对象就没问题了
exoticknight
2016-11-20 10:06:41 +08:00
明显是没搞清楚 python 是引用优先的
估计楼主肯定还会在用默认参数是对象的时候出现问题
woniu127
2016-11-20 10:12:51 +08:00
@exoticknight 学 python 还不够,认识还不够深,只能一个坑一个坑的踩了
BiggerLonger
2016-11-20 10:19:18 +08:00
應該是用 list.extend 方法
wwqgtxx
2016-11-20 10:24:42 +08:00
又一个被 C 的值传递坑的
IanPeverell
2016-11-20 10:53:46 +08:00
因为 append(ki)的时候传递的是 ki 的地址,所以如果想不改变值可以这么些 li.append([x for x in ki])
mornlight
2016-11-20 10:58:09 +08:00
第一个 list 里到最后是 10 个指向同一块内存的相同元素
dtfm
2016-11-20 11:15:46 +08:00
```
li1 = []
li2 = []
ki = [0]
j = 0
for i in range(10):
ki[0] = ki[0]+1
j = j+1
li1.append(ki[0])
li2.append(j)
print(li1)
print(li2)
```
在 ki 里面加一个 ki[0],这样每次都添加就好了
lzhCoooder
2016-11-20 11:29:45 +08:00
浅拷贝 你 li1 里的每个元素都是指向的或者说引用的同一个 ki
li2 里之所以不这样 是因为 j 是不可变类型,没加一次创建一个新 j
rebirth2
2016-11-20 12:40:51 +08:00
可以用 list slice, ki[:]这样直接创建新 list ,不过比较黑科技。。
onlyice
2016-11-20 13:02:14 +08:00
把代码粘贴到这里看看就懂了 http://www.pythontutor.com/visualize.html#py=2
bravecarrot
2016-11-20 13:13:10 +08:00
@onlyice 这个有意思啊 哈哈哈哈 不用开 ide 看了
bravecarrot
2016-11-20 13:25:14 +08:00
@woniu127 ls 的 @exoticknight 可能说的是这个

``` python
def foo(x, l = []):
l.append(x)
return l
y = foo(6)
z = foo(8)

p = foo(2, [3])
q = foo(4, [5])
print y
print z
print p
print q
```

output:
[6, 8]
[6, 8]
[3, 2]
[5, 4]
*************
同理,函数每次使用的默认参数的 list 都是同一个,每次都会操作它
20015jjw
2016-11-20 15:34:28 +08:00
lz 你显然没理解 reference 的概念..
enenaaa
2016-11-21 09:10:23 +08:00
我也觉得奇怪,其他语言标准库的 append 函数多半是值插入, python 非得是引用。
然后用个不常见 extend 函数代替。 比较坑
diydry
2016-11-21 16:18:09 +08:00
对象变动(Mutation)
Python 中可变(mutable)与不可变(immutable)的数据类型让新⼿很是头痛。 简单的说, 可
变(mutable)意味着"可以被改动", ⽽不可变(immutable)的意思是“常量(constant)”。 想把脑
筋转动起来吗? 考虑下这个例⼦:
foo = ['hi']
print(foo)
# Output: ['hi']
bar = foo
bar += ['bye']
print(foo)
# Output: ['hi', 'bye']
刚刚发⽣了什么? 我们预期的不是那样!我们期望看到是这样的:
foo = ['hi']
print(foo)
# Output: ['hi']
bar = foo
bar += ['bye']
print(foo)
# Output: ['hi']
print(bar)
# Output: ['hi', 'bye']
这不是⼀个 bug 。 这是对象可变性(mutability)在作怪。 每当你将⼀个变量赋值为另⼀个可
变类型的变量时, 对这个数据的任意改动会同时反映到这两个变量上去。 新变量只不过是
⽼变量的⼀个别名⽽已。 这个情况只是针对可变数据类型。 下⾯的函数和可变数据类型让
你⼀下就明⽩了:
def add_to(num, target=[]):
target.append(num)
return target
add_to(1)
# Output: [1]
add_to(2)
# Output: [1, 2]
add_to(3)
# Output: [1, 2, 3]
Python 进阶
对象变动 Mutation 51 你可能预期它表现的不是这样⼦。 你可能希望, 当你调⽤add_to 时, 有⼀个新的列表被
创建, 就像这样:
def add_to(num, target=[]):
target.append(num)
return target
add_to(1)
# Output: [1]
add_to(2)
# Output: [2]
add_to(3)
# Output: [3]
啊哈!这次又没有达到预期, 是列表的可变性在作怪。 在 Python 中当函数被定义时, 默认
参数只会运算⼀次, ⽽不是每次被调⽤时都会重新运算。 你应该永远不要定义可变类型的
默认参数, 除⾮你知道你正在做什么。 你应该像这样做:
def add_to(element, target=None):
if target is None:
target = []
target.append(element)
return target
现在每当你在调⽤这个函数不传⼊target 参数的时候, ⼀个新的列表会被创建。 举个例
⼦:
add_to(42)
# Output: [42]
add_to(42)
# Output: [42]
add_to(42)
# Output: [42]

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/321812

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX