今天在編程中遇到一個問題,思考半天,原來是因為對LEGB規則沒有深入理解。
import pprint
def pprint(dic_things):
? ? ehco = pprint.PrettyPrinter(width = 1 ,indent = 4)
? ? echo.pprint(dic_things)
pprint({'a':'1','b':'2','c':'5'})
AttributeError: 'function' object has no attribute 'PrettyPrinter'
心中一陣懵逼,為啥引用不了呢。難道函數內不能使用import模塊。分析一番,原來是要運用LEGB大法,這也是就python編譯器的執行規則。
Python 使用 LEGB 的順序來查找一個符號對應的對象
locals ->enclosing function -> globals -> builtins
那么我們來分析分析,這個錯誤吧。
Python 是動態語言,def 實際上是執行一條指令,用來創建函數(class 則是創建類的指令),而不僅僅是個語法關鍵字。函數并不是事先創建好的,而是執行到的時候才創建的。
Python語言函數代碼的執行流程,為了保證函數的定義先于其首次調用時執行,我們需要知道中語句的執行順序。
執行總是從程序的第一行代碼開始的,從上到下,從左到右,按順序依次執行第一條語句。
函數定義并不會改變程序的執行流程,但應該注意函數代碼塊中的語句并不是立即就執行的,而是等到函數被程序調用時才會執行。
函數調用可以看作程序執行流程中的一個迂回路徑,遇到函數調用時,并不會直接繼續執行下一條語句,而是跳到函數體的第一行,繼續執行完函數代碼塊的所有語句,再跳回到原來離開的地方。
看似比較簡單,一會你會發現,函數代碼塊中可以調用其他函數,當程序流程運行到一個函數之中時,可能需要執行其他函數中的語句。但當執行那個函數中的語句時,又可能再需要調用執行另一個函數的語句。
幸好Python對于它運行到哪里有很好的記錄,所以在每個函數執行結束之后,程序都能跳回到它離開的那個地方,直到執行到整個程序的結尾,才會結束程序。
兩個片段解釋的很清楚,當我們執行到
pprint({'a':'1','b':'2','c':'5'})
python解釋器首先在當前命名空間內尋找變量pprint,非常驚喜他找到了,一個函數名為pprint,
OK,放棄尋找并且執行這個函數(如此我們引入的指向模塊的pprint變量就被忽略了),函數執行以后,在函數內部第一句
ehco = pprint.PrettyPrinter(width = 1 ,indent = 4)
我們的變量pprint這時已經指向了函數pprint,肯定就會報錯啦,沒有這個方法。
那么修改一下,這次執行成功了,分析一下吧。
import pprint
def pprint(dic_things,pprint = pprint):
? ? pprint = pprint.PrettyPrinter(width = 1 ,indent = 4)
? ? pprint.pprint(dic_things)
pprint({'a':'1','b':'2','c':'5'})
老套路,發現
pprint({'a':'1','b':'2','c':'5'})
在自己的命名空間找,發現特殊語句def pprint,很好執行這個函數,這里出現了我們解決問題的關鍵,pprint = pprint,這里這個給pprint賦值的pprint(好吧,只能這么繞口),為什么就是我們想要的那個身為moudule的pprint.呢?首先從縮進上判斷,他縮進為主行縮進(就是沒有縮進),也就是說他的命名空間和
pprint({'a':'1','b':'2','c':'5'})
一致,但是他又是與def pprint同行也就是同時創建,那么當解釋器準備解釋pprint = pprint這句話時,這個命名空間中并沒有(function)pprint這個變量!!!怎么辦,python解釋器就會向外層尋找,這樣就找到了我們的(module)pprint。
其實最好的辦法還是,引入時就把名字改了
import pprint as pprint_module
這樣就避免名字相同,我們還要用人腦去捋順他的程序執行套路,不過也好,這個坑清晰了兩個問題。
1.定義函數時,函數的參數的命名和函數體本身同級,并且他倆同時創建。
2.可以通過縮進來輔助確定一個變量的命名空間(不一定)
PYTHON的作用域由def、class、lambda等語句產生,if、try、for等語句并不會產生新的作用域。變量名引用分為三個作用域進行查找:首先是本地,然后是函數內(如果有的話),之后是全局,最后是內置。