仅为个人查阅使用,如有错误还请指正。
函数式编程是一种抽象计算的编程模式。
函数式编程的特点
1、把计算视为函数而非指令。
2、纯粹的函数式编程语言编写的函数没有变量。
3、支持高阶函数,代码简洁。
Python支持的函数式编程支持以下特点
1、不是纯函数式编程:允许有变量。
2、支持高阶函数:函数也可以作为变量传入
3、支持闭包:有了闭包就能返回函数。
4、有限度的支持匿名函数。
-
高阶函数
直接上定义:能接收函数做参数的函数。
开始解释
-
变量可以指向函数
说白了就是函数本身可以赋值给变量
a = abs print(abs) # output:<built-in function abs> print(a) # output:<built-in function abs>
既然赋值成功,是不是还可以进行调用呢?
a = abs print(a(-10)) # output:10
objk,说明变量
a
现在已经指向了abs
函数本身。调用a()
函数和调用abs()
完全相同。 -
函数名也是变量
大致的意思就是,函数名就是指向函数的变量。
abs
这个变量指向了abs
函数。也就是说,如果你把
abs
指向了其他函数,那abs()
就不在是绝对值函数了。实例
abs = len # 把abs这个变量指向len函数 print(abs) # 此时abs它就是一个len函数,<built-in function len> print(abs(10)) # 你想要去求绝对值,是会报错的。 print(abs([1,2,3])) # 而是需要你求长度。3前面两
应该说的明明白白了。
既然变量可以指向函数,那么一个函数就可以接收另一个函数作为参数,这就是高阶函数。
体会一下最简单的高阶函数
import math def sqrt_add(x, y, f): return f(x) + f(y) print(sqrt_add(25, 9, math.sqrt)) # output:8.0
-
-
Python 内置的四大高阶函数
-
map()
该函数,它接收一个函数和一个可迭代对象。
map
将传入的函数依次作用到序列的每个元素。并把结果作为新的迭代器返回。没错,返回的map对象它是一个迭代器。
要获取具体数据,可以通过
next()
方法。也可以通过list()
函数。实例
def f(x): return x * x print(list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]))) # output:[1, 4, 9, 16, 25, 36, 49, 64, 81] # 首字母大写,后续字母小写的规则 def format_name(s): return s[0].upper() + s[1:].lower() print(list(map(format_name, ['adam', 'LISA', 'barT']))) # output:['Adam', 'Lisa', 'Bart']
-
reduce()
该函数接收的参数和
map()
类似。但行为和
map()
不同,reduce()
传入的函数 f 必须接收两个参数,reduce()
对list
的每个元素反复调用函数f
,并返回最终结果值。用求和,求积来简化对它的理解。当然Python 求和可以直接用sum(),这里是为了理解。
from functools import reduce def f(x, y): return x + y print(reduce(f, [1, 3, 5, 7, 9])) # output:25 # 还可以接收第三个参数,作为计算的初始值。 print(reduce(faa, [1, 3, 5, 7, 9], 100)) # output:125 # 第二个例子,求积函数 def prod(x, y): return x * y print(reduce(prod, [2, 4, 5, 7, 12])) # 3360
-
filter()
该函数接收的参数和
map()
类似。这个函数 f 的作用是对每个元素进行判断,返回True
或False
,如果是True
就保留该元素。没错,返回的结果跟
map()
一样,都是属于迭代器。实例
# 从一个list [1, 4, 6, 7, 9, 12, 17]中删除偶数,保留奇数 def is_odd(x): return x % 2 == 1 print(list(filter(is_odd, [1, 4, 6, 7, 9, 12, 17]))) # output:[1, 7, 9, 17] # 删除None或者空字符串 def is_not_empty(s): return s and len(s.strip()) > 0 print(list(filter(is_not_empty, ['test', None, '', 'str', ' ', 'END']))) # output:['test', 'str', 'END']
可见,
filter()
函数的作用是从一个序列中筛出符合条件的元素。 -
sorted()
排序算法的核心就是两个数比较大小。如果是比较字符串或者字典呢?用数学上的大小是没有意义的,需要通过抽象函数来比较。
-
对list进行排序
print(sorted([23, -454, 11, -6, 5])) # output:[-454, -6, 5, 11, 23]
-
可以接受
key
函数来实现自定义排序。print(sorted([23, -454, 11, -6, 5], key=abs)) # output:[5, -6, 11, 23, -454] # key指定的函数将作用于list的每一个元素上,并把返回的结果进行排序。
-
对字符串进行排序,是按照ASCII的大小比较的。
现在提出忽略大小写,然后进行排序,我们不需要先把字符串全改小写。
按照前面一个例子,只要通过
key
接收的这个函数进行处理即可。print(sorted(["harden", "Durant", "jordan", "curry", "O'Neal"], key=str.lower)) # output:['curry', 'Durant', 'harden', 'jordan', "O'Neal"]
综上所述:
sorted()
函数排序的精髓在于实现一个映射函数。
-
-
-
返回函数
-
函数作为返回值
直接用实例来分析
def f(x): print("run f_function ...") def g(): print("run g_function ...") return x return g print(f(5)) # output: run f_function ... <function f.<locals>.g at 0x0000017364A0BF28> # 当调用f函数时,返回的并不是x的值,而是g函数。 # 要去调用g函数,才会返回x的值 print(f(5)()) # output: run f_function ... run g_function ... 5 is ok
在这个例子中,函数
f
中又定义了函数g
,并且,内部函数g
可以引用外部函数f
的参数和局部变量。当f
返回函数g
时,相关参数和变量都保存在返回的函数中。这种称为”闭包“。 -
闭包
闭包的特点是返回的函数还引用了外层函数的局部变量和参数。
看看简单,其实要用起来还是很难的。
现在让你用闭包实现一个功能,分别计算
1x1
,2x2
,3x3
def count(): fs = [] for i in range(1, 4): def f(): return i*i fs.append(f) return fs f1, f2, f3 = count() print(f1(), f2(), f3())
你希望的结果是1,4,9。我也希望是这样的。实际输出的是9,9,9
原因就是当
count()
函数返回了3个函数时,这3个函数所引用的变量i的值已经变成3。当你去调用的是,f1()
--> 返回的值就是9。因此,返回函数不要引用任何循环变量,或者后续会发生变化的变量。
改正上面的函数
def count(): fs = [] for i in range(1, 4): def f(i): return lambda : i*i fs.append(f(i)) return fs f1, f2, f3 = count() print(f1(), f2(), f3())
把原先的
return i*i
换成了return lambda : i*i
。使用lambda
是一个匿名函数。参数不变的原因是跟函数绑定在一起的值不变。
-
-
匿名函数
用
map()
函数为例,再次来计算f(x)
= x²,之前的方法是,定义一个函数。def f(x): return x * x print(list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
现在的方式是,直接写一个匿名函数
print(list(map(lambda x: x*x, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
很显然,
lambda x: x * x
就是函数f()
。关键字lambda表示匿名函数,冒号前面的
x
表示函数参数。匿名函数有个限制,就是只能有一个表达式,不写return,返回值就是表达式的结果。
当然,匿名是一个函数对象,就可以赋值给一个变量,再利用变量来调用该函数。
-
装饰器
在不修改原函数的前提下,动态的给它新增功能。称之为“装饰器”。
通过高阶函数返回新函数
def f1(x): return x * 2 def new_fn(f): def fn(x): print("call " + f.__name__ + " ()") return f(x) return fn g1 = new_fn(f1) print(g1(5)) # 这种调用方式的结果:f1的原始定义函数被彻底隐藏了。 f1 = new_fn(f1) print(f1(5))
Python内置的@语法就是为了简化装饰器的调用
@new_fn
==f1 = new_fn(f1)
-
无参装饰器
由前面的介绍,我们知道Python的 decorator 本质上就是一个高阶函数, 它接收一个函数作为参数,然后,返回一个新函数。
引入函数中提到的终极螺旋组合。
from functools import reduce def log(f): def fn(x): print 'call ' + f.__name__ + '()...' return f(x) return fn # 首先是factorial函数要去调用是可型的。因为只要一个参数。 @log def factorial(n): return reduce(lambda x,y: x*y, range(1, n+1)) print(factorial(10)) # 那如果add函数也要去调用这个装饰器就会报错,因为有两个参数,不匹配。 @log def add(x, y): return x + y print(add(1, 2))
要让
@log
自适应任何参数定义的函数,可以用*args
和**kw
,保证任意个数的参数总是能正常调用。改正
def log(f): def fn(*args, **kw): print('call ' + f.__name__ + '()...') return f(*args, **kw) return fn
-
带参装饰器
实例
def log(prefix): def log_decorator(f): def wrapper(*args, **kw): print('[%s] %s()...' % (prefix, f.__name__)) return f(*args, **kw) return wrapper return log_decorator @log('DEBUG') def test(): pass print(test())
这是3层嵌套。你只需理解带参装饰器一般都是需要3层循环,可以按照这个模板去写。
这里简单的讲一下:
@log('DEBUG') def test(): pass # 这个定义相当于是 test = log('DEBUG')(test) # 再进行转换 log_decorator = log("DEBUG") test = log_decorator(test) # 再进行转换就是 log_decorator = log("DEBUG") @log_decorator def test(): pass # 所以,带参数的log函数首先返回一个decorator函数,再让这个decorator函数接收test并返回新函数。
不管有没有理解,现在要去解析带参装饰器实例代码的执行流程。
首先执行
log("DEBUG")
,返回的是log_decorator
函数,再调用返回的函数,参数是test
函数,返回值最终是wrapper
函数。 -
完善装饰器
以上两张方法其实基本上就完事了,为什么还要完善,是因为对原函数加了装饰器,所以原函
数的函数名就变成了新函数的函数名。以上一个为例子,
test
的函数名变成了wrapper
。可以通过
__name__.py
去获取函数名。这就意味着依赖函数名的代码就会失效,比如test.__doc__
等其他属性。所以需要把原始函数的__name__
等属性复制到wrapper()
函数中。Python为了方便起见,不需要编写
wrapper.__name__ = f.__name
,wrapper.__doc__ = f.__doc__
这样的操作。直接导入模块就好了。通过
functools
这个工具来完成这个复制过程。import functools def log(f): @functools.wraps(f) def fn(*args, **kw): print('call ' + f.__name__ + '()...') return f(*args, **kw) return fn def log(prefix): def log_decorator(f): @functools.wraps(f) def wrapper(*args, **kw): print('[%s] %s()...' % (prefix, f.__name__)) return f(*args, **kw) return wrapper return log_decorator
以上是修改了装饰器部分。可以看出再指向函数的前面写上方法。
-
-
偏函数
前面提到了
functools
模块,该模块功能很多,其中一个就是偏函数。偏函数的作用就是降低函数调用的难度。
我在函数那个章节提到过,是通过设置参数默认值来降低函数调用的难度。
在函数里面提到的
int()
函数,可以进行二进制转换。但是默认是十进制。为了避免每次调用都是
int(x, base=2)
,我们就写一个函数。来定制。functools.partial
可以把一个参数多的函数变成一个参数少的新函数,少的参数需要在创建时指定默认值,这样,新函数调用的难度就降低了。import functools int2 = functools.partial(int, base=2) print(int2('1000000')) # output:64 print(int2('1010101')) # output:85
最后再补充一点:创建偏函数时,实际上可以接收函数对象、
*args
和**kw
这3个参数。