寫 Python 代碼不可不知的函數式編程技術

2021-02-13 機器之心

作者:Raivat Shah

參與:魔王、Jamin

本文對 Python 中的函數式編程技術進行了簡單的入門介紹。近來,越來越多人使用函數式編程(functional programming)。因此,很多傳統的命令式語言(如 Java 和 Python)開始支持函數式編程技術。本文對 Python 中的函數式編程技術進行了簡單的入門介紹。
本文適合對函數式編程有基本了解的讀者。如果你對函數式編程並不熟悉,可以先閱讀這篇文章:https://medium.com/@yannickdot/functional-programming-101-6bc132674ec5。

本文作者是新加坡國立大學計算機學院和「USP」博學計劃學生 Raivat Shah,專注於編程與數據研究。在 Python 中,函數是「頭等公民」(first-class)。也就是說,函數與其他數據類型(如 int)處於平等地位。因而,我們可以將函數賦值給變量,也可以將其作為參數傳入其他函數,將它們存儲在其他數據結構(如 dicts)中,並將它們作為其他函數的返回值。由於其他數據類型(如 string、list 和 int)都是對象,那麼函數也是 Python 中的對象。我們來看示例函數 foo,它將自己的名稱列印出來:

def foo():
   print("foo")

由於函數是對象,因此我們可以將函數 foo 賦值給任意變量,然後調用該變量。例如,我們可以將函數賦值給變量 bar:

bar = foo
bar()
#will print "foo" to the console

語句 bar = foo 將函數 foo 引用的對象賦值給變量 bar。當對象可調用時(callable),它們與函數一樣,如 object()。這是通過 __call__ 方法實現的。

class Greeter:
   def __init__(self, greeting):
      self.greeting = greeting
   def __call__(self, name):
      return self.greeting + " " + name

每一次配置 Greeter 類的對象時,我們都會創建一個新的對象,即打招呼時可以喊的新名字。如下所示:

morning = Greeter("good morning") #creates the callable object
morning("john") # calling the object
#prints "good morning john" to the console

我們可以調用 morning 對象的原因在於,我們已經在類定義中使用了 __call__ 方法。為了檢查對象是否可調用,我們使用內置函數 callable:

callable(morning) #true
callable(145) #false. int is not callable. 

函數和其他對象一樣,可以存儲在數據結構內部。例如,我們可以創建 int to func 的字典。當 int 是待執行步驟的簡寫時,這就會派上用場。

# store in dictionary
mapping = {
   0 : foo,
   1 : bar
}
x = input() #get integer value from user
mapping[x]() #call the func returned by dictionary access

函數還可以作為其他函數的參數和返回值。接受函數作為輸入或返回函數的函數叫做高階函數,它是函數式編程的重要組成部分。高階函數具備強大的能力。就像《Eloquent JavaScript》中解釋的那樣:「高階函數允許我們對動作執行抽象,而不只是抽象數值。」我們來看一個例子。假設我們想對一個項目列表(list of items)執行迭代,並將其順序列印出來。我們可以輕鬆構建一個 iterate 函數:

def iterate(list_of_items):
    for item in list_of_items:
        print(item)

看起來很酷吧,但這只不過是一級抽象而已。如果我們想在對列表執行迭代時進行列印以外的其他操作要怎麼做呢?

這就是高階函數存在的意義。我們可以創建函數 iterate_custom,待執行迭代的列表和要對每個項應用的函數都是 iterate_custom 函數的輸入:

def iterate_custom(list_of_items, custom_func):
   for item in list_of_items:
        custom_func(item)

我們已經把抽象的級別提高了一層,使代碼具備更強的可重用性。現在,我們不僅可以在列印列表時調用該函數,還可以對涉及序列迭代的列表執行任意操作。函數還能被返回,從而使事情變得更加簡單。就像我們在 dict 中存儲函數一樣,我們還可以將函數作為控制語句,來決定適合的函數。例如:

def add(x, y):
    return x + y
def sub(x, y):
    return x - y
def mult(x, y):
    return x * y
def calculator(opcode):
    if opcode == 1:
       return add
    elif opcode == 2:
       return sub
    else:
       return mult 
my_calc = calculator(2) #my calc is a subtractor
my_calc(5, 4) #returns 5 - 4 = 1 
my_calc = calculator(9) #my calc is now a multiplier
my_calc(5, 4) #returns 5 x 4 = 20. 

嵌套函數

函數還可以在其他函數內部,這就是「內部函數」。內部函數在創建輔助函數時非常有用,輔助函數即作為子模塊來支持主函數的小型可重用函數。在問題需要特定函數定義(參數類型或順序)時,我們可以使用輔助函數。這種不遵循傳統做法的操作使得解決問題變得更加簡單,示例參見:http://www-inst.eecs.berkeley.edu/~cs61a/sp12/lectures/lect4-2x3.pdf。假設你想定義一個斐波那契函數 fib(n),該函數只有一個參數 n,我們必須返回第 n 個斐波那契數。定義此類函數的一種可行方式是:使用輔助函數來追蹤斐波那契數列的前兩個項(因為斐波那契數是前兩個數之和)。

def fib(n):
    def fib_helper(fk1, fk, k):
        if n == k:
           return fk
        else:
           return fib_helper(fk, fk1+fk, k+1)
    if n <= 1:
       return n
    else:
       return fib_helper(0, 1, 1)

將該計算從函數主體移到函數參數,這具備非常強大的力量。因為它減少了遞歸方法中可能出現的冗餘計算。

如果我們想在未給函數命名之前寫一個函數要怎麼做?如果我們想寫一個簡短的單行函數(如上述示例中的函數 foo 或 mult)要怎麼做?我們可以在 Python 中使用 lambda 關鍵字來定義此類函數。示例如下:

mult = lambda x, y: x * y
mult(1, 2) #returns 2

該 mult 函數的行為與使用傳統 def 關鍵字定義函數的行為相同。

注意:lambda 函數必須為單行,且不能包含程式設計師寫的返回語句。事實上,它們通常具備隱式的返回語句(在上面的示例中,函數想表達 return x * y,不過我們省略了 lambda 函數中的顯式返回語句)。lambda 函數更加強大和精準,因為我們還可以構建匿名函數(即沒有名稱的函數):

(lambda x, y: x * y)(9, 10) #returns 90

當我們只需要一次性使用某函數時,這種方法非常方便。例如,當我們想填充字典時:

import collections
pre_fill = collections.defaultdict(lambda: (0, 0))
#all dictionary keys and values are set to 0

接下來我們來看 Map、Filter 和 Reduce,以更多地了解 lambda。map 函數基於指定過程(函數)將輸入集轉換為另一個集合。這類似於上文提到的 iterate_custom 函數。例如:

def multiply_by_four(x):
    return x * 4
scores = [3, 6, 8, 3, 5, 7]
modified_scores = list(map(multiply_by_four, scores))
#modified scores is now [12, 24, 32, 12, 20, 28]

在 Python 3 中,map 函數返回的 map 對象可被類型轉換為 list,以方便使用。現在,我們無需顯式地定義 multiply_by_four 函數,而是定義 lambda 表達式:

modified_scores = list(map(lambda x: 4 * x, scores))

當我們想對集合內的所有值執行某項操作時,map 函數很有用。就像名稱所顯示的那樣,filter 函數可以幫助篩除不想要的項。例如,我們想要去除 scores 中的奇數,那麼我們可以使用 filter:

even_scores = list(filter(lambda x: True if (x % 2 == 0) else False, scores))
#even_scores = [6, 8]

由於提供給 filter 的函數是逐個決定是否接受每一個項的,因此該函數必須返回 bool 值,且該函數必須是一元函數(即只使用一個輸入參數)。reduce 函數用於「總結」或「概述」數據集。例如,如果我們想要計算所有分數的總和,就可以使用 reduce:

sum_scores = reduce((lambda x, y: x + y), scores)
#sum_scores = 32

這要比寫循環語句簡單多了。注意:提供給 reduce 的函數需要兩個參數:一個表示正在接受檢查的項,另一個表示所用運算的累積結果。本文是關於函數式編程的一篇入門文章,雖然儘量完備地介紹了相關的知識,但並不是那麼深入。如想了解更多,大家可以閱讀以下資源:

Best Practices for Using Functional Programming in Python:https://kite.com/blog/python/functional-programming/

Functional Programming Tutorials and Notes:https://www.hackerearth.com/zh/practice/python/functional-programming/functional-programming-1/tutorial/

原文連結:https://medium.com/better-programming/introduction-to-functional-programming-in-python-3d26cd9cbfd7

✄---
加入機器之心(全職記者 / 實習生):hr@jiqizhixin.com
投稿或尋求報導:content@jiqizhixin.com
廣告 & 商務合作:bd@jiqizhixin.com

相關焦點

  • 寫Python 代碼不可不知的函數式編程技術
    選自 Medium作者:Raivat Shah參與:魔王、Jamin本文對 Python 中的函數式編程技術進行了簡單的入門介紹。近來,越來越多人使用函數式編程(functional programming)。因此,很多傳統的命令式語言(如 Java 和 Python)開始支持函數式編程技術。本文對 Python 中的函數式編程技術進行了簡單的入門介紹。本文適合對函數式編程有基本了解的讀者。
  • 白話 Python 的函數式編程
    今天和大家聊聊 Python 的函數式編程特性。
  • Python中的函數式編程
    (維基百科:函數式編程)所謂編程範式(Programming paradigm)是指編程風格、方法或模式,比如面向過程編程(C語言)、面向對象編程(C++)、面向函數式編程(Haskell),並不是說某種程式語言一定屬於某種範式,例如 Python 就是多範式程式語言。
  • 函數式編程
    函數式編程的幾個技術map & reduce :這個技術不用多說了,函數式編程最常見的技術就是對一個集合做Map和Reduce操作。這比起過程式的語言來說,在代碼上要更容易閱讀。我們先用一個最簡單的例子來說明一下什麼是函數式編程。先看一個非函數式的例子:int cnt;void increment(){    cnt++;}那麼,函數式的應該怎麼寫呢?
  • 10分鐘學會python函數式編程
    一旦你設置了一個變量,它就永遠保持這種狀態(注意,在純函數式語言中,它們不是變量)。因此,函數式編程沒有副作用。副作用指的是函數改變它自己以外的東西。讓我們看一些典型Python代碼的示例:如果你在函數式思維方式中考慮得更多,而不是命令式思維方式,那麼你最終會習慣它。現在寫一個像「square(num)」這樣的普通函數雖然很好,但卻是不對的。我們必須定義一個完整的函數才能在map中使用它?好吧,我們可以使用lambda(匿名)函數在map中定義一個函數。
  • Python——五分鐘理解函數式編程與閉包
    函數式編程函數式編程這個概念我們可能或多或少都聽說過,剛聽說的時候不明覺厲,覺得這是一個非常黑科技的概念。但是實際上它的含義很樸實,但是延伸出來許多豐富的用法。在早期程式語言還不是很多的時候,我們會將語言分成 高級語言與低級語言。
  • Python(27)常用指引:函數式編程指引
    函數式編程指引本文檔提供恰當的 Python 函數式編程範例,在函數式編程簡單的介紹之後,將簡單介紹Python中關於函數式編程的特性如 iterator
  • 如何用十分鐘學會函數式 Python?
    函數式編程到底是什麼?本文將詳解其概念,同時分享怎樣在 Python 中使用函數式編程。主要內容包括列表解析式和其他形式的解析式。通常,函數式編程不使用循環,而是使用遞歸。遞歸是個數學概念,通常的意思是「把結果作為自己的輸入」。使用遞歸函數,函數可以反覆調用自己。
  • 代碼這樣寫更優雅(Python版)
    點擊文章末尾閱讀原文報名作者微課
  • 函數式編程,真香
    最開始接觸函數式編程的時候是在小米工作的時候,那個時候看老大以前寫的代碼各種 compose,然後一些 ramda 的一些工具函數,看著很吃力,然後極力吐槽函數式編程,現在回想起來,那個時候的自己真的是見識短淺,只想說,'真香'。
  • 大數據入門:Scala函數式編程
    命令式編程VS函數式編程命令式編程,程序邏輯的基本元素是:變量+操作符+控制結構,這些元素構成一條條的代碼指令,因此,稱之為命令式編程。函數式編程,程序邏輯由:map+匿名函數組成。整個代碼中,看不到變量、控制結構、操作符等元素,看到的只有函數,因此,函數式編程的本質就是:程序邏輯的基本元素是函數。
  • 初始Python函數編程與代碼的可復用性
    前面我們寫的Python程序代碼都沒有涉及到Python函數,所有的代碼都被順序安排在一起,程序的執行過程也是順序執行。下面的代碼是計算自然數10以內的累加和:代碼聲明了變量sum並初始化為零,然後連續9條語句求自然數1至自然數9的累加和,最後輸出sum變量。
  • Python 為什麼沒有 main 函數?為什麼我不推薦寫 main 函數?
    python_cat)毫無疑問 Python 中沒有所謂的 main 入口函數,但是網上經常看到一些文章提「Python 的 main 函數」、「建議寫 main 函數」……有些人是知情的,他的意圖可能是模仿那些正宗的 main 函數,但還有不少人明顯是被誤導了(或自己誤解了),就寫出來很累贅的代碼。
  • js代碼優化之編程函數
    在編程的世界中,有這樣的一個原則,簡稱二八定律二八定律:影響程序的80%性能的往往是20%的代碼在js的編寫過程中,函數設計就相當於那20%,時刻影響著你的代碼,可以說是至關重要。那麼對於函數的設計原則,你又了解多少?1.
  • 函數式編程聖經
    上帝看到約翰·麥卡錫發明了表處理語言 Lisp,卻只用來學術研究,很是傷心,就把 Lisp 解釋器的秘密告訴了他的學生史蒂芬·羅素,史蒂芬·羅素將eval函數在IBM 704機器上實現後,函數式編程的大門第一次向人類打開了。
  • Java如何支持函數式編程?
    Java 8開始,引入了函數式編程接口與Lambda表達式,便於開發者寫出更少更優雅的代碼。什麼是函數式編程?函數式編程的特點是什麼?本文通過代碼實例,從Stream類、Lambda表達式和函數接口這三個語法概念來分享Java對函數式編程的支持。文末福利:Java微服務沙箱體驗挑戰。
  • Python之函數學習(八)
    python語言,即可以進行函數式的編程的語言,又是可以進行面向對象編程的語言,所謂函數,簡單的理解就是將一些語句集合到一起,這樣可以在程序中多次的調用
  • 【第1679其】函數式編程淺析
    說到抽象,我們再回到編程,從某一種角度看,編程和詩歌還挺像的,都是為了講一個「故事」。只不過在編程中,我們用的是代碼的組合。在閱讀代碼時,我們都希望能儘快了解其主旨,這要求代碼具備較好的可讀性,函數式編程為提高代碼的可讀性提供了一種方案。為什麼要使用函數式編程在我看來,使用函數式編程最大的好處在於其能提高我們代碼的可讀性。
  • 慢步學python,編程基礎知識,流程控制語句if
    慢步業餘學python編程,今天休息繼續分享python編程知識。面向過程編程,重要的是程序的流程,今天說說流程控制語句if。if結構python中常見的流程結構很多,if結構,在很多程式語言內也很常見。這個例子,先是敲入一個字符串變量a,然後放入判斷結構if裡面。type()函數可以得到括號內目標的類型。
  • 「技術文章」《Python 資料庫 GUI CGI編程》
    1.寫在前邊上一次,我們介紹了Python的入門的文章,今天我們就來介紹下Python的資料庫,GUI, CGI的編程,以及Python 2.x 與Python 3.x的區別。2.連接資料庫 Python 標準資料庫接口為 Python DB-API,MySQLdb 是用於Python連結Mysql資料庫的接口。