关于Python对象的一些特性

背景

今天的《Python程序设计》课程教了生成器、递归函数。接着布置了一道题目,要求设计一个函数输出杨辉三角。

一位同学把他的代码发给我,告诉我这个代码有问题,希望我帮他看看。

代码与分析

1
2
3
4
5
6
7
8
9
def yanghui():
n=1
lst=[]
while True:
yield lst
lst += [ fact(n) // (fact(n-i)*fact(i)) for i in range(n+1) ]
n+=1
y = yanghui()
[next(y)for i in range(5)]
1
2
3
4
5
[[1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1],
[1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1],
[1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1],
[1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1],
[1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1]]

看到bug所在了吗,我个人认为,生成器yanghui每次输出的返回值其实是同一个lst,接着因为每次操作都是原地操作,所以最后输出的lst也是同一个lst,即最后的那个lst
以下是简单描述该原理的C代码

1
2
3
4
5
6
7
8
9
10
11
12
int* lst
int* yanghui(int i){
// ...代码
return lst
}
int main(){
int** YH;
// ...代码
for(int i=0;i<n;i++){
YH[i] = yanghui(i);
}
}

在这种情况下,YH这个数组下存放的其实是同一个指针lst

解决方案

解决的办法很简单,有两种,第一种是用yield lst.copy(),第二种是让lst = ....,而非原地操作。

参考解

参考解1:同学的解决方案

1
2
3
4
5
6
7
8
9
10
11
def yanghui():
n=1
lst = [1]
while True:
yield lst
lst = [ fact(n) // (fact(n-i)*fact(i)) for i in range(n+1) ]
n+=1


y = yanghui()
[next(y)for i in range(5)]

参考解2:我的解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def fact(n):
result = 1
if n == 0:
return result
for i in range(1,n+1):
result *= i
return result

C = lambda a,b: ((fact(a))//(fact(b)*fact(a-b)))

def YH(n):
counter = 0
ret = []
while counter <= n:
for i in range(0,counter+1):
ret.append(C(counter,i))
yield ret
ret = []
counter += 1

for i in YH(10):
print(i)