成人免费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閉包前端

2024-06-19 10:01:50

2020-04-03 14:25:55

diff Meld工具

2013-05-02 09:44:57

PHP閉包

2016-10-27 19:26:47

Javascript閉包

2019-11-07 21:51:18

閉包前端函數

2022-06-17 11:10:43

PandasPolarsPython

2024-03-11 08:21:49

2020-10-14 15:15:28

JavaScript(

2011-05-25 14:48:33

Javascript閉包

2009-07-22 07:43:00

Scala閉包

2019-12-20 11:17:35

SerialVersionUID

2020-12-02 08:31:47

Elasticsear

2023-11-02 08:53:26

閉包Python
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 一区二区中文字幕 | 亚洲精品一区二区三区丝袜 | 亚洲精品国产一区 | 亚洲一区 中文字幕 | 日韩视频国产 | 欧美一区二区综合 | 国产日韩精品视频 | 欧美日韩一卡二卡 | 成人夜晚看av | 在线播放亚洲 | 天堂一区 | 成人综合在线视频 | 亚洲精品日韩一区二区电影 | 成人av免费看 | 国产在线观看网站 | 99精品国产一区二区三区 | 黄网站免费在线看 | 91免费在线看 | 久久福利电影 | 久久在线 | 久久综合久色欧美综合狠狠 | 黄色毛片黄色毛片 | 精品日韩一区 | 91精品国产91久久久久久吃药 | 99久久精品免费视频 | 性色av香蕉一区二区 | 午夜精品久久久久久久久久久久久 | www.五月婷婷.com | 国产高清一区二区 | 日韩在线视频一区 | 久久久久久久av麻豆果冻 | 欧美精品一区二区三区在线播放 | 青青久草 | 国产污视频在线 | 免费观看成人鲁鲁鲁鲁鲁视频 | 成年人在线视频 | 日本一区二区在线视频 | 久久精品一区二区三区四区 | 久久久精彩视频 | 日韩影音 | 四虎永久在线精品免费一区二 |