在這篇10分鐘的文章中,您將學習Python中的函數式范型。您還將學習列表推導式。 目錄
函數式范式在命令式編程范式中,我們通過給計算機一個任務序列來執行任務,然后計算機會執行這些任務。在執行它們時,計算機可以改變狀態。例如,我們設A = 5,然后改變A的值。因為我們的A是變量,所以它內部的值是變化的。 在函數式編程范式中,我們不告訴計算機去做什么,而是告訴它是什么東西。一個數的最大公約數是什么,等等。 變量不會變化。一旦我們設置了一個變量,它就會永遠保持這種狀態。因此,函數在函數式范型中沒有副作用。副作用就是函數改變了它外部的東西。讓我們來看一個例子: 輸出是5。在函數式范型中,改變變量是一個很大的禁忌,而讓函數影響它們范圍之外的東西也是一個大大的禁忌。函數唯一能做的就是計算某些東西并返回它。 現在您可能會想“沒有變量,就沒有副作用?為什么這很好?”問得好,讀這篇文章的古怪陌生人。 如果一個函數使用相同的參數被調用兩次,那么它肯定會返回相同的結果。如果您學過數學函數,您就會喜歡這一點。我們稱之為函數的引用透明性。由于函數沒有副作用,如果我們構建一個計算程序,我們就可以加快該程序的速度。如果程序知道func(2)等于3,我們可以將其存儲在一個表中。這將防止程序在我們已經知道答案的情況下去運行相同的函數。 通常,在函數式編程中,我們不使用循環,我們使用遞歸。遞歸是一個數學概念,它意味著“自食其力”。對于遞歸函數,該函數將自己作為一個子函數進行調用。下面是Python中遞歸函數的一個很好的例子: 一些編程語言也很懶。這意味著他們直到最后一秒才開始計算或做任何事情。如果我們編寫一些代碼來執行2 + 2,一個函數式程序只會在我們需要使用結果時才會計算這個結果。我們很快就會探討Python中的惰性。 Python的map函數是如何運行的為了理解映射,讓我們首先看看什么是可迭代對象。一個可迭代對象是我們可以迭代的任何東西。這些是列表或數組,但是Python有許多不同的可迭代對象。我們甚至可以通過實現魔術方法來創建我們自己的可迭代對象。一個魔術方法就像一個API,它可以幫助我們的對象變得更Python化。要使一個對象成為一個可迭代對象,我們需要實現2個魔術方法: 第一個魔術方法是__iter__,或者叫特殊iter(雙下劃線)方法,它會返回迭代對象,我們通常在循環開始時使用它。特殊next方法,__next__,會返回下一個對象是什么。 讓我們來看看這個: 這將輸出: 在Python中,迭代器是一個對象,它只有一個簡單的__iter__魔術方法。這意味著我們可以訪問該對象中的位置,但不能遍歷該對象。有些對象有魔術方法__next__,但沒有__iter__魔術方法,如sets(將在本文后面討論)。對于本文,我們將假設我們接觸的所有東西都是一個可迭代的對象。 現在我們知道了什么是可迭代對象,讓我們回到map函數。 map函數允許我們將一個函數應用到一個可迭代對象中的每個項。我們希望將一個函數應用到一個列表中的每個項,但是要知道這對大多數可迭代對象來說都是可行的。map的語法接受兩個輸入,即要應用的函數和可迭代的對象。 假設我們有一個像這樣的數字列表: 我們相對每個數字進行平方,我們可以像這樣寫代碼: 函數式Python是惰性的。如果我們不包含list(),函數將存儲該可迭代對象的定義,而不是列表本身。我們需要告訴Python“把這個轉換成一個列表”,以便我們使用它。 在Python中突然從非惰性求值變成惰性求值是很奇怪的。如果您更多地以函數式思維而不是命令式思維進行思考,您就會習慣它。 現在寫一個像square(num)這樣的普通函數就很好了,但是它看起來不夠好。我們必須定義一個完整的函數才可以在一個映射中使用它嗎?好吧,我們可以使用lambda(匿名)函數在map中定義一個函數。 Python中的lambda表達式Lambda函數是一個只有一行代碼的函數,適用于短期內使用。我們經常將它們隨同高階函數,如filter、map和reduce函數,一起使用。這個lambda表達式會對傳給它的數字進行平方: 現在我們來運行這個函數: ![]() 我聽到您在說:“Brandon,參數在哪里?這是什么鬼東西?它看起來一點也不像一個函數?” 嗯,這確實很令人困惑,但我可以解釋它。我們將某個東西賦值給變量square。這部分: ![]() 告訴Python這是一個lambda函數,輸入被稱為x。冒號之后的任何東西都是我們對輸入所執行的操作,它返回的就是這些操作的結果。 為了將我們的平方程序簡化成一行,我們可以這樣做: ![]() 在一個lambda表達式中,所有的參數都在左邊,而我們要用它們做的事情都在右邊。沒人能否認,這有點亂。編寫只有其他函數式程序員才能閱讀的代碼是一種樂趣。另外,將一個函數轉換成一行程序是非常酷的事情。 Python中的reduce函數reduce是一個函數,它將給定的函數應用于一個可迭代對象并返回一個東西。通常我們會在一個列表上進行計算,將其縮減至一個數字。Reduce看起來是這樣的: ![]() 我們可以(通常也會)使用lambda表達式作為函數。 列表的乘積是每一個數字相乘。編寫的程序是這樣: ![]() 但是使用reduce我們可以這樣寫: ![]() 我們得到了相同的乘積。代碼更短,并且具有函數式編程的知識,因此更簡潔。 fileter函數filter函數接受一個iterable并過濾掉我們不希望存在于該iterable中的所有東西。 filter接受一個函數和一個列表。它將該函數應用于列表中的每一項,如果該函數返回True,則不執行任何操作。如果該函數返回False,它會從該列表中刪除該項。 語法如下: ![]() 讓我們看一個小例子,沒有filter,我們會這樣寫: ![]() 使用filter, 這就變成了: ![]() Python中的高階函數高階函數可以將函數作為參數并返回函數。一個例子是: ![]() 或者第二個定義,return functions,的一個簡單例子是: ![]() 高階函數使非變化變量更容易處理。如果我們所做的只是在一系列函數中傳遞數據,那么我們就不需要在任何地方存儲變量。 Python中的所有函數都是一級對象。當一個對象具有以下特性中的一個或多個時,我們將其定義為一級對象:
因此Python中的所有函數都是一級函數,可以作為高階函數使用。 帶函數的部分應用部分應用(也稱為閉包)很奇怪,但是也很酷。我們可以調用一個函數而不提供它需要的所有參數。我們來在一個例子中看一下這一點。我們想要創建一個函數,它接受兩個參數,一個基數和一個指數,然后返回基數的指數次方,就像這樣: ![]() 現在我們想要一個專用的平方函數,來使用power數求出一個數的平方: ![]() 這是可行的,但如果我們想要一個立方函數呢?或者一個4次方函數呢?我們能一直寫下去嗎?嗯,我們可以。但是程序員是很懶的。如果我們重復地做同一件事時,這是一個信號,表明有一種更快的方法來加快做這些事情的速度,那將允許我們不再重復地做這些事情。我們可以在這里使用部分應用。讓我們看一個使用了一個部分應用的平方函數的例子: ![]() 這難道不酷嗎?我們可以通過告訴Python第二個參數是什么來只使用一個參數調用需要兩個參數的函數。 我們還可以使用一個循環,來生成一個冪函數,其運行范圍可以從立方到1000次冪。 ![]() 函數式編程不是Python化您可能已經注意到了,我們在函數式編程中想要做的很多事情都是圍繞列表進行的。除了reduce函數和部分應用外,我們所看到的所有函數都會生成列表。Guido (Python的發明者)不喜歡Python中的函數式的東西,因為Python已經有了自己的生成列表的方法。 如果我們在一個Python IDLE會話中輸入“import this”,我們會得到: ![]() 這就是Python之禪。這是一首關于某些東西Python化意味著什么的詩。這里我們要涉及的部分是: 應該有一種——最好是只有一種——顯而易見的方法來做到它。 在Python中,map 與 filter可以做與列表表達式(接下來討論)相同的事情。這打破了Python之禪中的一條規則,因此函數式編程的這些部分不是“Python式的”。 另一個話題是Lambda。在Python中,lambda函數是一個普通函數。Lambda是語法糖。這兩個是等價的: ![]() 一個普通函數可以做lambda函數所能做的所有事情,但反過來卻不行。一個lambda函數不能完成一個普通函數所能完成的所有工作。 這是一個關于函數式編程為什么不能很好地適應整個Python生態系統的簡短討論。您可能已經注意到我之前提到過列表推導式,我們現在將討論它們。 列表推導式之前我提到過,我們可以用列表推導式完成我們可以用map或filter所做的任何事情。這是我們要學習的關于它們的部分。 列表推導式是在Python中生成列表的一種方式。其語法是: ![]() 讓我們對一個列表中的每個數字進行平方,并以此作為一個例子: ![]() 好吧,這樣我們就可以看到我們如何將一個函數應用到列表中的每一項。我們如何來應用一個filter函數呢?好吧,看看這段之前的代碼: ![]() 我們可以像這樣來把它轉換成一個列表推導式: ![]() 列表推導式支持像這樣的if語句。我們不需要再應用很多個函數來得到我們想要的東西了。如果我們試圖創造使用列表的機會,那么使用列表推導式可能會更清晰、更容易一些。 如果我們想要對列表中所有小于0的數進行平方呢?那么,使用lambda、map和filter,我們會這樣寫: ![]() 這有點冗長而復雜。使用一個列表推導式,它就會變成這樣: ![]() 列表推導式只對列表有好處。map和filter作用于任何可迭代對象之上,那是怎么回事呢?我們可以對遇到的任何可迭代對象使用任何推導式。 任何可迭代對象的推導式我們可以使用一個推導式來生成任何可迭代對象。因為我們使用的是Python 2.7,所以,我們甚至可以生成一個字典(hashmap)。 ![]() 如果它是一個可迭代對象,我們可以生成它。我們來看集合的最后一個例子。如果您不知道集合是什么,請查看我寫的另一篇文章(https://skerritt.blog/a-primer-on-set-theory/ )。其中的TL;DR(集合定義)是:
![]() 您可能會注意到,集合具有與字典相同的花括號。Python是很聰明的。它會根據我們是否為字典提供額外的值來判斷我們寫的是一個字典推導式還是一個集合推導式。如果您想了解更多關于推導式的內容,請查看這個可視化指南。 結論函數式編程是漂亮而純粹的。函數式代碼可以是簡潔的,但也可能是混亂的。您應該根據需要去使用它。
|
|