首页 > 技术文章 > 十二、函数式编程:匿名函数、高阶函数、装饰器

loveapple 2018-07-25 10:05 原文

一、匿名函数

即没有函数名的函数,又称lambda表达式。

使用关键字lambda

定义:lambda parameter_list:expression 其中parameter_list是参数列表,expression是简单表达式,不能是复杂的代码块。(x+y是表达式;a=x+y不是表达式是代码语句)

特点:1)无函数名;2)无return关键字

学习方法:对比普通函数

例:

 1 #coding=utf-8
 2 
 3 def add(x,y):
 4     #普通函数
 5     return x+y
 6 
 7 f=lambda x,y:x+y#lambda表达式
 8 
 9 print(add(1,2))
10 print(f(1,2))

二、三元表达式

即表达式版本的if...else

例如:比较x和y,x大于y时取x,否则取y.

其他语言的三元表达式:

x>y?x:y

python语言的三元表达式:

伪代码如下

条件为真时返回的结果 if 条件判断 else 条件为假时的结果

x if x>y else y

1 #coding=utf-8
2 
3 x=2
4 y=3
5 r=x if x>y else y
6 print(r)

lambda 后面非常适合三元表达式

三、map

推荐在python代码中多多使用

map是一个类,定义 map(func, *iterables),其中func是方法名,*iterables是一个或多个列表(集合)

适用场景:映射,如抛物线

例如已知列表list_x=[1,2,3,4,5,6,7,8],求list_x中各元素的平方

1、常规实现方法:

 1 # coding=utf-8
 2 
 3 def square(x):
 4     return x * x
 5 
 6 
 7 list_x = [1, 2, 3, 4, 5, 6, 7, 8]
 8 list_y = []
 9 for x in list_x:
10     r = square(x)
11     list_y.append(r)
12 print(list_y)

2、使用map类

1)基础使用

1 # coding=utf-8
2 
3 list_x = [1, 2, 3, 4, 5, 6, 7, 8]
4 r = map(lambda x: x * x, list_x)
5 # print(r)
6 print(list(r))

2)如果lambda有多个参数呢?例如求x*x+y

1 # coding=utf-8
2 
3 list_x = [1, 2, 3, 4, 5, 6, 7, 8]
4 list_y = [1, 2, 3, 4, 5, 6, 7, 8]
5 
6 r = map(lambda x, y: x * x + y, list_x, list_y)
7 
8 print(list(r))

map(func, *iterables)中的*iterables是可变参数,支持多个列表

注意,参数顺序要正确,例如下面的代码,打印结果不同

 1 # coding=utf-8
 2 
 3 list_x = [1, 2, 3, 4, 5, 6, 7, 8]
 4 list_y = [1, 1, 1, 1, 1, 1, 1, 1]
 5 
 6 r1 = map(lambda x, y: x * x + y, list_y, list_x)
 7 r2 = map(lambda x, y: x * x + y, list_x, list_y)
 8 
 9 print(list(r1))
10 print(list(r2))

打印结果

[2, 3, 4, 5, 6, 7, 8, 9]
[2, 5, 10, 17, 26, 37, 50, 65]
3)特殊情况,如果两个列表长度不一致,结果如何?

1 # coding=utf-8
2 
3 list_x = [1, 2, 3, 4, 5, 6, 7, 8]
4 list_y = [1, 1, 1, 1]
5 
6 r = map(lambda x, y: x * x + y, list_x, list_y)
7 
8 print(list(r))

打印结果

[2, 5, 10, 17]

两个列表元素个数要一致,如果不一致,结果取小的。

六、reduce

1、python3中的reduce不在全局命名空间,需要import导入

from functools import reduce

2、reduce是连续计算,会对参数序列中元素进行累积。

3、reduce函数的定义:

    reduce(function, sequence[, initial]) -----> value

  function参数是一个有两个参数的函数,reduce依次从sequence中取一个元素,和上一次调用function的结果做参数再次调用function。 第一次调用function时,如果提供initial参数,会以sequence中的第一个元素和initial作为参数调用function,否则会以序列sequence中的前两个元素做参数调用function。

代码一:基本使用

1 #coding=utf-8
2 from functools import reduce
3 
4 list_x=[1,2,3,4,5,6,7,8]
5 r=reduce(lambda x,y:x+y,list_x)
6 print(r)
36

计算过程

每一次的计算结果作为reduce中lambda的参数

(((((1+2)+3)+4)+5)+6)+7)+8=36

实现了序列求和

代码二:有初始值

1 #coding=utf-8
2 from functools import reduce
3 
4 list_x=[1,2,3,4,5,6,7,8]
5 # r=reduce(lambda x,y:x+y,list_x)
6 #含初始值10
7 r=reduce(lambda x,y:x+y,list_x,10)
8 
9 print(r)

运行结果

46

问题:用reduce计算旅行者坐标,起点(0,0),移动列表[(1,3),(2,-2),(-2,3)],计算旅行者的最终位置

七、filter过滤器

1、filter函数会对指定序列执行过滤操作。

2、filter函数的定义:

    filter(function or None, sequence) ----> list, tuple, or string

  function是一个谓词函数,接受一个参数,返回布尔值True或False。

  filter函数会对序列参数sequence中的每个元素调用function函数,最后返回的结果包含调用结果为True的元素。

  返回值的类型和参数sequence的类型相同

3、代码示例一

 1 #coding=utf-8
 2 
 3 '''过滤掉列表中的0'''
 4 list_x=[1,0,2,0,3,0,4,0]
 5 
 6 #lambda必须返回一个可以判断真假的结果,r中的返回True或False
 7 r=filter(lambda x:True if x!=0 else False,list_x)
 8 #r1中的lambda返回非0
 9 r1=filter(lambda x:x,list_x)
10 print(list(r))
11 print(list(r1))

代码示例二

#coding=utf-8

'''过滤掉列表中小写字母'''
list_n=['a','B','C','f','e']
r=filter(lambda x:True if x.isupper() else False,list_n)
print(list(r))
"C:\Program Files\Python36\python3.exe" E:/pyClass/eleven/c16.py
['B', 'C']

八、命令式编程VS函数式编程

命令式编程即过程控制,主要关键字:def if ..else  for

函数式编程,必须关键字:三个类map reduce filter,一个表达式lambda

lambda是函数式编程的最基本单元

lisp语言是函数式编程的鼻祖

python支持函数式编程,但本质还是命令式编程,适当使用可使代码简洁。

九、装饰器一

很有用,并非python独有,如java中的注释。

1、什么是装饰器

2、适用场景

3、能解决什么问题

 1 #coding=utf-8
 2 
 3 '''1)原有函数f1,实现打印功能'''
 4 # def f1():
 5 #     print("this is a function")
 6 
 7 
 8 '''
 9 2)新需求:现在要在f1中增加打印时间戳的功能
10 '''
11 import time
12 #
13 # def f1():
14 #     print(time.time())
15 #     print("this is a function")
16 #
17 # #调用
18 # f1()
19 
20 '''
21 3)新需求:有多个函数,要求在每个函数中增加打印时间戳的功能
22 原则:开闭原则告诉我们要对扩展开发,对修改关闭;
23 尽量不修改原有函数的定义和实现,而是通过扩展实现新的需求
24 '''
25 #定义一个打印时间戳的函数
26 def f1():
27     print("this is a function f1")
28 
29 def f2():
30     print("this is a function f2")
31 
32 def print_current_time(func):
33     print(time.time())
34     func()
35 
36 print_current_time(f1)
37 print_current_time(f2)

运行结果

"C:\Program Files\Python36\python3.exe" E:/pyClass/eleven/c17.py
1532585365.6654336
this is a function f1
1532585365.6654336
this is a function f2

以上代码虽然满足需求:没有修改原来的函数,实现了新需求,但是只是在每个函数之前增加了一句打印时间戳的语句,并没有提醒和原有函数的关联性,其本质和下面的代码没有区别。

 

1 print(time.time())
2 f1()
3 print(time.time())
4 f2()

十、装饰器二

 1 #coding=utf-8
 2 
 3 import time
 4 
 5 '''
 6 使用闭包,定义一个装饰器(decorator),内部嵌套被封装(wrapper)函数
 7 def decorator():
 8 
 9     def wrapper():
10         pass
11     return wrapper
12 '''
13 def f1():
14     print("this is a function f1")
15 
16 #定义装饰器
17 def decorator(func):
18     def wrapper():
19         print(time.time())
20         func()
21     return wrapper
22 
23 #调用
24 f=decorator(f1)
25 f()

十一、装饰器三

上面的代码,定义复杂,调用也复杂。我们可以接受定义复杂,但不能接受调用复杂。那有没有什么方式可以不用改变调用方式,来实现呢?

就是使用语法糖,关键字@

 1 #coding=utf-8
 2 import time
 3 
 4 #定义装饰器
 5 def decorator(func):
 6     def wrapper():
 7         print(time.time())
 8         func()
 9     return wrapper
10 
11 #使用语法糖
12 @decorator
13 def f1():
14     print("this is a function f1")
15 
16 #原有调用方式不变
17 f1()

运行结果

"C:\Program Files\Python36\python3.exe" E:/pyClass/eleven/19.py
1532586681.7921555
this is a function f1

十二、装饰器四

上面讲的被装饰的函数是不带参数的,如果f1()带参数呢?

解决方式很简单,只要在定义装饰器的内部函数wrapper()中增加参数即可,调用方式不变

代码如下:

 1 #coding=utf-8
 2 import time
 3 
 4 #定义装饰器
 5 def decorator(func):
 6     def wrapper(func_name):
 7         print(time.time())
 8         func(func_name)
 9     return wrapper
10 
11 #使用语法糖
12 @decorator
13 def f1(func_name):
14     print("this is a function "+func_name)
15 
16 #原有调用方式不变
17 f1('f1')

打印结果

"C:\Program Files\Python36\python3.exe" E:/pyClass/eleven/c20.py
1532587192.487322
this is a function f1

进一步,如果被装饰的函数有多个,且每个函数的参数个数也不同,如f1()有一个参数,f2()有两个参数,f3()有三个参数。。。该如何实现?

装饰器需要具有通用性,任何类型的函数都可以使用,那么可变参数可以解决这个问题。

代码如下:

 1 #coding=utf-8
 2 import time
 3 
 4 #定义装饰器
 5 def decorator(func):
 6     def wrapper(*args):
 7         print(time.time())
 8         func(*args)
 9     return wrapper
10 
11 #使用语法糖
12 @decorator
13 def f1(a):
14     print("this is a function "+a)
15 
16 #使用语法糖
17 @decorator
18 def f2(a,b):
19     res=a+b
20     print(res)
21     
22 
23 #原有调用方式不变
24 f1('python')
25 print(f2(1,3))

十三、装饰器五

如果f3()中含有关键字参数呢?

1 def f3(a,b,**kw):
2     print(a)
3     print(b)
4     print(kw)

在内层函数wrapper()中增加**kw参数

代码实现如下

#coding=utf-8
import time

#定义装饰器
def decorator(func):
    def wrapper(*args,**kw):
        print(time.time())
        func(*args,**kw)
    return wrapper

#使用语法糖
@decorator
def f1(a):
    print("this is a function "+a)

#使用语法糖
@decorator
def f2(a,b):
    print(a)
    print(b)

@decorator
def f3(a,b,**kw):
    print(a)
    print(b)
    print(kw)


#原有调用方式不变
f3('test1','test2',x=1,y=2,z=3)

运行结果

"C:\Program Files\Python36\python3.exe" E:/pyClass/eleven/c20.py
1532588529.5278778
test1
test2
{'x': 1, 'y': 2, 'z': 3}

小tips:func(*args,**kw)通杀所有函数

十四、装饰器六

1、解决什么问题:

装饰器实际上就是为了给某程序增添功能,但又不修改源代码,满足以下三个条件:

1)不能修改被装饰的函数的源代码

2)不能修改被装饰的函数的调用方式

3)满足1)2)的情况下给程序增添功能

2、适用场景

  • 1.可以在外层函数加上时间计算函数,计算函数运行时间;
  • 2.计算函数运行次数;
  • 3.可以用在框架的路由传参上;
  • 4.插入日志,作为函数的运行日志;
  • 5.事务处理,可以让函数实现事务的一致性,让函数要么一起运行成功,要么一起运行失败;
  • 6.缓存,实现缓存处理;
  • 7.权限的校验,在函数外层套上权限校验的代码,实现权限校验;

推荐阅读