在python中,省去了變數聲明的過程,在引用變數時,往往一個簡單的賦值語句就同時完成了,聲明變數類型,變數定義和關聯的過程,那麼python的變數到底是怎樣完成定義的呢? 動態類型 python使用動態類型和他提供的多態性來提供python語言的簡潔靈活的基礎。在python中我們是不會聲明所使用 ...
在python中,省去了變數聲明的過程,在引用變數時,往往一個簡單的賦值語句就同時完成了,聲明變數類型,變數定義和關聯的過程,那麼python的變數到底是怎樣完成定義的呢?
動態類型
python使用動態類型和他提供的多態性來提供python語言的簡潔靈活的基礎。在python中我們是不會聲明所使用對象的確切類型的。所謂的python動態類型,就是在程式運行的過程中自動決定對象的類型。
對象、變數和引用
當我們在賦值一個變數時,在python中其實自動做了很多事情。
1.創建變數:當代碼第一次賦值給一個變數時就創建了這個變數,在之後的賦值過程關聯值,python在代碼運行之前先檢驗變數名,可以當成是最初的賦值創建變數。
2.變數聲明:python中類型只存在於對象中,而不是變數,變數是通用的,他只是在程式的某一段時間引用了某種類型的對象而已,比如定義a =1 ,a = 'a',一開始定義了變數a為指向了整型的對象,然後變數又指向了字元串類型的變數,可見,變數是不固定的類型。
3.變數使用:變數出現在表達式中就會馬上被對象所取代,無論對象是什麼內類型,變數在使用前必須要先定義。
值得註意的是,變數必須在初始化名字之後才能更新他們,比如計數器初始化為0,然後才能增加他。
也就是說,當我們給變數賦值的時候,比如a=3,python執行三個不同操作去完成賦值。
1.創建一個對象代表3,
2.如果程式中沒有變數a,則創建他。
3.將變數與對象3連接起來。
變數與對象是連接關係,它們存儲在記憶體的不同位置,如果有列表嵌套這樣大的對象,對象還連接到它包含的對象。這種從變數到對象的連接稱為引用。
變數的引用以記憶體中的指針形式實現。一旦變數被使用,那麼python自動跟變數的對象連接。具體來說:
1.變數是系統表的元素,他指向對象存放的地址空間。
2.對象是分配的一塊記憶體,地址可被連接,有足夠大空間代表對象的值,
3.引用的過程自動完成變數指向對象地址的過程,即從變數到對象的指針。
對象的垃圾回收
每個對象都有兩個標準頭部信息,一個是類型標誌符,用於標記對象類型,另一個是引用計數器,用來決定是不是可回收對象。很顯然,在python中只有對象才有類別區分,變數不過是引用了對象,變數並不具有類別區分,他只是在特定時間引用某個特定對象。
對於引用計數器的使用,則關聯到python的垃圾回收機制,噹噹一個變數名賦予了一個新的對象,那麼之前舊的對象占用的地址空間就會被回收。舊對象的空間自動放入記憶體空間池,等待後來的對象使用。
計數器在垃圾回收的過程中有事如何工作的呢?計數器記錄的是當前指向對象的引用數目,如果在某時刻計數器設置為0,則表示未被引用,name這個對象的記憶體空間就會收回。
對象的垃圾回收有著很大的意義,這使得我們在python中任意使用對象而且不需要考慮釋放空間,省去了C與C++中大量的基礎代碼。
共用引用(深淺拷貝的緣由)
當一個變數使用多個對象時,舊的對象會被垃圾收回,那麼變數共用變數的對象有事一種什麼樣的類型呢?
a=‘hello world’ b=a print(b) 運行結果: hello world
值得一提的是,這裡b變數引用的a作為值,根據python中賦值是以對象來完成的,所以b引用的應該是a變數指向的對象地址的值,故可以判斷,改變a指向的對象並不會影響b的值。
a=‘hello world’ b=a a='new hello world' print(a) print(b) 運行結果: new hello world hello world
python中變數總是一個指定對象的指針,而不是能夠改變記憶體區域的標簽,即給一個變數賦新的值,不是替換一個對象原始值,而是創建一個新得對象供變數引用。
當然這條只限於對象的類型不可改變,如果引用對象是像列表一樣可供修改的對象那結果如何呢?
a=[1,2,3] b=a a.append(4) print(a) print(b) 運行結果: [1, 2, 3, 4] [1, 2, 3, 4]
結果很顯然,對於可變類型對象,變數不會創建一個顯得對象,而是沿用之前的對象,即使對象已經被改變了。可以簡單的理解為,兩個對象同時指向了一個列表的記憶體地址,而列表又映射了裡面各元素的記憶體地址,變數的共用並不關註列表的改變,他們只關心列表的記憶體空間是否改變,所以,可變對象在引用時自身可以改變,所以不需要創建新的對象,所以共用對象會隨之前對象的變化而變化。
這其實是我們不希望看到的。我們可以使用拷貝對象創建引用:
a=[1,2,3] b=a[:] a.append(4) print(a) print(b) 運行結果: [1, 2, 3, 4] [1, 2, 3]
這種方法並不適用於不可索引但是可變的字典與集合,所以python的copy模塊用於變數引用:
import copy a=[1,2,3,[1,2]] b=copy.copy(a) c=copy.deepcopy(a) d=a a.append(4) a[3][0]=5 print(a) print(b) print(c) print(d) 運行結果: [1, 2, 3, [5, 2], 4] [1, 2, 3, [5, 2]] [1, 2, 3, [1, 2]] [1, 2, 3, [5, 2], 4]
共用引用的補充
其實是關於垃圾回收的一點補充,對於一些小的整數或字元串,並不像我們說的那樣計數器標記為0就被收回。這和python的緩存機制有關。對於一般的對象,python適用於垃圾收回。
a=[1,2,3] b=[1,2,3] c=a print(a == c) print(a == b) print(a is c) print(a is b) 運行結果: True True True False
對於小的整數與字元串則不同:
a=111 b=111 c=a print(a == c) print(a == b) print(a is c) print(a is b) 運行結果: True True True True
在python中,任何東西都是在賦值與引用中工作的,對於理解python動態類型,在以後的工作與學習時是有很大幫助的,這是python唯一的賦值模型,所以準確的理解與應用十分有必要。不對類型作約束,這使得python代碼極為靈活,高效,並且省去了大量的代碼,極為簡潔,所以python是這樣一門有藝術的編程。