Python作用域與名字空間源碼學(xué)習(xí)筆記
作用域與名字空間
問題:
PI = 3.14 def circle_area(r): return PI * r ** 2 class Person(object): def __init__(self, name): self.name = name def say(self): print('i am', self.name)
以這個(gè)程序?yàn)槔?,代碼中出現(xiàn)的每個(gè)變量的作用域分別是什么?程序中總共涉及多少個(gè)名字空間?Python又以怎樣的順序去查找一個(gè)變量呢?
1. 名字綁定
1.1 賦值
在Python中,變量只是一個(gè)與實(shí)際對(duì)象綁定起來的名字,變量定義本質(zhì)上就是建立名字與對(duì)象的約束關(guān)系。因此,賦值語句本質(zhì)上就是建立這樣的約束關(guān)系,將右邊的對(duì)象與左邊的名字綁定起來:
a = 1
賦值語句是最基本的將名字與對(duì)象綁定的方式,除此之外還有很多其他方式都起到了這樣的作用。
1.2 模塊導(dǎo)入
當(dāng)我們導(dǎo)入一個(gè)模塊時(shí),也會(huì)在當(dāng)前上下文創(chuàng)建一個(gè)名字,并與被導(dǎo)入對(duì)象綁定。
# 在當(dāng)前上下文創(chuàng)建一個(gè)名字test,與被導(dǎo)入的module對(duì)象綁定 import test
1.3 函數(shù)、類定義
# 函數(shù)名circle_area與function對(duì)象綁定 def circle_area(r): return PI * r ** 2 # 類名Person與類型對(duì)象綁定 class Person(object): def __init__(self): pass
1.4 as關(guān)鍵字
# 將名字t與module對(duì)象綁定 import test as t
2. 作用域
問題:當(dāng)我們引入一個(gè)名字之后,它的可見范圍有多大呢?
a = 1 def func1(): print(a) # 1 def func2(): a = 2 print(a) # 2 print(a) # 1
在不同的代碼區(qū)域引入的名字,其影響范圍是不一樣的。第1行定義的a可以影響到func1,而func2中定義的a則不能。此外,一個(gè)名字可能會(huì)在多個(gè)代碼區(qū)域中定義,但最終在某個(gè)代碼區(qū)域中只能使用其中一個(gè)。
2.1 靜態(tài)作用域
一個(gè)名字能夠施加影響的程序正文區(qū)域,便是該名字的作用域。在Python中,一個(gè)名字在程序中某個(gè)區(qū)域能否起作用,是由名字引入的位置決定的,而不是運(yùn)行時(shí)動(dòng)態(tài)決定的。因此,Python具有靜態(tài)作用域,也稱為詞法作用域。那么,作用域具體是如何劃分的呢?
2.2 劃分作用域
- Python在編譯時(shí),根據(jù)語法規(guī)則將代碼劃分為不同的代碼塊,每個(gè)代碼塊形成一個(gè)作用域。首先,整個(gè).py文件構(gòu)成最頂層的作用域,這就是全局作用域,也成為模塊作用域;其次,當(dāng)代碼遇到函數(shù)定義,函數(shù)體成為當(dāng)前作用域的子作用域;再者,當(dāng)代碼遇到類定義,類定義體成為當(dāng)前作用域的子作用域。
- 一個(gè)名字在某個(gè)作用域引入后,它的影響范圍就被限制在該作用域內(nèi)。其中,全局作用域?qū)λ兄苯踊蜷g接內(nèi)嵌于其中的子作用域可見;函數(shù)作用域?qū)ζ渲苯幼幼饔糜蚩梢?,并且可以傳遞。
- 例子中的作用域的嵌套關(guān)系如下:
訪問關(guān)系如下:
2.3 閉包作用域
閉包的概念:在計(jì)算機(jī)科學(xué)中,閉包,又稱詞法閉包或函數(shù)閉包,是引用了自由變量的函數(shù)。這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。閉包在運(yùn)行時(shí)可以有多個(gè)實(shí)例,不同的引用環(huán)境和相同的函數(shù)組合可以產(chǎn)生不同的實(shí)例
代碼示例:
>>> pi = 3.14 >>> def closure_print(name: str): def circle_area(r: int): print(name, pi * r * r) return circle_area >>> circle_area1 = closure_print("circle1: ") >>> circle_area2 = closure_print("circle2: ") >>> circle_area1(1) circle1: 3.14 >>> circle_area2(2) circle2: 12.56
劃分作用域:
思考:circle_area1和circle_area2函數(shù)對(duì)象是怎么拿到name的?
2.4 類作用域
代碼示例:
>>> language = 'chinese' >>> class Male: gender: str = 'male' def __init__(self, name: str): self.name = name def Speak(self): print('i speak', language) def Gender(self): print('i am', gender) >>> male = Male('zhangsan') >>> male.Gender() Traceback (most recent call last): File "<pyshell#11>", line 1, in <module> male.Gender() File "<pyshell#9>", line 8, in Gender print('i am', gender) NameError: name 'gender' is not defined >>> male.Speak() i speak chinese
作用域分析:
全局作用域?qū)ζ渌袃?nèi)嵌其中的作用域均可見,所以在函數(shù)Speak()中可以訪問到language
類作用域和函數(shù)作用域不一樣,它對(duì)其子作用域是不可見的,所以在函數(shù)Gende()中g(shù)ender是不可見的
思考:
>>> male.gender 'male' >>> Male.gender 'male' >>> male.gender = 'male2' >>> male.gender 'male2' >>> Male.gender 'male'
2.5 復(fù)雜嵌套
2.5.1 函數(shù)嵌套類
在Python中,類可以動(dòng)態(tài)創(chuàng)建,甚至在函數(shù)中返回。通過在函數(shù)中創(chuàng)建并返回類,可以按函數(shù)參數(shù)對(duì)類進(jìn)行動(dòng)態(tài)定制
代碼示例:
>>> language = 'chinese' >>> def MakeMale(sSortName: str): class Male: sortName = sSortName def __init__(self, name: str): self.name = name def Speak(self): print('i speak', language) def Sort(self): print(sSortName) return Male >>> ChineseMale: type = MakeMale('Chinese Men') >>> chineseMale = ChineseMale('zhangsan') >>> chineseMale.Speak() i speak chinese >>> chineseMale.sortName Chinese Men >>> chineseMale.Sort() Chinese Men
2.5.2 類嵌套類
代碼示例:
>>> class OutClass: inName = 'in' class InClass: name = inName Traceback (most recent call last): File "<pyshell#26>", line 1, in <module> class OutClass: File "<pyshell#26>", line 3, in OutClass class InClass: File "<pyshell#26>", line 4, in InClass name = inName NameError: name 'inName' is not defined
3. 名字空間
作用域是語法層面的概念,是靜態(tài)的。當(dāng)程序開始執(zhí)行后,作用域中的名字綁定關(guān)系需要存儲(chǔ)起來,存儲(chǔ)的地方就是名字空間。由于名字綁定關(guān)系是由名字和對(duì)象組成的鍵值對(duì),因此用dict是理想的存儲(chǔ)容器(之前在介紹dict的相關(guān)內(nèi)容時(shí)也有提到)
以計(jì)算圓面積的例子來認(rèn)識(shí)作用域背后的運(yùn)行時(shí)實(shí)體——名字空間。代碼示例如下:
>>> PI = 3.14 >>> def closure_print(name: str): def circle_area(r: int): print(name, PI * r * r) return circle_area
3.1 Globals
在Python中,每個(gè)模塊都有一個(gè)dict對(duì)象,用于存儲(chǔ)全局作用域中的名字,這就是全局名字空間Globals。在上述的例子中,根據(jù)我們之前對(duì)作用域的劃分,可以肯定全局名字空間中一定包含兩個(gè)名字:PI和closure_print。
如果其他模塊也需要使用PI或closure_print函數(shù),就需要通過import語句將模塊導(dǎo)入,導(dǎo)入后我們就可以獲得一個(gè)模塊對(duì)象:
# 假設(shè)我們?cè)趖est.py中導(dǎo)入上述模塊testglobal.py >>> import testglobal >>> testglobal <module 'testglobal' from 'D:\\myspace\\code\\pythonCode\\mix\\namespace\\testglobal.py'> >>> type(testglobal) <class 'module'>
通過內(nèi)置函數(shù)dir()我們可以知道模塊對(duì)象下有哪些屬性可以訪問:
>>> dir(testglobal) ['PI', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'closure_print'] >>> testglobal.closure_print <function closure_print at 0x000002F33B14A050>
在Python中,一個(gè)對(duì)象可以訪問哪些屬性,成為對(duì)象的屬性空間。因此,模塊的屬性空間和全局名字空間本質(zhì)上就是同一個(gè)東西,都通過一個(gè)dict對(duì)象進(jìn)行存儲(chǔ)。那么如何找到這個(gè)dict對(duì)象呢——通過__dict__屬性:
>>> testglobal.__dict__
此外,我們也可以通過內(nèi)置函數(shù)globals()來獲取當(dāng)前模塊的全局名字空間:
>>> globals()
我們分別打印它們的id,本質(zhì)上就是同一個(gè)對(duì)象:
>>> id(testglobal.__dict__) 2219833831040 >>> id(globals()) 2219833831040
3.2 Locals
Python執(zhí)行一個(gè)作用域內(nèi)的代碼時(shí),需要一個(gè)容器來訪問當(dāng)前作用域的名字,這就是局部名字空間Locals
當(dāng)Python執(zhí)行closure_print()函數(shù)時(shí),將分配一個(gè)棧幀對(duì)象PyFrameObject來保存上下文信息以及執(zhí)行狀態(tài)。作為代碼執(zhí)行時(shí)必不可少的上下文信息,全局名字空間和局部名字空間也會(huì)在PyFrameObject上記錄:
struct _frame { PyObject_VAR_HEAD struct _frame *f_back; /* previous frame, or NULL */ PyCodeObject *f_code; /* code segment */ PyObject *f_builtins; /* builtin symbol table (PyDictObject) */ PyObject *f_globals; /* global symbol table (PyDictObject) */ PyObject *f_locals; /* local symbol table (any mapping) */ PyObject **f_valuestack; /* points after the last local */ PyObject *f_trace; /* Trace function */ int f_stackdepth; /* Depth of value stack */ char f_trace_lines; /* Emit per-line trace events? */ char f_trace_opcodes; /* Emit per-opcode trace events? */ /* Borrowed reference to a generator, or NULL */ PyObject *f_gen; int f_lasti; /* Last instruction if called */ int f_lineno; /* Current line number. Only valid if non-zero */ int f_iblock; /* index in f_blockstack */ PyFrameState f_state; /* What state the frame is in */ PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */ PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */ };
3.3 Enclosings
在作用域存在嵌套的情況下,Python將內(nèi)層代碼塊依賴的所有外層名字存儲(chǔ)在一個(gè)容器內(nèi),這就是閉包名字空間Enclosings
對(duì)于示例:
>>> pi = 3.14 >>> def closure_print(name: str): def circle_area(r: int): name = 1 print(name, pi * r * r) return circle_area
當(dāng)Python執(zhí)行到print(name, pi * r * r)語句時(shí),按照Locals、Enclosings、Globals這樣的順序查找語句中涉及的名字:名字name在Enclosings中找到,名字pi在Globals中找到,名字r在Locals中找到。那么還有一個(gè)名字print是如何找到的呢?
3.4 Builtin
Python在builtin模塊中提供了很多內(nèi)建函數(shù)和類型,構(gòu)成運(yùn)行時(shí)的另一個(gè)名字空間:內(nèi)建名字空間Builtin
全局名字空間中有一個(gè)名字指向內(nèi)建名字空間:
>>> import builtins >>> id(testglobal.__builtins__) 3065787874688 >>> id(builtins.__dict__) 3065787874688
4. 問題與總結(jié)
函數(shù)作用域?qū)?nèi)部所有的作用域均可見,包括內(nèi)部嵌套的類作用域和函數(shù)作用域(例如閉包);類作用域?qū)?nèi)部所有的作用域均不可見,包括內(nèi)部嵌套的類作用域和函數(shù)作用域。
“只要在當(dāng)前Locals命名空間中無同名變量且沒有g(shù)lobal,nonlocal等關(guān)鍵字的聲明的話,就一定創(chuàng)建一個(gè)該名字的新局部變量”,以nonlocal的使用為例:
示例1:
>>> def closure_print(name: str): def circle_area(r: int): print(locals()) print(name, PI * r * r) return circle_area >>> c = closure_print('circle1') >>> c(1) {'r': 1, 'name': 'circle1'} circle1 3.14
示例2:
>>> PI = 3.14 >>> def closure_print(name: str): def circle_area(r: int): print(locals()) name += '1' print(name, PI * r * r) return circle_area >>> c = closure_print('circle1') >>> c(1) {'r': 1} Traceback (most recent call last): File "<pyshell#4>", line 1, in <module> c(1) File "<pyshell#2>", line 4, in circle_area name += '1' UnboundLocalError: local variable 'name' referenced before assignment
示例3:
>>> PI = 3.14 >>> def closure_print(name: str): def circle_area(r: int): print(locals()) name = 'circle2' print(locals()) print(name, PI * r * r) return circle_area >>> c = closure_print('circle1') >>> c(1) {'r': 1} {'r': 1, 'name': 'circle2'} circle2 3.14
示例4:
>>> PI = 3.14 >>> def closure_print(name: str): def circle_area(r: int): print(locals()) nonlocal name name += '1' print(locals()) print(name, PI * r * r) return circle_area >>> c = closure_print('circle1') >>> c(1) {'r': 1, 'name': 'circle1'} {'r': 1, 'name': 'circle11'} circle11 3.14
locals()輸出的到底是什么?C源碼如下:
int PyFrame_FastToLocalsWithError(PyFrameObject *f) { /* Merge fast locals into f->f_locals */ PyObject *locals, *map; PyObject **fast; PyCodeObject *co; Py_ssize_t j; Py_ssize_t ncells, nfreevars; if (f == NULL) { PyErr_BadInternalCall(); return -1; } // 初始賦值locals為f->f_locals locals = f->f_locals; if (locals == NULL) { locals = f->f_locals = PyDict_New(); if (locals == NULL) return -1; } // 獲取對(duì)應(yīng)的PyCodeObject co = f->f_code; // 獲取co_varnames字段 map = co->co_varnames; if (!PyTuple_Check(map)) { PyErr_Format(PyExc_SystemError, "co_varnames must be a tuple, not %s", Py_TYPE(map)->tp_name); return -1; } fast = f->f_localsplus; j = PyTuple_GET_SIZE(map); if (j > co->co_nlocals) j = co->co_nlocals; if (co->co_nlocals) { // 將co_varnames加入到locals中 if (map_to_dict(map, j, locals, fast, 0) < 0) return -1; } // 閉包相關(guān) ncells = PyTuple_GET_SIZE(co->co_cellvars); nfreevars = PyTuple_GET_SIZE(co->co_freevars); if (ncells || nfreevars) { // 將co_cellvars加入到locals if (map_to_dict(co->co_cellvars, ncells, locals, fast + co->co_nlocals, 1)) return -1; /* If the namespace is unoptimized, then one of the following cases applies: 1. It does not contain free variables, because it uses import * or is a top-level namespace. 2. It is a class namespace. We don't want to accidentally copy free variables into the locals dict used by the class. */ if (co->co_flags & CO_OPTIMIZED) { // 將co_freevars加入到locals if (map_to_dict(co->co_freevars, nfreevars, locals, fast + co->co_nlocals + ncells, 1) < 0) return -1; } } return 0; }
以上就是Python作用域與名字空間源碼學(xué)習(xí)筆記的詳細(xì)內(nèi)容,更多關(guān)于Python作用域名字空間的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解python使用金山詞霸的翻譯功能(調(diào)試工具斷點(diǎn)的使用)
這篇文章主要介紹了詳解python使用金山詞霸的翻譯功能(調(diào)試工具斷點(diǎn)的使用),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01基于Python+Matplotlib實(shí)現(xiàn)直方圖的繪制
Matplotlib是Python的繪圖庫,它能讓使用者很輕松地將數(shù)據(jù)圖形化,并且提供多樣化的輸出格式。本文將為大家介紹如何用matplotlib繪制直方圖,感興趣的朋友可以學(xué)習(xí)一下2022-04-04tensorflow+k-means聚類簡(jiǎn)單實(shí)現(xiàn)貓狗圖像分類的方法
這篇文章主要介紹了tensorflow+k-means聚類簡(jiǎn)單實(shí)現(xiàn)貓狗圖像分類,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04使用PyInstaller將Python程序文件轉(zhuǎn)換為可執(zhí)行程序文件
與py2exe一樣,PyInstaller程序也可以將Python的.py程序文件轉(zhuǎn)換為.exe,并且還有Linux的版本,下面我們就來詳細(xì)看一下如何使用PyInstaller將Python程序文件轉(zhuǎn)換為可執(zhí)行程序文件2016-07-07python wav模塊獲取采樣率 采樣點(diǎn)聲道量化位數(shù)(實(shí)例代碼)
這篇文章主要介紹了python wav模塊獲取采樣率 采樣點(diǎn)聲道量化位數(shù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-01-01