Python:高阶函数
前言
最近在學習深度學習,已經跑出了幾個模型,但Pyhton的基礎不夠扎實,因此,開始補習Python了,大家都推薦廖雪峰的課程,因此,開始了學習,但光學有沒有用,還要和大家討論一下,因此,寫下這些帖子,廖雪峰的課程連接在這里:廖雪峰
Python的相關介紹,以及它的歷史故事和運行機制,可以參見這篇:python介紹
Python的安裝可以參見這篇:Python安裝
Python的運行模式以及輸入輸出可以參見這篇:Python IO
Python的基礎概念介紹,可以參見這篇:Python 基礎
Python字符串和編碼的介紹,可以參見這篇:Python字符串與編碼
Python基本數據結構:list和tuple介紹,可以參見這篇:Python list和tuple
Python控制語句介紹:ifelse,可以參見這篇:Python 條件判斷
Python控制語句介紹:循環實現,可以參見這篇:Python循環語句
Python數據結構:dict和set介紹Python數據結構dict和set
Python函數相關:Python函數
Python高階特性:Python高級特性
目錄:
- 前言
- 函數式編程
- 高階函數
- 函數名也是變量
- 傳入函數
- Map/Reduce
- 練習
- 練習
- Filter
- 用filter求素數
- 練習 回文判斷
- Sorted
- 練習
- 高階函數
函數式編程
函數是Python內建支持的一種封裝,我們通過把整個大任務拆成各個子函數,通過一層一層的函數調用,就可以把復雜任務分解成簡單的任務,這種分解過程就是面向過程的程序設計。函數就是面向過程的程序設計的基本單元。
而函數式編程(請注意多了一個“式”字)——Functional Programming,雖然也可以歸結到面向過程的程序設計,但其思想更接近于數學計算。
我們首先要搞明白計算機(Computer)和計算(Compute)的概念。
在計算機的層次上,CPU執行的是加減乘除的指令代碼,以及各種條件判斷和跳轉指令,所以,匯編語言機器碼是最貼近計算機的語言。
而計算則指數學意義上的計算,甚至于更加廣義的對信息的處理,越是抽象的計算,離計算機硬件越遠。
對應到編程語言,就是越低級的語言,越貼近計算機,抽象程度低,執行效率高,比如C語言;越高級的語言,越貼近計算,抽象程度高,執行效率低,比如Lisp語言。
函數式編程就是一種抽象程度很高的編程范式,純粹的函數式編程語言編寫的函數沒有變量,因此,任意一個函數,只要輸入是確定的,輸出就是確定的,這種純函數我們稱之為沒有副作用。而允許使用變量的程序設計語言,由于函數內部的變量狀態不確定,同樣的輸入,可能得到不同的輸出,因此,這種函數是有副作用的。
函數式編程的一個特點就是,允許把函數本身作為參數傳入另一個函數,還允許返回一個函數!
Python對函數式編程提供部分支持。由于Python允許使用變量,因此,Python不是純函數式編程語言。
高階函數
高階函數英文叫Higher-order function。什么是高階函數?我們以實際代碼為例子,一步一步深入概念。
即變量可以由函數擔當。
以Python內置的求絕對值的函數abs()為例,調用該函數用以下代碼:
但是,如果只寫abs呢?
>>> abs <built-in function abs>可見,abs(-10)是函數調用,而abs是函數本身。
要獲得函數調用結果,我們可以把結果賦值給變量:
但是,如果把函數本身賦值給變量呢?
>>> f = abs >>> f <built-in function abs>結論:函數本身也可以賦值給變量,即:變量可以指向函數。
如果一個變量指向了一個函數,那么,可否通過該變量來調用這個函數?用代碼驗證一下:
c語言里的函數指針
成功!說明變量f現在已經指向了abs函數本身。直接調用abs()函數和調用變量f()完全相同。
函數名也是變量
那么函數名是什么呢?函數名其實就是指向函數的變量!對于abs()這個函數,完全可以把函數名abs看成變量,它指向一個可以計算絕對值的函數!
如果把abs指向其他對象,會有什么情況發生?
把abs指向10后,就無法通過abs(-10)調用該函數了!因為abs這個變量已經不指向求絕對值函數而是指向一個整數10!
當然實際代碼絕對不能這么寫,這里是為了說明函數名也是變量。要恢復abs函數,請重啟Python交互環境。
注:由于abs函數實際上是定義在import builtins模塊中的,所以要讓修改abs變量的指向在其它模塊也生效,要用import builtins; builtins.abs = 10。
傳入函數
既然變量可以指向函數,函數的參數能接收變量,那么一個函數就可以接收另一個函數作為參數,這種函數就稱之為高階函數。
一個最簡單的高階函數:
當我們調用add(-4, -5, abs)時,參數x,y和f分別接收-5,6和abs,根據函數定義,我們可以推導計算過程為:
x = -4 y = -5 f = abs f(x) + f(y) ==> abs(-4) + abs(-5) ==> 9 return 9Map/Reduce
MapReduce既是一種編程模型,也是一種與之關聯的、用于處理和產生大數據集的實現。用戶要特化一個map程序去處理key/value對,并產生中間key/value對的集合,以及一個reduce程序去合并有著相同key的所有中間key/value對
Python內建了map()和reduce()函數。
我們先看map。map()函數接收兩個參數,一個是函數,一個是Iterable,map將傳入的函數依次作用到序列的每個元素,并把結果作為新的Iterator返回。
舉例說明,比如我們有一個函數f(x)=x2x2,要把這個函數作用在一個list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()實現如下:
現在,我們用Python代碼實現:
>>> def f(x): ... return x * x ... >>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> list(r) [1, 4, 9, 16, 25, 36, 49, 64, 81]map()傳入的第一個參數是f,即函數對象本身。由于結果r是一個Iterator,Iterator是惰性序列,因此通過list()函數讓它把整個序列都計算出來并返回一個list。
你可能會想,不需要map()函數,寫一個循環,也可以計算出結果:
的確可以,但是,從上面的循環代碼,能一眼看明白“把f(x)作用在list的每一個元素并把結果生成一個新的list”嗎?
所以,map()作為高階函數,事實上它把運算規則抽象了,因此,我們不但可以計算簡單的f(x)=x2x2,還可以計算任意復雜的函數,比如,把這個list所有數字轉為字符串:
只需要一行代碼。
再看reduce的用法。reduce把一個函數作用在一個序列[x1, x2, x3, …]上,這個函數必須接收兩個參數,reduce把結果繼續和序列的下一個元素做累積計算,其效果就是:
當然求和運算可以直接用Python內建函數sum(),沒必要動用reduce。
但是如果要把序列[1, 3, 5, 7, 9]變換成整數13579,reduce就可以派上用場:
>>> from functools import reduce >>> def fn(x, y): ... return x * 10 + y ... >>> reduce(fn, [1, 3, 5, 7, 9]) 13579這個例子本身沒多大用處,但是,如果考慮到字符串str也是一個序列,對上面的例子稍加改動,配合map(),我們就可以寫出把str轉換為int的函數:
>>> from functools import reduce >>> def fn(x, y): ... return x * 10 + y ... >>> def char2num(s): ... digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9} ... return digits[s] ... >>> reduce(fn, map(char2num, '13579')) 13579整理成一個str2int的函數就是:
from functools import reduceDIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}def str2int(s):def fn(x, y):return x * 10 + ydef char2num(s):return DIGITS[s]return reduce(fn, map(char2num, s))還可以用lambda函數進一步簡化成:
from functools import reduceDIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}def char2num(s):return DIGITS[s]def str2int(s):return reduce(lambda x, y: x * 10 + y, map(char2num, s))也就是說,假設Python沒有提供int()函數,你完全可以自己寫一個把字符串轉化為整數的函數,而且只需要幾行代碼!
lambda函數的用法在后面介紹
練習
利用map()函數,把用戶輸入的不規范的英文名字,變為首字母大寫,其他小寫的規范名字。輸入:[‘adam’, ‘LISA’, ‘barT’],輸出:[‘Adam’, ‘Lisa’, ‘Bart’]:
def normalize(name):name=name.lower()name2=name[0].upper()+name[1:]return name2L1 = ['adam', 'LISA', 'barT'] L2 = list(map(normalize, L1)) print(L2)Python提供的sum()函數可以接受一個list并求和,請編寫一個prod()函數,可以接受一個list并利用reduce()求積:
def time(x,y):return x*ydef prod(l):return(reduce(time,l))練習
利用map和reduce編寫一個str2float函數,把字符串’123.456’轉換成浮點數123.456:
from functools import reducedef str2float(s):before=s.split('.')[0]after=s.split('.')[1]DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}def char2num(s):return DIGITS[s]before=reduce(lambda x,y:x*10+y,map(char2num,before))after=reduce(lambda x,y:x/10+y,map(char2num,after[::-1]))return before+after/10Filter
Python內建的filter()函數用于過濾序列。
和map()類似,filter()也接收一個函數和一個序列。和map()不同的是,filter()把傳入的函數依次作用于每個元素,然后根據返回值是True還是False決定保留還是丟棄該元素。
例如,在一個list中,刪掉偶數,只保留奇數,可以這么寫:
def is_odd(n):return n % 2 == 1list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])) # 結果: [1, 5, 9, 15]把一個序列中的空字符串刪掉,可以這么寫:
def not_empty(s):return s and s.strip() list(filter(not_empty, ['A', '', 'B', None, 'C', ' '])) # 結果: ['A', 'B', 'C']可見用filter()這個高階函數,關鍵在于正確實現一個“篩選”函數。
注意到filter()函數返回的是一個Iterator,也就是一個惰性序列,所以要強迫filter()完成計算結果,需要用list()函數獲得所有結果并返回list。
用filter求素數
計算素數的一個方法是埃氏篩法,它的算法理解起來非常簡單:
首先,列出從2開始的所有自然數,構造一個序列:
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...取序列的第一個數2,它一定是素數,然后用2把序列的2的倍數篩掉:
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...取新序列的第一個數3,它一定是素數,然后用3把序列的3的倍數篩掉:
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...取新序列的第一個數5,然后用5把序列的5的倍數篩掉:
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...不斷篩下去,就可以得到所有的素數。
用Python來實現這個算法,可以先構造一個從3開始的奇數序列:
def _odd_iter():n = 1while True:n = n + 2yield n注意這是一個生成器,并且是一個無限序列。
然后定義一個篩選函數:
def _not_divisible(n):return lambda x: x % n > 0最后,定義一個生成器,不斷返回下一個素數:
def primes():yield 2it = _odd_iter() # 初始序列while True:n = next(it) # 返回序列的第一個數yield nit = filter(_not_divisible(n), it) # 構造新序列這個生成器先返回第一個素數2,然后,利用filter()不斷產生篩選后的新的序列。
由于primes()也是一個無限序列,所以調用時需要設置一個退出循環的條件:
# 打印1000以內的素數: for n in primes():if n < 1000:print(n)else:break注意到Iterator是惰性計算的序列,所以我們可以用Python表示“全體自然數”,“全體素數”這樣的序列,而代碼非常簡潔。
練習 回文判斷
def is_palindrome(n):sn=str(n)num=len(sn)if num%2==0:ti=int(num/2)else:ti=int((num+1)/2)for i in range(0,ti):if sn[i]!=sn[-i-1]:return Falseelse:return TrueSorted
排序算法
排序也是在程序中經常用到的算法。無論使用冒泡排序還是快速排序,排序的核心是比較兩個元素的大小。如果是數字,我們可以直接比較,但如果是字符串或者兩個dict呢?直接比較數學上的大小是沒有意義的,因此,比較的過程必須通過函數抽象出來。
Python內置的sorted()函數就可以對list進行排序:
>>> sorted([36, 5, -12, 9, -21]) [-21, -12, 5, 9, 36]此外,sorted()函數也是一個高階函數,它還可以接收一個key函數來實現自定義的排序,例如按絕對值大小排序:
>>> sorted([36, 5, -12, 9, -21], key=abs) [5, 9, -12, -21, 36]key指定的函數將作用于list的每一個元素上,并根據key函數返回的結果進行排序。對比原始的list和經過key=abs處理過的list:
list = [36, 5, -12, 9, -21]keys = [36, 5, 12, 9, 21]然后sorted()函數按照keys進行排序,并按照對應關系返回list相應的元素:
keys排序結果 => [5, 9, 12, 21, 36]| | | | | 最終結果 => [5, 9, -12, -21, 36]我們再看一個字符串排序的例子:
>>> sorted(['bob', 'about', 'Zoo', 'Credit']) ['Credit', 'Zoo', 'about', 'bob']默認情況下,對字符串排序,是按照ASCII的大小比較的,由于’Z’ < ‘a’,結果,大寫字母Z會排在小寫字母a的前面。
現在,我們提出排序應該忽略大小寫,按照字母序排序。要實現這個算法,不必對現有代碼大加改動,只要我們能用一個key函數把字符串映射為忽略大小寫排序即可。忽略大小寫來比較兩個字符串,實際上就是先把字符串都變成大寫(或者都變成小寫),再比較。
這樣,我們給sorted傳入key函數,即可實現忽略大小寫的排序:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower) ['about', 'bob', 'Credit', 'Zoo']要進行反向排序,不必改動key函數,可以傳入第三個參數reverse=True:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) ['Zoo', 'Credit', 'bob', 'about']從上述例子可以看出,高階函數的抽象能力是非常強大的,而且,核心代碼可以保持得非常簡潔。
小結
sorted()也是一個高階函數。用sorted()排序的關鍵在于實現一個映射函數。
練習
以名字為關鍵字對列表排序,L = [(‘Bob’, 75), (‘Adam’, 92), (‘Bart’, 66), (‘Lisa’, 88)]
之后以分數為關鍵字對列表排序。
總結
以上是生活随笔為你收集整理的Python:高阶函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python学习笔记:高级特性
- 下一篇: Python学习笔记:返回函数