作為一個由c/c++轉過來的菜鳥,剛接觸Python的變量的時候很不適應,應為他的行為很像指針,void* ,不知道大家有沒有這樣的感覺。其實Python是以數據為本,變量可以理解為標簽。作為c/c++的菜鳥,把跟蹤變量地址的習慣帶入Python,舉個小例子說明Python的變量,對象,及參數傳遞。
1 '''例子1''' 2 x = 1 3 def fun(x): 4 x = 2 5 return None 6 7 fun(x) 8 print(x)
其實不打印也可以,我們用pycharm單步調試,看一下在每一行執行中,變量x的值的變化,及其地址的變化(其實這句話應該改成:變量x的指向變化更准確)
Python中id()函數,可以返回對象的地址,id()的官方解釋是:Return the “identity” of an object,既然是identity,肯定是唯一的;官方又說:CPython implementation detail: This is the address of the object in memory。我們暫時把id()返回值看做是對象在內存中的地址。
第一步:進入debug,在監視窗口,添加對Id(x),和id(y) 的觀察,藍色高亮,表示下一步將執行,我們看到這時,x,y都沒有分配地址
第二步:執行下一步,我們發現變量x,開始分配地址, 1392686144,我們記下這個數。
第三步:進入函數中,執行 x = 2語句,我們發現,x的地址已經變成 1392686176,這就是Python 變量的特性,我們不能理解成把變量x賦值為2,而是“名字為x的標簽指向對象2”,這樣更准確。
第四步:返回fun(x)函數,我們發現X的id()值又變回原來的數字,在這個例子中,我們把局部變量和全局變量用同一個標簽指示,當調用函數,進入函數內部執行時,系統會創建堆棧,保留進入函數前的運行環境及數據。進入函數后,有創建了一個同名的標簽x,x = 2,把局部標簽指向局部對象2,這是局部標簽x指向一個新的對象,內存地址肯定變化,當return none,返回函數調用時,堆棧撤銷,局部的對象,變量隨之撤銷,局部標簽x也撤銷,此時x做回自己,變成全局標簽x,依舊指向數字對象1.這就是為什么在函數內部,標簽x指向其他對象后,返回調用,又恢復調用前的內存地址。
第一個例子中,從標簽x的內存地址變化,幫我們理解Python的變量的行為。
在第二個例子中,我們仍然通過監視標簽的內存地址變化,理解參數傳遞的過程
1 '''例子2''' 2 a = [] 3 def fun(a): 4 a.append(1) 5 return None 6 7 fun(a) 8 print(a)
第一步:執行完函數調用,參數賦值,藍色高亮是下一步將要執行的代碼。我們發現在這一步,發生了參數賦值,創建堆棧,局部標簽x的內存地址與外部標簽a的內存地址相同,說明這一步,完成參數賦值,我們是不是可以把Python的“賦值語句”理解為“標簽指向”這個動作?從這看,這樣理解是可以的。所以“參數賦值”這個動作,可以理解為統一標簽指向。
第二步: 當函數返回時,我們發現列表a的地址沒有改變,並且列表中元素1得到保留,沒有因為局部變量撤銷而消失,這回一個典型的通過標簽(引用),在局部過程中改變全局變量的例子。Python標簽的這種特性是不是很像c++中的引用?是不是很像c中的指針?
總結:Python的變量,我們用標簽來理解,參考c的void*,參考c++中的&,Python的賦值動作,可以理解為“標簽改變指向”的動作。參數傳遞過程,是交換標簽指向的過程