python函數(二),python函數
今天繼續函數的講解:
目錄:
1.函數對象
2.函數嵌套
3.名稱空間和範圍
4.閉包
5.裝飾器
6.迭代器
7.產生器
8.內建函數
第一部分:函數對象
在python中,一切皆對象,想int,str,list,dict,tuple等等,所以函數也不例外,對象都具有屬性。作為對象,它可以賦值給其他函數名,也可以當作參數傳遞給其他函數。下面給一個例子:
1 def foo(): 2 print('hello,world') 3 4 #foo 這隻是一個函數名字,該名字指向foo()函數的記憶體位址 5 6 bar=foo #將foo()函數的地址賦值給bar,則bar也指向foo()函數的地址 7 8 bar() 9 10 結果:11 hello,world
再看一個例子,這個例子將函數作為參數傳遞給其他函數
1 def test(func): 2 func() #執行func函數。 3 4 def hello(): 5 print('hello,world') 6 7 test(hello) #將函數作為參數傳遞個test函數, 8 9 結果:10 hello,world第二部分:函數嵌套:
函數的嵌套就是在一個函數內部再定義一個或多個函數,並在調用外部函數的時候,外部函數接著調用內部的函數。類似下面的樣子:
1 def fun1(): #定義一個函數 2 a='txt' 3 def fun2(): #在原來的函數內再定義一個函數, 4 print(a) 5 fun2() 6 7 fun1() 8 9 10 結果:11 txt
先瞭解上面的定義方式,下面會用到函數嵌套的知識。
第三部分:名稱空間和範圍 1.名稱空間(namespace)
名稱空間就是變數名字與值的綁定關係。python中有三類名稱空間:局部名稱空間/全域名稱空間/內建名稱空間,下面對他們進行解釋:
局部名稱空間:每個函數或類都有的自己的名稱空間,這個自己的名稱空間就叫局部名稱空間,它隨著函數的執行和類的執行個體的調用而產生,在函數結束或類執行個體消亡後被刪除。它包括了函數的參數和定義的變數。
全域名稱空間:每個模組擁有自己的名稱空間,這個叫做全域名稱空間,它記錄了模組的變數,包括函數/類/其他匯入的模組/模組層級別的變數和常量
內建名稱空間:催著python解譯器運行而建立的產生的名稱空間,所有模組都可以訪問,它存放內建的函數和一些異常。
1 x=5 2 y=10 3 def change_x(): 4 x=10 #在局部名稱空間建立變數x 5 def print_xy(): 6 print(x,y) #首先在print_x的局部名稱空間找x,找不到的話從上層名稱空間尋找.從局部名稱空間找y,找不到到全域名稱空間找, 7 print_xy() 8 9 change_x()10 print(x,y) #列印全域名稱空間的x,從這裡看出局部名稱空間的x並沒有覆蓋全域名稱空間中的x11 12 #結果13 10,1014 5,10
從上面的例子,我們也可以得到在名稱空間中尋找變數的順序:
1.現在局部名稱空間尋找變數,若找到則使用該變數,停止搜尋;若未找到,則到上一層的局部名稱空間尋找。
2.在局部名稱空間找不到的情況下,到全域名稱空間尋找,如果找到則使用,放棄搜尋;若未找到,則到內建命名空間尋找
2.範圍:
範圍分兩類:
全域範圍:對應內建名稱空間和全域名稱空間
局部範圍:對應局部名稱空間
上面已經說過,變數名的尋找順序為:局部名稱空間->全域名稱空間->內建名稱空間
我們可以通過兩個內建函數查看範圍的變數:globals() 和locals(),其中globals()可以查看全域範圍中的變數,locals()可以查看局部範圍中的變數。下面一個例子:
1 x=1000 2 print(globals()) 3 4 結果: 5 {'__file__': 'D:/PycharmProjects/untitled8/user.py', 6 '__cached__': None, 7 '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000000009D6CC0>, 8 '__doc__': None, 9 '__name__': '__main__',10 '__package__': None, 11 '__spec__': None, 12 '__builtins__': <module 'builtins' (built-in)>, #這裡就是內建名稱空間的變數的引用13 'x': 1000 #我們在全域名稱空間聲明的變數x也可以看到14 }
下面看一個locals()的例子:
1 x=10002 def foo():3 x=1004 print(locals())5 6 foo()7 8 #結果9 {'x': 100} #這裡只有一個局部名稱空間中聲明的變數
在全域範圍聲明的變數在全域有效,在任何位置都可以訪問到,除非使用del刪除,否則一直存活,知道檔案執行完畢即python解譯器退出;
在局部範圍聲明的變數在局部有效,只能在局部範圍內訪問,只在函數調時的後有效,函數調用結束後失效
第四部分:閉包
閉包的定義:內建函式包含外部範圍而非對全域範圍的引用,該內建函式就是閉包:
1 x=1000 #聲明一個全域變數 2 def f1(): 3 x=10 #聲明一個局部變數 4 def f2(): #定義一個閉包函數 5 print(x) 6 return f2 #返回f2函數的記憶體位址 7 8 f=f1() #將f1函數的執行結果賦值給f.即f和f1都指向同一個記憶體位址 9 print(f)10 f() #調用函數f,間接調用函數f2()11 12 #結果13 <function f1.<locals>.f2 at 0x00000000006CE1E0> #從此處可以看到f1的內建函式f2的記憶體位址,也是f指向的地址14 10
包含__closure__方法的對象就成為閉包對象,我們通過下面的例子認識下:
1 x=2 2 def f1(): 3 y=2 4 def f2(): 5 print(x,y) 6 return f2 7 f=f1() 8 f() 9 print(f) #返回f指向的內容,即f2()函數的記憶體位址10 print(f.__closure__)11 print(dir(f.__closure__[0])) #__closure__ 返回閉包的指標12 print(f.__closure__[0].cell_contents) #cell_contents 返回閉包的結果13 14 #結果15 2 216 <function f1.<locals>.f2 at 0x00000000007DE1E0>17 (<cell at 0x00000000007A5D38: int object at 0x0000000052F301F0>,)18 ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__','__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',20 '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__','__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
22 '__subclasshook__', 'cell_contents']23 2
第五部分:裝飾器
裝飾器的概念:裝飾其他的函數,修飾添加功能。裝飾器本身可以是任何可調用的對象,被裝飾的對象是任何可調用的對象。
看下面的例子,我們已經開發了一個函數,現在想添加一個功能,在調用函數的時候,列印函數啟動並執行時間。完成的前提是不能修改我們之前寫好的函數,如下:
1 import time 2 def timer(fun): #將一個函數作為參數傳遞進來 3 def wrapper(): #閉包函數wrapper 4 start_time=time.time() 5 res=fun() #調用函數,並擷取傳回值 6 end_time=time.time() 7 print('%s' % (end_time-start_time)) 8 return res #將傳回值返回 9 return wrapper 10 11 @timer # 這句的功能類似這樣 index=wrapper(index)12 def index():13 time.sleep(3)14 print('welcome')15 return 116 17 res=index()18 print(res)19 20 21 結果:22 welcome23 3.01080536842346224 1
上面是一個沒有參數的裝飾器的應用,在原函數 ”index()“ 代碼沒有變動的情況下,實現了計算時間的功能,並且不影響對 index() 的函數的調用,非常方便。看了上面的例子,我們可以得出為什麼會使用裝飾器的原因:1,開放封閉原則:對程式的修改是封閉的,對程式的擴充是開放的;2,裝飾器就是為了在不修改被裝飾對象的原始碼以及調用當時的前提下,為其添加新功能。
再來看下面的一個例子有參數的裝飾器,該例子為原來的函數添加認證的功能:
1 def check_auth(args,kwargs): #從db檔案中擷取使用者名稱/密碼,如果正確,則返回True,否則返回False 2 res=[] 3 with open('db','r',encoding='utf-8') as f: 4 res=f.read().split() 5 for i in res: 6 if args[0]==i.split(',')[0] and args[1] == i.split(',')[1]: 7 return True 8 else: 9 return False10 11 12 def check_driver(driver='file'): #裝飾器成了三層,因為在調用裝飾器函數的時候有傳遞參數,所以需要在原來兩層裝飾器的基礎上在加一層13 def auth(func): #閉包函數14 def wrapper(*args,**kwargs): #閉包函數,如果原來函數接受參數,則將參數傳遞到閉包函數中,*args,**kwargs會從全域變數中找。15 if driver =='file':16 result=check_auth(args,kwargs)17 if result: #如果驗證通過,則允許存取使用者18 func(*args,**kwargs) #調用原來的函數。實現頁面的顯示19 elif driver == 'mysql':20 print('=========mysql==========')21 elif driver == 'ldap': #根據裝飾器獲得的參數進行不同的操作。這裡簡寫了22 print('==========ldap==========')23 return wrapper24 return auth25 26 @check_driver('file') #這裡的裝飾器添加了參數。27 def index():28 print('welcome %s your password is %s' % (name,password))29 30 31 name=input('Input username: ').strip()32 password=input('Input password:').strip()33 index(name,password) #函數調用處沒有改變
第六部分:迭代器
迭代器的概念:重複的過程稱為迭代,且每次迭代的結果作為下次的初始值。重點在重複,且本次迭代的結果作為下次迭代的初始值。
我們先來看一個便利數組的例子:
1 a=['a','b','c','d','e'] 2 3 for i in a: 4 print(i) 5 6 結果: 7 a 8 b 9 c10 d11 e
上面就是一個迭代的例子,重複讀取列表a的內容,並且下次從本次讀取的索引的下一個進行取數,並列印。
下面介紹兩個概念:可迭代對象,迭代器。
可迭代對象:實現了__iter__()方法的對象成為可迭代對象,向list,tuple,dict
迭代器:實現了__iter__()方法,並且實現了__next__()方法的對象就是迭代器。如file
這也就是說迭代器肯定是可迭代對象,而迭代對象不一定是迭代器。
既然已經有可迭代對象用來迭代資料了,那麼為什麼要有迭代器呢?答案是對於沒有索引的資料類型,必須提供一種不依賴於索引的迭代方式,即下面講到的next()方法調用。
迭代對象通過執行__iter__()方法,就會獲得一個迭代器:
1 a=list([1,2,3,4,5])2 b=a.__iter__()3 print(b.__next__()) #14 print(b.__next__()) #25 print(b.__next__()) #36 print(b.__next__()) #47 print(b.__next__()) #58 print(b.__next__()) #StopIteration
如上:對list執行__iter__()方法,產生一個迭代器,迭代器使用__next__()方法擷取下一個值,當迭代器中的值取完後,繼續取值會觸發一個StopIteration的異常。
那麼有沒有現成的函數說明一個對象是可迭代對象還是一個迭代器,有 collections模組的 Iterable,Iterator 方法:
1 from collections import Iterable,Iterator2 a=list([1,2,3,4,5]) #a是一個列表3 print(isinstance(a,Iterable)) #True4 print(isinstance(a,Iterator)) #False5 f=open('db',mode='r',encoding='utf-8') #f是一個檔案對象6 print(isinstance(f,Iterator)) #True7 print(isinstance(f,Iterable)) #True8 f.close()
其實,迭代器有一些優點和缺點,如下:
優點:1.提供一種不依賴下標的迭代方式
2.就迭代器本身來說,更節省記憶體,因為一次迭代一個值,不會因為原對象有大量的值而佔用大量記憶體
缺點:1.對於有下標的對象來說(如list,tuple),迭代器只能取一次下標的值,不能取多次同一下標的值,不如序列類型對象取值靈活
2.迭代器無法擷取迭代器對象長度
第七部分:產生器
概念:只要函數體包含yield關鍵字,則該函數就是產生器函數。下面舉一個例子:
1 def foo(): 2 print('one') 3 yield 1 4 print('two') 5 yield 2 6 print('three') 7 yield 3 8 print('for') 9 yield 410 11 g=foo()12 next(g) #輸出one,卡在 yield 1執行後就是說,列印’one',返回1,然後停止執行13 print(next(g)) #輸出two ,輸出傳回值2,再次停止
產生器很普通函數的最大區別就是普通函數執行return後函數立即終止了,下次調用函數還是從函數起始部分開始調用,而產生器在執行到yield那裡會返回數值,下載再次調用函數,函數從上次停止的地方繼續執行。
在看一個好玩的例子:
1 #輸出9x9乘法表 2 def get_num(li): 3 for i in li: 4 for j in li: 5 if i!=j: 6 yield i,j 7 def shiqi(): 8 a=['1','2','3','4','5','6','7','8'] 9 new_num=[]10 11 for i,j in get_num(a):12 new_num.append(i+j)13 print(new_num)
14 shiqi()
綜上:
yield的功能:
1.為函數封裝好__inter__和__next__方法
2.return只能返回一次值,函數就終止了;而產生器(yield)能返回多次值,每次返回將函數暫停,下次__next__會從上次暫停位置繼續執行
第八部分:內建函數
python的內建函數如下: