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++中大量的基礎代碼。

  共享引用(深淺拷貝的緣由)

  在談共享引用之前,我們先要對列表的數據結構有一定的了解,列表不同於數組和鏈表,相較於數組(只能存儲單一的數據類型),列表可存取的數據類型是多樣的(多態)。

  上圖是數組,每個數組存了一個4個字節的數字,數組開辟了4*4個字節的連續的存儲空間,所以數組要求單一數據類型,否則長度不可控,無法索引。這里對數組進行索引就直接拿到某段內存里面存取的數據。

  可以通過存儲區的起始地址Loc (e0)加上邏輯地址(第i個元素)與存儲單元大小(c)的乘積計算而得,即:

  Loc(ei) = Loc(e0) + c*i

  訪問指定元素時無需從頭遍歷,通過計算便可獲得對應地址,其時間復雜度為O(1)。

  而列表作為python的數據類型,顯然不能像數組一樣存取數據,如果直接講列表存儲的數據放到內存空間,那么我們就無法直接索引了,python列表的做法於數組極為相似,只是他存儲的時候使用的是存儲數據的內存地址,這個內存地址大小是固定的,根據內存地址找到相應的地址存儲的數據,所以列表不是一段連續的數據類型(如果列表中的數據類型一致,存在連續的內存空間是更好的選擇)。

  說了這么多其實要說明的就是,深淺拷貝就是拷貝列表的內存地址還是包括內存地址指向的數據一同拷貝的問題

  當一個變量使用多個對象時,舊的對象會被垃圾收回,那么變量共享變量的對象有事一種什么樣的類型呢?

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是這樣一門有藝術的編程。

  等於賦值的情況:

n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]} 
n2 = n1

  淺拷貝賦值:

import copy
n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]}
n3 = copy.copy(n1)

  深拷貝:

  在http://www.cnblogs.com/adc8868/p/5647501.html博客中看到的,覺得解釋的比文字好用,借鑒學習。

  順序表

  聲明數組的時候就開辟了一塊固定大小的內存空間,空間的頭部空間寫有數組容量及當前存放數。

  兩者的根本差異在於,如果數組數量增加,那么一體式將包括頭部一同搬遷到新的物理空間,數組指向的起始內存位置改變了,但分離式只是將內存空間指向不同的位置,數組的起始位置未發生改變。

   python列表的本質

  Python中的list和tuple兩種類型采用了順序表的實現技術,具有前面討論的順序表的所有性質。

  tuple是不可變類型,即不變的順序表,因此不支持改變其內部狀態的任何操作,而其他方面,則與list的性質類似。

  Python標准類型list就是一種元素個數可變的線性表,可以加入和刪除元素,並在各種操作中維持已有元素的順序(即保序),而且還具有以下行為特征:

  基於下標(位置)的高效元素訪問和更新,時間復雜度應該是O(1);

為滿足該特征,應該采用順序表技術,表中元素保存在一塊連續的存儲區中。

  允許任意加入元素,而且在不斷加入元素的過程中,表對象的標識(函數id得到的值)不變。

為滿足該特征,就必須能更換元素存儲區,並且為保證更換存儲區時list對象的標識id不變,只能采用分離式實現技術。

  在Python的官方實現中,list就是一種采用分離式技術實現的動態順序表。這就是為什么用list.append(x) (或 list.insert(len(list), x),即尾部插入)比在指定位置插入元素效率高的原因。

  在Python的官方實現中,list實現采用了如下的策略:在建立空表(或者很小的表)時,系統分配一塊能容納8個元素的存儲區;在執行插入操作(insert或append)時,如果元素存儲區滿就換一塊4倍大的存儲區。但如果此時的表已經很大(目前的閥值為50000),則改變策略,采用加一倍的方法。引入這種改變策略的方式,是為了避免出現過多空閑的存儲位置。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM