[toc]
# 函數式編程
## 高階函數
### 特點
1. 變量可以指向函數
2. 函數名也是變量
3. 參數可以傳入函數
```python
def add(x, y, f):
return f(x) + f(y)
print(add(-5, 6, abs)) # 11
```
### 常用的高階函數
#### map
> 接收兩個參數,一個是函數,一個是Iterable,map將傳入的函數依次作用到序列的每個元素,并把結果作為新的Iterator返回。
```python
def f(x):
return x*x
r = map(f,[1,2,3,4,5,6,7,8]) # <map object at 0x0000023D81A60FD0>
list(r) # [1, 4, 9, 16, 25, 36, 49, 64]
list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])) # ['1', '2', '3', '4', '5', '6', '7', '8', '9']
```
#### reduce
> reduce把一個函數作用在一個序列``[x1, x2, x3, ...]``上,這個函數必須接收兩個參數,reduce把結果繼續和序列的下一個元素做累積計算,其效果就是
```
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
```
```python
from functools import reduce
# add只能是是兩個參數
def add(x,y):
return x+y
# 當然也可以是可變參數
def add(*numbers):
sum = 0
for n in numbers:
sum += n
return sum
reduce(add,[1,2,3,4,5]) # 1+2+3+4+5=15
def fn(x, y):
return x * 10 + y
reduce(fn, [1, 3, 5, 7, 9])
# 函數式編程實現字符串轉int
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def str2int(str):
def fn(x,y):
return x*10+y
def char2num(i):
return DIGITS[i]
return reduce(fn,map(char2num,str))
str2int('2878') # 2878
# 通過lambda函數進一步簡化
def char2num(s):
return DIGITS[s]
def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))
```
#### filter
> 和map()類似,filter()也接收一個函數和一個序列(包括無限的惰性序列)。和map()不同的是,filter()把傳入的函數依次作用于每個元素,然后根據返回值是True還是False決定保留還是丟棄該元素。
```python
# 刪掉偶數,只保留奇數
def is_odd(n):
return n % 2 == 1
list(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']
```
**用Python來實現埃氏篩法**
埃氏篩法:取序列的第一個素數,篩選掉其倍數,返回新的序列,再取第一個素數,再篩選掉其倍數,再返回新的序列
```python
# 構建一個從3開始的奇數序列(偶數除2以外都是合數)
def _odd_iter():
n = 1
while True:
n += 2
yield n
# 構建一個篩選函數,篩選不能整除的數
def _not_divisible(n):
return lambda x : x % n > 0;
# 定義一個生成器
def primes():
yield 2
it = _odd_iter()
while True:
n = next(it)
yield n
# it = filter(_not_divisible(n),it) # 注意這里的n,n是上文的n,it里面的元素傳入_not_divisible里面的x
it = filter(lambda x,n = n : x % n > 0, it)
for num in primes():
if num < 100:
print(num)
else:
pass
```
> xxx 這個地方還是有點不太好理解
#### sorted
```python
sorted([36, 5, -12, 9, -21]) # [-21, -12, 5, 9, 36] 默認從小到大
sorted([36, 5, -12, 9, -21], key=abs) # 按絕對值大小排序,key指定的函數將作用于list的每一個元素上,并根據key函數返回的結果進行排序。
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower) # 忽略大小寫的排序,因為字符串默認是按照ASCII的大小比較的 'Z' < 'a'
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) # reverse可以指定進行反向排序
# 按成績從大到小排序
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
sorted(L,key=lambda t : t[1],reverse=True) # [('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)]
```
## 函數作為返回值
### 實現函數懶加載
```python
def lazy_sum(*args):
# 里面的變量全部被閉包了
def sum(num):
ax = 0
for n in args:
ax += n
ax *= num
return ax
return sum
# 注意,內存地址不一樣,表示每次返回的都是一個新的函數
func = lazy_sum(1,2,3,4,5) # <function lazy_sum.<locals>.sum at 0x00000176841D5510>
func2 = lazy_sum(1,2,3,4,5) # <function lazy_sum.<locals>.sum at 0x00000236D9D4A7B8>
func(10) # 15 實現函數懶加載,此處的傳參是傳給內部函數
```
### 閉包
```python
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count() # f1,f2,f3都是9,因為執行的時候i都變成3了
def count():
fs = []
for i in range(1, 4):
def f(i):
return i*i
fs.append(f(i)) # f(i)立刻被執行,因此i的當前值被傳入f()
return fs
f1, f2, f3 = count() # f1 = 1,f2 = 4,f3 = 9
```
### 示列
#### 計數器
```python
# example 1:
def counter():
n = [0]
def compute():
n[0] += 1
return n[0]
return compute
# example 2:
def counter():
n = 0
def compute():
# n = n + 1 # n為標量(數值,字符串,浮點數),在compute中被修改,就被認為是compute內部的變量,而不是外部的變量,此時就會報錯
y = n + 1 # 更改為y就沒事,但實現不了
return y
return compute
```
## 匿名函數 lambda
```python
list(map(lambda x : x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])) # lambda x : x * x 能寫在一行的函數,冒號前面的表示參數
def build(x, y):
return lambda: x * x + y * y
```
## 裝飾器
Python一切皆對象,有對象就有屬性
```python
def now():
print('2018-2-27 13:40:26')
print(now) # 函數名也是變量名 <function now at 0x0000016D2DBE2E18>
func = now # 賦值給另外一個變量以后,還是對同一個內存進行引用
print(func) # <function now at 0x0000016D2DBE2E18>
print(func.__name__) # now
```
現在,假設我們要增強now()函數的功能,比如,在函數調用前后自動打印日志,但又不希望修改now()函數的定義,這種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)。
```python
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
# 通過@語法把decorator置于函數的定義處,相當于now = log(now)
@log
def now():
print('2015-3-25')
now() # 此處執行的是wrapper
```
**裝飾器有參數**
```python
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log('execute')
def now():
print('2015-3-25')
now() # 最終執行的是log('execute')(now) => decorator(now) => wrapper(*args, **kw);
now.__name__ # 使用裝飾器后的__name__已經變成wrapper了,所以,需要把原始函數的__name__等屬性復制到wrapper()函數中,否則,有些依賴函數簽名的代碼執行就會出錯。
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log('execute')
def now():
print('2015-3-25')
r = now.__name__ # now
```
## 偏函數
用于固定已有的函數參數,簡化參數傳入
```python
# 新建一個函數來固定參數
def int2(x, base=2):
return int(x, base)
# 通過functools.partial來固定參數
import functools
int2 = functools.partial(int, base=2)
int2('1000000', base=10) # 固定后一樣可以傳入新的
# 相當于
kw = { 'base': 2 }
int('10010', **kw)
max2 = functools.partial(max, 10)
max2(5, 6, 7)
# 相當于
args = (10, 5, 6, 7)
max(*args)
```