自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

 [[385610]]

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

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

什么是 Python 閉包?

首先,讓我使用一個(gè)簡單的示例來說明什么是 Python 中的閉包??聪旅娴暮瘮?shù):

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

在一個(gè)函數(shù)內(nèi)部定義另外一個(gè)函數(shù),并返回這個(gè)函數(shù),這種特性就是閉包。檢查 outer 函數(shù)的返回值,可以確認(rèn)這是一個(gè)函數(shù)。

  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. >>> 

閉包這種特性能做什么呢?因?yàn)楹瘮?shù)返回的是一個(gè)函數(shù),我們就可以調(diào)用這個(gè)函數(shù),比如:

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

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

閉包內(nèi)的變量訪問

從前述的運(yùn)行結(jié)果來看,inner 函數(shù)可以訪問 outer 函數(shù)內(nèi)部定義的變量 x,但是卻無法修改它,下面的代碼運(yùn)行時(shí)會(huì)報(bào)錯(cuò):

  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. >>> 

為了解決這個(gè)問題,我們可以加上 nonlocal 關(guān)鍵字,告訴 inner 函數(shù),這不是一個(gè)本地變量:

  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. >>> 

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

用閉包來替換遞歸

利用上述閉包會(huì)保留調(diào)用結(jié)果的特性,我們可以用這個(gè)來替換遞歸,比如利用閉包計(jì)算斐波那契數(shù)列:

  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 

可以這樣調(diào)用來生產(chǎn)斐波那契數(shù)列:

  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. >>> 

而使用遞歸方法計(jì)算斐波那契數(shù)列的方法如下所示:

  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) 就可以計(jì)算出結(jié)果:

  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]: 

現(xiàn)在使用 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 倍,這還只是計(jì)算到第 20 個(gè)數(shù)的情況下,如果計(jì)算到 100,那使用遞歸會(huì)計(jì)算很久甚至無法計(jì)算出來。

閉包的其他用處

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

學(xué)生成績數(shù)據(jù):

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

現(xiàn)在需要根據(jù)學(xué)生成績進(jìn)行分類,通常情況下我們會(huì)寫多個(gè)函數(shù)來進(jìn)行分類,而分類的標(biāo)準(zhǔn)又會(huì)經(jīng)常變化,這時(shí)候閉包就很方便了:

  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) 

如果分類標(biāo)準(zhǔn)變化,直接個(gè)性函數(shù)的參數(shù)即可,主要代碼邏輯不變,如果想查找成績分類為 A 的學(xué)生,只需要調(diào)用 grade_A(students) 即可:

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

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

最后的話

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

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

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

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

本文轉(zhuǎn)載自微信公眾號(hào)「Python七號(hào)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系Python七號(hào)公眾號(hào)。

 

責(zé)任編輯:武曉燕 來源: Python七號(hào)
相關(guān)推薦

2021-09-09 21:10:23

Lite-XL編輯器Lua

2020-04-08 17:53:40

TypeScriptJavaScript代碼

2024-01-22 09:51:32

Swift閉包表達(dá)式尾隨閉包

2021-01-07 10:15:55

開發(fā) Java開源

2021-05-11 07:10:18

標(biāo)準(zhǔn)庫DjangoOS

2021-02-21 16:21:19

JavaScript閉包前端

2020-04-03 14:25:55

diff Meld工具

2024-06-19 10:01:50

2016-10-27 19:26:47

Javascript閉包

2013-05-02 09:44:57

PHP閉包

2019-11-07 21:51:18

閉包前端函數(shù)

2020-10-14 15:15:28

JavaScript(

2011-05-25 14:48:33

Javascript閉包

2009-07-22 07:43:00

Scala閉包

2020-12-02 08:31:47

Elasticsear

2024-03-11 08:21:49

2022-06-17 11:10:43

PandasPolarsPython

2023-11-02 08:53:26

閉包Python

2024-01-23 13:20:00

分庫分表分布式

2019-12-20 11:17:35

SerialVersionUID
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)