在这里python中的两种对象做一个介绍,并对其在进行赋值传递的时候的特性做一个分析。

python的对象可以分为可变对象与不可变对象。

  • 不可变对象

    该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。

  • 可变对象

    该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,通俗点说就是原地改变。

在python里数值类型(int和float)、字符串str、元组tuple都是不可变类型。而列表list、字典dict、集合set是可变类型。

不可变对象

int类型

1
2
3
4
5
6
7
8
a = 2
b = a
c = 2
print(id(a),id(b),id(c))
b = b + 1
c = c + 0
print(a,b,c)
print(id(a),id(b),id(c))
1
2
3
94804014773312 94804014773312 94804014773312
2 3 2
94804014773312 94804014773344 94804014773312

可以看出:

  • 赋值过程传的是引用,将a赋值给b之后,两者id一样
  • 由于是不可变对象,改变b的值的时候,会开辟新的空间,然后b指向新的地址
  • 只要两个变量(a和c)的值一样,所指向的地址就一样
1
2
3
4
5
6
7
8
def change(b):
print("b's id before change:{}".format(id(b)))
b = b + 1
print("b's id after change:{}".format(id(b)))
a = 2
print("a's id before function:{}".format(id(a)))
change(a)
print("a's id after function:{}".format(id(a)))
1
2
3
4
a's id before function:94804014773312
b's id before change:94804014773312
b's id after change:94804014773344
a's id after function:94804014773312

可以看出,在函数传值的时候,传的也是引用,只不过在对不可变对象进行改变的时候会建立新的副本

string类型

1
2
3
4
5
6
7
8
a = 'value'
b = a
c = 'value'
print(id(a),id(b),id(c))
b = b + '1'
c = c + ''
print(a,b,c)
print(id(a),id(b),id(c))
1
2
3
139906629646072 139906629646072 139906629646072
value value1 value
139906629646072 139906483630008 139906629646072

string类型在进行操作的时候特性和int一致,函数引用就不再举例了

tuple类型

1
2
3
4
5
6
7
8
a = (1,2,3)
b = a
c = (1,2,3)
print(id(a),id(b),id(c))
b = b + (4,)
c = c + ()
print(a,b,c)
print(id(a),id(b),id(c))
1
2
3
139906492310656 139906492310656 139906492312600
(1, 2, 3) (1, 2, 3, 4) (1, 2, 3)
139906492310656 139906492284264 139906492312600

与int和string不同的是,就算两个变量的值一样(a和c),所存储的地址也不一定一样。

可变对象

list类型

1
2
3
4
5
6
7
a = [1,2,3]
b = a
c = [1,2,3]
print(id(a),id(b),id(c))
b[0] = 4
print(a,b)
print(id(a),id(b))
1
2
3
139906501528840 139906501528840 139906483601992
[4, 2, 3] [4, 2, 3]
139906501528840 139906501528840

可以看出:

  • 两个变量值一样(a和c),所存储的地址也不一定一样
  • 当变量的值改变的时候,不建立新的副本,直接在原来的存储位置进行改变(b[0] = 4)
1
2
3
4
5
6
7
8
def change(b):
print("b's id before change:{}".format(id(b)))
b[0] = 4
print("b's id after change:{}".format(id(b)))
a = [1,2,3]
print("a's id before function:{}".format(id(a)))
change(a)
print("a's id after function:{}".format(id(a)))
1
2
3
4
a's id before function:139906492315784
b's id before change:139906492315784
b's id after change:139906492315784
a's id after function:139906492315784

在函数传递的时候传的也是引用

如何为list类型变量建立新的副本

1
2
3
4
5
6
a = [1,2,3]
print(id(a))
b = a[:]
print(id(b))
c = a.copy()
print(id(c))
1
2
3
139906483601608
139906492315784
139906501528840

通过以上两种方式就可以为list建立新的副本

有趣的操作

1
2
3
4
5
6
7
8
a = [1,2,3]
print(id(a))
a = a + [4]
print(a)
print(id(a))
a += [5]
print(a)
print(id(a))
1
2
3
4
5
139906483484488
[1, 2, 3, 4]
139906552278984
[1, 2, 3, 4, 5]
139906552278984

可以看出,如果+和=不写在一起,是会建立新的副本的,写在一起的话,就会在原存储空间进行操作(也就是inplace了)