Python对象操作
对象 (Objections) 是 python 中数据的抽象,python 中所有的数据均可以用对象或者是对象之间的关系来表示。每个对象均有标识符( identity)、类型 (type)、值 (value)。
- 标识符:对象一旦创建,那么它的标识符就不会改变,可以把标识符看作对象在内存中的地址。 is 操作可以用来比较两个对象的标识符,函数 id() 用来返回对象标识符。
- 类型:对象的类型也是不可变的,对象的类型决定了该对象支持的操作,另外也决定了该对象可能的值。 type() 函数返回一个对象的类型。
- 值:一些对象的值可以改变,我们叫它可变对象;值不可改变的对象我们叫它不可变对象。
对象的操作包括:
- 构造操作:这类操作主要是基于一些已知信息,产生这个类的实例对象;
- 解析操作:这类操作是获取对象的一些有用信息,其结果反应了对象的一些特征,但返回的不是对象本身。
- 变化操作 :这类操作是修改对象内部的信息和状态。
如果一个类型,具只具有1和2两种操作,也就说只具有构造和解析操作,那么这个类型就是不可变类型,这个类型的对象就是不可变对象。
如果一个类型,具有1、2、3三种操作,这个类型就是可变类型,这个类型的对象就是可变对象。
显然可变对象和不可变对象的区别在于 3 变化操作。
不可变对象
python 中的不可变对象有整型 (int),浮点型 (float),字符串型 (str), 元组 (tuple)
赋值
首先看一下整型,浮点型,字符串型和元组的赋值操作:
i1 = 205464113
i2 = 205464113
i3 = i1
print(id(i1))
print(id(i2))
print(id(i3))
print(i1 is i2)
print(i1 is i3)
print('**********')
f1 = 1.574
f2 = 1.574
f3 = f1
print(id(f1))
print(id(f2))
print(id(f3))
print(f1 is f2)
print(f1 is f3)
print('**********')
str1 = 'james'
str2 = 'james'
str3 = str1
print(id(str1))
print(id(str2))
print(id(str3))
print(str1 is str2)
print(str1 is str3)
print('**********')
t1 = (1, 2, 3)
t2 = (1, 2, 3)
t3 = t1
print(id(t1))
print(id(t2))
print(id(t3))
print(t1 is t2)
print(t1 is t3)
print('**********')
得到的结果:
- 整型,浮点型,字符串型行为一致:只要变量的类型和数值相同,他们 id 相同,是同一对象,指向内存同一地址。
- 元组行为有些差异:如果是直接用相同值赋值两个元组,他们的 id 不同,是不同对象,但是如果赋值一个元组,再用这个元组赋值另一个元组,则是同一对象,指向内存同一地址。
修改
i = 1
print(id(i))
i = 2
print(id(i))
print('*********************')
f = 1.738
print(id(f))
f = 2.738
print(id(f))
print('*********************')
str = 'abc'
print(id(str))
str = 'xyz'
print(id(str))
print('*********************')
t = (1, 2, 3)
print(id(t))
t = (4, 5, 6)
print(id(t))
print('*********************')
得到的结果
可以看到四种数据类型行为一致:对不可变对象进行修改的时候,由于对象不可变,需要新建内存和对象,将修改的值赋值给新的对象,原对象不变,不过这种情况由于没有变量引用此对象,所以 python 的垃圾回收机制可能会回收原对象。
再看一种情况:
i1 = 1
i2 = i1
print(id(i1))
print(id(i2))
i1 = 2
print(id(i1))
print(id(i2))
print('*************')
f1 = 1.7
f2 = f1
print(id(f1))
print(id(f2))
f1 = 2.9
print(id(f1))
print(id(f2))
print('*************')
s1 = 'abc'
s2 = s1
print(id(s1))
print(id(s2))
s1 = 'xyz'
print(id(s1))
print(id(s2))
print('*************')
t1 = (1, 2, 3)
t2 = t1
print(id(t1))
print(id(t2))
t1 = (4, 5, 6)
print(id(t1))
print(id(t2))
print('*************')
得到的结果如下:
以整型为例:
i1 = 1
i2 = i1
print(id(i1))
print(id(i2)
这一段和赋值一样,然后对 i1 进行修改:
i1 = 2
print(id(i1))
print(id(i2))
这个时候,由于是不可变对象,所以需要新建内存和对象并赋值 2,然后变量 i1 就是这个新对象的实例,但是 i2 没有任何变化。
可变对象
python 中的可变对象有列表 (list),集合 (set), 字典 (dict)
赋值
首先看一下列表,集合和字典 的赋值操作:
l1 = [1, 2, 3]
l2 = [1, 2, 3]
l3 = l1
print(id(l1))
print(id(l2))
print(id(l3))
print(l1 is l2)
print(l1 is l3)
print('**************')
s1 = {1, 2, 3}
s2 = {1, 2, 3}
s3 = s1
print(id(s1))
print(id(s2))
print(id(s3))
print(s1 is s2)
print(s1 is s3)
print('**************')
d1 = {'key1': 1, 'key2': 2}
d2 = {'key1': 1, 'key2': 2}
d3 = d1
print(id(d1))
print(id(d2))
print(id(d3))
print(d1 is d2)
print(d1 is d3)
print('**************')
得到的结果:
这里三种数据类型的行为和元组 (tuple) 是一致的,用相同的值直接赋值,所得到的两个变量 id 是不同的,因此不是同一对象,但是用复制的方法赋值,得到的就是同一对象。
修改
l = [1]
print(l)
print(id(l))
l.append(2)
print(l)
print(id(l))
print('*********')
s = {1}
print(s)
print(id(s))
s.add(2)
print(s)
print(id(s))
print('*********')
d = {'key1': 1}
print(d)
print(id(d))
d['key2'] = 2
print(d)
print(id(d))
print('*********')
得到的结果:
由于是可变对象,可以对对象进行修改,所以修改之后的对象指向的内存地址是不变的。(这是和可变对象的本质区别)
第二种情况:
l1 = [1]
l2 = l1
print(l1)
print(l2)
print(id(l1))
print(id(l2))
l1.append(2)
print(l1)
print(l2)
print(id(l1))
print(id(l2))
print('*************')
s1 = {1}
s2 = s1
print(s1)
print(s2)
print(id(s1))
print(id(s2))
s1.add(2)
print(s1)
print(s2)
print(id(s1))
print(id(s2))
print('*************')
d1 = {'key1': 1}
d2 = d1
print(d1)
print(d2)
print(id(d1))
print(id(d2))
d1['key2'] = 2
print(d1)
print(d2)
print(id(d1))
print(id(d2))
print('*************')
得到的结果:
显然和第一种情况是一样的。
总结
可变对象和不可变对象的本质差异在于对象是否可以修改。如果可以修改,则是可变对象。如果两个变量指向同一可变对象,修改其中一个变量,会导致另一变量的改变。如果不可以修改,则是不可变对象。如果两个变量指向同一对象,修改其中一个可变变量,不会导致另一可变变量的改变。(因为会新建一个对象,原对象不变)
另外元组 tuple 有点特殊,按照本质确实应该是也就是属于不可变对象,但是在赋值的时候有一点差异,如果复制赋值和其他不可变对象一致,但是相同值赋值的行为和可变对象一致。
不过总而言之,按照修改行为,确实是不可变对象。