在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Python/ 基本環(huán)境
附錄
進(jìn)程通信
操作系統(tǒng)
迭代器
模塊
描述符
裝飾器
第三部分 擴(kuò)展庫
內(nèi)置類型
數(shù)據(jù)存儲(chǔ)
數(shù)據(jù)類型
基本環(huán)境
文件與目錄
異常
程序框架
數(shù)學(xué)運(yùn)算
函數(shù)
元類
字符串
表達(dá)式

基本環(huán)境

虛擬機(jī)

Python 是一種半編譯半解釋型運(yùn)行環(huán)境。首先,它會(huì)在模塊 "載入" 時(shí)將源碼編譯成字節(jié)碼 (Byte Code)。而后,這些字節(jié)碼會(huì)被虛擬機(jī)在一個(gè) "巨大" 的核心函數(shù)里解釋執(zhí)行。這是導(dǎo)致 Python 性能較低的重要原因,好在現(xiàn)在有了內(nèi)置 Just-in-time 二次編譯器的 PyPy 可供選擇。

當(dāng)虛擬機(jī)開始運(yùn)行時(shí),它通過初始化函數(shù)完成整個(gè)運(yùn)行環(huán)境設(shè)置:

  • 創(chuàng)建解釋器和主線程狀態(tài)對(duì)象,這是整個(gè)進(jìn)程的根對(duì)象。
  • 初始化內(nèi)置類型。數(shù)字、列表等類型都有專門的緩存策略需要處理。
  • 創(chuàng)建 builtin 模塊,該模塊持有所有內(nèi)置類型和函數(shù)。
  • 創(chuàng)建 sys 模塊,其中包含了 sys.path、modules 等重要的運(yùn)行期信息。
  • 初始化 import 機(jī)制。
  • 初始化內(nèi)置 Exception。
  • 創(chuàng)建 main 模塊,準(zhǔn)備運(yùn)行所需的名字空間。
  • 通過 site.py 將 site-packages 中的第三方擴(kuò)展庫添加到搜索路徑列表。
  • 執(zhí)行入口 py 文件。執(zhí)行前會(huì)將 main.dict 作為名字空間傳遞進(jìn)去。
  • 程序執(zhí)行結(jié)束。
  • 執(zhí)行清理操作,包括調(diào)用退出函數(shù),GC 清理現(xiàn)場(chǎng),釋放所有模塊等。
  • 終止進(jìn)程。

Python 源碼是個(gè)寶庫,其中有大量的編程范式和技巧可供借鑒,尤其是對(duì)內(nèi)存的管理分配。個(gè)人建議有 C 基礎(chǔ)的兄弟,在閑暇時(shí)翻看一二。

類型和對(duì)象

先有類型 (Type),而后才能生成實(shí)例 (Instance)。Python 中的一切都是對(duì)象,包括類型在內(nèi)的每個(gè)對(duì)象都包含一個(gè)標(biāo)準(zhǔn)頭,通過頭部信息就可以明確知道其具體類型。

頭信息由 "引用計(jì)數(shù)" 和 "類型指針" 組成,前者在對(duì)象被引用時(shí)增加,超出作用域或手工釋放后減小,等于 0 時(shí)會(huì)被虛擬機(jī)回收 (某些被緩存的對(duì)象計(jì)數(shù)器永遠(yuǎn)不會(huì)為 0)。

以 int 為例,對(duì)應(yīng) Python 結(jié)構(gòu)定義是:

#define PyObject_HEAD \
    Py_ssize_t ob_refcnt; \
    struct _typeobject *ob_type;

typedef struct _object {
    PyObject_HEAD
} PyObject;

typedef struct {
    PyObject_HEAD  // 在 64 位版本中,頭長(zhǎng)度為 16 字節(jié)。
    long ob_ival;  // long 是 8 字節(jié)。
} PyIntObject;

可以用 sys 中的函數(shù)測(cè)試一下。

>>> import sys

>>> x = 0x1234  # 不要使用 [-5, 257) 之間的小數(shù)字,它們有專門的緩存機(jī)制。

>>> sys.getsizeof(x) # 符合長(zhǎng)度預(yù)期。
24

>>> sys.getrefcount(x) # sys.getrefcount() 讀取頭部引用計(jì)數(shù),注意形參也會(huì)增加一次引用。
2

>>> y = x   # 引用計(jì)數(shù)增加。
>>> sys.getrefcount(x)
3

>>> del y   # 引用計(jì)數(shù)減小。
>>> sys.getrefcount(x)
2

類型指針則指向具體的類型對(duì)象,其中包含了繼承關(guān)系、靜態(tài)成員等信息。所有的內(nèi)置類型對(duì)象都能從 types 模塊中找到,至于 int、long、str 這些關(guān)鍵字可以看做是簡(jiǎn)短別名。

>>> import types

>>> x = 20

>>> type(x) is types.IntType  # is 通過指針判斷是否指向同一對(duì)象。
True

>>> x.__class__    # __class__ 通過類型指針來獲取類型對(duì)象。
<type 'int'>

>>> x.__class__ is type(x) is int is types.IntType
True

>>> y = x

>>> hex(id(x)), hex(id(y))  # id() 返回對(duì)象標(biāo)識(shí),其實(shí)就是內(nèi)存地址。
('0x7fc5204103c0', '0x7fc5204103c0')
>>> hex(id(int)), hex(id(types.IntType))
('0x1088cebd8', '0x1088cebd8')

除了 int 這樣的固定長(zhǎng)度類型外,還有 long、str 這類變長(zhǎng)對(duì)象。其頭部多出一個(gè)記錄元素項(xiàng)數(shù)量的字段。比如 str 的字節(jié)數(shù)量,list 列表的長(zhǎng)度等等。

#define PyObject_VAR_HEAD \
    PyObject_HEAD \
    Py_ssize_t ob_size;  /* Number of items in variable part */

typedef struct {
    PyObject_VAR_HEAD
} PyVarObject;

有關(guān)類型和對(duì)象更多的信息,將在后續(xù)章節(jié)中詳述。

名字空間

名字空間是 Python 最核心的內(nèi)容。

>>> x
NameError: name 'x' is not defined

我們習(xí)慣于將 x 稱為變量,但在這里,更準(zhǔn)確的詞語是 "名字"。

和 C 變量名是內(nèi)存地址別名不同,Python 的名字實(shí)際上是一個(gè)字符串對(duì)象,它和所指向的目標(biāo)對(duì)象一起在名字空間中構(gòu)成一項(xiàng) {name: object} 關(guān)聯(lián)。

Python 有多種名字空間,比如稱為 globals 的模塊名字空間,稱為 locals 的函數(shù)堆棧幀名字空間,還有 class、instance 名字空間。不同的名字空間決定了對(duì)象的作用域和生存周期。

>>> x = 123

>>> globals()   # 獲取 module 名字空間。
{'x': 123, ......}

可以看出,名字空間就是一個(gè)字典 (dict)。我們完全可以直接在名字空間添加項(xiàng)來創(chuàng)建名字。

>>> globals()["y"] = "Hello, World"

>>> y
'Hello, World'

在 Python 源碼中,有這樣一句話:Names have no type, but objects do.

名字的作用僅僅是在某個(gè)時(shí)刻與名字空間中的某個(gè)對(duì)象進(jìn)行關(guān)聯(lián)。其本身不包含目標(biāo)對(duì)象的任何信息,只有通過對(duì)象頭部的類型指針才能獲知其具體類型,進(jìn)而查找其相關(guān)成員數(shù)據(jù)。正因?yàn)槊值娜躅愋吞卣鳎覀兛梢栽谶\(yùn)行期隨時(shí)將其關(guān)聯(lián)到任何類型對(duì)象。

>>> y
'Hello, World'

>>> type(y)
<type 'str'>

>>> y = __import__("string") # 將原本與字符串關(guān)聯(lián)的名字指向模塊對(duì)象。

>>> type(y)
<type 'module'>

>>> y.digits   # 查看模塊對(duì)象的成員。
'0123456789'

在函數(shù)外部,locals() 和 globals() 作用完全相同。而當(dāng)在函數(shù)內(nèi)部調(diào)用時(shí),locals() 則是獲取當(dāng)前函數(shù)堆棧幀的名字空間,其中存儲(chǔ)的是函數(shù)參數(shù)、局部變量等信息。

>>> import sys

>>> globals() is locals()
True

>>> locals()
{
    '__builtins__': <module '__builtin__' (built-in)>,
    '__name__': '__main__',
    'sys': <module 'sys' (built-in)>,
}

>>> def test(x):     # 請(qǐng)對(duì)比下面的輸出內(nèi)容。
... y = x + 100
... print locals()    # 可以看到 locals 名字空間中包含當(dāng)前局部變量。
... print globals() is locals()  # 此時(shí) locals 和 globals 指向不同名字空間。

... frame = sys._getframe(0)   # _getframe(0) 獲取當(dāng)前堆棧幀。
... print locals() is frame.f_locals # locals 名字空間實(shí)際就是當(dāng)前堆棧幀的名字空間。
... print globals() is frame.f_globals # 通過 frame 我們也可以函數(shù)定義模塊的名字空間。

>>> test(123)
{'y': 223, 'x': 123}
False
True
True

在函數(shù)中調(diào)用 globals() 時(shí),總是獲取包含該函數(shù)定義的模塊名字空間,而非調(diào)用處。

>>> pycat test.py

a = 1
def test():
    print {k:v for k, v in globals().items() if k = "__builtins__"}

>>> import test

>>> test.test()
{
    '__file__': 'test.pyc',
    '__name__': 'test',
    'a': 1,
     'test': <function test at 0x10bd85e60>,
}

可通過 .dict 訪問其他模塊的名字空間。

>>> test.__dict__      # test 模塊的名字空間
{
    '__file__': 'test.pyc',
    '__name__': 'test',
    'a': 1,
    'test': <function test at 0x10bd85e60>,
}

>>> import sys

>>> sys.modules[__name__].__dict__ is globals() # 當(dāng)前模塊名字空間和 globals 相同。
True

與名字空間有關(guān)的內(nèi)容很多,比如作用域、LEGB 查找規(guī)則、成員查找規(guī)則等等。所有這些,都將在相關(guān)章節(jié)中給出詳細(xì)說明。

使用名字空間管理上下文對(duì)象,帶來無與倫比的靈活性,但也犧牲了執(zhí)行性能。畢竟從字典中查找對(duì)象遠(yuǎn)比指針低效很多,各有得失。

內(nèi)存管理

為提升執(zhí)行性能,Python 在內(nèi)存管理上做了大量工作。最直接的做法就是用內(nèi)存池來減少操作系統(tǒng)內(nèi)存分配和回收操作,那些小于等于 256 字節(jié)對(duì)象,將直接從內(nèi)存池中獲取存儲(chǔ)空間。

根據(jù)需要,虛擬機(jī)每次從操作系統(tǒng)申請(qǐng)一塊 256KB,取名為 arena 的大塊內(nèi)存。并按系統(tǒng)頁大小,劃分成多個(gè) pool。每個(gè) pool 繼續(xù)分割成 n 個(gè)大小相同的 block,這是內(nèi)存池最小存儲(chǔ)單位。

block 大小是 8 的倍數(shù),也就是說存儲(chǔ) 13 字節(jié)大小的對(duì)象,需要找 block 大小為 16 的 pool 獲取空閑塊。所有這些都用頭信息和鏈表管理起來,以便快速查找空閑區(qū)域進(jìn)行分配。

大于 256 字節(jié)的對(duì)象,直接用 malloc 在堆上分配內(nèi)存。程序運(yùn)行中的絕大多數(shù)對(duì)象都小于這個(gè)閾值,因此內(nèi)存池策略可有效提升性能。

當(dāng)所有 arena 的總?cè)萘砍鱿拗?(64MB) 時(shí),就不再請(qǐng)求新的 arena 內(nèi)存。而是如同 "大對(duì)象" 一樣,直接在堆上為對(duì)象分配內(nèi)存。另外,完全空閑的 arena 會(huì)被釋放,其內(nèi)存交還給操作系統(tǒng)。

引用傳遞

對(duì)象總是按引用傳遞,簡(jiǎn)單點(diǎn)說就是通過復(fù)制指針來實(shí)現(xiàn)多個(gè)名字指向同一對(duì)象。因?yàn)?arena 也是在堆上分配的,所以無論何種類型何種大小的對(duì)象,都存儲(chǔ)在堆上。Python 沒有值類型和引用類型一說,就算是最簡(jiǎn)單的整數(shù)也是擁有標(biāo)準(zhǔn)頭的完整對(duì)象。

>>> a = object()

>>> b = a
>>> a is b
True

>>> hex(id(a)), hex(id(b))  # 地址相同,意味著對(duì)象是同一個(gè)。
('0x10b1f5640', '0x10b1f5640')

>>> def test(x):   
... print hex(id(x))  

>>> test(a)
0x10b1f5640     # 地址依舊相同。

如果不希望對(duì)象被修改,就需使用不可變類型,或?qū)ο髲?fù)制品。

不可變類型:int, long, str, tuple, frozenset

除了某些類型自帶的 copy 方法外,還可以:

  • 使用標(biāo)準(zhǔn)庫的 copy 模塊進(jìn)行深度復(fù)制。
  • 序列化對(duì)象,如 pickle、cPickle、marshal。

下面的測(cè)試建議不要用數(shù)字等不可變對(duì)象,因?yàn)槠鋬?nèi)部的緩存和復(fù)用機(jī)制可能會(huì)造成干擾。

>>> import copy

>>> x = object()
>>> l = [x]    # 創(chuàng)建一個(gè)列表。

>>> l2 = copy.copy(l)  # 淺復(fù)制,僅復(fù)制對(duì)象自身,而不會(huì)遞歸復(fù)制其成員。
>>> l2 is l    # 可以看到復(fù)制列表的元素依然是原對(duì)象。
False
>>> l2[0] is x
True

>>> l3 = copy.deepcopy(l) # 深度復(fù)制,會(huì)遞歸復(fù)制所有深度成員。
>>> l3 is l    # 列表元素也被復(fù)制了。
False
>>> l3[0] is x
False

循環(huán)引用會(huì)影響 deepcopy 函數(shù)的運(yùn)作,建議查閱官方標(biāo)準(zhǔn)庫文檔。

引用計(jì)數(shù)

Python 默認(rèn)采用引用計(jì)數(shù)來管理對(duì)象的內(nèi)存回收。當(dāng)引用計(jì)數(shù)為 0 時(shí),將立即回收該對(duì)象內(nèi)存,要么將對(duì)應(yīng)的 block 塊標(biāo)記為空閑,要么返還給操作系統(tǒng)。

為觀察回收行為,我們用 del 監(jiān)控對(duì)象釋放。

>>> class User(object):
...     def __del__(self):
...         print "Will be dead"

>>> a = User()
>>> b = a

>>> import sys
>>> sys.getrefcount(a)
3

>>> del a    # 刪除引用,計(jì)數(shù)減小。
>>> sys.getrefcount(b)
2

>>> del b    # 刪除最后一個(gè)引用,計(jì)數(shù)器為 0,對(duì)象被回收。
Will be dead

某些內(nèi)置類型,比如小整數(shù),因?yàn)榫彺娴木壒?,?jì)數(shù)永遠(yuǎn)不會(huì)為 0,直到進(jìn)程結(jié)束才由虛擬機(jī)清理函數(shù)釋放。

除了直接引用外,Python 還支持弱引用。允許在不增加引用計(jì)數(shù),不妨礙對(duì)象回收的情況下間接引用對(duì)象。但不是所有類型都支持弱引用,比如 list、dict ,弱引用會(huì)引發(fā)異常。

改用弱引用回調(diào)監(jiān)控對(duì)象回收。

>>> import sys, weakref

>>> class User(object): pass

>>> def callback(r):   # 回調(diào)函數(shù)會(huì)在原對(duì)象被回收時(shí)調(diào)用。
...      print "weakref object:", r
...      print "target object dead"

>>> a = User()

>>> r = weakref.ref(a, callback) # 創(chuàng)建弱引用對(duì)象。

>>> sys.getrefcount(a)   # 可以看到弱引用沒有導(dǎo)致目標(biāo)對(duì)象引用計(jì)數(shù)增加。
2      # 計(jì)數(shù) 2 是因?yàn)?getrefcount 形參造成的。

>>> r() is a    # 透過弱引用可以訪問原對(duì)象。
True

>>> del a     # 原對(duì)象回收,callback 被調(diào)用。
weakref object: <weakref at 0x10f99a368; dead>
target object dead

>>> hex(id(r))    # 通過對(duì)比,可以看到 callback 參數(shù)是弱引用對(duì)象。
'0x10f99a368'    # 因?yàn)樵瓕?duì)象已經(jīng)死亡。

>>> r() is None    # 此時(shí)弱引用只能返回 None。也可以此判斷原對(duì)象死亡。
True

引用計(jì)數(shù)是一種簡(jiǎn)單直接,并且十分高效的內(nèi)存回收方式。大多數(shù)時(shí)候它都能很好地工作,除了循環(huán)引用造成計(jì)數(shù)故障。簡(jiǎn)單明顯的循環(huán)引用,可以用弱引用打破循環(huán)關(guān)系。但在實(shí)際開發(fā)中,循環(huán)引用的形成往往很復(fù)雜,可能由 n 個(gè)對(duì)象間接形成一個(gè)大的循環(huán)體,此時(shí)只有靠 GC 去回收了。

垃圾回收

事實(shí)上,Python 擁有兩套垃圾回收機(jī)制。除了引用計(jì)數(shù),還有個(gè)專門處理循環(huán)引用的 GC。通常我們提到垃圾回收時(shí),都是指這個(gè) "Reference Cycle Garbage Collection"。

能引發(fā)循環(huán)引用問題的,都是那種容器類對(duì)象,比如 list、set、object 等。對(duì)于這類對(duì)象,虛擬機(jī)在為其分配內(nèi)存時(shí),會(huì)額外添加用于追蹤的 PyGC_Head。這些對(duì)象被添加到特殊鏈表里,以便 GC 進(jìn)行管理。

typedef union _gc_head {
    struct {
        union _gc_head *gc_next;
        union _gc_head *gc_prev;
        Py_ssize_t gc_refs;
    } gc;
    long double dummy;
} PyGC_Head;

當(dāng)然,這并不表示此類對(duì)象非得 GC 才能回收。如果不存在循環(huán)引用,自然是積極性更高的引用計(jì)數(shù)機(jī)制搶先給處理掉。也就是說,只要不存在循環(huán)引用,理論上可以禁用 GC。當(dāng)執(zhí)行某些密集運(yùn)算時(shí),臨時(shí)關(guān)掉 GC 有助于提升性能。

>>> import gc

>>> class User(object):
...     def __del__(self):
...         print hex(id(self)), "will be dead"

>>> gc.disable()    # 關(guān)掉 GC

>>> a = User()  
>>> del a     # 對(duì)象正?;厥?,引用計(jì)數(shù)不會(huì)依賴 GC。
0x10fddf590 will be dead

同 .NET、JAVA 一樣,Python GC 同樣將要回收的對(duì)象分成 3 級(jí)代齡。GEN0 管理新近加入的年輕對(duì)象,GEN1 則是在上次回收后依然存活的對(duì)象,剩下 GEN2 存儲(chǔ)的都是生命周期極長(zhǎng)的家伙。每級(jí)代齡都有一個(gè)最大容量閾值,每次 GEN0 對(duì)象數(shù)量超出閾值時(shí),都將引發(fā)垃圾回收操作。

#define NUM_GENERATIONS 3

/* linked lists of container objects */
static struct gc_generation generations[NUM_GENERATIONS] = {
    /* PyGC_Head, threshold, count */
    {{{GEN_HEAD(0), GEN_HEAD(0), 0}}, 700, 0},
    {{{GEN_HEAD(1), GEN_HEAD(1), 0}}, 10, 0},
    {{{GEN_HEAD(2), GEN_HEAD(2), 0}}, 10, 0},
};

GC 首先檢查 GEN2,如閾值被突破,那么合并 GEN2、GEN1、GEN0 幾個(gè)追蹤鏈表。如果沒有超出,則檢查 GEN1。GC 將存活的對(duì)象提升代齡,而那些可回收對(duì)象則被打破循環(huán)引用,放到專門的列表等待回收。

>>> gc.get_threshold()  # 獲取各級(jí)代齡閾值
(700, 10, 10)

>>> gc.get_count()   # 各級(jí)代齡鏈表跟蹤的對(duì)象數(shù)量
(203, 0, 5)

包含 del 方法的循環(huán)引用對(duì)象,永遠(yuǎn)不會(huì)被 GC 回收,直至進(jìn)程終止。

這回不能偷懶用 del 監(jiān)控對(duì)象回收了,改用 weakref。因 IPython 對(duì) GC 存在干擾,下面的測(cè)試代碼建議在原生 shell 中進(jìn)行。

>>> import gc, weakref

>>> class User(object): pass
>>> def callback(r): print r, "dead"

>>> gc.disable()     # 停掉 GC,看看引用計(jì)數(shù)的能力。

>>> a = User(); wa = weakref.ref(a, callback)
>>> b = User(); wb = weakref.ref(b, callback)

>>> a.b = b; b.a = a    # 形成循環(huán)引用關(guān)系。

>>> del a; del b     # 刪除名字引用。
>>> wa(), wb()     # 顯然,計(jì)數(shù)機(jī)制對(duì)循環(huán)引用無效。
(<__main__.User object at 0x1045f4f50>, <__main__.User object at 0x1045f4f90>)

>>> gc.enable()     # 開啟 GC。
>>> gc.isenabled()     # 可以用 isenabled 確認(rèn)。
True

>>> gc.collect()     # 因?yàn)闆]有達(dá)到閾值,我們手工啟動(dòng)回收。
<weakref at 0x1045a8cb0; dead> dead  # GC 的確有對(duì)付基友的能力。 
<weakref at 0x1045a8db8; dead> dead  # 這個(gè)地址是弱引用對(duì)象的,別犯糊涂。

一旦有了 del,GC 就拿循環(huán)引用沒辦法了。

>>> import gc, weakref

>>> class User(object):
... def __del__(self): pass    # 難道連空的 __del__ 也不行?

>>> def callback(r): print r, "dead"

>>> gc.set_debug(gc.DEBUG_STATS | gc.DEBUG_LEAK) # 輸出更詳細(xì)的回收狀態(tài)信息。
>>> gc.isenabled()      # 確保 GC 在工作。
True

>>> a = User(); wa = weakref.ref(a, callback)
>>> b = User(); wb = weakref.ref(b, callback)
>>> a.b = b; b.a = a

>>> del a; del b
>>> gc.collect()      # 從輸出信息看,回收失敗。
gc: collecting generation 2...
gc: objects in each generation: 520 3190 0
gc: uncollectable <User 0x10fd51fd0>   # a
gc: uncollectable <User 0x10fd57050>   # b
gc: uncollectable <dict 0x7f990ac88280>  # a.__dict__
gc: uncollectable <dict 0x7f990ac88940>  # b.__dict__
gc: done, 4 unreachable, 4 uncollectable, 0.0014s elapsed.
4

>>> xa = wa()
>>> xa, hex(id(xa.__dict__))
<__main__.User object at 0x10fd51fd0>, '0x7f990ac88280',

>>> xb = wb()
>>> xb, hex(id(xb.__dict__))
<__main__.User object at 0x10fd57050>, '0x7f990ac88940'

關(guān)于用不用 del 的爭(zhēng)論很多。大多數(shù)人的結(jié)論是堅(jiān)決抵制,諸多 "牛人" 也是這樣教導(dǎo)新手的??僧吘?del 承擔(dān)了析構(gòu)函數(shù)的角色,某些時(shí)候還是有其特定的作用的。用弱引用回調(diào)會(huì)造成邏輯分離,不便于維護(hù)。對(duì)于一些簡(jiǎn)單的腳本,我們還是能保證避免循環(huán)引用的,那不妨試試。就像前面例子中用來監(jiān)測(cè)對(duì)象回收,就很方便。

編譯

Python 實(shí)現(xiàn)了棧式虛擬機(jī) (Stack-Based VM) 架構(gòu),通過與機(jī)器無關(guān)的字節(jié)碼來實(shí)現(xiàn)跨平臺(tái)執(zhí)行能力。這種字節(jié)碼指令集沒有寄存器,完全以棧 (抽象層面) 進(jìn)行指令運(yùn)算。盡管很簡(jiǎn)單,但對(duì)普通開發(fā)人員而言,是無需關(guān)心的細(xì)節(jié)。

要運(yùn)行 Python 語言編寫的程序,必須將源碼編譯成字節(jié)碼。通常情況下,編譯器會(huì)將源碼轉(zhuǎn)換成字節(jié)碼后保存在 pyc 文件中。還可用 -O 參數(shù)生成 pyo 格式,這是簡(jiǎn)單優(yōu)化后的 pyc 文件。

編譯發(fā)生在模塊載入那一刻。具體來看,又分為 pyc 和 py 兩種情況。

載入 pyc 流程:

  • 核對(duì)文件 Magic 標(biāo)記。
  • 檢查時(shí)間戳和源碼文件修改時(shí)間是否相同,以確定是否需要重新編譯。
  • 載入模塊。

如果沒有 pyc,那么就需要先完成編譯:

  • 對(duì)源碼進(jìn)行 AST 分析。
  • 將分析結(jié)果編譯成 PyCodeObject。
  • 將 Magic、源碼文件修改時(shí)間、PyCodeObject 保存到 pyc 文件中。
  • 載入模塊。

Magic 是一個(gè)特殊的數(shù)字,由 Python 版本號(hào)計(jì)算得來,作為 pyc 文件和 Python 版本檢查標(biāo)記。PyCodeObject 則包含了代碼對(duì)象的完整信息。

typedef struct {
    PyObject_HEAD
    int co_argcount;  // 參數(shù)個(gè)數(shù),不包括 *args, **kwargs。
    int co_nlocals;  // 局部變量數(shù)量。
    int co_stacksize;  // 執(zhí)行所需的??臻g。
    int co_flags;   // 編譯標(biāo)志,在創(chuàng)建 Frame 時(shí)用得著。
    PyObject *co_code;  // 字節(jié)碼指令。
    PyObject *co_consts;  // 常量列表。
    PyObject *co_names;  // 符號(hào)列表。
    PyObject *co_varnames; // 局部變量名列表。
    PyObject *co_freevars; // 閉包: 引用外部函數(shù)名字列表。
    PyObject *co_cellvars; // 閉包: 被內(nèi)部函數(shù)引用的名字列表。
    PyObject *co_filename; // 源碼文件名。
    PyObject *co_name;  // PyCodeObject 的名字,函數(shù)名、類名什么的。
    int co_firstlineno;  // 這個(gè) PyCodeObject 在源碼文件中的起始位置,也就是行號(hào)。
    PyObject *co_lnotab;  // 字節(jié)碼指令偏移量和源碼行號(hào)的對(duì)應(yīng)關(guān)系,反匯編時(shí)用得著。
    void *co_zombieframe;  // 為優(yōu)化準(zhǔn)備的特殊 Frame 對(duì)象。
    PyObject *co_weakreflist; // 為弱引用準(zhǔn)備的...
} PyCodeObject;

無論是模塊還是其內(nèi)部的函數(shù),都被編譯成 PyCodeObject 對(duì)象。內(nèi)部成員都嵌套到 co_consts 列表中。

>>> pycat test.py
"""
    Hello, World
"""

def add(a, b):
    return a + b

c = add(10, 20)

>>> code = compile(open("test.py").read(), "test.py", "exec")

>>> code.co_filename, code.co_name, code.co_names
('test.py', '<module>', ('__doc__', 'add', 'c'))

>>> code.co_consts
('\n Hello, World\n', <code object add at 0x105b76e30, file "test.py", line 5>, 10,
20, None)

>>> add = code.co_consts[1]
>>> add.co_varnames
('a', 'b')

除了內(nèi)置 compile 函數(shù),標(biāo)準(zhǔn)庫里還有 py_compile、compileall 可供選擇。

>>> import py_compile, compileall

>>> py_compile.compile("test.py", "test.pyo")
>>> ls
main.py* test.py  test.pyo

>>> compileall.compile_dir(".", 0)
Listing . ...
Compiling ./main.py ...
Compiling ./test.py ...

如果對(duì) pyc 文件格式有興趣,但又不想看 C 代碼,可以到 /usr/lib/python2.7/compiler 目錄里尋寶。又或者你對(duì)反匯編、代碼混淆、代碼注入等話題更有興趣,不妨看看標(biāo)準(zhǔn)庫里的 dis。

執(zhí)行

相比 .NET、JAVA 的 CodeDOM 和 Emit,Python 天生擁有無與倫比的動(dòng)態(tài)執(zhí)行優(yōu)勢(shì)。

最簡(jiǎn)單的就是用 eval() 執(zhí)行表達(dá)式。

>>> eval("(1 + 2) * 3")  # 假裝看不懂這是啥……
9

>>> eval("{'a': 1, 'b': 2}") # 將字符串轉(zhuǎn)換為 dict。
{'a': 1, 'b': 2}

eval 默認(rèn)會(huì)使用當(dāng)前環(huán)境的名字空間,當(dāng)然我們也可以帶入自定義字典。

>>> x = 100
>>> eval("x + 200")  # 使用當(dāng)前上下文的名字空間。
300

>>> ns = dict(x = 10, y = 20)
>>> eval("x + y", ns)  # 使用自定義名字空間。
30

>>> ns.keys()   # 名字空間里多了 __builtins__。
['y', 'x', '__builtins__']

要執(zhí)行代碼片段,或者 PyCodeObject 對(duì)象,那么就需要?jiǎng)佑?exec 。同樣可以帶入自定義名字空間,以避免對(duì)當(dāng)前環(huán)境造成污染。

>>> py = """
... class User(object):
...     def __init__(self, name):
...         self.name = name
...     def __repr__(self):
...         return "<User: {0:x}; name={1}>".format(id(self), self.name)
... """

>>> ns = dict()
>>> exec py in ns   # 執(zhí)行代碼片段,使用自定義的名字空間。

>>> ns.keys()   # 可以看到名字空間包含了新的類型:User。
['__builtins__', 'User']

>>> ns["User"]("Tom")  # 完全可用。貌似用來開發(fā) ORM 會(huì)很簡(jiǎn)單。
<User: 10547f290; name=Tom>

繼續(xù)看 exec 執(zhí)行 PyCodeObject 的演示。

>>> py = """
... def incr(x):
...     global z
...     z += x
... """

>>> code = compile(py, "test", "exec")   # 編譯成 PyCodeObject。

>>> ns = dict(z = 100)     # 自定義名字空間。
>>> exec code in ns     # exec 執(zhí)行以后,名字空間多了 incr。

>>> ns.keys()      # def 的意思是創(chuàng)建一個(gè)函數(shù)對(duì)象。
['__builtins__', 'incr', 'z']

>>> exec "incr(x); print z" in ns, dict(x = 50) # 試著調(diào)用這個(gè) incr,不過這次我們提供一個(gè)
150        # local 名字空間,以免污染 global。
>>> ns.keys()      # 污染沒有發(fā)生。
['__builtins__', 'incr', 'z']

動(dòng)態(tài)執(zhí)行一個(gè) py 文件,可以考慮用 execfile(),或者 runpy 模塊。

上一篇:異常下一篇:第三部分 擴(kuò)展庫