文章目录
函数式编程理解
对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
其可以定义为:
var result = subtract(multiply(add(1,2), 3), 4);
可以变换为:
add(1,2).multiply(3).subtract(4)
其只用"表达式",不用"语句":
高阶函数(Higher-order function)
函数本身也可以赋值给变量,即:变量可以指向函数。
a = abs
print(a(-10))--->10
函数名也是变量
函数名其实就是指向函数的变量!对于abs()这个函数,也可以吧abs看成变量,不过是指向了可以计算出绝对值的函数而已。
若令abs=10,abs(10)就会出错
(实际写代码。不可以这么写)
注:由于abs函数实际上是定义在import builtins模块中的,所以要让修改abs变量的指向在其它模块也生效,要用
import builtins; builtins.abs = 10
传入函数
当一个函数接收另一个函数作为参数,这个函数就是高阶函数
而函数式编程就是指这种高度抽象的编程范式。
如:
def add(a,b,f):
return f(a)+f(b)
print(add(-1,1,abs))-->2
map/reduce
Python内建了map()
和reduce()
函数。
map()
map()
函数接收两个参数,一个是函数,一个是Iterable
(可迭代对象,字符串,列表,元组),map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
(Iterator是惰性序列你不主动去遍历它,就不会计算其中元素的值)返回。
>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
注意:map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。
当然用for循环也可以实现;
L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
L.append(f(n))
print(L)
很难看出:把f(x)作用在list的每一个元素并把结果生成一个新的list
所以,map()作为高阶函数
,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x^2,还可以计算任意复杂的函数,
如:list所有数字转成字符串:
>>> 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
把结果继续和序列的下一个元素做累积计算,效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
举例:
from funtools import reduce
def add(x,y):
return x + y
reduce(add,[1,3,5,7])---->25
字符串转换
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> def char2num(s):
... digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
... return digits[s]
...
>>> reduce(fn, map(char2num, '13579'))
>'不能直接输出map(char2num, '13579'),因为map()函数转换后的序列是惰性的,
>print(map(char2num, '13579')),打印的是map作用后序列的地址。<map object at 0x0000002F024F3E10>'
13579
可整理为:
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def str2int(s):
def fn(x, y):
return x * 10 + y
def char2num(s):
return DIGITS[s]
return reduce(fn, map(char2num, s))
用lambda函数进一步简化成:
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def char2num(s):
return DIGITS[s]
def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))
练习:
1.利用map()函数,将英文名字变为首字母大写,其他小写的规范名字
L1 = ['adam', 'LISA', 'barT']
def normalize(name):
name = name[0].upper()+name[1:].lower()
return name
#map 每次是传入一个字符串'adam'
L2 = list(map(normalize, L1))
print(L2)
2.编写一个prod()函数,可以接受一个list并利用reduce()求积:
from functools import reduce
def prod(L):
def mu(x,y):
return x * y
return reduce(mu,L)
print(prod([3,5,7,9]))
3.利用map和reduce编写一个str2float函数,把字符串’123.456’转换成浮点数123.456:
from functools import reduce
DIC = {'.':'.','0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def str2num(s):#字符转换为数字
return DIC[s]
def cal(x, y): #计算
return 10*x +y
s = input('请输入浮点数:')
L = list(map(str2num,s))#将字符串中的所有字符映射为对应的数字和标点,
n = L.index('.')#获取位置
n1 = L[0:n]
n2 = L[n+1:]
L2 = reduce(cal,n1) + reduce(cal,n2)/10**len(n2)
print(L2)
filter(用于过滤序列)
和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,
filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]
注意到filter()函数返回的是一个Iterator
,也就是一个惰性序列,所以要强迫filter()
完成计算结果,需要用list()函数获得所有结果并返回list。
例子:
计算素数
def _odd_iter():
n = 1
while True:
n = 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) # 构造新序列
for n in primes():
if n < 50:
print(n)
else:
break
练习:回数是指从左向右读和从右向左读都是一样的数,例如12321,909。请利用filter()筛选出回数:
def is_palindrome(n):
return str(n)==str(n)[::-1]
output = filter(is_palindrome, range(1, 1000))
print('1~1000:', list(output))
if list(filter(is_palindrome, range(1, 200))) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191]:
print('测试成功!')
else:
print('测试失败!')
sorted
在排序中:
如果是数字,我们可以直接比较,但如果是字符串或者两个dict呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。
sorted()
函数就可以对list
进行排序:
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
sorted()
也是一个高阶函数,可以接收一个key函数来实现自定义的排序
例如按绝对值大小排序:
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
key
指定的函数将作用于list的每一个元素上,并根据key函数
返回的结果进行排序
也可以对字符串进行排序:
(比较是按照ASCII码值进行比较的)
>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']
也可以对字符串的的小写进行排序:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
反向排序:
不必改动key函数,可以传入第三个参数reverse=True:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
练习:
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
#sorted每次作用一个元素,如第一次取('Bob', 75)
名字排序:
def by_name(t):
return t[0]
L2 = sorted(L, key=by_name)
print(L2)
成绩从高到低排序:
def by_score(t):
return t[1]
L2 = sorted(L, key=by_score)
print(L2)