文章目录
- 1.对象和对象的引用
- 2.可变对象和不可变对象
- 3.两种对象在内存中的存储
- 3.两种对象更改数值时的内存
- 4.两种对象的赋值和复制
- 不可变对象
- 可变对象
- 5.两种对象的+=和=+
- 6.两种对象的函数传参
今天在看一个递归的时候列表赋值又把我绕晕了,这个点每次看每次忘,所以来总结一下
1.对象和对象的引用
- 对象:指在内存中存储的数据,每个对象包含一个头部信息,有类型表示符和引用计数器
- 对象的引用:是一个变量,通过指针指向引用对象的内存空间,取对象的值。变量本身没有类型,指向那种类型的对象,就体现为哪种类型。
以下a变量就是对象的引用,1是a指向的内存中存储的数据
a = 1
2.可变对象和不可变对象
- 不可变对象类型
- 数值类型 int,float,long等
- 字符串 str
- 元组 tuple
- 可变对象类型
- 列表 list
- 字典 dict
- 可变 set
可变和不可变的区别:能否在不改变内存id的情况下,改变value值。
3.两种对象在内存中的存储
- 不可变对象
a = 1
- 可变对象list
b=[1,2,3]
变量b指向容器对象的引用地址
3.两种对象更改数值时的内存
- 不可变对象更改数值时的内存
一个变量指向了不可变对象,如果要更改数值,会在内存中创建一个新对象,把变量指向新对象,或指向已存在对象(此对象被变量引用,计数器+1);旧对象如果没有被引用,就等待垃圾回收。
a=1
print(id(a)) # 140725527819088
a=2
print(id(a)) # 140725527819120
- 可变对象更改数值时的内存
一个变量指向list容器对象, 添加元素时容器对象内存地址id不变,区域的长短会变。
b = [1,2,3]
print(id(b)) # 2155591512904
b.append(4)
print(id(b)) # 2155591512904
如果添加的元素是另一个列表,那么此容器对象中新添加的,其实是另一个列表的容器对象的引用
b = [1,2,3]
print(id(b)) # 2155591512904
b.append([4,5])
print(id(b)) # 2155591512904
4.两种对象的赋值和复制
不可变对象
a=1
b=a
print(id(a)) # 140725527819088
print(id(b)) # 140725527819088
对于不可变对象来说,变量的赋值时赋的是变量的引用。
b=2
print(id(a)) # 140725527819088
print(id(b)) # 140725527819120
由于对象不可变,当变量b改变时,会创建新的对象,指向新的内存,对a变量没有影响。
可变对象
- 可变对象赋值
a = [1,2,3]
b = a
print(id(a)) #2155591539400
print(id(b)) #2155591539400
对于可变对象来说,赋值也是赋的对象的引用,但是是容器对象的引用。
b[2] = 4
print(a[2]) # 4
当b的值发生变化时,是容器对象中的某个元素指向的对象发生改变,因此会影响到a.
- 可变对象浅拷贝
b=a[:]
或
b=a.copy()
例
a=[1,2,3,[4,5]]
b=a[:]
print(id(a)) # 2155591539272
print(id(b)) # 2155591540424
当浅拷贝时,会创建新的容器对象,但这些容器对象指向相同的元素对象。
a[0] = 7
print(b[0]) # 1
a[3][1]=6
print(b[3][1]) # 6
当a想要指向另一个不可变对象,对b没有影响;当指向的可变对象某元素改变时,会影响b。
- 可变对象深拷贝
import copy
a=[1,2,3,[4,5]]
b=copy.deepcopy(a)
print(id(a)) #2155591633288
print(id(b)) #2155591513096
a[0] = 7
print(b[0]) # 1
a[3][1]=6
print(b[3][1]) # 5
彻底复制,a,b再也不会引用相同的容器对象。分别指向的容器对象数值更改不会再互相影响。
5.两种对象的+=和=+
- 不可变对象
a=1
print(id(a)) #140725527819088
a += 1
print(id(a)) # 140725527819120
a = a+1
print(id(a)) #140725527819152
三次地址都不同,因为对于不可变对象 += 和=+ 调用的都是 __add__函数;要改变值,变量一定会引用其他的对象。
- 可变对象
b=[1,2,3]
print(id(b)) # 2155591281416
b += [4]
print(id(b)) # 2155591281416
b = b + [4]
print(id(b)) # 2155589911432
对于可变对象,+=时相当于调用__iadd__函数,相当于原地修改,不改变引用对象地址id。
而调用= + 时调用__add__函数,相当于浅复制.
6.两种对象的函数传参
函数中实参赋给形参值时,都是传递的对象的引用。但由于两种对象的可变和不可变性质,看起来效果不同。
- 不可变对象
def func(x):
x += 1
return x
a=3
func(a)
print(a) # 3
当a传入函数后,a 的对象(3) 创建了新的引用x,当变量x发生改变时,指向了新的对象;实参a并没有受到影响。
- 可变对象
def f1(x):
x += [1]
a=[1,2]
f1(a)
print(a) # [1,2,1]
a传入函数的是容器对象的引用,所以如果对变量x做类似append和+=的操作时,会改变实参a。
a=[1,2]
f1(a[:])
print(a) # [1,2]
a传入参数的是容器对象的拷贝,不论在函数中对变量x做什么操作,都不会影响实参a。但如果a的元素中有列表,浅拷贝时有影响,深拷贝没有。
def f2(x):
x = x + [1]
a = [1,2]
f1(a)
print(a) # [1,2]
f1(a[:])
print(a) # [1,2]
由于函数f2中对变量x做了 =+的操作,相当于做了浅拷贝,所以对a没有影响