參考了網上一些資料,感謝他們的分享。本文將深入研究一下 Python 動態類型的特點,Python 一切皆為對象。更多 Python 進階系列文章,請參考 Python 進階學習 玩轉數據系列
內容提要:
Python 是動態類型語言:所謂的 Python 動態類型,就是在程序運行的過程中自動決定對象的類型。
動態類型(dynamic typing) 是 Python 一個重要的核心概念。Python 的變量(variable)不需要聲明,而在賦值時,變量可以重新賦值為任意值,這些都與動態類型的概念相關。
靜態類型(static typing)語言中,變量的類型必須先聲明,即在創建的那一刻就已經確定好變量的類型,而後的使用中,你只能將這一指定類型的數據賦值給變量。如果強行將其他不相干類型的數據賦值給它,就會引發錯誤。Java 是動態類型語言。
靜態類型賦值:
int x = 5;變量 x 保存的是值 5,而不是地址
。
基本類型存儲了實際的數值,而並非指向一個對象的引用,所以在為其賦值的時候,是直接從一個地方賦值到了另外一個地方,此時賦值的是變量所保存的數據值。
動態類型賦值:
x = 5變量 x 指向對象 5 的引用
。
在 Python中,一切皆對象。對象是儲存在內存中的實體。但我們並不能直接接觸到該對象。我們在程序中寫的對象名,只是指向這一對象的引用(reference)。引用和對象分離,是動態類型的核心
。引用可以隨時指向一個新的對象,當第一次賦值給一個變量時就創建了這個變量,在之後的賦值過程關聯值。
變量是沒有類型的,Python 中類型只存在於對象中,只有對象的值有類型,變量是通用的,它只是在程序的某一段時間引用了某種類型的對象而已。比如定義a =1 ,a = ‘a’,一開始定義了變量 a 為指向了整型的對象,然後變量又指向了字符串類型的對象,可見,變量是不固定的類型。
當我們給變量賦值的時候,比如 a=5,python 執行三個不同操作去完成賦值。
變量的引用以內存中的指針形式實現。一旦變量被使用,那麼 Python 自動跟變量的對象連接。具體來說:
多個變量引用不可變對象,共享不可變對象引用:
a = 'Hello'
b = a
print("before a:{}".format(a))
print("before b:{}".format(b))
a = "Hi"
print("after a:{}".format(a))
print("after b:{}".format(b))
輸出:
before a:Hello
before b:Hello
after a:Hi
after b:Hello
a = 'Hello',變量 a 指向內存中的字符串對象 'Hello' 的引用
b = a,變量 b 指向的應該是變量 a 指向的對象地址的值 也指向對象 'Hello' 的引用。
a = 'Hi',變量 a 指向內存中的字符串對象 'Hi' 的引用
此時,變量 a 不再指向對象 'Hello',改變變量 a 指向的對象並不會影響 b 的值,變量 b 仍指向對象 'Hello'
所以針對不可變對象,Python 中變量總是一個指定對象的指針,而不是能夠改變內存區域的標簽,即給一個變量賦新的值,不是替換一個對象原始值,而是創建一個新得對象供變量引用
多個變量引用可變對象,共享可變對象引用:
a = [1,'hi',3]
b = a
print("before a:{}".format(a))
print("before b:{}".format(b))
a[0] = 10
print("after a:{}".format(a))
print("after b:{}".format(b))
輸出:
before a:[1, 'hi', 3]
before b:[1, 'hi', 3]
after a:[10, 'hi', 3]
after b:[10, 'hi', 3]
a = [1,‘hi’,3]
變量 a 指向 一個 list 列表對象。list 列表對象是包含多個對象的引用(每個引用指向一個對象,a[0] 指向 對象 1,a[1] 指向對象 ‘hi’ ,a[2] 指向 對象 3)
b = a
變量 b 也指向這個 list 列表對象。
a[0] = 10
並不是改變變量 a 的指向,而是對 a[0], 也就是表對象的一部份(一個元素),進行操作,所以所有指向該對象的引用都受到影響。
與之形成對比的是,我們之前的賦值操作都沒有對對象自身發生作用,只是改變引用指向
對於可變類型對象,變量不會創建一個新的對象,而是沿用之前的對象,即使對象已經被改變了。可以簡單的理解為,兩個對象同時指向了一個列表的內存地址,而列表又映射了裡面各元素的內存地址,變量的共享並不關注列表的改變,他們只關心列表的內存空間是否改變,所以,可變對象在引用時自身可以改變,所以不需要創建新的對象,所以共享對象會隨之前對象的變化而變化
。
列表可以通過引用其元素,改變對象自身(in-place change)。這種對象類型,稱為可變數據對象(mutable object),詞典也是這樣的數據類型。
而像之前的數字和字符串,不能改變對象本身,只能改變引用的指向,稱為不可變數據對象(immutable object)。
每個對象都有兩個標准頭部信息,一個是類型標志符,用於標記對象類型,另一個是引用計數器,用來決定是不是可回收對象。很顯然,在 Python 中只有對象才有類別區分,變量不過是引用了對象,變量並不具有類別區分,他只是在特定時間引用某個特定對象
。
對於引用計數器的使用,則關聯到 Python 的垃圾回收機制,當一個變量名賦予了一個新的對象,那麼之前舊的對象占用的地址空間就會被回收。舊對象的空間自動放入內存空間池,等待後來的對象使用
。
例如:
a=5
a='Hello'
第一個語句中,5 是儲存在內存中的一個整數對象。通過賦值,變量 a 指向對象 5 的引用。
第二個語句中,內存中建立對象‘Hello’,是一個字符串(string)。變量 a 指向了 ‘Hello’ 對象的引用。此時,對象 5 不再有引用指向它。Python 會自動將沒有引用指向的對象銷毀(destruct),釋放相應內存。
計數器在垃圾回收的過程中又是如何工作的呢?計數器記錄的是當前指向對象的引用數目,如果在某時刻計數器設置為0,則表示未被引用,那麼這個對象的內存空間就會收回。
對象的垃圾回收有著很大的意義,這使得我們在 Python 中任意使用對象而且不需要考慮釋放空間,省去了C與C++中大量的基礎代碼。對於一些小的整數或短字符串,並不像我們說的那樣計數器標記為0就被收回。這和 Python 的緩存機制有關,Python 會緩存這些對象,而不是頻繁的建立和銷毀。對於一般的對象,Python 適用於垃圾收回。
小的整型:
因為 111 對象會被緩存起來,所以 變量 a, b, c 指向的是同一個對象 111
a=111
b=111
c=a
print("a == c:{ }".format(a == c))
print("a == b:{ }".format(a == b))
print("a is c:{ }".format(a is c))
print("a is b:{ }".format(a is b))
輸出:
a == c:True
a == b:True
a is c:True
a is b:True
短字符串
因為 ‘111’ 對象會被緩存起來,所以 變量 a, b, c 指向的是同一個對象 ‘111’
a='111'
b='111'
c=a
print("a == c:{ }".format(a == c))
print("a == b:{ }".format(a == b))
print("a is c:{ }".format(a is c))
print("a is b:{ }".format(a is b))
輸出:
a == c:True
a == b:True
a is c:True
a is b:True
參數 x 是一個新的引用,指向 a 所指的對象。如果參數是不可變(immutable)
的對象,a 和 x 引用之間相互獨立。對參數x的操作不會影響引用a。這樣的傳遞類似於C語言中的值傳遞。
def f(x):
x = 100
print("x:{}".format(x))
a = 1
print("before a:{}".format(a))
f(a)
print("after a:{}".format(a))
輸出:
before a:1
x:100
after a:1
如果傳遞的是可變(mutable)
的對象,那麼改變函數參數,有可能改變原對象。所有指向原對象的引用都會受影響,編程的時候要對此問題留心.
def f(x):
x[0] = 100
print("x:{}".format(x))
a = [1,2,3]
print("before a:{}".format(a))
f(a)
print("after a:{}".format(a))
輸出:
before a:[1, 2, 3]
x:[100, 2, 3]
after a:[100, 2, 3]