Python進階09 動態型別
動態型別(dynamic typing)是Python另一個重要的核心概念。我們之前說過,Python的變數(variable)不需要宣告,而在賦值時,變數可以重新賦值為任意值。這些都與動態型別的概念相關。
1. 動態型別
在我們接觸的物件中,有一類特殊的物件,是用於儲存資料的。常見的該類物件包括各種數字,字串,表,詞典。在C語言中,我們稱這樣一些資料結構為變數。而在Python中,這些是物件。
物件是儲存在記憶體中的實體。而我們的變數,實際上只是指向這一物件的參考(reference),類似於C語言的指標。
(在C語言中,變數自身就是儲存於記憶體中的實體)
變數和它所指的物件的分離,就是動態型別的核心。由於變數只類似於一個指標,所以它可以隨時指向一個新的物件,即使這個新的物件的型別發生了變化。
a = 3 a = 'at'
第一個語句中,3是儲存在記憶體中的一個整數物件。通過賦值,我們將在記憶體中建立這一物件,並將變數a指向改物件。
第二個語句中,我們在記憶體中建立物件‘at’, 其型別是字串(string)。變數a在此又指向了'at'。而此時,物件3不再有變數指向它,Python會自動將沒有變數指向的物件銷燬(destruct),從而釋放相應記憶體。
(對於小的整數和短字串,實際上Python會快取這些物件,而不是針對每次賦值而分別建立和銷燬。但從邏輯層面上來說,上面的說法並沒有問題。我們將忽略這一細節)
a = 5 b = a a = a + 2
再看這個例子。通過前兩個句子,我們讓a,b指向同一個整數物件5(b = a
不止是整數如此,其它資料物件也是如此:
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.
2. 從動態型別看函式的引數傳遞
函式的引數傳遞,實際上是讓函式的各個引數作為變數,指向物件。比如說:
def f(x): x = 100 print x a = 1 f(a) print a
在呼叫函式f()時,實際上函式讓引數作為一個變數,指向a所指的物件。如果引數是不可變(immutable)的物件,那麼如上面所講,各個變數之間相當於相互獨立。引數傳遞類似於C語言中的值傳遞。
如果是引數是可變(mutable)的物件,那麼存在有改變物件自身的可能性,所有指向該物件的變數(無論是函式中的引數,還是主程式中的變數)都會受影響,程式設計的時候要對此問題留心。比如說:
def f(x): x[0] = 100 print x a = [1,2,3] f(a) print a
動態型別是Python的核心機制之一。可以在應用中慢慢熟悉。
總結:
變數和物件的分離,物件是記憶體中儲存資料的實體,變數指向物件。
可變物件,不可變物件
函式值傳遞