Python中的可变和不可变数据类型

1 数据类型分类

       ~~~~~~       首先给出可变和不可变数据类型的分类,
(1)可变数据类型:列表(list)、字典(dict);
(2)不可变数据类型:整型(int)、浮点型(float)、字符串型(string)、元组(tuple)。
       ~~~~~~       并以列表和字符串类型为例来详细说明可变和不可变数据类型的区别,

可变数据类型

       ~~~~~~       先来看实际程序的运行结果(运行环境:Python3.6)

>>> a = [1,2,3]
>>> print('a_id:',id(a))
a_id:2036765059144

>>> a += [4]        //该句等价于 a.append(4)
>>> print('a+_id:', id(a))
a+_id:2036765059144

       ~~~~~~       代码运行结果显示,在列表 a 最后添加元素后,虽然 a 所指向的变量的值改变了,但是 a 所指向的变量的地址却未改变,相当于在列表末尾申请了一块内存地址用来存放新插入的元素,列表的头地址并未发生变化,请继续看下面的程序

>>> aa = [4,5,6]
>>> print('aa_id:', id(aa))
aa_id: 1339025147784   // aa所指向的变量的id地址

>>> bb = [4,5,6]
>>> print('bb_id:',id(bb))       
bb_id: 1339026126856          // 和aa的id值不相同

       ~~~~~~        可以看出,在可变数据类型中,对于值相同的不用对象,Python在内存中会存放两个不同的对象,即每个对象都有自己的 id 地址,相当于在内存中同时保留了相同的对象,而不是像不可变数据类型中的只增加了引用计数。

不可变数据类型

a = 'hello'
b = 'hello'
c = b
d = b+'hello' // 修改b所指向的变量

print('id(a):', id(a))
print('id(b):', id(b))
print('id(c):', id(c))
print('id(d):', id(d))

id(a): 139900106251096
id(b): 139900106251096
id(c): 139900106251096
id(d): 139900105643888

       ~~~~~~       总结:Python中的不可变数据类型不允许变量的值发生变化,如果改变了变量的值,就相当于创建了一个对象,变量的id地址显然也会发生改变;而对于相同的对象,在内存中则只有一个对象,在内部会有一个引用计数来记录有多少个变量引用这个对象。
       ~~~~~~       而可变数据类型允许变量的值发生变化,即如果对变量进行append、+= 等操作后,只是改变了变量的值,而不会新建一个对象,变量的引用对象的地址也不会变化;但对于相同值的不同对象,在内存中则会存在不同的对象,即每个对象都有自己的地址,每个地址里都存在实实在在的对象,而不是引用计数。

2 不要使用可变类型作为参数的默认值

       ~~~~~~       看完第一节的内容,你肯定会问了解这些有什么用呢?下面我就举其中一例来展示,关键时刻能帮助大家避开一些不必要的麻烦,见如下代码:

class Group:  
    def __init__(self, members = []):   #使用可变数据类型作为默认参数,后面会出现问题
        self.members = members
        
    def append_name(self, name):
        self.members.append(name)
        
    def drop_name(self, name):
        self.members.remove(name)    

#创建实例,创建实例的同时传入members参数
>>>test1 = Group(['张三', '李四'])
>>>test1.members
['张三', '李四']
>>>test1.append_name('王五')
>>>test1.drop_name('张三')
>>>test1.members
['李四', '王五']

# 下面再创建两个实例,均使用members的默认参数
>>>test2 = Group()
>>>test2.append_name('赵六')
>>>test2.members
['赵六']

>>>test3 = Group()
>>>test3.members
['赵六']
>>>test3.append_name('')
['赵六', '孙七']

>>> test1.members
['李四', '王五']     # 对test1.membres属性不产生任何影响

>>>test2 is test3   # test2 和 test3 是不同的对象(id值不相等)
False

       ~~~~~~       思考: test2 和 test3 是不同的对象(id值是不同的),为什么修改test2的members会影响test3的members呢?
       ~~~~~~        解答: 如果你分别打印出test2和test3的members属性,就会发现二者指向的其实是同一对象,

>>> test2.members is test3.members
True

       ~~~~~~        这是因为在创建 test2 和 test3 对象时,由于二者均未传入参数,所以二者会共享同一个 members 空列表,后期修改时即使改变了 members 列表的值,但由于列表是可变对象,其对应的 id 值并不会改变,所以才会产生上述问题。

3 如何防御可变参数

       ~~~~~~        该错误一般情况下很难发现,所以建议大家一般不要采用上述方式将可变对象设为默认参数,正确的用法应该是如下方式:

class Group:  
    def __init__(self, members = None):   
       if memberes is None:
           self.members = []  
       else:
           self.members = members
        
    def append_name(self, name):
        self.members.append(name)
        
    def drop_name(self, name):
        self.members.remove(name)    

       ~~~~~~       当 members 为 None 时,会为实例创建一个新的空列表,而每一个空列表均是可变对象,会在内存中保留实实在在的对象,这样两个实例之间的 members 属性将不会产生影响,避免了上述错误的发生。
       ~~~~~~        通过前面两节的学习,相信你一定对可变和不可变数据类型有了更加清晰深入的理解,但以上这些仅代表我个人理解,如有不正之处,还望能批评指正,我会在第一时间进行修改和完善!

你可能感兴趣的:(Python中的可变和不可变数据类型)