python 函数编程
又见函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99//python中的函数式
面向过程编程利用选择和循环结构,以及函数,模块等,对指令进行封装
面向对象实现了另一种形式封装:包含数据的对象的一系列方法,这些方法能造成对象的状态改变
函数是编程的本质也在于封装:以函数为中心进行代码封装
函数是第一级对象,能像普通对象一样使用.
函数式编程强调了函数的纯粹性,一个纯函数是没有副作用的,即这个函数的运行不会影响其它函数
函数式编程要求其变量都是不可变更的
纯函数方便进行并行运算: 在并行化编程时,我们经常担心不同进程之间相互干扰的问题.当多个进程同时修改一个变量时,进程的先后顺序会影响最终的结果.函数式编程消灭了副作用(竞跑条件:不确认进程执行先后顺序,结果不同)
python并非是一门函数式编程语言,后来加入了lambda,map,filter,reduce等高阶函数,从而加入了函数式编程特征
学习函数式编程能深刻的影响编程的思维方式: 自上而下,在最高层用一个函数来解决这个大问题,在这个函数内部,在用其它函数解决小问题
//并行运算
所谓的并行运算,是指多条指令同时执行
一般来说,一台单核处理器计算机同一时间内只能执行一条指令,这种每次执行一条指令的工作方式称为串行运算
大规模并行运算通常是在有多个主机组的集群上进行的.主机之间通过网络设备通信.同时一个集群造价不菲.
然而,我们可以在单机上通过多进程或多线程的方式,模拟多主机的并行运算
事实上,单机的处理器按照"分时复用"的方式,把运算能力分配给多个进程.处理器在进程之间频繁切换.因此,即使处理器同一时间只能处理一个指令,但通过在进程间的切换,也能造成多个进程齐头并进的效果.
多进程和多线程的区别:
一个程序运行后,就称为一个进程:进程有自己的内存空间,用来存储自身的运行状态,数据和相关代码.一个进程一般不会直接读取其他进程的内存空间.进程运行过程中,可以完成进程描述的工作.但在一个进程内部,又可以有多个称为"线程"的任务,处理器可以在多个线程之间切换,从而形成并行多线程处理.线程看起来和进程类似,但线程之间可以共享同一个进程的内存空间
//被解放的函数
函数可以像一个普通对象一样,称为其他函数的参数
例如:
def square_sum(a,b):
return a**2 + b**2
def cubic_sum(a,b):
return a**3 + b**3
def arg_demo(f,a,b):
return f(a,b)
print(arg_demo(square_sum(3,5))
print(arg_demo(cubic_sum(3,5))
函数可以称为另一个函数的返回结果
例如:
def line_count():
def line(x):
return 2*x+1
return line
my_line=line_count()
print(my_line(2))
函数对象的作用域与他的def的缩进层级相同
//闭包
def line_count():
b = 15
def line(x):
return 2*x+b
b = 5
if __name__ == '__main__':
my_line = line_count():
print(my_line(5))
line()定义的隶属程序块中引用了高层及的变量b.b的定义并不在line()内部,而是一个外部对象.我们称b为line()的环境变量.尽管b位于line()定义外部,但当line被函数line_count()返回时,还是会带有b的信息
一个函数和它的环境变量合在一起,就构成了一个闭包.闭包中包含的是内部函数返回时的外部对象的值.
闭包可以提高代码的可复用性
闭包可以减少函数参数的作用
//装饰器
装饰器是一种高级的python语法,可以对一个函数,方法或者类进行加工.
例如:
def decorator_demo(old_function):
def new_function(a,b):
print("input",a,b)
return old_function(a,b)
return new_function
@decorator_demo
def square_sum(a,b):
return a**2 + b**2
@decorator_demo
def square_diff(a,b):
return a**2 - b**2
if __name__ == '__main__':
print(square_sum(3,4))
print(square_diff(3,4))
装饰器通过def形式定义,例如:decorator_demo()
装饰器接收一个可调用对象作为输入参数,并返回一个新的可调用对象.
装饰器新建了一个函数对象new_function(),在里面我们定义了打印功能,并调用old_function(a,b)保留原有函数功能
我们通过@decorator_demo语法调用.实际上是将square_sum()或square_diff()传递给了decorator_demo(),并将decorator_demo()返回的新函数对象赋给原来的函数名square_sum()和square_diff()
所以,当我们调用square_sum(3,4)时,实际发生的是:
square_sum = decorator_demo(square_sum)
square_sum(3,4)
装饰器起到的作用就是名称绑定,让同一个变量名指向一个新返回的函数对象,从而达到修改函数对象的目的
在使用装饰器时,我们往往会在新函数内部调用旧的函数,以便保留函数的功能函数式编程常用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33//lambda
lambda定义匿名函数
例如:
add = lambda x,y: x + y
//map
map接收一个方法和一个集合作为参数.它创建一个新的空集合,以每一个集合中的元素作为参数调用这个传入的方法,然后把返回值插入到新创建的集合中,最后返回那个新集合
例如:
name_lengths = map(len,['haha','hi','heihei'])
squares = map(labda x:x*x,[1,2,3,4])
//reduce
reduce接收一个方法和一个集合做参数.返回通过这个方法迭代容器中所有元素产生的结果
例如:
sum = reduce(lambda a,x:a+x,[0,1,2,3,4])
reduce()使用集合中的第一个元素作为第一次迭代的a,然后从第二个元素开始迭代
sentences = ['Mary read a story to Sam and Isla.',
'Isla cuddled Sam.',
'Sam chortled.']
sam_count = reduce(lambda a, x: a + x.count('Sam'),
sentences,
0)
初始的累加和由第三个参数来指定
//为什么map和reduce更好?
1. 它们大多是一行代码
2. 迭代中最重要的部分:集合,操作和返回值,在所有的map和reduce中总是在相同的位置
3. 循环中的代码可能会改变之前定义的变量或之后要用到的变量.照例,map和reduce是函数式的
4. map和reduce是元素操作.每次有人读到for循环,他们都要逐行读懂逻辑.几乎没有什么规律性的结构可以帮助理解代码.相反,map和reduce都是创建代码块来组织复杂的算法,并且读者也能非常快的理解元素并在脑海中抽象出来
5. map和reduce有许多提供便利的"好朋友",它们是基本行为的修订版.例如filter,all,any以及find
python函数式编程
概述
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30//什么是函数式编程?
函数式编程使用一系列的函数解决问题.函数仅接受输入并产生输出,不包含任何能影响产生输出的内部状态.任何情况下,使用相同的参数调用函数始终能产生同样的结果
在一个函数式的程序中,输入的数据"流过"一系列的函数,每一个函数根据它的输入产生输出.函数式风格避免编写有"边界效应"(side effects)的函数: 修改内部状态,或者是其他无法反应在输出上的变化.完全没有边界效应的函数被称为"纯函数式的"(purely functional).避免边界效应意味着不使用在程序运行时可变的数据结构,输出只依赖于输入.
可以认为函数式编程刚好站在了面向对象编程的对立面.对象通常包含内部状态(字段),和许多能修改这些状态的函数,程序则由不断修改状态构成;函数式编程则极力避免状态改动,并通过在函数间传递数据流进行工作.但这并不是说无法同时使用函数式编程和面向对象编程,事实上,复杂的系统一般会采用面向对象技术建模,但混合使用函数式风格还能让你额外享受函数式风格的优点
//为什么使用函数式编程?
* 逻辑可证
这是一个学术上的优点,没有边界效应使得更容易从逻辑上证明程序是正确的(而不是通过测试)
* 模块化
函数式编程推崇简单原则,一个函数只做一件事情,将大的功能拆分成尽可能小的模块.小的函数更易于阅读和检查错误
* 组件化
小的函数更容易加以组合形成新的功能
* 易于调试
细化的,定义清晰的函数使得调试更加简单.当程序不正常运行时,每一个函数都是检查数据是否正确的接口,能更快速地排除没有问题的代码,定位到出现问题的地方
* 易于测试
不依赖于系统状态的函数无须在测试前构造测试桩,使得编写单元测试更加容易
* 更高的生产率
函数式编程产生的代码比其他技术更少(往往是其他技术的一半左右),并且更容易阅读和维护
//如何辨认函数式风格?
支持函数式编程的语言通常具有如下特征,大量使用这些特征的代码即可被认为是函数式的:
* 函数是一等公民
函数能作为参数传递,或者是作为返回值返回.
* 匿名函数(lambda)
* 封装控制结构的内置模板函数
* 闭包(closure)
* 内置的不可变数据结构
* 递归函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40//定义一个函数
方式一: 定义函数
def add(x,y):
return x + y
方式二: 定义匿名函数
add = lambda x,y: x + y
add作为函数名进行调用.提供lambda的目的是为了编写偶尔为之的,简单的,可预见不会被修改的匿名函数
//使用函数赋值
haha = add
print(haha(1,2))
//闭包
闭包是一类特殊的函数:如果一个函数定义在另一个函数的作用域中,并且函数中引用了外部函数的局部变量,那么这个函数就是一个闭包
def f():
n = 1
def inner():
print n
inner()
n = 'x'
inner()
函数inner定义在f的作用域中,并且在inner中使用了f中的局部变量n,这就构成了一个闭包.闭包绑定了外部的变量,所以调用函数f的结果是打印1和'x'.这类似于普通的模块函数和模块中定义的全局变量的关系:修改外部变量能影响内部作用域中的值,而在内部作用域中定义同名变量则将遮蔽(隐藏)外部变量
由于闭包绑定的是外部函数的局部变量,而一旦离开外部函数作用域,这些局部变量将无法再从外部访问;另外闭包还有一个重要的特性,每次执行至闭包定义处时都会构造一个新的闭包,这个特性使得旧的闭包绑定的变量不会随第二次调用外部函数而更改.所以闭包实际上不会被外部状态影响,完全符合函数式风格的要求
//函数作为参数
def sum_(lst):
amount = 0
for num in lst:
amount = add(amount,num)
return amount
print sum_(lst)
sum_函数定义了这样一种流程:
1.使用初始值与列表的第一个元素相加
2.使用上一次相加的结果与列表的下一个元素相加
3.重复第二步,直到列表中没有更多元素
4.将最后一次相加的结果返回
//作为返回结果