第二模塊:03python學習之函數進階
1.名稱空間
定義:相比上一節的作用域,名稱空間更能解釋。名稱空間又名name space, 顧名思義就是存放名字的地方,存什麽名字呢?舉例說明,若變量x=1,1存放於內存中,那名字x存放在哪裏呢?名稱空間正是存放名字x與1綁定關系的地方。
名稱空間分以下三種:
- locals:是函數內的名稱空間,包括局部變量和形參,當前函數空間內
- globals:全局變量,函數定義所在模塊的空間
- bulildings:內置模塊的名稱空間
針對作用域的查找順序,遵循LEGB的規則
- L:LOCALS 本地的
- E:ENCLOSING 相鄰的
- G:GLOBLS 全局的
- B:BUILTINGS 內置函數的
函數在調用變量時,遵循從上到下,逐層向下獲取變量,LEGB 代表名字查找順序: locals -> enclosing function -> globals -> __builtins__
看以下代碼
level = ‘L0‘ n = 22 def func(): level = ‘L1‘ n = 33 print(locals()) def outer(): n = 44 level = ‘L2‘ print(locals(),n) def inner(): level = ‘L3‘ print(locals(),n) #此外打印的n是多少? inner() outer() func()View Code
2.閉包
關於閉包可以這麽定義:即函數定義和函數表達式位於另一個函數的函數體內(嵌套函數)。而且,這些內部函數可以訪問它們所在的外部函數中聲明的所有局部變量、參數。當其中一個這樣的內部函數在包含它們的外部函數之外被調用時,就會形成閉包。也就是說,內部函數會在外部函數返回後被執行。而當這個內部函數執行時,它仍然必需訪問其外部函數的局部變量、參數以及其他內部函數。這些局部變量、參數和函數聲明(最初時)的值是外部函數返回時的值,但也會受到內部函數的影響。
是不是覺得很麻煩,繞來繞去,先看代碼
def outer(): name = ‘alex‘ def inner():print("在inner裏打印外層函數的變量",name) return inner f = outer() f()
你會發現在與普通的嵌套函數不同的是子函數返回的是函數自己,而不是其他變量什麽的。這個時候可以這麽定義,在外部可以調用內部函數,同時使用內部的值這個就可以稱之為閉包。雖然函數內部的子函數返回子函數自身,在返回的同時,不止是一個函數對象,同時在外面包裹了一層作用域,使得函數在調用的時候優先使用外部的作用域。
3.裝飾器***
對於程序開發來說,我們一般需要遵守‘開放封閉原則’,所謂開放就是說程序有較好的拓展性以及延展性,因為對於一個程序的功能不可能永遠不變,會各種各樣的拓展功能,例如說我們登錄一個程序,以前只有賬戶密碼驗證,後面鑄件出現了QQ驗證,微信認證,微博驗證,這就要求我們的程序可以較好的拓展。而所謂的封閉就是說已經完成的程序代碼最好不動,有可能的是這個操作會發生巨大的工作量,涉及到不同的部門或者不同的流程這樣就會有巨大的變更動作,也有可能是代碼時間已經很久遠,不好重新構建。下面用幾個例子來說明這個事情。
你所在的部門屬於基礎支撐部門,產生核心代碼,其他部門代碼從你這邊調取
你的核心代碼如下
############### 基礎平臺提供的功能如下 ############### def f1(): print ‘f1‘ def f2(): print ‘f2‘ def f3(): print ‘f3‘ def f4(): print ‘f4‘ ############### 業務部門A 調用基礎平臺提供的功能 ############### f1() f2() f3() f4() ############### 業務部門B 調用基礎平臺提供的功能 ############### f1() f2() f3() f4()
這個時候老板要求,核心代碼隨意調用有可能泄密,要求調用的時候需要賬戶密碼驗證。對於初學者我們有可能會想到這樣,對每一個被調用的函數進行重編
代碼如下
############### 基礎平臺提供的功能如下 ############### def f1(): # 驗證1 # 驗證2 # 驗證3 print ‘f1‘ def f2(): # 驗證1 # 驗證2 # 驗證3 print ‘f2‘ def f3(): # 驗證1 # 驗證2 # 驗證3 print ‘f3‘ def f4(): # 驗證1 # 驗證2 # 驗證3 print ‘f4‘ ############### 業務部門不變 ############### ### 業務部門A 調用基礎平臺提供的功能### f1() f2() f3() f4() ### 業務部門B 調用基礎平臺提供的功能 ### f1() f2() f3() f4()
但是這種情況,我們就發現重復會很多很多,這只是三四個模塊,如果多呢?這樣就太多重復代碼了,有可能說我這樣功能實現了呀,但是作為一個程序員應該是想著用最簡潔的語句完成最多的事情,不過我們有可能在學了一點,想到以下的這種方式。
############### 基礎平臺提供的功能如下 ############### def check_login(): # 驗證1 # 驗證2 # 驗證3 pass def f1(): check_login() print ‘f1‘ def f2(): check_login() print ‘f2‘ def f3(): check_login() print ‘f3‘ def f4(): check_login() print ‘f4‘
專門做一個認證函數,每個基礎模塊做修改調用一次認證函數,認證通過就可以繼續下去,但是我們這樣還是違背了開放封閉原則,對源代碼做了修改,最好的辦法就是認證一次後不需要在此認證。比較高級的代碼就是下面這個,後面會對這個代碼說明。
def w1(func): def inner(): # 驗證1 # 驗證2 # 驗證3 return func() return inner @w1 def f1(): print ‘f1‘ @w1 def f2(): print ‘f2‘ @w1 def f3(): print ‘f3‘ @w1 def f4(): print ‘f4‘
解釋上面的代碼,對於python程序是由上至下執行,首先返回倆個值
return fi()直接執行f1函數
return inner返回inner函數,並沒有執行
沒錯,從表面上看解釋器僅僅會解釋這兩句代碼,因為函數在沒有被調用之前其內部代碼不會被執行。
從表面上看解釋器著實會執行這兩句,但是 @w1 這一句代碼裏卻有大文章,@函數名 是python的一種語法糖。
如上例@w1內部會執行一下操作:
- 執行w1函數,並將 @w1 下面的 函數 作為w1函數的參數,即:@w1 等價於 w1(f1)
所以,內部就會去執行:
def inner:
#驗證
return f1() # func是參數,此時 func 等於 f1
return inner # 返回的 inner,inner代表的是函數,非執行函數
其實就是將原來的 f1 函數塞進另外一個函數中 - 將執行完的 w1 函數返回值賦值給@w1下面的函數的函數名
w1函數的返回值是:
def inner:
#驗證
return 原來f1() # 此處的 f1 表示原來的f1函數
然後,將此返回值再重新賦值給 f1,即:
新f1 = def inner:
#驗證
return 原來f1()
所以,以後業務部門想要執行 f1 函數時,就會執行 新f1 函數,在 新f1 函數內部先執行驗證,再執行原來的f1函數,然後將 原來f1 函數的返回值 返回給了業務調用者。
如此一來, 即執行了驗證的功能,又執行了原來f1函數的內容,並將原f1函數返回值 返回給業務調用著
所以我們可以這麽理解,裝飾器只是作為一個語法糖包含在函數外面,將函數作為參數傳遞給認證函數執行,對於調用者而言其實是執行,調用的f1(),其實是調用的inner函數,然後做認證,執行真正f1()函數
對於裝飾器還有其他
帶參數
#單參數 def w1(func): def inner(arg): # 驗證1 # 驗證2 # 驗證3 return func(arg) return inner @w1 def f1(arg): print ‘f1‘ 一個參數 #倆個參數 def w1(func): def inner(arg1,arg2): # 驗證1 # 驗證2 # 驗證3 return func(arg1,arg2) return inner @w1 def f1(arg1,arg2): print ‘f1‘ 兩個參數 #多個參數 def w1(func): def inner(*args,**kwargs): # 驗證1 # 驗證2 # 驗證3 return func(*args,**kwargs) return inner @w1 def f1(arg1,arg2,arg3): print ‘f1‘
雙裝飾器
def w1(func): def inner(*args,**kwargs): # 驗證1 # 驗證2 # 驗證3 return func(*args,**kwargs) return inner def w2(func): def inner(*args,**kwargs): # 驗證1 # 驗證2 # 驗證3 return func(*args,**kwargs) return inner @w1 @w2 def f1(arg1,arg2,arg3): print ‘f1‘
其他類型的需要後面再添加
4.列表生成器
現在有個需求,看列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
,要求你把列表裏的每個值加1,你怎麽實現?你可能會想到2種方式
第一種
a = [1,3,4,6,7,7,8,9,11] b = [] for i in a:b.append(i+1) a = b
第二種
b = [] >>> for i in a:b.append(i+1) >>> a = b
稍微裝逼一點的
a =[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] a = map(lambda x:x+1, a) for i in a:print(i)
最裝逼的就是下面這種了
a = [i+1 for i in range(10)]
直接一行代碼搞定,減少代碼量,這種就叫做列表生成式
5.生成器
通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素占用的空間都白白浪費了。
所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出後續的元素呢?這樣就不必創建完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。
要創建一個generator,有很多種方法。
第一種方法很簡單,只要把一個列表生成式的[]
改成(),
就創建了一個generator:
第二種方法,yield 把函數變成生成器
5.1看以下代碼
list1 = [i for i in range(100)] list1 =[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 , 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
我們很容易就用列表生成式生成一個100元素的列表,那麽如果要生成一個一個出現的額生成式,只需要將[]變為()就可以了,看以下代碼
>>> li = (i for i in range(100)) >>> li <generator object <genexpr> at 0x0220C6E8>
對於一個生成式獲取他下一個元素就有倆種方式next(li) 和 li.__next__(),就可以輕松獲取到下一個元素
不過需要註意的是,在調用next()的函數的時候,在最後一個位置會拋出一個錯誤,必須記住生成器只能往前,不能後退,也不可以切片,走到最後直接報錯
>>> next(li) 8 >>> next(li) 9 >>> next(li) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration #每次調用next(g)就計算出g的下一個元素的值,直到計算到最後一個元素,沒有更多的元素時,拋出StopIteration的錯誤。
range對於python2與3的區別
在python3中,range和文件的打開也是生成器
5.2用yield語法生成一個生成器
如果一個函數定義中包含yield
關鍵字,那麽這個函數就不再是一個普通函數,而是一個generator
def fib(max): n,a,b = 0,0,1 while n < max: #print(b) yield b a,b = b,a+b n += 1 return ‘done‘ >>> f = fib(6) >>> f <generator object fib at 0x104feaaa0>
yield與return的區別
return返回並中止function
yield返回數據,並凍結當前進程
next 喚醒凍結的函數執行過程,繼續執行直到下一個yield
return在生成器中,代表生成器的中止,會直接報錯
5.3生成器send
例子:執行到yield時,gen函數作用暫時保存,返回i的值;temp接收下次c.send("python"),send發送過來的值,c.next()等價c.send(None)
def fib(max): a, b, n = 0, 1, 0 while n < max: send_msg = yield b print(send_msg) a, b = b, a+b n += 1 f = fib(10) print(next(f)) f.send(‘寄個快遞‘) print(next(f))
f.send(None) #第一次相當於f.__next__()
總結:
生成器是這樣一個函數,它記住上一次返回時在函數體中的位置。對生成器函數的第二次(或第 n 次)調用跳轉至該函數中間,而上次調用的所有局部變量都保持不變。
生成器不僅“記住”了它數據狀態;生成器還“記住”了它在流控制構造(在命令式編程中,這種構造不只是數據值)中的位置。
生成器的特點:
- 節約內存
- 叠代到下一次的調用時,所使用的參數都是第一次所保留下的,即是說,在整個所有函數調用的參數都是第一次所調用時保留的,而不是新創建的
6.叠代器
我們已經知道,可以直接作用於for
循環的數據類型有以下幾種:
- 一類是集合數據類型,如
list
、tuple
、dict
、set
、str
等; - 一類是
generator
,包括生成器和帶yield
的generator function。
這些可以直接作用於for
循環的對象統稱為可叠代對象:Iterable
。
可以使用isinstance()
判斷一個對象是否是Iterable
對象:
>>> from collections import Iterable >>> isinstance([], Iterable) True >>> isinstance({}, Iterable) True >>> isinstance(‘abc‘, Iterable) True >>> isinstance((x for x in range(10)), Iterable) True >>> isinstance(100, Iterable) False
而生成器不但可以作用於for循環,還可以被next()函數不斷調用並返回下一個值,直到最後拋出StopIteration錯誤表示無法繼續返回下一個值了。
*可以被next()函數調用並不斷返回下一個值的對象稱為叠代器:Iterator。
可以使用isinstance()判斷一個對象是否是Iterator對象:
>>> from collections import Iterator >>> isinstance((x for x in range(10)), Iterator) True >>> isinstance([], Iterator) False >>> isinstance({}, Iterator) False >>> isinstance(‘abc‘, Iterator) False
生成器都是Iterator
對象,但list
、dict
、str
雖然是Iterable
,卻不是Iterator
。
把list
、dict
、str
等Iterable
變成Iterator
可以使用iter()
函數:
>>> isinstance(iter([]), Iterator) True >>> isinstance(iter(‘abc‘), Iterator) True
總結
凡是可作用於for
循環的對象都是Iterable
類型;
凡是可作用於next()
函數的對象都是Iterator
類型,它們表示一個惰性計算的序列;
集合數據類型如list
、dict
、str
等是Iterable
但不是Iterator
,不過可以通過iter()
函數獲得一個Iterator
對象。
Python3的for
循環本質上就是通過不斷調用next()
函數實現的,例如:
for x in [1, 2, 3, 4, 5]:
pass
實際上完全等價於:
# 首先獲得Iterator對象:
it = iter([1, 2, 3, 4, 5])
# 循環:
while True:
try:
# 獲得下一個值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循環
break
第二模塊:03python學習之函數進階