日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網營銷解決方案
下次別用遞歸了,試試閉包吧!

遞歸函數使用起來非???,簡潔優(yōu)雅,可以用來炫耀編程技巧。但是,在大多數情況下,遞歸函數具有非常高的時間和空間復雜性,我們應該避免使用它。更好的解決方案之一是在可能的情況下使用動態(tài)規(guī)劃,對于能夠分解為子問題的問題,動態(tài)規(guī)劃可能是最佳方法。然而某些動態(tài)規(guī)劃的狀態(tài)轉移方程不太容易定義。

今天分享 Python 的另一種牛的技術--閉包,可以用來作為替代遞歸函數。它可能不會勝過動態(tài)規(guī)劃,但在思考方面要容易得多。換句話說,由于思想的抽象,我們有時可能難以使用動態(tài)規(guī)劃,但是使用閉包會容易一些。

什么是 Python 閉包?

首先,讓我使用一個簡單的示例來說明什么是 Python 中的閉包??聪旅娴暮瘮担?/p>

 
 
 
 
  1. def outer(): 
  2.     x = 1 
  3.     def inner(): 
  4.         print(f'x in outer function: {x}') 
  5.     return inner 

在一個函數內部定義另外一個函數,并返回這個函數,這種特性就是閉包。檢查 outer 函數的返回值,可以確認這是一個函數。

 
 
 
 
  1. >>> def outer(): 
  2. ...     x = 1 
  3. ...     def inner(): 
  4. ...         print(f'x in outer function: {x}') 
  5. ...     return inner 
  6. ... 
  7. >>> outer 
  8.  
  9. >>> outer() 
  10. .inner at 0x7fb2ecdaca60> 
  11. >>> 

閉包這種特性能做什么呢?因為函數返回的是一個函數,我們就可以調用這個函數,比如:

 
 
 
 
  1. >>> outer()() 
  2. x in outer function: 1 
  3. >>> 

不過我們一般會這么使用閉包,這樣太丑陋了。你可能會好奇這個跟遞歸有什么關系?別著急,讓我們慢慢體會閉包的牛逼之處。

閉包內的變量訪問

從前述的運行結果來看,inner 函數可以訪問 outer 函數內部定義的變量 x,但是卻無法修改它,下面的代碼運行時會報錯:

 
 
 
 
  1. >>> def outer(): 
  2. ...     x = 1 
  3. ...     def inner(): 
  4. ...         print(f'x in outer function (before modifying): {x}') 
  5. ...         x += 1 
  6. ...         print(f'x in outer function (after modifying): {x}') 
  7. ...     return inner 
  8. ... 
  9. >>> f = outer() 
  10. >>> f() 
  11. Traceback (most recent call last): 
  12.   File "", line 1, in  
  13.   File "", line 4, in inner 
  14. UnboundLocalError: local variable 'x' referenced before assignment 
  15. >>> 

為了解決這個問題,我們可以加上 nonlocal 關鍵字,告訴 inner 函數,這不是一個本地變量:

 
 
 
 
  1. >>> def outer(): 
  2. ...     x = 1 
  3. ...     def inner(): 
  4. ...         nonlocal x 
  5. ...         print(f'x in outer function (before modifying): {x}') 
  6. ...         x += 1 
  7. ...         print(f'x in outer function (after modifying): {x}') 
  8. ...     return inner 
  9. ... 
  10. >>> 
  11. >>> f = outer() 
  12. >>> f() 
  13. x in outer function (before modifying): 1 
  14. x in outer function (after modifying): 2 
  15. >>> f() 
  16. x in outer function (before modifying): 2 
  17. x in outer function (after modifying): 3 
  18. >>> f() 
  19. x in outer function (before modifying): 3 
  20. x in outer function (after modifying): 4 
  21. >>> 

有沒有發(fā)現,x 的值竟然被保存了下來,每次調用一下,就增加了 1,這就是閉包的妙處。

用閉包來替換遞歸

利用上述閉包會保留調用結果的特性,我們可以用這個來替換遞歸,比如利用閉包計算斐波那契數列:

 
 
 
 
  1. def fib(): 
  2.     x1 = 0 
  3.     x2 = 1 
  4.     def get_next_number(): 
  5.         nonlocal x1, x2 
  6.         x3 = x1 + x2 
  7.         x1, x2 = x2, x3 
  8.         return x3 
  9.     return get_next_number 

可以這樣調用來生產斐波那契數列:

 
 
 
 
  1. >>> def fib(): 
  2. ...     x1 = 0 
  3. ...     x2 = 1 
  4. ...     def get_next_number(): 
  5. ...         nonlocal x1, x2 
  6. ...         x3 = x1 + x2 
  7. ...         x1, x2 = x2, x3 
  8. ...         return x3 
  9. ...     return get_next_number 
  10. ... 
  11. >>> fibonacci = fib() 
  12. >>> for i in range(2, 21): 
  13. ...     num = fibonacci() 
  14. ...     print(f'The {i}th Fibonacci number is {num}') 
  15. ... 
  16. The 2th Fibonacci number is 1 
  17. The 3th Fibonacci number is 2 
  18. The 4th Fibonacci number is 3 
  19. The 5th Fibonacci number is 5 
  20. The 6th Fibonacci number is 8 
  21. The 7th Fibonacci number is 13 
  22. The 8th Fibonacci number is 21 
  23. The 9th Fibonacci number is 34 
  24. The 10th Fibonacci number is 55 
  25. The 11th Fibonacci number is 89 
  26. The 12th Fibonacci number is 144 
  27. The 13th Fibonacci number is 233 
  28. The 14th Fibonacci number is 377 
  29. The 15th Fibonacci number is 610 
  30. The 16th Fibonacci number is 987 
  31. The 17th Fibonacci number is 1597 
  32. The 18th Fibonacci number is 2584 
  33. The 19th Fibonacci number is 4181 
  34. The 20th Fibonacci number is 6765 
  35. >>> 

而使用遞歸方法計算斐波那契數列的方法如下所示:

 
 
 
 
  1. def fib_recursion(n:int) -> int: 
  2.     if n <= 1: 
  3.         return n 
  4.     return fib_recursion(n-1) + fib_recursion(n-2) 

把之前的閉包版本封裝一下:

 
 
 
 
  1. def fib(): 
  2.     x1 = 0 
  3.     x2 = 1 
  4.     def get_next_number(): 
  5.         nonlocal x1, x2 
  6.         x3 = x1 + x2 
  7.         x1, x2 = x2, x3 
  8.         return x3 
  9.     return get_next_number 
  10.  
  11. def fib_closure(n): 
  12.     f = fib() 
  13.     for i in range(2, n+1): 
  14.         num = f() 
  15.     return num 

這樣使用 fib_closure(20) 就可以計算出結果:

 
 
 
 
  1. In [4]: fib_closure(20) 
  2. Out[4]: 6765 
  3.  
  4. In [5]: fib_recursion(20) 
  5. Out[5]: 6765 
  6.  
  7. In [6]: 

現在使用 IPython 來測試下這兩者的性能:

 
 
 
 
  1. In [6]: %time fib_closure(20) 
  2. CPU times: user 10 μs, sys: 1e+03 ns, total: 11 μs 
  3. Wall time: 14.1 μs 
  4. Out[6]: 6765 
  5.  
  6. In [7]: %time fib_recursion(20) 
  7. CPU times: user 2.76 ms, sys: 15 μs, total: 2.78 ms 
  8. Wall time: 2.8 ms 
  9. Out[7]: 6765 

可以看出兩差相差近 1000 倍,這還只是計算到第 20 個數的情況下,如果計算到 100,那使用遞歸會計算很久甚至無法計算出來。

閉包的其他用處

Python 的閉包不僅僅用于替換遞歸,還有很多場景可以使用閉包。比如學生成績的分類函數:

學生成績數據:

 
 
 
 
  1. students = { 
  2.     'Alice': 98, 
  3.     'Bob': 67, 
  4.     'Chris': 85, 
  5.     'David': 75, 
  6.     'Ella': 54, 
  7.     'Fiona': 35, 
  8.     'Grace': 69 

現在需要根據學生成績進行分類,通常情況下我們會寫多個函數來進行分類,而分類的標準又會經常變化,這時候閉包就很方便了:

 
 
 
 
  1. def make_student_classifier(lower_bound, upper_bound): 
  2.     def classify_student(exam_dict): 
  3.         return {k:v for (k,v) in exam_dict.items() if lower_bound <= v < upper_bound} 
  4.     return classify_student 
  5.  
  6. grade_A = make_student_classifier(80, 100) 
  7. grade_B = make_student_classifier(70, 80) 
  8. grade_C = make_student_classifier(50, 70) 
  9. grade_D = make_student_classifier(0, 50) 

如果分類標準變化,直接個性函數的參數即可,主要代碼邏輯不變,如果想查找成績分類為 A 的學生,只需要調用 grade_A(students) 即可:

 
 
 
 
  1. In [13]: grade_A(students) 
  2. Out[13]: {'Alice': 98, 'Chris': 85} 

閉包使用上述分類函數很容易修改且更加易讀。

最后的話

本文介紹了一種稱為 Python 閉包的技術。在大多數情況下,可以使用它來重寫遞歸函數,并且在很大程度上優(yōu)于后者。

實際上,從性能的角度來看,閉包可能不是某些問題的最佳解決方案,尤其是在使用動態(tài)規(guī)劃的情況下。但是,閉包寫起來要容易一些,比遞歸性能高。當我們對性能不是很敏感時,有時寫動態(tài)計劃會有點浪費時間,但是閉包可能就足夠了。

閉包也可以用來定義一些邏輯相同但命名不同的函數,比如本文中的分類函數,在這些情況下,它更加整潔而優(yōu)雅,更加易讀。

下次試試閉包吧,別用效率低下的遞歸了。

本文轉載自微信公眾號「Python七號」,可以通過以下二維碼關注。轉載本文請聯(lián)系Python七號公眾號。


分享名稱:下次別用遞歸了,試試閉包吧!
網站路徑:http://www.5511xx.com/article/cohghoj.html