Python 中變量與內存的關係

       老手都是重新手一路過來的,提起Python中難以理解的概念,可能不少人對於Python變量賦值的機制有些疑惑,不過對於習慣於求根究底的程序員,只有深刻理解了某個事物本質,掌握了它的客觀規律,才能駕輕就熟、運用自如,進階更高層次來看待這個事物,此刻「庖丁解牛」這個成語可以貼切表達這個意思,你看見的是整頭的牛,而我看見的是牛的內部肌理筋骨,就是這個狀態!!!程序員

那麼爲何Python變量賦值的機制難以理解呢?學習

       我想多是咱們的思惟被C語言變量賦值的機制所固化了。在C語言中變量所分配到的地址是內存空間中一個固定的位置,當咱們改變變量值時,對應內存空間中的值也相應改變。在Python中變量存儲的機制是徹底不同的,當給一個變量賦值時首先解釋器會給這個值分配內存空間,而後將變量指向這個值的地址,那麼當咱們改變變量值的時候解釋器又會給新的值分配另外一個內存空間,再將變量指向這個新值的地址,因此和C語言相比,在Python中改變的是變量所指向的地址,而內存空間中的值是固定不變的。測試

       接下來咱們要由淺入深的去驗證下咱們的結論。在Ubuntu 16.04 LTS 32 位的環境下經過id方法查看變量的內存地址的方式來進行驗證,爲何要強調環境呢?由於不一樣的環境下測試結果可能會因爲解釋器的優化不一樣而有所不一樣。優化

那這裏咱們就以Python的int類型爲例,能夠看到執行 i += 1 後,變量i的內存地址會發生變化,事實上 i += 1 並非在原有變量i的地址上加1,而是從新建立一個值爲6的int對象,變量i則引用了這個新的對象,所以當變量i和變量j的值相同時會指向同個內存地址。一樣以Python的float 類型爲例也驗證了這個變量存儲管理的機制。指針

陸陸續續的試了列表、字典、字符串、元組等變量類型賦值的效果,我發現其實Python中的對象分爲可變類型和不可變類型,列表、字典是可變類型,而整數、浮點、短字符串、元組等是不可變類型。可變類型的變量賦值與咱們瞭解的C語言機制相同,而不可變類型的變量賦值時,其實是從新建立一個不可變類型的對象,並將原來的變量從新指向新建立的對象,固然若是沒有其餘變量引用原有對象時,原有對象就會被回收。這也是Python做爲動態類型語言的特色,即變量不須要預先聲明類型,當變量在賦值時解釋器會根據值的類型建立對應的內存空間進行存儲,並將變量指向這個地址空間便可,好比運行a=1時,解釋器將變量指向整形值1的地址,當運行a=0.1時,解釋器將變量指向浮點值0.1的地址。對象

可是問題又來了!!!爲何Python能夠這樣肆無忌憚地完成動態類型的特徵?blog

這裏要深究到PyObject這個結構體的層面。一般來講,不管什麼語言最終被計算機識別到的都是內存中的字節信息,對象實際上就是在更高的層次上把內存上的數據做爲一個總體來考慮,好比一個整數或是一個字符串。Python中全部的東西都是對象,它們擁有一些相同的內容,這些內容定義在PyObject這個結構體中。內存

ob_refcnt是一個整形變量,它的做用是實現引用計數機制。好比一個對象A,當有一個新的PyObject *引用該對象時,A的引用計數增長;而當這個PyObject *被刪除時,A的引用計數減小。當A的引用計數減小到0時,A就能夠從堆上被刪除,以釋放出內存供別的對象使用。ob_type是一個指向_typeobject結構體的指針,這個結構體實際上也是一個對象,它是用來指定一個對象類型的類型對象,這個類型對象記錄了不一樣的對象所需的內存空間的大小信息。那麼簡單的說,Python中對象機制的核心一個是引用計數,一個就是類型。字符串

PyObject是一個定長對象的結構體,對於可變長度對象的結構體是PyVarObject,它比PyObject結構體多一個ob_size變量,用於指定容器中包含的元素數量。好比list中有5個元素,那麼PyVarObject.ob_size的值就是5。PyVarObject實際上只是對PyObject的一個擴展而已,任何一個PyVarObject所佔用的內存,開始部分的字節定義和PyObject是同樣的。這就能夠解釋說,當Python建立一個整形對象PyIntObject,首先它會爲這個對象分配內存,並進行初始化,而後這個對象會由一個PyObject*變量來維護,由於每個對象都擁有相同的對象頭部,這使得對象的引用變得很是的統一。不管對象實際上的類型是什麼,只須要經過PyObject*指針就能夠引用任意的一個對象。容器

深刻淺出了Python變量賦值的機制之後,你們就不以爲這是難以理解的概念了吧,其實學習的樂趣就體如今恍然大悟、融會貫通的那一時刻!

相關文章
相關標籤/搜索