天天看点

Python中可变和不可变对象

import sys

print(“当前值为1的字符串对象的引用计数为:{}”.format(sys.getrefcount(“1”)))

a = “1”

b = “1”

dic = {“1”:“1”}

list1 = [“1”, “2”]

print(“变量a指向的内存地址为:{}”.format(id(a)))

print(“变量b指向的内存地址为:{}”.format(id(b)))

print(“变量list中第一个索引位置指向的内存地址为:{}”.format(id(list1[0])))

print(“变量dic中的键为字符串1的地址为:{}”.format(id(list(dic.keys())[0])))

print(“只要值为“1”,那么他们的对象地址都是相同的,都指向了内存中的同一个位置”)

print(“当前值为1的字符串对象的引用计数为:{},相比最开始增加了5个”.format(sys.getrefcount(“1”)))

a = True

b = True

print(“变量a和变量b重新赋值为bool类型True”)

print(“变量a重新指向的内存地址为:{}”.format(id(a)))

print(“变量b重新指向的内存地址为:{}”.format(id(b)))

print(“bool类型也是一样,此时变量a和变量b指向了新的同一个bool类型对象所在的位置,原先的值为1的字符串对象的引用-2”)

print(“当前值为1的字符串对象的引用计数为:{},相比之前减少了2个”.format(sys.getrefcount(“1”)))

print("如果堆中的一个对象的引用计数变为0,那么会触发python的垃圾回收机制将该内存区域回收")

list2 = ["1", "2", "3", ["1", "1"]]
print(id(list2[0]), id(list2[3][0]), id(list2[3][1]))
print("我们发现不管多深的嵌套,最终值相同的变量都指向了同一个位置")

tup1 = ("1", 2, 3)
tup2 = ("1", 2, 3)
print(id(tup1), id(tup2))
print(id(tup1[0]))
print(id(tup1[1]), id(tup2[1]))
print("我们发现,对于元组,这也是适用的")
print("总结一下,对于上面我们测试了bool、字符串、数值类型、元组类型,都满足只要变量的值相同,那么变量指向的位置就相同这一条规律")
print("但python中所有的变量都是这个样子吗?下面的例子会有一些不同")
print("那么我们接下来需要测试的是python中剩余三个常用的数据类型:列表、字典、集合")

list1 = [1, 2, 3]
list2 = [1, 2, 3]
print("以上我们生成了两个list,那么它们在内存中指向的位置为:{} {}".format(id(list1), id(list2)))
print("我们发现它们指向了内存中不同的位置,这好像与上面我们总结出来的规律不同")
print("那么现在我让list2 = list1")
list2 = list1
print("此时list1和list2所指向的内存位置为:{} {},我们发现它们都指向了之前list1指向的内存位置".format(id(list1), id(list2)))
print("此时我们使用内置函数改变list1的值")
list1.append(4)
print(list1, list2)
print("我们发现list2的值也随之改变,为什么?此时就要引入python的可变数据类型和不可变数据类型")
print("可变数据类型是指在堆中的python对象是可以改变的,即可以将该对象扩充或者减小内存")
print("不可变数据类型是指在堆中的对象是不能改变的,一旦申请就固定下来")
print("python中的可变数据类型为列表、字典、集合")
print("以上list2随着list1修改的原因为list2和list1指向了内存中的同一个位置,这个位置是一个list对象,list对象是可以修改的,所以会有影响")
list1 = [1, 2, 3, 4, 5]
print(list1, list2)
print("但是当我们重新对list1赋值,则不会引起list2的改变,因为list1此时指向了新的list对象,而list2仍然指向了之前的list对象")

print("我们引出函数传参的概念,那么python中函数是如何传递参数的呢?")
print("我们知道在C语言调用函数时,采用的是值传递,即形参和实参分配不同的内存地址,调用函数时将实参的值传递给形参")
print("在这种情况下,函数修改形参的值并不会对实参进行修改,那么如果我们想要修改实参,那么就需要将实参的指针传递给函数,然后在函数中修改指针指向的数据,达到修改实参的目的")
print("后来在C++中引入了引用的概念,即在函数定义时在形参前面加一个&符号,表示传递实参的引用,通过这种方法达到修改实参的目的,这种方法被称为传引用")
print("那么在python中是传递值还是传递引用呢?")

def numadd10(num):
    num = num + 10

x = 1
numadd10(x)
print(x)

print("我们发现函数并没有修改x的值")

def listmodify(l):
    l[0] = 1
list1 = [100, 100]
listmodify(list1)
print(list1)

print("我们发现函数修改了list的值")

print("那么对于指向int对象变量而言,python函数调用为传值,对于指向list对象的变量,python函数调用为传引用")
print("加上对于可变数据类型和不可变数据类型的理解,我们可以总结如下:")
print("在参数传递时,实参将变量赋值给了形参,此时实参和形参都指向内存中的同一个位置")
print("当我们在函数体内修改形参时,对于不可变对象类型而言,该形参指向了新的内存位置,而实参没有被改变")
print("对于可变对象类型而言,直接在原位置修改,此时形参和实参指向的位置相同,所以实参也被修改")

print("那么我们对python中的数据类型分类讨论就可以了吗?事实上没有这么简单,我们来看以下代码")

tup1 = ([1, 2], 3)
tup2 = tup1

print("tup1和tup2都为不可变数据类型")
tup1[0][0] = 2
print(tup1, tup2)
print("上面这种情况如何解释?")
print("我们做如下分析:tup2和tup1指向同一个tuple对象,我们修改tup1中的列表中的第一个元素,发现两个元组的数据都被修改了")
print("由此引出两个问题:为什么元组内的数据可以被修改?")
print("为什么对不可变类型做修改不是新创建一个对象而是在原对象上修改?")
print("整个tuple对象并非是一个统一的整体,tup1[0]是一个引用,tup1[1]也是引用,我们修改了tup1[0]指向的对象的值(它是一个列表,所以能够修改),此时tup2[0]也指向该列表,所以也被修改")

# tup1[1] = 1,尝试修改元组的第一层引用报错

print("tuple是不可变数据类型是指元组的第一层不可变,即整个元组都是引用,即存储的都是其他对象的地址,这些地址是不能够改变的,然而这些地址中可变类型对象是可以被修改的")

print("综合起来理解就是python变量中存储的都是各种对象的引用,可变和不可变只是对这些对象的第一层引用而言。如果不可变类型中存在指向可变对象类型的引用,那么可变对象是可以修改的, 例如tuple中存在list, list的修改会引起tuple的修改,而不是重新开辟空间创建新的tuple对象")
print("所有的重新创建的可变数据类型,即使它们和之前创建的对象的值相通,它们也不会指向同一个位置,而是重新创建对象,如下:")
list1 = [1, 2, 3, 4]
list2 = [1, 2, 3, 4]
print(id(list1), id(list2))
tup1 = ([1, 2, 3], 2, 3, 4)
tup2 = ([1, 2, 3], 2, 3, 4)
print("虽然是不可变类型,但是里面存在list的引用,然而引用指向的位置不同所以 {} {}".format(id(tup1), id(tup2)))

tup3 = (1, 2, 3)
tup4 = (1, 2, 3)
print("不可变数据类型,相同{} {}".format(id(tup3), id(tup4)))

tup5 = (list1, 1, 2)
tup6 = (list1, 1, 2)
print(id(tup5), id(tup6))
list1[0] = 1000
print(tup5)
print("只要元组中有指向可变类型的引用,那么它们指向的地址就是不同的")

import copy
list1 = [(1, 2, 3), 2, 3]
list2 = copy.deepcopy(list1)
print(id(list1[0]), id(list2[0]))

print("以上说明了不可变对象只会在内存中存在一个副本,即使是深拷贝,也都会指向同一个副本")