注意:以下为个人理解,只为个人能直观理解和记忆,不保证完全的正确性
一 “python中一切都是对象”,这句话怎么理解?
https://blog.csdn.net/fragmentalice/article/details/81363494
可以从数据的角度看,整型数值1、浮点型数值1.0、字符串’1’、元组(1,1)、字典{‘a’:1}、列表[1]、集合{1}、空值None,这些数据在python中称为对象,存储在内存中。
其中,整数数值、浮点型数值、字符串、元组,对应的内存单元中的值不能改变,将这些对象称为不可变对象。而字典、列表、集合,对应的内存单元中的值能够改变,将这些对象称为可变对象。
x=1、x=1.0、x=‘1’、x=(1,1)、x={‘a’:1}、x=[1]、x={1},这些表达式中,x称为变量,或者说是对象的引用。
注意:可变对象list、dict、set中的元素是引用变量而非实际对象,由引用变量继续指向可变或不可变对象,明确这点才能理解浅拷贝。【这么指来指去,可变对象只不过是不可变对象的引用变量
的集合,最终都是指向不可变对象了】
个人理解:“python中一切都是对象”,这句话应该是"python中一切数据都是对象",且要补充上"一切变量都是引用";由于变量都是引用类型,比较统一,所以变量不需要指明数据类型,list中元素也能是不同的数据类型【因为list中实际装的还是引用】。
二 对象的地址
内置函数id()能够查看对象的地址,通过id(对象)、id(对象的引用)的方式,都可以返回对象的地址
不可变对象:
不可变对象的值与地址一一对应,即整个内存中,为该值的内存单元只有这一块,且这一块内存单元的值始终不会变,是常量;所有指向该值的变量,指向的都是这个对象。如:
a=1
b=int(1.0)
c=int('1')
d=(1,1)[0]
e={'x':1}['x']
f=[1][0]
g={1}.pop()
以上所有变量指向的值均为整型数值1,指向的地址均相同
当变量参与运算后要改变变量(指向的对象)的值时,并没有改变对象的值,而是将对象的值复制一份并运算得到一个新的对象,然后将变量指向新的对象
x=1
print(id(x))
x=x+1
print(id(x)) # 此时x指向整数数值2这个对象了
x指向不同的地址
可变对象:
可变对象的值与内存地址之间没有固定关系,不同地址中的可变对象可以有相同的值,相同的值可以对应不同的对象。
id([1])
id([1])
第一个[1]和第二个[1]是两个对象,位于不同的内存中
x=[1]
y=[1]
x、y指向不同的地址
当变量参与运算后要改变可变对象的值时,可变对象不需要复制,直接在原对象上修改。
x=[1]
id(x)
x.append(2)
id(x)
x指向同一个地址
三 直接赋值、浅拷贝、深拷贝
https://www.runoob.com/w3cnote/python-understanding-dict-copy-shallow-or-deep.html
https://blog.csdn.net/fragmentalice/article/details/81363494
这里copy、deepcopy是copy模块中的函数,需要import copy;list、dict、set的copy函数同copy模块中copy函数,都是浅拷贝
1 直接赋值、浅拷贝、深拷贝:
可变对象list、dict、set中的元素是引用变量而非实际对象,由引用变量指向最终的可变或不可变对象。所以对于list,如a=[1],实际上是一个嵌套的引用变量,引用变量a指向内存单元1,内存单元1中存有引用变量a[0],引用变量a[0]指向不可变对象整型数值1
直接赋值: 直接将父对象的引用赋值给新的变量,新旧变量指向同一个父对象;所以a,b指向同一个对象;
浅拷贝: 在内存中复制父对象的内存单元,也即父对象中的引用元素被复制了一份,但最终指向的对象不复制;
深拷贝: 在内存中复制父对象的内存单元,递归复制父对象、子孙对象中引用元素指向的可变对象的内存单元;
浅拷贝复制到父对象中引用变量这一级(级1),深拷贝一直复制到不可变对象上一级(级2);只有可变对象中嵌套可变对象(级1不等于级2),浅拷贝、深拷贝的区别才会表现出来;如果可变对象中引用本来全部指向不可变对象了(级1等于级2),如a=[1,2,3],这时浅拷贝、深拷贝没什么区别。
2 对象引用计数:
python内存中每个对象都维护一个引用计数器用于垃圾回收、释放内存。当对象被引用时计数器加1,当计数器值为0时,解释器会释放对象所占的内存。
sys.getrefcount()可以获取对象的引用次数,参数为对象或对象的引用
注意:获取的是整个python程序对该变量的引用次数,包括python底层模块中使用的,并非只是用户自己模块中引用的次数
del关键字可以手动删除引用变量,使计数器减1。
a=1
b=[1]
c=b
print(sys.getrefcount(1)) # 183,参数为不可变对象1
print(sys.getrefcount(a)) # 183,参数为不可变对象1的引用a
print(sys.getrefcount([1])) # 1,参数为可变对象[1]
print(sys.getrefcount(b)) # 3,参数为另一个可变对象[1]的引用b
print(sys.getrefcount(c)) # 3,参数为上一个可变对象[1]的引用c
del a # 删除引用变量a
print(sys.getrefcount(1)) # 182
结果分析:
b指向的可变对象的引用次数是3次,而不是2次;说明python底层在等号左边写[1]时,就已经创建了一个引用变量了
【能不能理解为:1这个写法本来就是对内存中值为1的对象的引用,1本来就是一个引用变量的变量名,只不过这个变量名很特殊,而这个变量指向的内存单元的值是预设的;
哈哈,如果这么想的话,"对象"只是一个概念,对应于内存中的一块内存单元,用户写的所有变量、常量都是对这个"对象"的表述、引用;
这么想的话,不存在“函数的参数为引用或对象"这种说法了,其实都只是“引用”;a和1都是引用】
补充:看一下字面值常量
的概念
3 对象相等: is、==
is判断两个引用变量指向的是否为同一个内存地址,==判断两个引用变量最终指向的对象的值是否相等
a=1
b=1
print(a is b) # True
print(a==b) # True
c=[1]
d=[1]
print(c is d) # False
print(c==d) # True
四 举例
# 可变对象、不可变对象的地址
x=1
print(id(x)) # 1549658336 整型数值1的地址
print(id(1)) # 1549658336 整型数值1的地址
y=[1]
print(id(y[0])) # 1549658336 整型数值1的地址
print(id(y)) # 53427736 列表[1]的地址
print(id([1])) # 53452432 另一个列表[1]的地址
# 直接赋值、浅拷贝、深拷贝
ls=[[1],2,3]
ls1=ls # 直接赋值
ls2=copy.copy(ls) # 浅拷贝
ls3=copy.deepcopy(ls) # 深拷贝
print('第一组:---------------------------')
print(id(ls)) # 58539664
print(id(ls1)) # 58539664
print(id(ls2)) # 58539824
print(id(ls3)) # 58400320
print('第二组:---------------------------')
print(id(ls[0])) # 58398800
print(id(ls1[0])) # 58398800
print(id(ls2[0])) # 58398800
print(id(ls3[0])) # 58539584
print('第三组:---------------------------')
print(id(ls[1])) # 1558178032
print(id(ls1[1])) # 1558178032
print(id(ls2[1])) # 1558178032
print(id(ls3[1])) # 1558178032
print('第四组:---------------------------')
print(id(ls[0][0])) # 1558178016
print(id(ls1[0][0])) # 1558178016
print(id(ls2[0][0])) # 1558178016
print(id(ls3[0][0])) # 1558178016
直接赋值、浅拷贝、深拷贝结果分析:
第一组输出:
直接赋值的ls1指向的还是原对象,所以ls1和ls指向的地址相同
浅拷贝、深拷贝都重新复制了父对象到新的内存单元,所以都指向新的地址
第二组输出:
浅拷贝中的引用元素仍然指向原来的子对象,所以ls2[0]和ls[0]、ls1[0]的指向的地址相同
深拷贝,由于a[0]引用指向可变对象,进一步复制了可变对象,所以ls3[0]和ls2[0]的指向不同的地址
第三组输出:
深拷贝,由于a[1]引用指向不可变对象,不会复制不可变对象,所以ls3[1]和ls2[1]指向相同的地址
第四组输出:
深拷贝,a[0][0]指向不可变对象整数型数值1;内存中只存在一个整数型数值1对象,所以任何变量指向的地址都相同