python學習——函式進階
首先來看下面這個函式。
1 def func(x,y): 2 bigger = x if x > y else y 3 return bigger 4 ret = func(10,20) 5 print(ret) 6 7 #執行結果 : 20
在上面的函式中我們把較大值通過return這個關鍵字返回回來了,如果我不返回而是直接列印可不可以?如下:
def func(x,y): bigger = x if x > y else y func(10,20) print(bigger) #執行結果 : NameError: name 'bigger' is not defined
此時它會說,bigger沒有定義,這是為什麼,在函式中我明明定義了bigger就是較大的那個數,那問題出在哪兒呢?
在這裡我們首先回憶一下python程式碼執行的時候遇到函式是怎麼做的。從python直譯器開始執行之後,就在記憶體中開闢了一個空間,每當遇到一個變數的時候,就把變數名和值之間的對應關係記錄下來。但是當遇到函式定義的時候直譯器只是象徵性的將函式名讀入記憶體,表示知道這個函式的存在了,至於函式內部的變數和邏輯直譯器根本不關心。等執行到函式呼叫的時候,python直譯器會再開闢一塊記憶體來儲存這個函式裡的內容,這個時候,才關注函式裡面有哪些變數,而函式中的變數會儲存在新開闢出來的記憶體中。函式中的變數只能在函式的內部使用,並且會隨著函式執行完畢,這塊記憶體中的所有內容也會被直譯器釋放了。
我們給這個“存放名字與值的關係”的空間起了一個名字——叫做名稱空間
程式碼在執行的時候,建立的儲存“變數名與值的關係”的空間叫做全域性名稱空間,在函式的執行中開闢的臨時的空間叫做區域性名稱空間
一、名稱空間和作用域
首先看一下這張圖:
這是python之禪中說的,名稱空間非常好!在python中名稱空間有以下幾種:
內建名稱空間
全域性名稱空間
區域性名稱空間
內建名稱空間中存放了python直譯器為我們提供的名字:input,print,str,list,tuple...它們都是我們熟悉的,拿過來就可以用的方法。
三種名稱空間之間的載入與取值順序:
載入順序:內建名稱空間(程式執行前載入)->全域性名稱空間(程式執行中:從上到下載入)->區域性名稱空間(程式執行中:呼叫時才載入)
取值:
在區域性呼叫:區域性名稱空間->全域性名稱空間->內建名稱空間
1 a = 1 2 b = 2 3 def func(): 4 print(a) 5 print(b) 6 func() 7 print(10) 8 9 #執行結果 :1,2,10在區域性使用變數取值情況
所以存在這種情況:當呼叫函式時,函式被執行,但是函式內部(區域性)並沒有a,b這兩個值,只能道函式外面(全域性)來找,找到了,就答應,而之後列印的10在全域性中就直接找到了。
在全域性呼叫:全域性名稱空間->內建名稱空間
1 a = 1 2 b = 2 3 def func(a,b): 4 print(a) 5 print(b) 6 7 func(10,20) 8 print(a,b) 9 10 # 執行結果:10,20,1 2在全域性引用變數
此時對a,b傳值了,所以首先答應出來,在全域性再次列印a,b的時候,函式內部的a,b在函式執行完畢之後就被釋放了,所以只能列印全域性a,b的值;還有即使a,b沒被釋放,在全域性也不能列印區域性變數的值!
1 print(max(1,2,3,3)) 2 3 #結果:3
所以三種名稱空間有以下這種關係:
所以他們的關係是這樣的,內建名稱空間在直譯器一開啟就被載入到記憶體中了,在定義函式之前的所有變數都是全域性名稱空間中的變數,而在函式內部的所有變數都是區域性名稱空間中的變數,當然只有函式被呼叫時才被載入到記憶體中,但隨著函式執行完畢就被自動釋放了。
二、函式的巢狀和作用鏈域
函式的巢狀呼叫
1 def func1(a,b): 2 return a if a > b else b 3 4 def func2(x,y,z): 5 ret1 = func1(x,y) 6 ret2 = func1(ret1,z) 7 return ret2 8 9 ret = func2(1,2,3) 10 print(ret) 11 12 #執行結果:3函式的巢狀呼叫
函式的巢狀定義
1 # 函式的巢狀定義 一 2 3 def func1(): 4 print('in func1 now') 5 def func2(): 6 print('in func2 now') 7 func2() 8 9 func1() 10 11 # 函式的巢狀定義 二 12 13 def func1(): 14 def func2(): 15 def func3(): 16 print('in func3 now') 17 func3() 18 print('in func2 now') 19 func2() 20 print('in func1 now') 21 func1()函式的巢狀定義
函式的作用鏈域
1 # 函式的作用鏈域 一 2 def func1(): 3 a = 1 4 def func2(): 5 print(a) 6 func2() 7 func1() 8 9 # 執行結果:1 10 11 # 函式的作用鏈域 二 12 def func1(): 13 a = 100 14 def func2(): 15 def func3(): 16 print(a) 17 func3() 18 func2() 19 20 func1() 21 22 # 執行結果:100 23 24 # 函式的作用鏈域 三 25 def func1(): 26 a = 1000 27 def func2(): 28 a = 10000 29 def func3(): 30 print('a in func1 ',a) 31 func3() 32 func2() 33 34 func1() 35 36 # 執行結果:a in func1 10000函式的作用鏈域
nonlocal 關鍵字
1.外部必須有這個變數
2.在內部函式宣告nonlocal變數之前不能再出現同名變數
3.內部修改這個變數如果想在外部有這個變數的第一層函式中生效
1 def func1(): 2 a = 1 3 def func2(): 4 nonlocal a 5 a = 2 6 func2() 7 print(a) 8 func1() 9 10 # 執行結果:2nonlocal 關鍵字
三、函式名的本質
函式名本質上就是一個函式的記憶體地址(函式即變數)
1.可以被引用
1 def func(): 2 print('in func now ') 3 4 ret = func 5 print(ret) 6 7 # 執行結果:<function func at 0x0000023C04CDC730>函式名可以被引用
當列印函式名的時候返回的是一個記憶體地址。
2.可以被當作容器型別的元素
1 def func1(): 2 print('func1') 3 4 def func2(): 5 print('func2') 6 7 def func3(): 8 print('func3') 9 10 l = [func1,func2,func3] 11 d = {'func1':func1,'func2':func2,'func3':func3} 12 #呼叫 13 l[0] 14 print(l[0]) 15 l[0]() 16 print(d['func2']) 17 d['func2']() 18 19 #執行結果:<function func1 at 0x000001F345C0C7B8> func1 <function func2 at 0x000001F345C0C840> func2函式名可以被當作容器型別的元素
當我們不呼叫時(不在後面加上“()”),返回函式名所在的記憶體地址,加上之後返回函式值。
3.可以當作函式的引數和返回值(就是把函式名當作普通變數來用)
第一類物件(first-class object)指 1.可在執行期建立 2.可用作函式引數或返回值 3.可存入變數的實體。
四、閉包函式
1、首先說什麼是閉包
1 def func1(): 2 name = 'liulonghai' 3 def func2(): 4 print(name)
2、閉包函式
內部函式包含對外部作用域而非全域性作用域名字的引用,該內部函式稱為閉包函式 #函式內部定義的函式稱為內部函式
判斷一個函式是否是閉包可以這樣寫
1 def func1(): 2 name = 'liulonghai' 3 def func2(): 4 print(name) 5 print(func2.__closure__) 6 print(func1.__closure__) 7 func2() 8 9 func1() 10 11 #執行結果:liulonghai 12 # (<cell at 0x000001B30E5F0108: function object at 0x000001B31163D7B8>, 13 # <cell at 0x000001B3103A7378: str object at 0x000001B3103A0370>) 14 # None判斷一個函式是否是閉包函式
可以通過(__closure__)這個雙下方法來檢視一個函式名是不是閉包,當打印出"(<cell at 0x000001B30E5F0108: function object at 0x000001B31163D7B8>, <cell at 0x000001B3103A7378: str object at 0x000001B3103A0370>)" 這樣就表面此函式是一個閉包,其實就是‘cell’ ,如果不是,則返回None
由於有了作用域的關係,我們就不能拿到函式內部的變數和函數了。如果我們就是想拿怎麼辦呢?返回?我們都知道函式內的變數我們要想在函式外部用,可以直接返回這個變數,那麼如果我們想在函式外部呼叫函式內部的函式呢?是不是直接就把這個函式的名字返回就好了?
這才是閉包函式最常用的用法
1 def func(): 2 name = 'liulonghai' 3 def inner(): 4 print(name) 5 6 return inner 7 8 f = func() 9 f() 10 11 #執行結果:liulonghai閉包函式最常用的形式
閉包函式的巢狀
1 def wrapper(): 2 name = 'liulonghai' 3 def outter(): 4 money = 1000000 5 def inner(): 6 print(name,money) 7 return inner 8 return outter 9 10 w = wrapper() 11 o = w() 12 o() 13 14 #執行結果:liulonghai 1000000閉包函式的巢狀
閉包函式獲取網頁
1 from urllib.request import urlopen 2 3 def index(): 4 url = "http://www.baidu.com" 5 def get(): 6 return urlopen(url).read() 7 return get 8 9 baidu = index() 10 content = baidu() 11 print(content)閉包函式獲取網頁資訊