Fork me on GitHub

python函数式编程

python函数式编程
此处输入图片的描述

高阶函数

map()和reduce()

参考论文
map(): 这个函数接收两个参数,一个是函数,一个是Iterable(可迭代对象),map将传入的函数$f(x)$依次作用到序列的每个元素上,并把结果作为新的Iterator(迭代器)返回。

1
2
#实现将一个数字序列转化成字符序列
list(map(str,[1,2,3,4,5]))

reduce(): 把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数$f(x,y)$,reduce把结果继续和序列的下一个元素做累积计算。

1
2
3
4
5
6
7
#对一个序列求和
>>> from functools import reduce
>>> def add(x, y):
... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25

filter()

用于过滤序列,和map()类似,filter()也接收一个函数和一个序列。
和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。返回一个Iterator(这说明返回的依旧是一个惰性序列)。

1
2
3
4
5
6
7
8
9
10
11
12
13
#删掉一个序列中的空字符串
"""
注意filter()给函数默认加bool()修正
比如:
bool(" a")=True
bool(" ")=True
bool(" ".Strip())=False
bool(None)=False
"""
def not_empty(s):
return s and s.strip()
list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))
# 结果: ['A', 'B', 'C']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#用filter()求素数的方法——埃氏筛法
#定义一个筛选函数
def _not_divisible(n):
return lambda x: x % n > 0
#初始化自然数序列
def _iter():
n = 1
while True:
n = n + 2
yield n
def primes():
yield 2
it = _iter()
while True:
n = next(it)
yield n
it = filter(_not_divisible(n), it)
1
2
3
4
5
6
7
8
9
10
"""
def _iter():
n = 1
while True:
yield n
n = n + 1
"""
def is_palindrome(n):
return str(n) == str(n)[::-1]
list(filter(is_palindrome),range(1000))

sorted()

可以实现对list进行排序。
同时,可以接受一个key 函数来实现自定义排序。

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

key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。

1
2
3
4
>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']


返回函数

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

1
2
3
4
5
6
7
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum

♥♥♥♥ 闭包♥♥♥♥
建议参考教程——知乎专栏
闭包概念:在一个内部函数中,对外部作用域的变量进行引用,(并且一般外部函数的返回值为内部函数),那么内部函数就被认为是闭包。
 在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这称为“闭包(Closure)”的程序结构。
 需要注意的问题是,循环在python中没有域的概念,向列表中添加函数的时候并不会保存循环中变量的值。返回的函数并没有立刻执行,而是直到调用了f()才执行。

1
2
3
4
5
6
7
8
9
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()结果应该是1,4,9,但实际结果是:

1
2
3
4
5
6
>>> f1()
9
>>> f2()
9
>>> f3()
9

返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
原因:闭包函数调用外部循环变量时,并没有保存这个值,只保存了变量的地址,要等到调用闭包函数时才会取具体的值,然而此时函数值可能已经发生了变化。
解决办法:再定义一个函数,将g()形成闭包。主要是要在函数内部,把可变的循环值i作为函数参数调用。简单来说,一定要有f(i),在调用过程中,i就会被传入。

1
2
3
4
5
6
7
8
9
def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return fs

利用闭包返回一个计数器函数,每次调用它返回递增整数:

1
2
3
4
5
6
7
def createCounter()
s=[0]
def conter():
s[0] += 1
return s[0]

return counter

外函数返回了内函数的引用当我们在python中定义一个函数def demo(): 的时候,内存当中会开辟一些空间,存下这个函数的代码、内部的局部变量等等。这个demo只不过是一个变量名字,它里面存了这个函数所在位置的引用而已。我们还可以进行x = demo, y = demo, 这样的操作就相当于,把demo里存的东西赋值给x和y,这样x 和y 都指向了demo函数所在的引用,在这之后我们可以用x() 或者 y() 来调用我们自己创建的demo() ,调用的实际上根本就是一个函数,x、y和demo三个变量名存了同一个函数的引用。

闭包中内函数修改外函数局部变量:

在闭包内函数中,我们可以随意使用外函数绑定来的临时变量,但是如果我们想修改外函数临时变量数值的时候发现出问题了!
在基本的python语法当中,一个函数可以随意读取全局数据,但是要修改全局数据的时候有两种方法:1 global 声明全局变量 2 全局变量是可变类型数据的时候可以修改
在闭包内函数也是类似的情况。在内函数中想修改闭包变量(外函数绑定给内函数的局部变量)的时候:

  1. 在python3中,可以用nonlocal 关键字声明 一个变量, 表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。
  2. 在python2中,没有nonlocal这个关键字,我们可以把闭包变量改成可变类型数据进行修改,比如列表。

闭包的作用:

  1. 装饰器!!!装饰器是做什么的??其中一个应用就是,我们工作中写了一个登录功能,我们想统计这个功能执行花了多长时间,我们可以用装饰器装饰这个登录模块,装饰器帮我们完成登录函数执行之前和之后取时间。
  2. 面向对象!!!经历了上面的分析,我们发现外函数的临时变量送给了内函数。大家回想一下类对象的情况,对象有好多类似的属性和方法,所以我们创建类,用类创建出来的对象都具有相同的属性方法。闭包也是实现面向对象的方法之一。在python当中虽然我们不这样用,在其他编程语言入比如avaScript中,经常用闭包来实现面向对象编程
  3. 实现单利模式!!
  4. 闭包可以保存当前的运行环境,以一个类似棋盘游戏的例子来说明。假设棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向(direction),步长(step),该函数控制棋子的运动。 这里需要说明的是,每次运动的起点都是上次运动结束的终点。

匿名函数

在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。

1
2
3
4
5
6
7
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
"""
其中,lambda x: x * x相当于:
def f(x):
return x * x
"""

同时,匿名函数可以作为返回函数。


装饰器(decorator)

装饰器就是一个返回函数的高阶函数,基于闭包原理。
定义一个打印日志的decorator:

1
2
3
4
5
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper

定义一个带参数的decorator:

1
2
3
4
5
6
7
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator

函数也是对象,它有name等属性,但你去看经过decorator装饰之后的函数,

1
2
3
@log
def now():
print('2015-3-25')

它们的name已经从原来的’now’变成了’wrapper’:

1
2
>>> now.__name__
'wrapper'

因为返回的那个wrapper()函数名字就是’wrapper’,所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写wrapper.__name = func.\name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

1
2
3
4
5
6
7
8
import functools

def log(func):
@functools.wraps(func)#复制依赖函数
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper


偏函数(Partial function)

偏函数可以通过设定参数的默认值,降低函数调用的难度。由Python的functools模块提供。
举例说明:

1
2
3
4
5
6
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85

上面的新的int2函数,仅仅是把base参数重新设定默认值为2,但也可以在函数调用时传入其他值:

1
2
>>> int2('1000000', base=10)
1000000

创建偏函数时,实际上可以接收函数对象、args和*kw这3个参数,当传入:

1
2
3
4
int2 = functools.partial(int, base=2)
#相当于
kw = { 'base': 2 }
int('10010', **kw)


Reference

教程

-------------本文结束感谢阅读-------------