Python的可变对象和不可变对象

2024-03-19 09:50:03
标签: python

作者: Sam(甄峰) sam_code@hotmail.com


0. 概念:

在Python中,对象可以分为两种主要类型:可变对象不可变对象

可变对象:其值可以在创建后修改,包括List,dict和set.

不可变对象:其值在创建后不能被修改,包括int, float, string,bool,tuple.

这里大家就会很疑惑,平时在编程时,int,float等修改不是很常见么?这就涉及到Python对象的三个属性: 地址,类型,值

可变对象: 当使用 = 尝试修改其内容时,对象的值发生了改变,类型和地址并没有变化。即修改的val放到此对象的原来的内存地址了。

不可变对象:当尝试使用 = 修改其内容时,内存地址发生改变,即把对象的指向另一块内存地址,这块地址的内容是咱们所希望的。看起来也达到了目标。


所以不可变对象并非指不能使用 = 来修改其引用。而是指不能在此地址修改内容。例如:

str_1 = "test string"

str_1[0] = "T" 这里会导致错误,因为尝试修改其内容。

TypeError: 'str' object does not support item assignment

 

例1:修改变量值,查看地址是否变化:

    #不可变类型

    string_test1 = "Hello World!"

    iVal_test1 = 1

    tuple_test = (2, 3, 4, 5)

    #可变类型

    List_test = [1, 2, 3, 4]

    

    print("string_test1:", string_test1, "id:", id(string_test1))

    print("iVal_test1:",iVal_test1, "id:", id(iVal_test1))

    print("tuple_test:",tuple_test, "id:", id(tuple_test))

    print("List_test:",List_test, "id:", id(List_test))

    string_test1 = "SpaceX"

    iVal_test1 = 2

    List_test[0] = 0

    tuple_test = (9, 8, 7, 6)

    print("After string_test1:", string_test1, "id:", id(string_test1))

    print("After iVal_test1:",iVal_test1, "id:", id(iVal_test1))

    print("After tuple_test:",tuple_test, "id:", id(tuple_test))

    print("After List_test:",List_test, "id:", id(List_test))

 


string_test1: Hello World! id: 2411681403952

iVal_test1: 1 id: 2411677679856

tuple_test: (2, 3, 4, 5) id: 2411679470976

List_test: [1, 2, 3, 4] id: 2411681408448

After string_test1: SpaceX id: 2411681404784

After iVal_test1: 2 id: 2411677679888

After tuple_test: (9, 8, 7, 6) id: 2411679471136

After List_test: [0, 2, 3, 4] id: 2411681408448

 

可以看到,尝试修改后,只有可变类型的list地址没有修改。不可变类型修改内容后,位置发生了变化。


例2:两个变量指向同一个地址:

#两个变量指向同一个地址

    iVal_a = 3

    iVal_b = iVal_a

    print("iVal_a:", iVal_a, "id:", id(iVal_a))

    print("iVal_b:", iVal_b, "id:", id(iVal_b))

    list_test_a = [1, 2, 3, 4]

    list_test_b = list_test_a

    print("list_test_a:", list_test_a, "id:", id(list_test_a))

    print("list_test_b:", list_test_b, "id:", id(list_test_b))

    

    iVal_a = 4

    print("After iVal_a:", iVal_a, "id:", id(iVal_a))

    print("After iVal_b:", iVal_b, "id:", id(iVal_b))

    list_test_a[-1] = 100

    print("After list_test_a:", list_test_a, "id:", id(list_test_a))

    print("After list_test_b:", list_test_b, "id:", id(list_test_b))


iVal_a: 3 id: 2752586449200

iVal_b: 3 id: 2752586449200

list_test_a: [1, 2, 3, 4] id: 2752594502976

list_test_b: [1, 2, 3, 4] id: 2752594502976

After iVal_a: 4 id: 2752586449232

After iVal_b: 3 id: 2752586449200

After list_test_a: [1, 2, 3, 100] id: 2752594502976

After list_test_b: [1, 2, 3, 100] id: 2752594502976

 

不可变object内容变化后,地址变了。但iVal_b因为还继续指向之前的地址,所以值没有变化。

可变object val变化后,地址没变,另一个指向此地址的object的值也跟着变了(因为地址是同一份)


这里还有个概念要注意:对象内存复用。

Python会把一些Object的内存进行复用。

Python中对不可变对象的缓存是一种性能优化机制。由于不可变对象的值不会更改,Python可以在内存中缓存相同的对象,以减少内存占用和提高性能。

这意味着如果创建多个相同值的不可变对象,它们实际上可能会引用相同的对象。

 

例3:

    #test id 重用内存:

    

    #test id 重用内存:

    

    iVal_a = 12

    iVal_b = 12

    fVal_c = 18.1234

    fVal_d = 18.1234

    

    print("iVal_a:", iVal_a, "id:", id(iVal_a), "iVal_b:", iVal_b, "id:", id(iVal_b), "id(12):", id(12))

    print("fVal_c", fVal_c, id(fVal_c), "fVal_d", fVal_d, "id:", id(fVal_d), "id(18.1234)", id(18.1234))

    iVal_a = 2

    fVal_c = 11.1231

    print("After ", "iVal_a:", iVal_a, "id:", id(iVal_a), "iVal_b:", iVal_b, "id:", id(iVal_b), "id(12):", id(12))

    print("After ", "fVal_c", fVal_c, id(fVal_c), "fVal_d", fVal_d, "id:", id(fVal_d), "id(18.1234)", id(18.1234))


iVal_a: 12 id: 2063254159952 iVal_b: 12 id: 2063254159952 id(12): 2063254159952

fVal_c 18.1234 2063255187600 fVal_d 18.1234 id: 2063255187600 id(18.1234) 2063255187600

After  iVal_a: 2 id: 2063254159632 iVal_b: 12 id: 2063254159952 id(12): 2063254159952

After  fVal_c 11.1231 2063255187824 fVal_d 18.1234 id: 2063255187600 id(18.1234) 2063255187600


可以看到,两个不可变对象若值相等,则它们指向的地址与这个值的常量相同。


1. 可变对象和不可变对象作为参数使用时的影响:

在Python中,参数传递方式与对象的可变性有关。

不可变对象的参数传递,可以认为是C/C++中的值传递。在函数内,为实参创建了一个副本,即改变函数内的参数,不会改变原始对象的值。

可变对象的参数传递,可以认为是C++的引用传递。函数内对参数的修改会改变原始对象的值。

 


 

 

阅读(0) 收藏(0) 转载(0) 举报/Report
相关阅读

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有