動態型別 (dynamic typing) 是 Python 另一個重要的核心概念。我們之前説過,Python 的變數 (variable) 不需要宣告,而在賦值時,變數可以重新賦值為任意值。這些都與動態型別的概念相關。
動態型別
在我們接觸的物件中,有一類特殊的物件,是用於儲存資料的。常見的該類物件包括各種數字,字串,表,詞典。在 C 語言中,我們稱這樣一些資料結構為變數。而在 Python 中,這些是物件。
物件是儲存在內存中的實體。但我們並不能直接接觸到該物件。我們在程式中寫的物件名,只是指向這一物件的引用 (reference) 。
引用和物件分離,是動態型別的核心。引用可以隨時指向一個新的物件:
a = 3
a = ‘at’
第一個語句中,3 是儲存在內存中的一個整數物件。通過賦值,引用 a 指向物件 3 。
第二個語句中,內存中建立物件 ‘at’,是一個字串 (string) 。引用 a 指向了’at’ 。此時,物件 3 不再有引用指向它。 Python 會自動將沒有引用指向的物件銷燬 (destruct),釋放相應內存。
(對於小的整數和短字串,Python 會 WordPress 加速緩存這些物件,而不是頻繁的建立和銷燬。)
a = 5
b = a
a = a + 2
再看這個例子。通過前兩個句子,我們讓 a,b 指向同一個整數物件 5(b = a 的含義是讓引用 b 指向引用 a 所指的那一個物件) 。但第三個句子實際上對引用 a 重新賦值,讓 a 指向一個新的物件 7 。此時 a,b 分別指向不同的物件。我們看到,即使是多個引用指向同一個物件,如果一個引用值發生變化,那麼實際上是讓這個引用指向一個新的引用,並不影響其他的引用的指向。從效果上看,就是各個引用各自獨立,互不影響。
其它資料物件也是如此:
L1 = [1,2,3]
L2 = L1
L1 = 1
但注意以下情況
L1 = [1,2,3]
L2 = L1
L1[0] = 10
print L2
在該情況下,我們不再對 L1 這一引用賦值,而是對 L1 所指向的表的元素賦值。結果是,L2 也同時發生變化。
原因何在呢?因為 L1,L2 的指向沒有發生變化,依然指向那個表。表實際上是包含了多個引用的物件(每個引用是一個元素,比如 L1[0],L1[1]…, 每個引用指向一個物件,比如 1,2,3), 。而 L1[0] = 10 這一賦值操作,並不是改變 L1 的指向,而是對 L1[0], 也就是表物件的一部份 (一個元素),進行操作,所以所有指向該物件的引用都受到影響。
(與之形成對比的是,我們之前的賦值操作都沒有對物件自身發生作用,只是改變引用指向。)
列表可以通過引用其元素,改變物件自身 (in-place change) 。這種物件型別,稱為可變資料物件 (mutable object),詞典也是這樣的資料型別。
而像之前的數字和字串,不能改變物件本身,只能改變引用的指向,稱為不可變資料物件 (immutable object) 。
我們之前學的元組 (tuple),儘管可以呼叫引用元素,但不可以賦值,因此不能改變物件自身,所以也算是 immutable object.
從動態型別看函式的引數傳遞
函式的引數傳遞,本質上傳遞的是引用。比如説:
def f(x):
x = 100
print x
a = 1
f(a)
print a
引數 x 是一個新的引用,指向 a 所指的物件。如果引數是不可變 (immutable) 的物件,a 和 x 引用之間相互獨立。對引數 x 的操作不會影響引用 a 。這樣的傳遞類似於 C 語言中的值傳遞。
如果傳遞的是可變 (mutable) 的物件,那麼改變函式引數,有可能改變原物件。所有指向原物件的引用都會受影響,程式設計的時候要對此問題留心。比如説:
def f(x):
x[0] = 100
print x
a = [1,2,3]
f(a)
print a
動態型別是 Python 的核心機制之一。可以在應用中慢慢熟悉。
總結
引用和物件的分離,物件是內存中儲存資料的實體,引用指向物件。
可變物件,不可變物件
函式值傳遞
作者:Vamei 出處:http://www.cnblogs.com/vamei