1. 程式人生 > >Python進階09 動態型別

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

的含義是讓變數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.

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的核心機制之一。可以在應用中慢慢熟悉。

總結:

變數和物件的分離,物件是記憶體中儲存資料的實體,變數指向物件。

可變物件,不可變物件

函式值傳遞