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

基本環(huán)境

虛擬機

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

當虛擬機開始運行時,它通過初始化函數(shù)完成整個運行環(huán)境設置:

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

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

類型和對象

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

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

以 int 為例,對應 Python 結構定義是:

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

typedef struct _object {
    PyObject_HEAD
} PyObject;

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

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

>>> import sys

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

>>> sys.getsizeof(x) # 符合長度預期。
24

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

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

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

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

>>> import types

>>> x = 20

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

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

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

>>> y = x

>>> hex(id(x)), hex(id(y))  # id() 返回對象標識,其實就是內存地址。
('0x7fc5204103c0', '0x7fc5204103c0')
>>> hex(id(int)), hex(id(types.IntType))
('0x1088cebd8', '0x1088cebd8')

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

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

typedef struct {
    PyObject_VAR_HEAD
} PyVarObject;

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

名字空間

名字空間是 Python 最核心的內容。

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

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

和 C 變量名是內存地址別名不同,Python 的名字實際上是一個字符串對象,它和所指向的目標對象一起在名字空間中構成一項 {name: object} 關聯(lián)。

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

>>> x = 123

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

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

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

>>> y
'Hello, World'

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

名字的作用僅僅是在某個時刻與名字空間中的某個對象進行關聯(lián)。其本身不包含目標對象的任何信息,只有通過對象頭部的類型指針才能獲知其具體類型,進而查找其相關成員數(shù)據(jù)。正因為名字的弱類型特征,我們可以在運行期隨時將其關聯(lián)到任何類型對象。

>>> y
'Hello, World'

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

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

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

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

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

>>> import sys

>>> globals() is locals()
True

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

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

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

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

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

>>> 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() # 當前模塊名字空間和 globals 相同。
True

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

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

內存管理

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

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

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

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

當所有 arena 的總容量超出限制 (64MB) 時,就不再請求新的 arena 內存。而是如同 "大對象" 一樣,直接在堆上為對象分配內存。另外,完全空閑的 arena 會被釋放,其內存交還給操作系統(tǒng)。

引用傳遞

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

>>> a = object()

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

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

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

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

如果不希望對象被修改,就需使用不可變類型,或對象復制品。

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

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

  • 使用標準庫的 copy 模塊進行深度復制。
  • 序列化對象,如 pickle、cPickle、marshal。

下面的測試建議不要用數(shù)字等不可變對象,因為其內部的緩存和復用機制可能會造成干擾。

>>> import copy

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

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

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

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

引用計數(shù)

Python 默認采用引用計數(shù)來管理對象的內存回收。當引用計數(shù)為 0 時,將立即回收該對象內存,要么將對應的 block 塊標記為空閑,要么返還給操作系統(tǒng)。

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

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

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

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

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

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

某些內置類型,比如小整數(shù),因為緩存的緣故,計數(shù)永遠不會為 0,直到進程結束才由虛擬機清理函數(shù)釋放。

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

改用弱引用回調監(jiān)控對象回收。

>>> import sys, weakref

>>> class User(object): pass

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

>>> a = User()

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

>>> sys.getrefcount(a)   # 可以看到弱引用沒有導致目標對象引用計數(shù)增加。
2      # 計數(shù) 2 是因為 getrefcount 形參造成的。

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

>>> del a     # 原對象回收,callback 被調用。
weakref object: <weakref at 0x10f99a368; dead>
target object dead

>>> hex(id(r))    # 通過對比,可以看到 callback 參數(shù)是弱引用對象。
'0x10f99a368'    # 因為原對象已經(jīng)死亡。

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

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

垃圾回收

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

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

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;

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

>>> import gc

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

>>> gc.disable()    # 關掉 GC

>>> a = User()  
>>> del a     # 對象正常回收,引用計數(shù)不會依賴 GC。
0x10fddf590 will be dead

同 .NET、JAVA 一樣,Python GC 同樣將要回收的對象分成 3 級代齡。GEN0 管理新近加入的年輕對象,GEN1 則是在上次回收后依然存活的對象,剩下 GEN2 存儲的都是生命周期極長的家伙。每級代齡都有一個最大容量閾值,每次 GEN0 對象數(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 幾個追蹤鏈表。如果沒有超出,則檢查 GEN1。GC 將存活的對象提升代齡,而那些可回收對象則被打破循環(huán)引用,放到專門的列表等待回收。

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

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

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

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

>>> import gc, weakref

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

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

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

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

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

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

>>> gc.collect()     # 因為沒有達到閾值,我們手工啟動回收。
<weakref at 0x1045a8cb0; dead> dead  # GC 的確有對付基友的能力。 
<weakref at 0x1045a8db8; dead> dead  # 這個地址是弱引用對象的,別犯糊涂。

一旦有了 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) # 輸出更詳細的回收狀態(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'

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

編譯

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

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

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

載入 pyc 流程:

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

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

  • 對源碼進行 AST 分析。
  • 將分析結果編譯成 PyCodeObject。
  • 將 Magic、源碼文件修改時間、PyCodeObject 保存到 pyc 文件中。
  • 載入模塊。

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

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

無論是模塊還是其內部的函數(shù),都被編譯成 PyCodeObject 對象。內部成員都嵌套到 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')

除了內置 compile 函數(shù),標準庫里還有 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 ...

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

執(zhí)行

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

最簡單的就是用 eval() 執(zhí)行表達式。

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

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

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

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

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

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

要執(zhí)行代碼片段,或者 PyCodeObject 對象,那么就需要動用 exec 。同樣可以帶入自定義名字空間,以避免對當前環(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 會很簡單。
<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)建一個函數(shù)對象。
['__builtins__', 'incr', 'z']

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

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

上一篇:異常下一篇:第三部分 擴展庫