Python学习笔记2:函数式编程

时间:2021-04-13 22:41:41

    在上一篇博客中,我们学习了以下内容:Python的安装环境;变量和数据类型(Python内置的基本类型);List和Tuple(顺序的集合类型);条件判断和循环(控制程序流程);Dict和Set(根据key访问的集合类型);函数(定义和调用函数);切片(如何对list进行切片);迭代(如何用for循环迭代集合类型);列表生成式(如何快速生成列表)。在本篇博客中我们将学习函数式编程。

1.相关概念

函数:function
函数式:functional,编程范式
函数式编程的特点:
(1)把计算视为函数而非指令
(2)纯函数式编程:不需要变量,没有副作用,测试简单
(3)支持高阶函数,代码简洁
Python支持的函数式编程:
(1)不是纯函数式编程:允许有变量
(2)支持高阶函数:函数也可以作为变量传入
(3)支持闭包:有了闭包就能返回函数
(4)有限度地支持匿名函数

2.高阶函数
    高阶函数是指能接收函数做参数的函数。变量可以指向函数,函数的参数可以接收变量,因此一个函数可以接收另一个函数作为参数,能接收函数作为参数的函数就是高阶函数。

(1)map()函数
map()是 Python 内置的高阶函数,它接收一个函数 f 和一个 list,并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回。

def format_name(s):
return s[0].upper()+s[1:].lower()

print map(format_name, ['adam', 'LISA', 'barT'])

输出结果为:[‘Adam’, ‘Lisa’, ‘Bart’]

(2)reduce()函数
reduce()函数也是Python内置的一个高阶函数。reduce()函数接收的参数和 map()类似,一个函数 f,一个list,但行为和 map()不同,reduce()传入的函数 f 必须接收两个参数,reduce()对list的每个元素反复调用函数f,并返回最终结果值。
例如,编写一个f函数,接收x和y,返回x和y的和:

def f(x, y):
return x + y

调用 reduce(f, [1, 3, 5, 7, 9])时,reduce函数将做如下计算:

先计算头两个元素:f(1, 3),结果为4;
再把结果和第3个元素计算:f(4, 5),结果为9;
再把结果和第4个元素计算:f(9, 7),结果为16;
再把结果和第5个元素计算:f(16, 9),结果为25;
由于没有更多的元素了,计算结束,返回结果25。

上述计算实际上是对 list 的所有元素求和。虽然Python内置了求和函数sum(),但是,利用reduce()求和也很简单。
reduce()还可以接收第3个可选参数,作为计算的初始值。如果把初始值设为100,计算:

reduce(f, [1, 3, 5, 7, 9], 100)

结果将变为125,因为第一轮计算是:
计算初始值和第一个元素:f(100, 1),结果为101。

(3)filter()函数
filter()函数接收一个函数 f 和一个list,这个函数 f 的作用是对每个元素进行判断,返回 True或 False,filter()根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新list。
利用filter(),可以完成很多有用的功能,例如,删除 None 或者空字符串:

def is_not_empty(s):
return s and len(s.strip()) > 0
filter(is_not_empty, ['test', None, '', 'str', ' ', 'END'])

结果:[‘test’, ‘str’, ‘END’]
注意: s.strip(rm) 删除 s 字符串中开头、结尾处的 rm 序列的字符。当rm为空时,默认删除空白符(包括’\n’, ‘\r’, ‘\t’, ’ ‘)。

(4)sorted()函数
Python内置的 sorted()函数可对list进行排序,但 sorted()也是一个高阶函数,它可以接收一个比较函数来实现自定义排序,比较函数的定义是,传入两个待比较的元素 x, y,如果 x 应该排在 y 的前面,返回 -1,如果 x 应该排在 y 的后面,返回 1。如果 x 和 y 相等,返回 0。

def cmp_ignore_case(s1, s2):
t1=s1.lower()
t2=s2.lower()
if t1 > t2:
return 1
if t1 < t2:
return -1
return 0

print sorted(['bob', 'about', 'Zoo', 'Credit'], cmp_ignore_case)

输出:[‘about’, ‘bob’, ‘Credit’, ‘Zoo’]。

(5)返回函数
Python的函数不但可以返回int、str、list、dict等数据类型,还可以返回函数!
例如,定义一个函数 f(),我们让它返回一个函数 g,可以这样写:

def f():
print 'call f()...'
# 定义函数g:
def g():
print 'call g()...'
# 返回函数g:
return g

仔细观察上面的函数定义,我们在函数 f 内部又定义了一个函数 g。由于函数 g 也是一个对象,函数名 g 就是指向函数 g 的变量,所以,最外层函数 f 可以返回变量 g,也就是函数 g 本身。
调用函数 f,我们会得到 f 返回的一个函数:

>>> x = f()   # 调用f()
call f()...
>>> x # 变量x是f()返回的函数:
<function g at 0x1037bf320>
>>> x() # x指向函数,因此可以调用
call g()... # 调用x()就是执行g()函数定义的代码

(6)闭包
这种内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包(Closure)。闭包的特点是返回的函数还引用了外层函数的局部变量,所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。

# 希望一次返回3个函数,分别计算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()
#结果为:9,9,9

问题产生的原因是:因为函数只有在调用时才去获取外层参数,若函数在定义时便可获取到i的值,则可解决问题。而默认参数正好可以完成定义时获取i值,且运行时无需参数输入的功能。

def count():
fs = []#保存函数的引用
for i in range(1, 4):
def f(m=i):#使用默认参数,在函数定义时m已经被赋值
return m * m
fs.append(f)
return fs

f1, f2, f3 = count()
print f1(), f2(), f3()
#结果为:1,4,9

(7)匿名函数
在Python中,对匿名函数提供了有限支持。还是以map()函数为例,计算 f(x)=x2 时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:

>>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
[1, 4, 9, 16, 25, 36, 49, 64, 81]

通过对比可以看出,匿名函数 lambda x: x * x 实际上就是:

def f(x):
return x * x

关键字lambda 表示匿名函数,冒号前面的 x 表示函数参数。匿名函数有个限制,就是只能有一个表达式,不写return,返回值就是该表达式的结果。

(8)decorator装饰器
问题:定义了一个函数,想在运行时动态增加功能,但又不想改动函数本身的代码。如:

def f1(x):
return x*2
def f2(x):
return x*x
def f3(x):
return x*x*x

方法一:直接修改原函数的定义。

def f1(x):
print "call f1(x)..."
return x*2
def f2(x):
print "call f2(x)..."
return x*x
def f3(x):
print "call f3(x)..."
return x*x*x

该方法的缺点是需要修改原函数的定义,需要增加不能的功能时就需要多次修改原函数的定义,且对于多个函数加同一种功能时,该方法需要些大量重复的代码。

方法二:装饰器decorater,使用高阶函数,接收函数作为参数,对其包装,并返回一个新的函数。

# -*- coding:UTF-8 -*-

#装饰器函数
def new_fn(f):
def fn(x):
print "call"+f.__name__+"()..."
return f(x)
return fn

@new_fn
def f1(x):
return x*2
@new_fn
def f2(x):
return x*x
@new_fn
def f3(x):
return x*x*x


print f1(5)
print f2(2)
print f3(3)

'''
g1=new_fn(f1)
print g1(5)

f1=new_fn(f1)
print f1(5)
'''

装饰器的作用:可以极大地化简代码,避免每个函数编写重复性代码。
打印日志:@log
检测性能:@performance
数据库事务:@transaction
URL路由:@post(‘/register’)

# -*- coding:UTF-8 -*-
import time

#装饰器函数
def new_fn(f):
def fn(x):
print "call "+f.__name__+"()..."
return f(x)
return fn

@new_fn
def f1(x):
return x*2
@new_fn
def f2(x):
return x*x
@new_fn
def f3(x):
return x*x*x


print f1(5)
print f2(2)
print f3(3)

'''
g1=new_fn(f1)
print g1(5)

f1=new_fn(f1)
print f1(5)
'''


#解决在控制台打印中文的乱码问题
#print "----------------这是分割线----------------".decode('utf-8').encode('gbk')
print "----------------这是分割线----------------"

#日志函数
'''
只能给只有一个参数的函数加日志
def log(f):
def fn(x):
print "call "+f.__name__+"()..."
return f(x)
return fn
'''

#要让 @log 自适应任何参数定义的函数,可以利用Python的 *args 和 **kw,保证任意个数的参数总是能正常调用
def log(f):
def fn(*args,**kw):
print "call "+f.__name__+"()..."
return f(*args,**kw)
return fn

#性能函数
def performance(f):
def fn(*args,**kw):
startTime=time.clock()
res=f(*args,**kw)
endTime=time.clock()
print "running time is %f s"%(endTime-startTime)
return res
return fn

#阶乘函数
@performance
@log
def factorial(n):
return reduce(lambda x,y:x*y,range(1,n+1))

print factorial(20)

#加法函数
@performance
@log
def add(x,y):
return x+y

print add(1,2)
输出结果为:
call f1()...
10
call f2()...
4
call f3()...
27
----------------这是分割线----------------
call factorial()...
running time is 0.000052 s
2432902008176640000
call add()...
running time is 0.000022 s
3

(9)偏函数
functools.partial可以把一个参数多的函数变成一个参数少的新函数,少的参数需要在创建时指定默认值,这样,新函数调用的难度就降低了。

import functools

#sorted(iterable[, cmp[, key[, reverse]]])

def cmp_ignore_case(s1,s2):
t1=s1.lower()
t2=s2.lower()
if t1 > t2:
return 1
if t1 < t2:
return -1
return 0

sorted_ignore_case = functools.partial(sorted,cmp=cmp_ignore_case)
sorted_ignore_case2 = functools.partial(sorted,cmp=lambda x,y:cmp(x.lower(), y.lower()))
sorted_reverse = functools.partial(sorted,reverse=True)

print sorted(['bob', 'about', 'Zoo', 'Credit'])
print sorted_ignore_case(['bob', 'about', 'Zoo', 'Credit'])
print sorted_ignore_case2(['bob', 'about', 'Zoo', 'Credit'])
print sorted_reverse(['bob', 'about', 'Zoo', 'Credit'])

输出结果为:

['about', 'bob', 'Credit', 'Zoo']
['about', 'bob', 'Credit', 'Zoo']
['bob', 'about', 'Zoo', 'Credit']