python语言特性之引用

不得不承认的是,被我们所偏爱的python也并不完美,但这并不阻碍我们追求完美。有时候你会感觉这一路上危险重重,你必须得小心翼翼的避开那些陷阱,因为一旦踩上就是满盘皆输,你得学会避开危险,然后再去体验旅程。最后,提前祝你旅途愉快!

——Python很“懒”很节约,不喜欢开辟新内存空间

众所周知,Python里的列表可以用=号连接赋值,和C语言数组的指针并没有什么区别。

Python代码片段:

1
2
3
4
5
a = range(5)
b = a
b[3] = 10
print(a, b)
print(id(a), id(b))

上述代码中列表的赋值,实际上是共用了同一个对象,也就是a和b使用了同一块内存地址,赋值操作并没有为b开辟新的空间,当修改b的时候a也就跟着被改动,这个时候b就是a的引用。

用C语言描述则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>

void array_print(int*, int);

int main()
{
int a[5] = {1,2,3,4,5};
int *b = a;
b[3] = 10;
int i = 0;
array_print(a, 5);
prinf("内存地址:%X", a);
printf("\n");
array_print(b, 5);
prinf("内存地址:%X", b);
return 0;
}

void array_print(int* a, int size)
{
int i;
for(i = 0; i < size; i++)
printf("%d\t", a[i]);
printf("\n");
}

所以,如果你的初衷是拷贝一个数组的副本(创建一个新的对象,或者说开辟一块新的内存空间)可以使用copy方法或者deepcopy方法,再或者在拷贝的时候进行切片以强调要进行的是内容拷贝而不是对象引用。当存在嵌套情况的时候,普通的copy和切片方法对更深一层的元素依旧是给引用而不是重新分配,这个时候应该使用deepcopy。

1
2
3
4
5
6
7
8
9
10
#b和a拥有不同内存空间
a = [1, 2, 3, 4]
b = a[:] #等同于b = copy.copy(a)

#b和a拥有不同内存空间,但b[0]和a[0]确实同一块内存
a = [ [0.9, 0.7], 1, 2, 3 ]
b = a[:]

#使用deepcopy拷贝,被嵌套的内容也会被再内存中拷贝(新分配空间)
b = copy.deepcopy(a)

Python序列默认支持+和*操作的,这源于python的特殊方法也叫做双下划线方法double underline method.可以使用+号对两个序列进行拼接,产生一个新的序列。如果想把一个序列复制几分再拼接起来可以使用*运算符。

1
2
3
>>> a = ['_'] * 3
>>> a
>>> ['_', '_', '_']

这样的特性给我们带来了方便,但是在嵌套的时候,又会有那么一点小麻烦:

1
2
3
4
5
6
7
8
>>> a = [['_']*3]*3
>>> a
>>> [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> a[-1][-1] = 'O'
#美好的期望
>>> [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '0']]
#残酷的现实
>>> [['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]

这个问题和上面的问题并没有本质上的区别,只是看上去复杂了一点而已,当我们嵌套执行*操作的时候,外面的列表包含的三个列表实际上就是同一个列表的三个引用而已。当尝试改变某一个列表的内容时,这个致命的问题就会被暴露出来。

正确的做法,可以使用生成器表达式分别生成不同的对象,避免直接引用:

1
a = [['_'] * 3 for i in range(3)]
文章目录
  1. 1. ——Python很“懒”很节约,不喜欢开辟新内存空间