首頁  >  文章  >  後端開發  >  [python] 初探'函數式程式設計'

[python] 初探'函數式程式設計'

高洛峰
高洛峰原創
2017-02-16 11:09:491301瀏覽

函數式程式設計

上學期有上一門叫'人工智慧' 的課,老師強行要我們學了一個叫做prolog 的語言,哇那感覺確實難受,思維方式完全和之前學過的不一樣,寫個漢諾塔想了半天,最後還是在網上找了段代碼修改一下(怕被老師發現抄襲)才寫出來,貼一段出來感受一下:

hanoi(N) :- dohanoi(N, 'a', 'b', 'c').
dohanoi(0, _ , _ , _ )    :- !.
dohanoi(N, A, B, C)    :-
  N1 is N-1,
  dohanoi(N1, A, C, B),
  writeln([move, N, A-->C]), 
  dohanoi(N1, B, A, C).

當時是差不多弄懂了,主要是資料實在太少,debug 都無從談起,一遇上bug 就gg,我現在自己看也有點頭暈。不過據說 prolog 當年能和 Lisp 一爭高下,最近對 Lisp 也有點興趣,等弄完這些就去參拜一下這類函數式語言。

何謂函數式程式設計?廖大這裡寫道:

函數式程式設計就是一種抽象程度很高的程式設計範式,純粹的函數式程式語言所寫的函數沒有變量,因此,任意一個函數,只要輸入是確定的,輸出就是確定的,這種純函數我們稱之為沒有副作用。而允許使用變數的程式設計語言,由於函數內部的變數狀態不確定,同樣的輸入,可能得到不同的輸出,因此,這種函數是有副作用的。

可能看完還是有些不太理解,不急,先看完這幾個小節吧。

高階函數

在數學和電腦科學中,高階函數是至少滿足下列一個條件的函數:

  • 接受一個或多個函數作為輸入

  • 輸出一個函數

    是說,把函數本身當成參數傳遞,或是回傳一個函數。
例如,可以像普通賦值一樣將函數賦值給變數:

>>> min(1, 2)
1
>>> f = min
>>> f(1, 2)
1
>>> f
<built-in function min>
>>> min
<built-in function min>
也可以給函數賦值(代碼接上):

>>> min = 10
>>> min(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
>>> f(1, 2)
1
>>> min = f
>>> min(1, 2)
1
還可以傳參,例如,一個計算所有數字的和的函數:

>>> def add(a, b):
...     return a+b
...

>>> def mysum(f, *l):
...     a = 0
...     for i in l:
...             a = f(a, i)
...     return a
...
>>> mysum(add, 1, 2, 3)
6
>>> mysum(add, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
55
當然,將這個f 換成乘法就是計算所有數字的乘積了。

再來看看 python 內建的一些高階函數,常常會用到。

map/reduce

記得上學期上雲端運算的課程時依稀有聽過這個詞,不過這課很水,就沒怎麼聽,在這裡看到好像發現不太一樣? ?

不過沒啥說的,簡單說一下每個函數的作用。

對於 map,其計算式可以看成這樣:

map(f, [x1, x2, ..., xn]) = [f(x1), f(x2), ..., f(xn)]
對於 reduce,其計算式可以看成這樣:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
廖大那裡說得很清楚啦。

filter

filter 和 map 函數類似,接受一個函數和 iterable,傳回也是一個 list,不過其功能是根據函數傳回值是否為 True 來判斷是否保留該值。例如:

def is_odd(n):
    return n % 2 == 1

list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]

sorted

sorted 函數同樣是一個高階函數,對參數key 傳遞函數可以將需要排列的序列經過key 函數處理後再進行排序,不過不會改變序列的值,例如:

>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

裝飾器(decorator)

匿名函數就不說了,以後用時再仔細看吧,裝飾器我記得之前看flask 的時候都研究了好久,這次再來複習一下。

簡單裝飾器

首先是一個簡單的裝飾器,在每次調用函數前打印出日誌:

import logging

def log(func):
    def wrapper(*args, **kw):
        logging.warn("%s is running" % func.__name__)
        func(*args, **kw)
    return wrapper

這就是一個極其簡單的裝飾器,如何使用它呢?我最先看到的用法是在需要裝飾的函數前面加上@,但其實這是Python 的一個語法糖,最原始的用法反而更能讓人理解,先定義一個函數f:

def f():
    print("in function f")

f = log(f)
這樣定義了之後,我們再呼叫f 函數:

>>> f()
WARNING:root:f is running
in function f
使用@log 的結果與其一樣,其實@符號作為裝飾器的語法糖,與前面的賦值語句具有相同的功能,使代碼看起來更簡潔明了,避免再一次賦值操作,就像下面這樣:

@log
def f():
    print("in function f")
含參數的裝飾器

有時候我們還需要向裝飾器中傳入參數,例如,狀態,層次等信息,只需要在wrapper 函數外再'包裹'一層函數,如下所示:

import logging

def log(level):
    def decorator(func):
        def wrapper(*args, **kw):
            logging.warn("%s is running at level %d" % (func.__name__, level))
            return func(*args, **kw)
        return wrapper
    return decorator

@log(2)
def f():
    print("in function f")
    
>>> f()
WARNING:root:f is running at level 2
in function f

進一步理解

為了再進一步理解裝飾器,我們可以打印出函數f 的name 屬性:

#对于不加装饰器的 f,其 name 不变
>>> def f():
...     print("in function f")
...
>>> f.__name__
'f'

#对于添加装饰器的函数,其 name 改变了
>>> @log
... def f():
...     print("in function f")
...
>>> f.__name__
'wrapper'

聯繫到最前面的裝飾器賦值語句,就可以大致明白髮生了什麼:

f = log(f)

使得f 指向修改為log(f) 的回傳值,即wrapper 函數。每次執行原函數 f 時,則會呼叫 wrapper 函數,在我們這個範例中,則是先列印日誌,然後執行原函數 f。

不過這樣有一個問題,這樣使得原函數f 的元資訊被替換了,關於f 的許多資訊消失不見,這是很難令人接受的,不過好在我們有functools 模組,修改函數為:

import functools
import logging

def log(func):
    functools.wraps(func)
    def wrapper(*args, **kw):
        logging.warn("%s is running" % func.__name__)
        func(*args, **kw)
    return wrapper

>>> @log
... def f():
...     print("in function f")
...
>>> f.__name__
'f'
另外,還可以對同一個函數添加多個裝飾器:

@a
@b
@c
def f ():


# 等价于

f = a(b(c(f)))
總結

關於函數式編程我也不是很了解,這裡只是大概了解了一下其概念吧,平時肯定還是使用命令式編程用得多。不過有語言是純函數式語言,例如 Haskell 或 Lisp,學習它們會讓人打開一種新思路。

更多[python] 初探'函數式程式設計'相關文章請關注PHP中文網!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn