天天看點

python中可變和不可變對象(複值,拷貝,函數值傳遞)

python中有可變對象和不可變對象,

可變對象: list, dict.

不可變對象有: int, string, float, tuple.

python不可變對象

int, string, float, tuple

先來看一個例子

def int_test():     i = 77    j = 77    print(id(77))                    #140396579590760    print('i id:' + str(id(i)))      #i id:140396579590760    print('j id:' + str(id(j)))      #j id:140396579590760    print i is j                     #True    j = j + 1    print('new i id:' + str(id(i)))  #new i id:140396579590760    print('new j id:' + str(id(j)))  #new j id:140396579590736    print i is j                     #Falseif __name__ == '__main__':    int_test()      

有 i 和 j 倆個變量的值為77,通過列印 77的 ID 和變量 i,j 在記憶體中的 ID 我們得知它們都是指向同一塊記憶體。是以說 i 和 j 都是指向同一個對象的。然後我們修改j的值,讓 j 的值 +1.按道理 j 修改之後應該i的值也發生改變的,因為它們都是指向的同一塊記憶體,但結果是并沒有。因為 int類型是不可變類型,所有其實是 j 複制了一份到新的記憶體位址然後+1,然後 j 又指向了新的位址。是以j的記憶體 id 發生了變化。

記憶體配置設定情況如下:

python中可變和不可變對象(複值,拷貝,函數值傳遞)

python可變對象

dict,list

def dict_test():

    a = {}

    b = a

    print(id(a))

    a['a'] = 'hhhh'

    print('id a:' + str(id(a)))

    print('a:' + str(a))

    print('id b:' + str(id(b)))

    print('b:' + str(b))

if __name__ == '__main__':

    dict_test()

運作結果如下:  

  140367329543360

  id a:140367329543360

  a:{'a': 'hhhh'}

  id b:140367329543360

  b:{'a': 'hhhh'}

可以看到 a 最早的記憶體位址 id 是140367329543360 然後把 a 指派給 b 其實就是讓變量 b 的也指向 a 所指向的記憶體空間。然後我們發現當 a 發生變化後, b 也跟着發生變化了,因為 list 是可變類型,是以并不會複制一份再改變,而是直接在 a 所指向的記憶體空間修改資料,而 b 也是指向該記憶體空間的,自然 b 也就跟着改變了。

記憶體變化如下:

python中可變和不可變對象(複值,拷貝,函數值傳遞)

python函數的參數傳遞

由于python規定參數傳遞都是傳遞引用,也就是傳遞給函數的是原變量實際所指向的記憶體空間,修改的時候就會根據該引用的指向去修改該記憶體中的内容,是以按道理說我們在函數内改變了傳遞過來的參數的值的話,原來外部的變量也應該受到影響。但是上面我們說到了python中有可變類型和不可變類型,這樣的話,當傳過來的是可變類型 (list, dict)時,我們在函數内部修改就會影響函數外部的變量。而傳入的是不可變類型時在函數内部修改改變量并不會影響函數外部的變量,因為修改的時候會先複制一份再修改。下面通過代碼證明一下:

def test(a_int, b_list):    a_int = a_int + 1    b_list.append('13')    print('inner a_int:' + str(a_int))    print('inner b_list:' + str(b_list))if __name__ == '__main__':    a_int = 5    b_list = [10, 11]    test(a_int, b_list)    print('outer a_int:' + str(a_int))    print('outer b_list:' + str(b_list))運作結果如下:   inner a_int:6   inner b_list:[10, 11, '13']   outer a_int:5   outer b_list:[10, 11, '13']      

好啦!答案顯而易見啦,經過 test() 方法修改後,傳遞過來的 int 類型外部變量沒有發生改變,而 list 這種可變類型則因為 test() 方法的影響導緻内容發生了改變。

總結:

在很多的其他語言中在傳遞參數的時候允許程式員選擇值傳遞還是引用傳遞(比如c語言加上*号傳遞指針就是引用傳遞,而直接傳遞變量名就是值傳遞),而python隻允許使用引用傳遞,但是它加上了可變類型和不可變類型,讓我們感覺有點混亂了。聽說python隻允許引用傳遞是為友善記憶體管理,因為python使用的記憶體回收機制是計數器回收,就是每塊記憶體上有一個計數器,表示目前有多少個對象指向該記憶體。每當一個變量不再使用時,就讓該計數器-1,有新對象指向該記憶體時就讓計數器+1,當計時器為0時,就可以收回這塊記憶體了。當然我覺得它肯定不止用了計數器吧,應該還有其他的技術,比如分代回收什麼的。不再讨論之列,就這樣了...

ps:

值傳遞:表示傳遞直接傳遞變量的值,把傳遞過來的變量的值複制到形參中,這樣在函數内部的操作不會影響到外部的變量