成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

下次別用遞歸了,試試閉包吧!

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

 [[385610]]

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

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

什么是 Python 閉包?

首先,讓我使用一個簡單的示例來說明什么是 Python 中的閉包。看下面的函數:

  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. <function outer at 0x7fb2ecdac9d0> 
  9. >>> outer() 
  10. <function outer.<locals>.inner at 0x7fb2ecdaca60> 
  11. >>> 

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

  1. >>> outer()() 
  2. 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 "<stdin>", line 1, in <module> 
  13.   File "<stdin>", 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. in outer function (before modifying): 1 
  14. in outer function (after modifying): 2 
  15. >>> f() 
  16. in outer function (before modifying): 2 
  17. in outer function (after modifying): 3 
  18. >>> f() 
  19. in outer function (before modifying): 3 
  20. in outer function (after modifying): 4 
  21. >>> 

有沒有發現,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 閉包的技術。在大多數情況下,可以使用它來重寫遞歸函數,并且在很大程度上優于后者。

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

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

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

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

 

責任編輯:武曉燕 來源: Python七號
相關推薦

2021-09-09 21:10:23

Lite-XL編輯器Lua

2020-04-08 17:53:40

TypeScriptJavaScript代碼

2025-05-15 03:00:00

2024-01-22 09:51:32

Swift閉包表達式尾隨閉包

2021-01-07 10:15:55

開發 Java開源

2021-05-11 07:10:18

標準庫DjangoOS

2021-02-21 16:21:19

JavaScript閉包前端

2020-04-03 14:25:55

diff Meld工具

2024-06-19 10:01:50

2020-10-14 15:15:28

JavaScript(

2011-05-25 14:48:33

Javascript閉包

2009-07-22 07:43:00

Scala閉包

2013-05-02 09:44:57

PHP閉包

2016-10-27 19:26:47

Javascript閉包

2019-11-07 21:51:18

閉包前端函數

2024-03-11 08:21:49

2022-06-17 11:10:43

PandasPolarsPython

2020-12-02 08:31:47

Elasticsear

2023-11-02 08:53:26

閉包Python

2024-01-23 13:20:00

分庫分表分布式
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产成人亚洲精品 | 免费观看毛片 | 古装人性做爰av网站 | 欧美日韩精品在线一区 | 欧美一级二级视频 | 国产精品爱久久久久久久 | 国产激情在线 | 欧美一区二区三区大片 | 日本亚洲一区 | 国产精品日韩欧美 | 国产精品一区二区三 | 色婷婷av99xx | 日韩电影一区 | 日韩中文字幕久久 | 亚洲国产精品久久久久久 | 国产女人与拘做视频免费 | 国产精品美女久久久久久久久久久 | 国产一区中文字幕 | 久久里面有精品 | 精品在线观看入口 | 欧美精品a∨在线观看不卡 欧美日韩中文字幕在线播放 | 免费一区二区三区 | 国产精品99久久久久久动医院 | 欧美偷偷操 | 麻豆国产一区二区三区四区 | 久久精品久久久 | www..com18午夜观看 | 精产国产伦理一二三区 | 久久88 | 色婷婷久久久久swag精品 | av永久| 久久精品无码一区二区三区 | 久久69精品久久久久久久电影好 | 91人人澡人人爽 | 日韩欧美国产精品 | 亚洲性爰 | 欧美一区2区三区4区公司二百 | 亚洲精品久 | 国产精品一区在线播放 | 91精品欧美久久久久久久 | 视频一区在线播放 |