標籤:

為什麼Python裡面的locals()是只讀的

Python裡面的各個名字空間都可以抽象成一個dictionary, 比如函數的本地變數就可以通過build-in函數locals()來取得。但是很多資料都提到通過locals()取得的dictionary是只讀的,比較好奇為什麼會有這個限制。下面是一個點單的對locals()的調用 (在CPython 2.7下運行得到):

>>> def foo(a):n... return locals()n...n>>> dis.dis(foo)n 2 0 LOAD_GLOBAL 0 (locals)n 3 CALL_FUNCTION 0n 6 RETURN_VALUEn

-- 補充,按R大提到的,CPython的local variable都是放在frame的localplus數組裡面,而locals()返回的是dictionary對象,通過這個dictionary對象不能直接修改localplus上的值也是很直觀的。

locals在global空間(LOAD_GLOBAL), 這個build-in方法最終在bltinmodule.c裡面實現如下:

static PyObject *nbuiltin_locals(PyObject *self)n{n PyObject *d;nn d = PyEval_GetLocals();n Py_XINCREF(d);n return d;n}n

主要是轉到ceval.c的PyEval_GetLocals()上:

PyObject *nPyEval_GetLocals(void)n{n PyFrameObject *current_frame = PyEval_GetFrame();n if (current_frame == NULL)n return NULL;n PyFrame_FastToLocals(current_frame);n return current_frame->f_locals;n}n

又轉到frameobject.c里的PyFrame_FastToLocals(PyFrameObject *f)上。不考慮錯誤和cellvar的處理等,主要部分如下:

PyFrame_FastToLocals(PyFrameObject *f)n{n /* Merge fast locals into f->f_locals */n PyObject *locals, *map;n PyObject **fast;n PyCodeObject *co;n Py_ssize_t j;n locals = f->f_locals;n if (locals == NULL) {n locals = f->f_locals = PyDict_New();n }n co = f->f_code;nn map = co->co_varnames;n fast = f->f_localsplus;n j = PyTuple_GET_SIZE(map);n if (co->co_nlocals)n map_to_dict(map, j, locals, fast, 0);n

最主要的map_to_dict(同在frameobject.c)就會把存在f->localsplus裡面的local variable (所有的local variable name在co->varnames裡面)放到locals所指向的dictionary, 也同時放在了當前frame里(f->locals)。所有的local variable很自然成為一個名字空間放進dictionary,這裡放進數組風格的f_localsplus裡面是為了不用每次使用local variable都查找dictionary。可以搜索此變數名獲得更多信息。

static voidnmap_to_dict(PyObject *map, Py_ssize_t nmap, PyObject *dict, PyObject **values,n int deref)n{n Py_ssize_t j;n for (j = nmap; --j >= 0; ) {n PyObject *key = PyTuple_GET_ITEM(map, j);n PyObject *value = values[j];n if (deref) {n value = PyCell_GET(value);n }n if (value == NULL) {n if (PyObject_DelItem(dict, key) != 0)n PyErr_Clear();n }n else {n if (PyObject_SetItem(dict, key, value) != 0)n PyErr_Clear();n }n }n}n

上面的map_to_dict會遍歷所有的local variable names, 如果這個variable指向了Python對象則把variable name和對象放進要返回的dictionary對象(f->f_locals),這也就意味著每次對locals()的調用都會把frame object裡面的dictionary (f_locals)的所有variable name到object的映射根據當前狀態刷新,就相當於給所有的local vars照相(snapshot)。這樣就能比較容易理解為什麼對locals()返回的dictionary是無效的的,因為直接寫入這個dictionary只會影響dictionary自身而不會反映到當前frame的f_localsplus,每次調用locals()又會刷新要返回的dictionary內容。下面的例子可以幫助理解這點。

def f():n x = 10n lc = locals()n lc[x] = 20n # x->20 remains on dictionary before locals() is calledn print lcn # {x: 20} n locals()n # locals dictionary is refreshed in the above call to locals()n print lcn # {x: 10, lc: {...}} nnf()n

CPython官網對locals()的主要描述如下,也說明返回的dictionary只是代表了local symbol table, 並不能等價使用(修改)。

Update and return a dictionary representing the current local symbol table

推薦閱讀:

強烈推薦 | 數據分析師的必讀書單
用python-pandas作圖矩陣
Python進階課程筆記(四)
Scrapy學習實例(二)採集無限滾動頁面
特徵工程總結:R與Python的比較實現

TAG:Python |