Love, and to be Loved.

我愛你,你是自由的。

Python 動態類型(轉)

Python中只有一個賦值模型

1. 缺少類型聲明語句的情況

在Python中,類型是在運行過程中自動決定的,而不是通過代碼聲明。這意味著沒有必要事聲明變量。只要記住,這個概念實質上對變量,對象和它們之間的關系都適用。那麽這個概念也容易理解並掌握。

A 變量,對象和引用
變量創建:一個變量,當代碼第一次給它賦值時它就被創建了。之後的賦值將會改變已創建的變量名的值。Python在代碼運行之前先檢測變量名,可以當成是最初的賦值創建變量。
變量類型:變量永遠不會有任何的它關聯的類型信息或約束。類型的概念是存在於對象中而不是變量中。變量原本是通用的。它只是在一個特定的時間點,簡單地引用了一個特定的對像而已。
變量的使用:當變量出現在表達式中時,它會馬上被當前引用的對像所代替,無論這個對象是什麽類型。
此外,所有的變量都必須在其使用前明確地賦值。使用未賦值的變量會產生錯誤。
>>>a=3

在概念上說,Python將執行三個不同的步驟去完成這個請求。
1) 創建一個對象來代表值3
2) 創建一個變量a,如果它還沒有創建的話
3) 將變量與新的對象3連接

在python中從變量到對象的連接稱作引用。引用是一種關系,以內存中的指針形式實現。

  • 變量 是一個系統表的元素,擁有指向對象的連接空間。
  • 對象 是被分配的一塊內存,有足夠的空間去表現它們所代表的值。
  • 引用 是自動形成的從變量到對象的指針。

每一個對象都用兩個標準的頭部信息:一個類型標誌符去標識這個對象的類型,以及一個引用的計數器,用來決定是不是可以回收這個對象。

B 類型屬於對象,而不是變量
Python中的類型是與對象相關聯的,而不是和變量關聯。
變量沒有類型,變量指向對象。對象有類型,知道自己的類型,每個對象都包含了一個頭部信息,其中標記了這個對象的類型。

C 對象的垃圾收集
對象生命結束時發生了什麽變化?
每當一個變量名被賦與了一個新的對象,之前的那個對象占用的空間就會被收回(如果它沒有被其他變量名和對象所引用).這種自動回收對象空間的技術稱作垃圾收集。
在內部,Python是通過保持用每個對象中的計數器記錄引用指到這個對象上的次數來完成這一功能。一旦(並精確在同一時間)這個計數器被設置為零,這個對象的內存空間就會自動收回。垃圾收集最直接的,可感受到的好處就是這意味著可以在腳本中任意使用對象而不需要考慮釋放內存空間。

>>> x=42
>>> id(x)
674748828
>>> x="cli"
>>> id(x)    
676367648

2. 共享引用

上面所講都是單個變量被賦值引用了多個對象的情況。現在,在交互模式下,引入另一個變量,並看一下變量名和對象的變化。

>>> a=10
>>> b=a 
>>> id(a)
674749212
>>> id(b)
674749212

第二行會使用python創建變量b。使用的是變量a,並且它在這裏沒有被賦值,所以它被替換成其應用的對象10,從而b也成為這個對象的一個引用。實際效果就是變量a和b都引用相同的對象(也就是說指向了相同的內存空間。在Python中稱作是共享引用–多個變量名應用了同一個對象。)

>>> a=10 
>>> b=a  
>>> a='cli'
>>> id(a) 
676367648
>>> id(b)
674749212

變量a改變了,但是不影響變量b.這完全可以說明變量b是指向對象10內存空間的。

在ptyhon中,變量總是一個指向對象的指針,而不是可以改變的內存區域的標簽。給一變量賦一個新的值,並不是替換了原始的對象,而是讓這個變量去引用完全不同的一個對象。實際的效果就是對一個變量賦值,僅僅會影響那個被賦值的變量。

A 共享引用和在原處修改
有一些對象和類型確實會在實地改變對象。例如,在一個列表中對一個偏移進行賦值確實會改變這個列表對象,而不是生成一個新的列表對象。

>>> T1=[11,12,13]
>>> T2=T1
>>> T1
[11, 12, 13]
>>> T2
[11, 12, 13]
>>> T1=22
>>> T1
22
>>> T2
[11, 12, 13]

這個和先前一樣T1改變了T2沒有改變,T2改變也不影響T1

>>> T1=[11,12,13]
>>> T2=T1
>>> T1
[11, 12, 13]
>>> T2
[11, 12, 13]
>>> T2[1]=33     
>>> T1
[33, 12, 13]
>>> T2
[33, 12, 13]

發現T2改變了,T1也跟這改變了
同樣T1改變了,T2也改變了

>>> T1[1]=99  
>>> T2
[33, 99, 13]
>>> T1
[33, 99, 13]

這裏T1沒有改變,改變了T1所引用對象的一個元素。這類修改會覆蓋列表對象中的某部分。因為這個列表對象是與其他對象共享的(被其他對象引用),那麽一個像這樣在原處的改變不僅僅會對T1有影響。必須意識到當做了這樣的修改,它會影響程序的其他部分。

如果不想要這樣的現象發生,需要Python拷貝對象,而不是創建引用。方法包括內置列表函數以及標準庫的copy模塊,最常用的辦法就是從頭到尾的分片T1[:]

>>> T1=[11,12,13]
>>> T2=T1[:]
>>> T1
[11, 12, 13]
>>> T2
[11, 12, 13]
>>> T1[0]=99
>>> T1
[99, 12, 13]
>>> T2
[11, 12, 13]
>>> id(T1)
676366604
>>> id(T2)
675542060

T1和T2指向不同的對象,所以不會相互影響。
註意:這種分片技術不會引用在其他的可變的核心類型上(字典,因為它們不是序列),對字典應該使用D.copy()方法.而且,註意標準庫中的copy模塊有一個通用的拷貝任意對象的調用,也有一個拷貝嵌套對象的結構的調用.

>>> X={'name':'cli','age':27}  
>>> import copy
>>> Y=copy.copy(X)
>>> X
{'age': 27, 'name': 'cli'}
>>> Y
{'age': 27, 'name': 'cli'}
>>> id(X)
676370468
>>> id(Y)
676414436
>>> X={'name':{'FirstName':'cli','LastName':'cli'},'age':27}    
>>> X
{'age': 27, 'name': {'LastName': 'cli', 'FirstName': 'cli'}}
>>> Y=copy.copy(X)
>>> Y
{'age': 27, 'name': {'LastName': 'cli', 'FirstName': 'cli'}}
>>> Z=copy.deepcopy(X) 
>>> Z
{'age': 27, 'name': {'LastName': 'cli', 'FirstName': 'cli'}}

B 共享引用和相等

>>> x=33
>>> x='cli'

因為Python緩存並復用了小的整數和小的字符串,就像前文提到的那樣,這裏對象33也許不像前期所說的被收回,相反,它將可能仍保持在一個系統表中,等待下一次你的代碼生成另一個33來利用。盡快如此,大多數種類的對象都會在不再引用時馬上回收。對於那些不會被回收的,緩沖機制與代碼並沒有什麽關系。

判斷是否相等

>>> L=[1,2,3]
>>> M=L
>>> L==M
True
>>> L is M
True

檢查對象是否有相同的值。 is操作符,檢查對象的同一性。如果兩個變量名精準地指向同一個對象,它會返回True。所以這是一種更嚴格的相等測試。
實際上,is只是比較現實引用的指針。所以如果必要的話是代碼中檢測共享引用的一種方法。如果變量名引用值相等。但是為不同的對象,它的返回值將是False.

>>> L=[1,2,3]
>>> M=[1,2,3]
>>> L==M
True
>>> L is M
False
>>> id(L)
676367788
>>> id(M)
676367724

通過id()函數可以看到兩個變量指向不同的對象。

>>> X=33
>>> Y=33
>>> X==Y
True
>>> X is Y
True
>>> id(X)
674748936
>>> id(Y)
674748936

這個is測試返回True因為小的整數和字符串被緩存被復用了。
如果想更進一步了解,可以向Python查詢一個對象應用的次數:在sys模塊中的getrefcount函數返回對象應用的次數。

>>> import sys
>>> sys.getrefcount(33)
13
>>> sys.getrefcount(1) 
427
>>> sys.getrefcount(00)
296
>>> sys.getrefcount(99)
6

Origin