最近在學習python
,,對函數(shù)式編程 特別感興趣,當然,,這并不是python
的專利,,不過最近確實看到一遍文章正好以python
為例來講解函數(shù)式編程 ,特把它翻譯過來與大家分享,。
原文鏈接如下:
在這篇文章當中,,你將會學習什么是函數(shù)式編程 以及如果用python
進行實現(xiàn)。你也會學習列表領域能力以及其它形式的領悟能力,。
函數(shù)式編程在命令式的編程范式當中,,你通過告訴計算機一系列需要執(zhí)行的任務并在計算機執(zhí)行以后以完成你的目的,。當執(zhí)行任務的時候,狀態(tài)可能會發(fā)生改變,。比如,,假設你原先將A賦值為5,然后你改變A的數(shù)值,。你會有很多變量,,并且變量內(nèi)部的值也會發(fā)生改變。
在一個函數(shù)式編程范式當中,,你并不告訴計算機要干什么,,而是告訴它是干什么的。 什么是一個數(shù)字的最大公因子,,什么是從1到N的乘積,,等等。
正因為如此,,變量不能變化,。當時設置好了一個變量,它將永遠保持那樣的方式(注意下,,在純粹的函數(shù)式編程語言當中通常不稱之為變量),。因此,在函數(shù)式編程范式當中,,函數(shù)不會有副作用 ,。函數(shù)的副作用是指函數(shù)修改了一些函數(shù)作用范圍外的東西。讓我來看一個典型python
的例子:
1 2 3 4 5 6 a = 3 def som_func(): global a a = 5 some_func() print(a)
這段代碼的輸出是5.在函數(shù)式編程范式當中,,改變變量的值是不容許的事情,,同樣影響在函數(shù)作用域之外的變量也是不被容許的。函數(shù)唯一可以做的事情是做一些計算然后以結果的形式返回,。
現(xiàn)在你可能會想:“無變量,,無副作用?為什么這樣是好的呢,?”這是個非常好的問題,,非常少的默認人多這個。
如果一個函數(shù)用同樣的參數(shù)調用兩次,,應該確保返回同樣的結果,。如果你學習了數(shù)學函數(shù) ,你會了解這種機制的好處,。這個叫做參考透明(referengtial transparency) ,,因為函數(shù)沒有副作用,,如果你構建一個用來計算的程序,,你可以提供程序的運算速度,。如果程序知道func(2)等于3,我們可以將這個結果保存到一個表格當中,。當我們已經(jīng)知道函數(shù)運行結果的時候,,這樣可以防止重新運行同樣的函數(shù)。
典型的,,在函數(shù)式編程當中,,我們不使用循環(huán)。我們采用遞歸,。遞歸是一個數(shù)學上的概念,,通常,它意味著“自己調用自己”,。在一個遞歸的函數(shù)當中,,函數(shù)重復地以子函數(shù)的形式調用自己。這里有一個非常的python
編寫的遞歸函數(shù)的例子:
1 2 3 4 5 6 7 8 def factorial_recursive(n): # Base case: 1! = 1 if n == 1: return 1 # Recursive case: n! = n * (n-1)! else: return n * factorial_recursive(n-1)
一些編程語言也非常懶惰 ,。這意味著他們不計算或者干其它任何事情直到最后一秒,。如果你寫一些代碼用來計算2 + 2
,一個函數(shù)式的程序將只有當你實際需要使用這個結果的時候才會去進行計算,。我們將很快來解釋python
的懶惰性,。
Map要理解map,讓我們首先看看什么是可迭代的,??傻木褪侨魏文憧梢缘臇|西(翻譯起來好拗口……)。典型的有列表(list)和數(shù)組(array),,不過python
有很多不同可迭代的類型,。你甚至可以通過實現(xiàn)一些神奇的方法創(chuàng)建自己的可迭代的對象。一個神奇的方法像一個可以讓你的對象更加Pythonic 的API,。你需要實現(xiàn)兩個神奇方法來使得一個對象是可迭代的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Counter: def __init__(self, low, high): # set class attributes inside the magic method __init__ # for "inistalise" self.current = low self.high = high def __iter__(self): # first magic method to make this object iterable return self def __next__(self): # second magic method if self.current > self.high: raise StopIteration else: self.current += 1 return self.current - 1
第一個神奇方法,,“__iter__”或者dunder iter(雙下劃線開頭的iter)返回可迭代的對象,這個經(jīng)常在循環(huán)的開頭使用,。Dunder下一個返回下一個對象,。
讓我們進入一個快捷終端會話來驗證一下:
1 2 for c in Counter(3, 8): print(c)
這段代碼將會打印:
在python
里面,,一個迭代器是指只有__iter__方法的對象,。這意味著你可以訪問這個對象的位置,但是不能迭代遍歷這個對象,。一些對象會有__next__方法而沒有__iter__方法,,比如sets(將會在文章后面的內(nèi)容中講到)。對于本文,,我們假設我們所接觸到的每個對象都是一個可迭代的對象,。
到目前為止,,我們知道一個可迭代的對象是怎么樣的,讓我們回到map函數(shù),。map函數(shù)允許我們將一個函數(shù)作用于一個可迭代對象當中沒一個元素,。通常我們要將一個函數(shù)作用于列表中的每一個元素,并且我們知道對于大部分可迭代對象都是可能的,。Map函數(shù)有兩個輸入,,一個是需要作用的函數(shù),另外一個是可迭代的對象,。
讓我們假設我們有一個包含以下數(shù)字的列表:
然后我們需要將每一個數(shù)字進行平方處理,,我們可以編寫代碼如下:
1 2 3 4 5 x = [1, 2, 3, 5, 5] def square(num): return num * num print(list(map(square, x)))
python
當中的函數(shù)式函數(shù)是懶惰的。加入我們沒有包括“l(fā)ist()”函數(shù),,map將返回保存對應迭代對象的定義類型數(shù)據(jù),,而不是list本身。我們需要顯式地告訴python
將這些轉換為list為我們所用,。
如果突然從非懶惰模式切換到懶惰模式會有點怪異,,如果你更多地以函數(shù)式進行思考最終會習慣這種懶惰模式。
現(xiàn)在可以很好的很好地寫一個常規(guī)的“square(num)”函數(shù),,但是看起來離正常工作還差一點,,我們必須定義完整的函數(shù)僅僅為了在 map用一次?當然,,我們可以通過lambda表達式在map當中定義函數(shù),。
Lambda 表達式一個lambda表達式是一個單行函數(shù)。舉個例子,,這個lambda表達式將對它的輸入進行平方操作:
1 square = lambda x: x * x
現(xiàn)在讓我們運行一下:
我聽到你內(nèi)心肯定在問,。“Brandon,,哪里是參數(shù),?這是怎么一回事?這看起來一定都不像一個函數(shù),?”
確實,,這看起來有些迷惑人,不過可以解釋,。來讓我們給變量“square”賦一些值,。這部分如下:
告訴python
這是一個lambda函數(shù),并且輸入被稱作x,。任何在冒號之后的都是你要對輸入進行操作的內(nèi)容,,并且將這部分操作的內(nèi)容自動返回。
因此,,為了簡化我們的平方程序到一行代碼,,我們可以這樣做:
1 2 x = [1, 2, 3, 4, 5] print(list(map(lambda num: num * num, x)))
因此,,在一個lambda表達式當中,所有的參數(shù)在左邊,,然后你要對這些參數(shù)進行的操作在右邊,。這看起來有點復雜,,誰也無法否認,。事實是那樣子編碼以致于僅讓其他函數(shù)式程序員閱讀已經(jīng)成為一種確定的讓人滿足的事情。并且,,將一個函數(shù)簡化到一行看起來也特別酷,。
ReduceReduce函數(shù)將一個可迭代的對象轉變成一個元素。通常你將一個計算作用于一個列表然后將它reduce 到一個數(shù)字,。Reduce看起來是這個樣子的:
我們可以(并且經(jīng)常如此)使用lambda表達式作為函數(shù),。
一個列表的階乘是將任意一個單一的數(shù)字相乘。為了達到這樣的目的,,你將編碼如下:
1 2 3 4 product = 1 x = [1, 2, 3, 4] for num in x: product = product * num
但是通過reduce我們可以這樣寫:
1 2 3 from functools import reduce product = reduce((lambda x, y: x * y), [1, 2, 3, 4])
得到同樣的階乘結果,。代碼更加簡潔,加入函數(shù)式編程的知識代碼更為有序,。
Filterfilter函數(shù)傳入一個迭代對象作為參數(shù)并將這個迭代對象當中所有那些你不要的東西濾去,。
通常,filter傳入一個函數(shù)和一個列表,。將這個函數(shù)作用于列表當中的任意一個元素加入函數(shù)返回True,,不做任何事情。加入返回False,,將這個元素從列表當中刪除,。
語法看起來這樣:
讓我們看一個簡單的例子,不使用filter我們這樣編碼:
1 2 3 4 5 6 x = range(-5, 5) new_list = [] for num in x: if num < 0: new_list.append(num)
通過filter,,編碼是這樣的:
1 2 x = range(-5, 5) all_less_than_zero = list(filter(lambda num: num < 0, x))
高階函數(shù)高階函數(shù)可以傳入函數(shù)作為參數(shù)并返回函數(shù),。一個簡單的例子看起來是這樣的:
1 2 3 4 5 6 7 8 9 def summation(nums): return sum(nums) def action(func, numbers): return func(numbers) print(action(summations, [1, 2, 3])) # Output is 6
或者一個更簡單關于二次定義的例子,“返回函數(shù)”,,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 def rtnBrandon(): return "brandon" def rtnJohn(): return "john" def rtnPerson(): age = int(input("What's your age?")) if age == 21: return rtnBrandon() else: return rtnJohn()
你早先應該知道我提到函數(shù)式編程語言沒有變量是怎么說的嗎,?確實,高階函數(shù)可以將這個事情變得更為簡單,。你不需要儲存一個變量如果你所要做的是將數(shù)據(jù)通過函數(shù)的通道進行傳遞,。
Python
中的所有函數(shù)都是第一類對象。第一類對象定義為具有1項或者多項以下特征:
因此,,所有Python
中的函數(shù)都是第一類且可以作為高階函數(shù)使用,。
部分應用部分應用(又叫做閉包)更為復雜,但是超級酷,。你可以調用一個函數(shù),,但不提供它所需要的全部參數(shù),。讓我們通過一個例子看看這個過程。我們要創(chuàng)建一個函數(shù)需要傳入兩個參數(shù),,一個base和一個exponet,,然后返回base的exponent次方。如下所示:
1 2 def power(base, exponent): return base ** exponent
現(xiàn)在我們想要有一個專用的平方函數(shù),,通過使用power函數(shù)得到一個數(shù)字的平方:
1 2 def square(base): return power(base, 2)
這個可以工作,,但是如果需要3次方的函數(shù)呢?或者一個需要進行4次方運算的函數(shù)呢,?我們能夠一直那樣子寫嗎,?當然,你是可以的,。但是程序員都是懶惰的,。如果你一遍又一遍地重復一件事情,這就意味著有一個更快的方式加速速度而不用去做重復的事情,。我們可以采用部分應用的方式,。讓我看一個采用部分應用的square函數(shù)的例子:
1 2 3 4 5 6 from functools import partial square = partial(power, exponent = 2) print(square(2)) # output is 4
這樣是不是很酷!我們可以通過告訴Python
第二個參數(shù)是什么,,只用一個參數(shù)調用需要兩個參數(shù)的函數(shù),。
我們也可以使用一個循環(huán),來產(chǎn)生一個乘方函數(shù)來實現(xiàn)從3次到1000次的計算,。
1 2 3 4 5 6 7 8 from functools import partial powers = [] for x in range(2, 1001): powers.append(partial(power, exponent = x)) print(powers[0](3)) # output is 9
函數(shù)式編程并不是Pythonic你可能已經(jīng)注意到了,,我們想要在函數(shù)式編程當中做的大部分事情都會圍繞著列表。除了reduce函數(shù)和部分應用,,所有其他我們看到的函數(shù)都會產(chǎn)生列表,。Guido (Python
的發(fā)明者)不喜歡Python
當中的函數(shù)式編程的成分,因為Python
已經(jīng)有自己的生成列表的方式,。
假如你在Python
的命令行環(huán)境當中輸入“import this”,,你會得到如下提示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 >>> import this The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren’t special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one — and preferably only one — obvious way to do it. Although that way may not be obvious at first unless you’re Dutch. Now is better than never. Although never is often better than *right* now. If the implementation is hard to explain, it’s a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea — let’s do more of those!
這就是Python之禪 。這是關于任何要成為Pythonic的詩,。我們想要關聯(lián)的部分如下:
There should be one-and preferably only one - obivous way to do it.
在Python
當中,,map & reduce可以做list comprehension(后面討論)同樣的事情。這個打破了Python之禪 的一條規(guī)則,,于是這部分的函數(shù)式編程看起來不是那么的‘pythonic’,。
另外一個需要討論的點是Lambda。在Python
當中,,一個lambda函數(shù)是一個常規(guī)的函數(shù),。Lambda是語法糖。以下兩者實際上是等價的:
1 2 3 4 foo = lambda a: 2 def foo(a): return 2
理論上,一個常規(guī)的函數(shù)可以干任何lambda函數(shù)能做的事情,,但是反過來卻不行,。一個lambda函數(shù)并不能干一個常規(guī)函數(shù)可以做的任何事情。
這就是一個小爭論關于為什么函數(shù)式編程不能很好地匹配整個Python
生態(tài)系統(tǒng),。你應該注意到我早先提到的list comprehensions,,現(xiàn)在我們將著重討論它。
List comprehensions早些時候,,我提到任何map或者filter能夠做的事情,,你都可以用list comprehension來實現(xiàn)。這部分內(nèi)容我們將好好學習一下list comprehension,。
一個list comprehension是Python
產(chǎn)生列表的一種方式,,語法如下:舉例如下:
1 [function for item in iterable]
然后然我對一個列表當中的每一個數(shù)字進行平方操作,,舉例如下:
1 print([x * x for x in [1, 2, 3, 4]])
ok, 現(xiàn)在我們能夠看到我們?nèi)绾螌⒁粋€函數(shù)作用于一個列表當中的每一個元素,。如果是應用filter我們會怎么做呢?看下面這段之前出現(xiàn)過的代碼:
1 2 3 4 x = range(-5, 5) all_less_than_zero = list(filter(lambda num: num < 0, x)) print(all_less_than_zero)
我們可以轉換成list comprehension:
1 2 3 x = range(-5, 5) all_less_than_zero = [num for num in x if num < 0]
List comprehensions支持這樣的if表達式,。你不再需要將上百萬個函數(shù)作用于一些東西然后得到你所想要的,。事實上,假如你試著做一些改變讓列表看起來更加清晰和簡單,,那么使用list comprehension是一個不錯的選擇,。
那么如果你想要平方所有列表中小于0的數(shù)呢?采用lambda,,map和filter你會這么寫:
1 2 3 x = range(-5, 5) all_less_than_zero = list(map(lambda num: num * num, list(filter(lambda num: num < 0, x))))
那樣看起來相當冗長并且有些復雜,。如果用list comprehension只需要這樣:
1 2 3 x = range(-5, 5) all_less_than_zero = [num * num for num in x if num < 0]
list comprehension只是對于列表的處理有好處。Map和filter可以在任何可迭代對象上面工作,,但是那有如何呢,?我們也可以用任何comprehension來處理其它所遇到的可迭代對象。
Ohter comprehensions你可以針對任何可迭代對象的comprehension
可以通過使用comprehension產(chǎn)生任何可迭代對象,。從Python 2.7開始,,你甚至可以產(chǎn)生一個詞典(hashmap)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # Taken from page 70 chapter 3 of Fluent Python by Luciano Ramalho DIAL_CODES = [ (86, 'China'), (91, 'India'), (1, 'United States'), (62, 'Indonesia'), (55, 'Brazil'), (92, 'Pakistan'), (880, 'Bangladesh'), (234, 'Nigeria'), (7, 'Russia'), (81, 'Japan'), ] >>> country_code = {country: code for code, country in DIAL_CODES} >>> country_code {'Brazil': 55, 'Indonesia': 62, 'Pakistan': 92, 'Russia': 7, 'China': 86, 'United States': 1, 'Japan': 81, 'India': 91, 'Nigeria': 234, 'Bangladesh': 880} >>> {code: country.upper() for country, code in country_code.items() if code < 66} {1: 'UNITED STATES', 7: 'RUSSIA', 62: 'INDONESIA', 55: 'BRAZIL'}
假如這是一個可迭代對象,,那他就可以被產(chǎn)生,。讓我們最后看一個關于集合(sets)的例子。如果你不懂什么是一個集合,,你可以看我的另外一篇文章 ,。TLDR是:
1 2 3 4 5 # take from page 87, chapter 3 of Fluent Python by Luciano Ramalho >>> from unicodedata import name >>> {chr(i) for i in range(32, 256) if 'SIGN' in name(chr(i), '')} {'×', '¥', '°', '£', '?', '#', '?', '%', 'μ', '>', '¤', '±', '?', '§', '<', '=', '?', '$', '÷', '¢', '+'}
你應該已經(jīng)發(fā)現(xiàn)集合具有和詞典一樣的花括號,。Python
確實非常智能,。它會根據(jù)你是否提供額外的值來判斷你編寫的是dictionary comprehension還是set comprehension。如果你要了解comprehension更多,可以看一下這個可視化導則 ,。如果你想要了解comprehension和生成器更多,,可以看這篇文章 。
結論函數(shù)式編程是優(yōu)雅而純潔的,。函數(shù)式代碼可以非常簡潔,,也可能會非常復雜。一些Python
核心程序員不喜歡函數(shù)式編程范式,。你應該用你想用的,,用解決工作所適合的最好的工具。