Python-函数式编程

装饰器还没搞懂,搞懂了再来继续写吧~
函数既可作为返回值也可作为函数的参数…一切都是对象,一切都是指针,一切都是东西…
返回函数(引用)和返回函数值是不一样滴…. ◡̈⃝︎⋆︎*



函数式编程



**函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!**


一. 高阶函数


  1. 变量可以指向函数
  2. 函数名也是变量
  3. 函数可以作为return的返回值
  4. 函数可以作为另一个函数的参数

1.1 变量指向函数


   在讲变量和字符串的时候我们讲过,变量就是一个对象,可以当作一个指针使用,而函数名也是一个变量,也就是一个对象。函数名其实就是指向函数的变量!注意,函数名是一个变量!变量!变量!当作指针用。

   既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数




见过两种函数赋值给变量的形式,这两种形式是有区别的,分别总结一下。
一种是

a = f

另一种是

a = f()

1. a = f 型属于将变量指向函数。

如下用示例说明:

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
print(abs(-10))
print(abs)

输出:
10
<built-in function abs>
------------------------------------------------------------------------------------------
可见,abs(-10)是函数调用,而abs是函数本身。
要获得函数调用结果,我们可以把结果赋值给变量:

x = abs(-10)
print(x)

输出:
10
------------------------------------------------------------------------------------------
但是,如果把函数本身赋值给变量呢?

f = abs
print(f)

输出:
<built-in function abs>
------------------------------------------------------------------------------------------
结论:函数本身也可以赋值给变量,即:变量可以指向函数。
如果一个变量指向了一个函数,那么,可否通过该变量来调用这个函数?用代码验证一下:

f = abs
print(f(-10))

输出:
10
------------------------------------------------------------------------------------------
成功!说明变量f现在已经指向了abs函数本身。直接调用abs()函数和调用变量f()完全相同。

说明变量f现在已经指向了abs函数本身。直接调用abs()函数和调用变量f()完全相同。这是廖雪峰老师python教程上的例子,现在调用f()和调用abs()是一样的了。


再举一个工厂函数的例子:

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
def maker(N):

  def action(X):

    return X**N

  return action
------------------------------------------------------------------------------------------
这个嵌套函数的外层返回值为内层函数的函数名,注意没有括号,这里有无括号是有很大区别的。此时调用外部函数:

f=maker(2) //此时进入maker(2),且定义了一个action(X)函数,然后返回action函数名(对象)给f
------------------------------------------------------------------------------------------
那么如上所述,f便指向了action函数,且限制条件为N=2,可以理解为f为N等于2时的action函数。我们来调用它:

print(f(3)) //此时相当于调用了cation(3)函数,这也是在外部使用内部嵌套函数的方法,
//因为内部嵌套函数是不允许在外部访问的。

输出:
9 //证明f和action函数是一样的。
------------------------------------------------------------------------------------------
如上的示例也可以用print(f=maker(2)(3))来输出结果一样,两个括号连在一起相当于执行了这两个函数。

def maker(N):
def a(c):
return c**N
return a

f=maker(2)(3)
print(f)

OUTPUT: 9

2. a = f() 型属于将f()的返回值赋值给a的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
这里的a仅仅接收f()的返回值,如果f()没有返回值,那么a即被赋值为None。这里值得注意的一点是,
在a=f()的执行过程中,f()会运行一次,如:

def add(x,y):
z = x+y
print(z)
a = add(3,4)
print('******我是分隔符,嘿嘿嘿******')
print(a)

OUTPUT:
7
******我是分隔符,嘿嘿嘿******
None
------------------------------------------------------------------------------------------
这里在分隔符前输出了7,说明赋值过程函数add执行了,然而a的值为None,且只能通过print语句才可以显示。
这是因为add()函数没有return语句。


1.2 传入函数与返回函数名


一、传入函数(把函数作为参数)

&emsp;&emsp;既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数

一个最简单的高阶函数:

1
2
def add(x, y, f):
return f(x) + f(y)

当我们调用add(-5, 6, abs)时,参数x,y和f分别接收-5,6和abs,根据函数定义,我们可以推导计算过程为:

1
2
3
4
5
x = -5
y = 6
f = abs
f(x) + f(y) ==> abs(-5) + abs(6) ==> 11
return 11

用代码验证一下:

1
2
3
4
5
6
def add(x, y, f):
return f(x) + f(y)

print(add(-5, 6, abs))

OUTPUT: 11

总结:编写高阶函数,就是让函数的参数能够接收别的函数。把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式


二、返回函数名(把函数名作为返回值)

&emsp;&emsp;高阶函数除了可以接受函数作为参数外,还可以把函数名作为结果值返回。注意Python3返回的是迭代器对象


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
'''我们来实现一个可变参数的求和。通常情况下,求和的函数是这样定义的:'''

def calc_sum(*args): '在函数的参数章节讲过,*传入元组,**传入字典'
ax = 0
for n in args:
ax = ax + n
return ax
'''但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,
而是返回求和的函数:'''

def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum

'''当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:'''

f = lazy_sum(1, 3, 5, 7, 9)
print(f)
OUTPUT: <function lazy_sum.<locals>.sum at 0x101c6ed90>

'''调用函数f时,才真正计算求和的结果:'''

f()
OUTPUT: 25

'''
在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为
“闭包(Closure)”的程序结构拥有极大的威力。

请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:
'''

f1 = lazy_sum(1, 3, 5, 7, 9)
f2 = lazy_sum(1, 3, 5, 7, 9)
f1==f2
OUTPUT: False
'f1()和f2()的调用结果互不影响。'

三、闭包

&emsp;&emsp;注意到返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。

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
'''另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。
我们来看一个例子:'''

def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs

f1, f2, f3 = count()

'''在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。
你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:'''

>>> f1()
9
>>> f2()
9
>>> f3()
9

'''
全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变
量i已经变成了3,因此最终结果为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

'再看看结果:'

>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9
'''缺点是代码较长,可利用lambda函数缩短代码。'''


二. 装饰器