天天看點

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("以上說明了不可變對象隻會在記憶體中存在一個副本,即使是深拷貝,也都會指向同一個副本")