[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)
```