今天繼續函數的講解: 目錄: 1.函數對象 2.函數嵌套 3.名稱空間和作用域 4.閉包 5.裝飾器 6.迭代器 7.生成器 8.內置函數 第一部分:函數對象 在python中,一切皆對象,想int,str,list,dict,tuple等等,所以函數也不例外,對象都具有屬性。作為對象,它可以賦值給 ...
今天繼續函數的講解:
目錄:
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並沒有覆蓋全局名稱空間中的x 11 12 #結果 13 10,10 14 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=1000 2 def foo(): 3 x=100 4 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 2 16 <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 1 16 17 res=index() 18 print(res) 19 20 21 結果: 22 welcome 23 3.010805368423462 24 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 False 10 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 wrapper 24 return auth 25 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 c 10 d 11 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__()) #1 4 print(b.__next__()) #2 5 print(b.__next__()) #3 6 print(b.__next__()) #4 7 print(b.__next__()) #5 8 print(b.__next__()) #StopIteration
如上:對list執行__iter__()方法,生成一個迭代器,迭代器使用__next__()方法獲取下一個值,當迭代器中的值取完後,繼續取值會觸發一個StopIteration的異常。
那麼有沒有現成的函數說明一個對象是可迭代對象還是一個迭代器,有 collections模塊的 Iterable,Iterator 方法:
1 from collections import Iterable,Iterator 2 a=list([1,2,3,4,5]) #a是一個列表 3 print(isinstance(a,Iterable)) #True 4 print(isinstance(a,Iterator)) #False 5 f=open('db',mode='r',encoding='utf-8') #f是一個文件對象 6 print(isinstance(f,Iterator)) #True 7 print(isinstance(f,Iterable)) #True 8 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 4 10 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的內置函數如下: