【Python源碼剖析】對象模型概述

目錄

  • 一切皆對象
  • 類型、對象體系
  • 變量只是名字
  • 可變對象 與 不可變對象
  • 定長對象 與 變長對象
  • 更多章節
  • 附錄

Python 是一門 面向對象 語言,實現了一個完整的面向對象體系,簡潔而優雅。

與其他面向對象編程語言相比, Python 有自己獨特的一面。 這讓很多開發人員在學習 Python 時,多少有些無所適從。 那麼,Python 對象模型都有哪些特色呢?

一切皆對象

首先,在 Python 世界, 基本類型也是對象 ,與通常意義的“對象”形成一個有機統一。 換句話講, Python 不再區別對待基本類型和對象,所有基本類型內部均由對象實現。 一個整數是一個對象,一個字符串也是一個對象:

>>> a = 1
>>> b = 'abc'

其次, Python 中的 類型也是一種對象 ,稱為 類型對象 。 整數類型是一個對象,字符串類型是一個對象,程序中通過 class 關鍵字定義的類也是一個對象。

舉個例子,整數類型在 Python 內部是一個對象,稱為 類型對象 :

>>> int
<class 'int'>

通過整數類型 實例化 可以得到一個整數對象,稱為 實例對象 :

>>> int('1024')
1024

面向對象理論中的“  ”和“ 對象 ”這兩個基本概念,在 Python 內部都是通過對象實現的,這是 Python 最大的特點。

類型、對象體系

a 是一個整數對象( 實例對象 ),其類型是整數類型( 類型對象 ):

>>> a = 1
>>> type(a)
<class 'int'>
>>> isinstance(a, int)
True

那麼整數類型的類型又是什麼呢?

>>> type(int)
<class 'type'>

可以看到,整數類型的類型還是一種類型,即 類型的類型 。 只是這個類型比較特殊,它的實例對象還是類型對象。

Python 中還有一個特殊類型 object ,所有其他類型均繼承於 object ,換句話講 object 是所有類型的基類:

>>> issubclass(int, object)
True

綜合以上關係,得到以下關係圖:

內置類型已經搞清楚了,自定義類型及對象關係又如何呢?定義一個簡單的類來實驗:

class Dog(object):

    def yelp(self):
        print('woof')

創建一個 Dog 實例,毫無疑問,其類型是 Dog :

>>> dog = Dog()
>>> dog.yelp()
woof
>>> type(dog)
<class '__main__.Dog'>

Dog 類的類型自然也是 type ,其基類是 object (就算不顯式繼承也是如此):

>>> type(Dog)
<class 'type'>
>>> issubclass(Dog, object)
True

自定義子類及實例對象在圖中又處於什麼位置?定義一個獵犬類進行實驗:

class Sleuth(Dog):

    def hunt(self):
        pass

可以看到, 獵犬對象( sleuth )是獵犬類( Sleuth )的實例, Sleuth 的類型同樣是 type :

>>> sleuth = Sleuth()
>>> sleuth.hunt()
>>> type(sleuth)
<class '__main__.Sleuth'>
>>> type(Sleuth)
<class 'type'>

同時, Sleuth 類繼承自 Dog 類,是 Dog 的子類,當然也是 object 的子類:

>>> issubclass(Sleuth, Dog)
True
>>> issubclass(Sleuth, object)
True

現在不可避免需要討論 type 以及 object 這兩個特殊的類型。

理論上, object 是所有類型的 基類 ,本質上是一種類型,因此其類型必然是 type 。 而 type 是所有類型的類型,本質上也是一種類型,因此其類型必須是它自己!

>>> type(object)
<class 'type'>
>>> type(object) is type
True

>>> type(type)
<class 'type'>
>>> type(type) is type
True

另外,由於 object 是所有類型的 基類 ,理論上也是 type 的基類( __base__ 屬性):

>>> issubclass(type, object)
True
>>> type.__base__
<class 'object'>

但是 object 自身便不能有基類了。為什麼呢? 對於存在繼承關係的類,成員屬性和成員方法查找需要回溯繼承鏈,不斷查找基類。 因此,繼承鏈必須有一個終點,不然就死循環了。

這就完整了!

可以看到,所有類型的基類收斂於 object ,所有類型的類型都是 type ,包括它自己! 這就是 Python 類型、對象體系全圖,設計簡潔、優雅、嚴謹。

該圖將成為後續閱讀源碼、探索 Python 對象模型的有力工具,像地圖一樣指明方向。 圖中所有實體在 Python 內部均以對象形式存在,至於對象到底長啥樣,相互關係如何描述,這些問題先按下不表,後續一起到源碼中探尋答案。

變量只是名字

先看一個例子,定義一個變量 a ,並通過 id 內建函數取出其“地址”:

>>> a = 1
>>> id(a)
4302704784

定義另一個變量 b ,以 a 賦值,並取出 b 的“地址”:

>>> b = a
>>> id(b)
4302704784

驚奇地看到, a 和 b 這兩個變量的地址居然是相同的!這不合常理呀!

對於大多數語言( C 語言為例),定義變量 a 即為其分配內存並存儲變量值:

變量 b 內存空間與 a 獨立,賦值時進行拷貝:

在 Python 中,一切皆對象,整數也是如此, 變量只是一個與對象關聯的名字 :

而變量賦值,只是將當前對象與另一個名字進行關聯,背後的對象是同一個:

因此,在 Python 內部,變量只是一個名字,保存指向實際對象的指針,進而與其綁定。 變量賦值只拷貝指針,並不拷貝指針背後的對象。

可變對象 與 不可變對象

定義一個整數變量:

>>> a = 1
>>> id(a)
4302704784

然後,對其自增 1 :

>>> a += 1
>>> a
2
>>> id(a)
4302704816

數值符合預期,但是對象變了!初學者一臉懵逼,這是什麼鬼?

一切要從 可變對象 和 不可變對象 說起。 可變對象 在對象創建后,其值可以進行修改; 而 不可變對象 在對象創建后的整個生命周期,其值都不可修改。

在 Python 中,整數類型是不可變類型, 整數對象是不可變對象。 修改整數對象時, Python 將以新數值創建一個新對象,變量名與新對象進行綁定; 舊對象如無其他引用,將被釋放。

每次修改整數對象都要創建新對象、回收舊對象,效率不是很低嗎? 確實是。 後續章節將從源碼角度來解答: Python 如何通過 小整數池 等手段進行優化。

可變對象是指創建后可以修改的對象,典型的例子是 列表 ( list ):

>>> l = [1, 2]
>>> l
[1, 2]
>>> id(l)
4385900424

往列表裡頭追加數據,發現列表對象還是原來那個,只不過多了一個元素了:

>>> l.append(3)
>>> l
[1, 2, 3]
>>> id(l)
4385900424

實際上,列表對象內部維護了一個 動態數組 ,存儲元素對象的指針:

列表對象增減元素,需要修改該數組。例如,追加元素 3 :

定長對象 與 變長對象

Python 一個對象多大呢?相同類型對象大小是否相同呢? 想回答類似的問題,需要考察影響對象大小的因素。

標準庫 sys 模塊提供了一個查看對象大小的函數 getsizeof :

>>> import sys
>>> sys.getsizeof(1)
28

先觀察整數對象:

>>> sys.getsizeof(1)
28
>>> sys.getsizeof(100000000000000000)
32
>>> sys.getsizeof(100000000000000000000000000000000000000000000)
44

可見整數對象的大小跟其數值有關,像這樣 大小不固定 的對象稱為 變長對象 。

我們知道,位數固定的整數能夠表示的數值範圍是有限的,可能導致 溢出 。 Python 為解決這個問題,採用類似 C++ 中 大整數類 的思路實現整數對象 —— 串聯多個普通 32 位整數,以便支持更大的數值範圍。 至於需要多少個 32 位整數,則視具體數值而定,數值不大的一個足矣,避免浪費。

這樣一來,整數對象需要在頭部額外存儲一些信息,記錄對象用了多少個 32 位整數。 這就是變長對象典型的結構,先有個大概印象即可,後續講解整數對象源碼時再展開。

接着觀察字符串對象:

>>> sys.getsizeof('a')
50
>>> sys.getsizeof('abc')
52

字符串對象也是變長對象,這個行為非常好理解,畢竟字符串長度不盡相同嘛。 此外,注意到字符串對象大小比字符串本身大,因為對象同樣需要維護一些額外的信息。 至於具體需要維護哪些信息,同樣留到源碼剖析環節中詳細介紹。

那麼,有啥對象是定長的呢?—— 浮點數對象 float :

>>> sys.getsizeof(1.)
24
>>> sys.getsizeof(1000000000000000000000000000000000.)
24

浮點數背後是由一個 double 實現,就算表示很大的數,浮點數對象的大小也不變。

為啥 64 位的 double 可以表示這麼大的範圍呢?答案是:犧牲了精度。

>>> int(1000000000000000000000000000000000.)
999999999999999945575230987042816

由於浮點數存儲位數是固定的,它能表示的數值範圍也是有限的,超出便會拋錨:

>>> 10. ** 1000
Traceback (most recent call last):
	File "<stdin>", line 1, in <module>
OverflowError: (34, 'Result too large')

更多章節

洞悉 Python 虛擬機運行機制,探索高效程序設計之道!

到底如何才能提升我的 Python 開發水平,向更高一級的崗位邁進? 如果你有這些問題或者疑惑,請訂閱我們的專欄,閱讀更多章節:

  • 內建對象
  • 虛擬機
  • 函數機制
  • 類機制
  • 生成器與協程
  • 內存管理機制

附錄

更多 Python 技術文章請訪問:小菜學Python,轉至 原文 可獲得最佳閱讀體驗。

訂閱更新,獲取更多學習資料,請關注 小菜學編程 :

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

您可能也會喜歡…