解密 Python 中的對象模型

Python中一切皆對象

關於 Python,你確定聽過這麼一句話:"Python中一切皆對象"。沒錯,在 Python 的世界裏,一切都是對象。html

整型是一個對象、字符串是一個對象、字典是一個對象,甚至 int、str、list 等等,再加上咱們使用 class 自定義的類,它們也是對象。python

像 int、str、list 等基本類型,以及咱們自定義的類,因爲它們能夠表示類型,所以咱們稱之爲類型對象;類型對象實例化獲得的對象,咱們稱之爲實例對象。無論是哪一種對象,它們都屬於對象。數組

所以 Python 中面向對象的理念貫徹的很是完全,面向對象中的"類"和"對象"在 Python 中都是經過"對象"實現的。閉包

在面向對象理論中,存在着"類"和"對象"兩個概念,像 int、dict、tuple、以及使用 class 關鍵字自定義的類型對象實現了面向對象理論中"類"的概念,而 12三、(1, 2, 3),"xxx" 等等這些實例對象則實現了面向對象理論中"對象"的概念。可是在 Python 中,面向對象的"類"和"對象"都是經過對象實現的。app

咱們舉個栗子:ide

>>> # int它是一個類,所以它屬於類型對象, 類型對象實例化獲得的對象屬於實例對象
>>> int  
<class 'int'>
>>> int('0123') 
123
>>>

所以能夠用一張圖來描述面向對象在 Python 中的體現:函數

解密 Python 中的對象模型

類型、對象體系

a 是一個整數(實例對象),其類型是 int (類型對象)。學習

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

可是問題來了,按照面向對象的理論來講,對象是由類實例化獲得的,這在 Python 中也是適用的。既然是對象,那麼就一定有一個類來實例化它,換句話說對象必定要有類型。至於一個對象的類型是什麼,就看這個對象是被誰實例化的,被誰實例化那麼類型就是誰。而咱們說 Python 中一切皆對象,因此像 int、str、tuple 這些內置的類型也是具備相應的類型的,那麼它們的類型又是誰呢?優化

咱們使用 type 函數查看一下就行了。設計

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

咱們看到類型對象的類型,無一例外都是 type。type 應該是初學 Python 的時候就接觸了,當時使用 type 都是爲了查看一個對象的類型,然而 type 的做用遠沒有這麼簡單,咱們後面會說,總之咱們目前看到類型對象的類型是 type。

因此 int、str 等類型對象是 type 的對象,而 type 咱們也稱其爲元類,表示類型對象的類型。至於 type 自己,它的類型仍是 type,因此它連本身都沒放過,把本身都變成本身的對象了。

所以在 Python 中,你能看到的任何對象都是有類型的,咱們可使用 type 函數查看,也能夠獲取該對象的__class__屬性查看。

因此:實例對象、類型對象、元類,Python 中任何一個對象都逃不過這三種身份。

Python 中還有一個特殊的類型(對象),叫作 object,它是全部類型對象的基類。無論是什麼類,內置的類也好,咱們自定義的類也罷,它們都繼承自 object。所以, object 是全部類型對象的"基類"、或者說"父類"。

>>> issubclass(int, object)
True
>>>

所以,綜合以上關係,咱們能夠獲得下面這張關係圖:

解密 Python 中的對象模型

咱們自定義的類型也是如此,舉個栗子:

class Female:
    pass

print(type(Female))  # <class 'type'>
print(issubclass(Female, object))  # True

在 Python3 中,自定義的類即便不顯式的繼承 object,也會默認繼承自 object。

解密 Python 中的對象模型

那麼咱們自定義再自定義一個子類,繼承自 Female 呢?

class Female:
    pass

class Girl(Female):
    pass

# 自定義類的類型都是type
print(type(Girl))  # <class 'type'>

# 但Girl繼承自Female, 因此它是Female的子類
print(issubclass(Girl, Female))  # True
# 而Female繼承自object, 因此Girl也是object的子類
print(issubclass(Girl, object))  # True

# 這裏須要額外多提一句實例對象, 咱們以前使用type獲得的都是該類的類型對象
# 換句話說誰實例化獲得的它, 那麼對它使用type獲得的就是誰
print(type(Girl()))  # <class '__main__.Girl'>
print(type(Female()))  # <class '__main__.Female'>

# 可是咱們說Girl的父類是Female, Female的父類是object
# 因此Girl的實例對象也是Female和object的實例對象, Female的實例對象也是object的實例對象
print(isinstance(Girl(), Female))  # True
print(isinstance(Girl(), object))  # True

所以上面那張關係圖就能夠變成下面這樣:

解密 Python 中的對象模型

咱們說可使用 type 和__class__查看一個對象的類型,而且還能夠經過 isinstance 來判斷該對象是否是某個已知類型的實例對象;那若是想查看一個類型對象都繼承了哪些類該怎麼作呢?咱們目前都是使用 issubclass 來判斷某個類型對象是否是另外一個已知類型對象的子類,那麼可不能夠直接獲取某個類型對象都繼承了哪些類呢?

答案是能夠的,方法有三種,咱們分別來看一下:

class A: pass

class B: pass

class C(A): pass

class D(B, C): pass

# 首先D繼承自B和C, C又繼承A, 咱們如今要來查看D繼承的父類
# 方法一: 使用__base__
print(D.__base__)  # <class '__main__.B'>

# 方法二: 使用__bases__
print(D.__bases__)  # (<class '__main__.B'>, <class '__main__.C'>)

# 方法三: 使用__mro__
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
  • __base__: 若是繼承了多個類, 那麼只顯示繼承的第一個類, 沒有顯示繼承則返回一個\<class 'object'>;

  • __bases__: 返回一個元組, 會顯示全部直接繼承的父類, 若是沒有顯示的繼承, 則返回(\<class 'object'>,);

  • __mro__: mro 表示 Method Resolution Order, 表示方法查找順序, 會從自身除法, 找到最頂層的父類, 所以返回自身、繼承的基類、以及基類繼承的基類, 一直找到 object;

最後咱們來看一下 type 和 object,估計這兩個老鐵之間的關係會讓不少人感到困惑。

咱們說 type 是全部類的元類,而 object 是全部的基類,這就說明 type 是要繼承自 object 的,而 object 的類型是 type。

>>> type.__base__
<class 'object'>
>>> object.__class__
<class 'type'>
>>>

這就怪了,這難道不是一個先有雞仍是先有蛋的問題嗎?其實不是的,這兩個對象是共存的,它們之間的定義實際上是互相依賴的。至於究竟是怎麼肥事,咱們後面在看解釋器源碼的時候就會很清晰了。

總之目前記住兩點:

  1. type 站在類型金字塔的最頂端, 任何的對象按照類型追根溯源, 最終獲得的都是 type;

  2. object 站在繼承金字塔的最頂端, 任何的類型對象按照繼承追根溯源, 最終獲得的都是 object;

咱們說 type 的類型仍是 type,可是 object 的基類則再也不是 object,而是一個 None。爲何呢?其實答案很簡單,咱們說 Python 在查找屬性或方法的時候,會回溯繼承鏈,自身若是沒有的話,就會按照__mro__指定的順序去基類中查找。因此繼承鏈必定會有一個終點,不然就會像沒有出口的遞歸同樣出現死循環了。

最後將上面那張關係圖再完善一下的話:

解密 Python 中的對象模型

所以上面這種圖纔算是完整,其實只看這張圖咱們就能解讀出不少信息。好比:實例對象的類型是類型對象,類型對象的類型是元類;全部的類型對象的基類都收斂於 object,全部對象的類型都收斂於 type。所以 Python 算是將一切皆對象的理念貫徹到了極致,也正由於如此,Python 才具備如此優秀的動態特性。

事實上,目前介紹的有些基礎了,但 Python 中的對象的概念確實很是重要。爲了後面再分析源碼的時候可以更輕鬆,所以咱們有必要系統地回顧一下,而且上面的關係圖會使咱們在後面的學習變得輕鬆。由於等到看解釋器的時候,咱們可就沒完了,就不那麼輕鬆了(なん~~~てね)。

Python中的變量只是個名字

Python 中的變量只是個名字,站在 C 語言的角度來講的話,Python 中的變量存儲的只是對象的內存地址,或者說指針,這個指針指向的內存存儲的纔是對象。

因此在 Python 中,咱們都說變量指向了某個對象。在其它靜態語言中,變量至關因而爲某塊內存起的別名,獲取變量等於獲取這塊內存所存儲的值。而 Python 中變量表明的內存存儲的不是對象,只是對象的指針。

咱們用兩段代碼,一段 C 語言的代碼,一段 Python 的代碼,來看一下差異。

#include <stdio.h>

void main()
{
    int a = 123;
    printf("address of a = %p\n", &a);

    a = 456
    printf("address of a = %p\n", &a);
}
// 輸出結果
/*
address of a = 0x7fffa94de03c
address of a = 0x7fffa94de03c
*/

咱們看到先後輸出的地址是同樣的,再來看看 Python 的。

a = 666
print(hex(id(a)))  # 0x1b1333394f0

a = 667
print(hex(id(a)))  # 0x1b133339510

然而咱們看到 Python 中變量 a 的地址先後發生了變化,咱們分析一下緣由。

首先在 C 中,建立一個變量的時候必須規定好類型,好比 int a = 666,那麼變量 a 就是 int 類型,之後在所處的做用域中就不能夠變了。若是這時候,再設置 a = 777,那麼等因而把內存中存儲的 666 換成 777,a 的地址和類型是不會變化的。

而在 Python 中,a = 666 等因而先開闢一塊內存,存儲的值爲 666,而後讓變量 a 指向這片內存,或者說讓變量 a 存儲這塊內存的指針。而後 a = 777 的時候,再開闢一塊內存,而後讓 a 指向存儲 777 的內存,因爲是兩塊不一樣的內存,因此它們的地址是不同的。

解密 Python 中的對象模型

因此 Python 中的變量只是一個和對象關聯的名字罷了,它表明的是對象的指針。換句話說 Python 中的變量就是個便利貼,能夠貼在任何對象上,一旦貼上去了,就表明這個對象被引用了。

咱們再來看看變量之間的傳遞,在 Python 中是如何體現的。

a = 666
print(hex(id(a)))  # 0x1e6c51e3cf0

b = a
print(hex(id(b)))  # 0x1e6c51e3cf0

咱們看到打印的地址是同樣的,咱們再用一張圖解釋一下。

解密 Python 中的對象模型

咱們說 a = 666 的時候,先開闢一分內存,再讓 a 存儲對應內存的指針;而後 b = a 的時候,會把 a 的地址拷貝一份給 b,因此 b 存儲了和 a 相同的地址,它們都指向了同一個對象。

所以說 Python 是值傳遞、或者引用傳遞都是不許確的,準確的說 Python 是變量之間的賦值傳遞,對象之間的引用傳遞。

由於 Python 中的變量本質上就是一個指針,因此在 b = a 的時候,等於把a的地址拷貝一份給b,因此對於變量來講是賦值傳遞;而後 a 和 b 又都是指向對象的指針,所以對於對象來講是引用傳遞。

另外還有最關鍵的一點,咱們說 Python 中的變量是一個指針,當傳遞一個變量的時候,傳遞的是指針;可是在操做一個變量的時候,會操做變量指向的內存。

因此 id(a) 獲取的不是 a 的地址,而是 a 指向的內存的地址(在底層其實就是a),同理 b = a,是將 a 自己,或者說將 a 存儲的、指向某個具體的對象的地址傳遞給了 b。

另外在 C 的層面上,a 和 b 屬於指針變量,那麼 a 和 b 有沒有地址呢?顯然是有的,只不過在 Python 中你是看不到的,Python 解釋器只容許你看到對象的地址。

最後提一下變量的類型

咱們說變量的類型其實不是很準確,應該是變量指向(引用)的對象的類型,由於咱們說 Python 中變量是個指針,操做指針會操做指針指向的內存,因此咱們使用 type(a) 查看的是變量 a 指向的內存的類型,固然爲了方便也會直接說變量的類型,理解就行。那麼問題來了,咱們在建立一個變量的時候,並無顯示的指定類型啊,但 Python 顯然是有類型的,那麼 Python 是如何判斷一個變量指向的是什麼類型的數據呢?

答案是:解釋器是經過靠猜的方式,經過你賦的值(或者說變量引用的值)來推斷類型。因此在 Python 中,若是你想建立一個變量,那麼必須在建立變量的時候同時賦值,不然解釋器就不知道這個變量指向的數據是什麼類型。因此 Python 是先建立相應的值,這個值在 C 中對應一個結構體,結構體裏面有一個成員專門用來存儲該值對應的類型。當建立完值以後,再讓這個變量指向它,因此 Python 中是先有值後有變量。但顯然 C 中不是這樣的,由於 C 中變量表明的內存所存儲的就是具體的值,因此 C 中能夠直接聲明一個變量的同時不賦值。由於 C 要求聲明變量的同時必須指定類型,因此聲明變量的同時,其類型和內存大小就已經固定了。而 Python 中變量表明的內存是個指針,它只是指向了某個對象,因此因爲其便利貼的特性,能夠貼在任意對象上面,可是無論貼在哪一個對象,你都必須先有對象才能夠,否則變量貼誰去?

另外,儘管 Python 在建立變量的時候不須要指定類型,但 Python 是強類型語言,強類型語言,強類型語言,重要的事情說三遍。 並且是動態強類型,由於類型的強弱和是否須要顯示聲明類型之間沒有關係。

可變對象與不可變對象

咱們說一個對象其實就是一片被分配的內存空間,內存中存儲了相應的值,不過這些空間能夠是連續的,也能夠是不連續的。

不可變對象一旦建立,其內存中存儲的值就不能夠再修改了。若是想修改,只能建立一個新的對象,而後讓變量指向新的對象,因此先後的地址會發生改變。而可變對象在建立以後,其存儲的值能夠動態修改。

像整型就是一個不可變對象。

>>> a = 666
>>> id(a)
1365442984464
>>> a += 1
>>> id(a)
1365444032848
>>>

咱們看到在對 a 執行+1操做時,先後地址發生了變化,因此整型不支持本地修改,所以是一個不可變對象;

解密 Python 中的對象模型

原來a = 666,而咱們說操做一個變量等於操做這個變量指向的內存,因此a+=1,會將a指向的整型對象666和1進行加法運算,獲得667。因此會開闢新的空間來存儲這個667,而後讓a指向這片新的空間,至於原來的666所佔的空間怎麼辦,Python 解釋器會看它的引用計數,若是不爲0表明還有變量引用(指向)它,若是爲0證實沒有變量引用了,因此會被回收。

關於引用計數,咱們後面會詳細說,目前只須要知道當一個對象被一個變量引用的時候,那麼該對象的引用計數就會加1。有幾個變量引用,那麼它的引用計數就是幾。

可能有人以爲,每次都要建立新對象,銷燬舊對象,效率確定會很低吧。事實上確實如此,可是後面咱們會從源碼的角度上來看 Python 如何經過小整數對象池等手段進行優化。

而列表是一個可變對象,它是能夠修改的。

這裏先多提一句,Python中的對象本質上就是C中malloc函數爲結構體實例在堆區申請的一塊內存。Python中的任何對象在C中都會對應一個結構體,這個結構體除了存放具體的值以外,還存放了一些額外的信息,這個咱們在剖析Python中的內置類型的實例對象的時候會細說。

首先Python中列表,固然不光是列表,還有元組、集合,這些容器它們的內部存儲的也不是具體的對象,而是對象的指針。好比:lst = [1, 2, 3],你覺得lst存儲的是三個整型對象嗎?其實不是的,lst存儲的是三個整型對象的指針,當咱們使用lst[0]的時候,拿到的是第一個元素的指針,可是操做(好比print)的時候會自動操做(print)指針指向的內存。

不知道你是否思考過,Python底層是C來實現的,因此Python中的列表的實現必然要藉助C中的數組。可咱們知道C中的數組裏面的全部元素的類型必須一致,但列表卻能夠存聽任意的元素,所以從這個角度來說,列表裏面的元素它就就不多是對象,由於不一樣的對象在底層對應的結構體是不一樣的,因此這個元素只能是指針。

可能有人又好奇了,不一樣對象的指針也是不一樣的啊,是的,但C中的指針是能夠轉化的。Python底層將全部對象的指針,都轉成了 PyObject 的指針,這樣不就是同一種類型的指針了嗎?關於這個PyObject,它是咱們後面要剖析的重中之重,這個PyObject貫穿了咱們的整個系列。目前只須要知道Python中的列表存儲的值,在底層是經過一個 PyObject * 類型的數據來維護的。

>>> lst = [1, 2, 3]
>>> id(lst)
1365442893952
>>> lst.append(4)
>>> lst
[1, 2, 3, 4]
>>> id(lst)
1365442893952
>>>

咱們看到列表在添加元素的時候,先後地址並無改變。列表在C中是經過PyListObject實現的,咱們在介紹列表的時候會細說。這個PyListObject內部除了一些基本信息以外,還有一個成員叫ob_item,它是一個PyObject的二級指針,指向了咱們剛纔說的 PyObject * 類型的數組的首個元素的地址。

結構圖以下:

解密 Python 中的對象模型

顯然圖中的指針數組是用來存儲具體的對象的指針的,每個指針都指向了相應的對象(這裏是整型對象)。可能有人注意到,整型對象的順序有點怪,其實我是故意這麼畫的。由於 PyObject * 數組內部的元素是連續且有順序的,可是指向的整型對象則是存儲在堆區的,它們的位置是任意性的。可是無論這些整型對象存儲在堆區的什麼位置,它們和數組中的指針都是一一對應的,咱們經過索引是能夠正確獲取到指向的對象的。

另外咱們還能夠看到一個現象,那就是Python中的列表在底層是分開存儲的,由於PyListObject結構體實例並無存儲相應的指針數組,而是存儲了指向這個指針數組的二級指針。顯然咱們添加、刪除、修改元素等操做,都是經過這個二級指針來間接操做這個指針數組。

爲何要這麼作?

由於在 Python 中一個對象一旦被建立,那麼它在內存中的大小就不能夠變了。因此這就意味着那些能夠容納可變長度數據的可變對象,要在內部維護一個指向可變大小的內存區域的指針。而咱們看到 PyListObject 正是這麼作的,指針數組的長度、內存大小是可變的,因此 PyListObject 內部並無直接存儲它,而是存儲了指向它的二級指針。可是 Python 在計算內存大小的時候是會將這個指針數組也算進去的,因此 Python 中列表的大小是可變的,可是底層對應的 PyListObject 實例的大小是不變的,由於可變長度的指針數組沒有存在 PyListObject 裏面。但爲何要這麼設計呢?

這麼作的緣由就在於,遵循這樣的規則可使經過指針維護對象的工做變得很是簡單。一旦容許對象的大小可在運行期改變,那麼咱們就能夠考慮以下場景。在內存中有對象A,而且其後面緊跟着對象B。若是運行的某個時候,A的大小增大了,這就意味着必須將A整個移動到內存中的其餘位置,不然A增大的部分會覆蓋掉本來屬於B的數據。只要將A移動到內存的其餘位置,那麼全部指向A的指針就必須當即獲得更新。可想而知這樣的工做是多麼的繁瑣,而經過一個指針去操做就變得簡單多了。

定長對象與變長對象

Python 中一個對象佔用的內存有多大呢?相同類型的實例對象的大小是否相同呢?試一下就知道了,咱們能夠經過 sys 模塊中 getsizeof 函數查看一個對象所佔的內存。

import sys

print(sys.getsizeof(0))  # 24
print(sys.getsizeof(1))  # 28
print(sys.getsizeof(2 << 33))  # 32

print(sys.getsizeof(0.))  # 24
print(sys.getsizeof(3.14))  # 24
print(sys.getsizeof((2 << 33) + 3.14))  # 24

咱們看到整型對象的大小不一樣,所佔的內存也不一樣,像這種內存大小不固定的對象,咱們稱之爲變長對象;而浮點數所佔的內存都是同樣的,像這種內存大小固定的對象,咱們稱之爲定長對象。

至於 Python 是如何計算對象所佔的內存,咱們在剖析具體對象的時候會說,由於這要涉及到底層對應的結構體。

並且咱們知道 Python 中的整數是不會溢出的,而C中的整型顯然是有最大範圍的,那麼Python是如何作到的呢?答案是Python在底層是經過C的32位整型數組來存儲自身的整型對象的,經過多個32位整型組合起來,以支持存儲更大的數值,因此整型越大,就須要越多的32位整數。而32位整數是4字節,因此咱們上面代碼中的那些整型,都是4字節、4字節的增加。

固然Python中的對象在底層都是一個結構體,這個結構體中除了維護具體的值以外,還有其它的成員信息,在計算內存大小的時候,它們也是要考慮在內的,固然這些咱們後面會說。

而浮點數的大小是不變的,由於Python的浮點數的值在C中是經過一個double來維護的。而C中的值的類型一旦肯定,大小就不變了,因此Python的float也是不變的。

可是既然是固定的類型,確定範圍是有限的,因此當浮點數不斷增大,會犧牲精度來進行存儲。若是實在過大,那麼會拋出OverFlowError。

>>> int(1000000000000000000000000000000000.)  # 犧牲了精度
999999999999999945575230987042816
>>> 10 ** 1000  # 不會溢出
1000000000000000......
>>>
>>> 10. ** 1000  # 報錯了
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: (34, 'Result too large')
>>>

還有字符串,字符串毫無疑問確定是可變對象,由於長度不一樣大小不一樣。

import sys

print(sys.getsizeof("a"))  # 50
print(sys.getsizeof("abc"))  # 52

咱們看到多了兩個字符,多了兩個字節,這很好理解。可是這些說明了一個空字符串要佔49個字節,咱們來看一下。

import sys

print(sys.getsizeof(""))  # 49

顯然是的,顯然這 49 個字節是用來維護其它成員信息的,由於底層的結構體除了維護具體的值以外,還要維護其它的信息,好比:引用計數等等,這些在分析源碼的時候會詳細說。

小結

咱們這一節介紹了 Python 中的對象體系,咱們說 Python 中一切皆對象,類型對象和實例對象都屬於對象;還說了對象的種類,根據是否支持本地修改能夠分爲可變對象和不可變對象,根據佔用的內存是否不變能夠分爲定長對象和變長對象;還說了 Python 中變量的本質,Python 中的變量本質上是一個指針,而變量的名字則存儲在對應的名字空間(或者說命名空間)中,固然名字空間咱們沒有說,是由於這些在後續系列會詳細說(又是後續, 無論咋樣, 坑先挖出來),不過這裏能夠先補充一下。

名字空間分爲:全局名字空間(存儲全局變量)、局部名字空間(存儲局部變量)、閉包名字空間(存儲閉包變量)、內建名字空間(存儲內置變量, 好比 int、str, 它們都在這裏),而名字空間又分爲靜態名字空間和動態名字空間:好比局部名字空間,由於函數中的局部變量在編譯的時候就能夠肯定,因此函數對應的局部名字空間使用一個數組存儲;而全局變量在運行時能夠進行動態添加、刪除,所以全局名字空間使用的是一個字典來保存,字典的 key 就是變量的名字(依舊是個指針,底層是指向字符串(PyUnicodeObject)的指針),字典的 value 就是變量指向的對象的指針(或者說變量自己)。

a = 123
b = "xxx"

# 經過globals()便可獲取全局名字空間
print(globals())  #{..., 'a': 123, 'b': 'xxx'}

# 咱們看到雖然顯示的是變量名和變量指向的值
# 可是在底層,字典存儲的鍵值對也是指向具體對象的指針
# 只不過咱們說操做指針會操做指向的內存,因此這裏print打印以後,顯示的也是具體的值,可是存儲的是指針
# 至於對象自己,則存儲在堆區,而且被指針指向

#  此外,咱們往全局名字空間中設置一個鍵值對,也等價於建立了一個全局變量
globals()["c"] = "hello"
print(c)  # hello

# 此外這個全局名字空間是惟一的,即便你把它放在函數中也是同樣
def foo():
    globals()["d"] = "古明地覺"

# foo一旦執行,{"d": "古明地覺"}就設置進了全局名字空間中
foo()  
print(d)  # 古明地覺

怎麼樣,是否是有點神奇呢?因此名字空間是 Python 做用域的靈魂,它嚴格限制了變量的活動範圍,固然這些後面都會慢慢的說,由於飯要一口一口吃。所以這一節算是回顧基礎吧,雖然說是基礎可是其實也涉及到了一些解釋器的知識,不過這一關咱們早晚是要過的,因此就提早接觸一下吧。

原文連接:https://www.cnblogs.com/traditional/p/13391098.html做者:古明地盆文章轉自:Python開發者

相關文章
相關標籤/搜索