计算机(Computer)与计算(Compute)这两个概念区分:在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。
函数式编程(Functional Programming):把大段代码拆解成函数,通过一层一层地调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数是面向过程的程序设计的基本单元。而函数式编程虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用。函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
1、高阶函数(函数作为参数)
(1)变量可以指向函数
- 把函数本身赋值给变量,即“变量可以指向函数”。
abs(-10) # 这是函数调用
f1 = abs # 把函数本身赋值给一个变量
res1 = f1(-100) # 100
print('res1', res1)
(2)函数名也是变量
abs(-10) # 10
abs = print
abs(-10) # -10
abs = 1
abs(-10) # TypeError: 'int' object is not callable
- 可见函数名本身也是变量,我们可以改变它的指向。
(3)高阶函数
def add(x, y, fn):
return fn(x) + fn(y)
res2 = add(-5, 6, abs)
print('res2', res2)
- 既然变量可以指向函数,函数参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。函数式编程就是指这种高度抽象的编程范式。
- 高阶函数的抽象能力是非常强大的,而且,核心代码可以保持得非常简洁。
(4)高阶函数 map()
- map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列中的每个元素上,并把结果作为新的Iterator返回。
def square(x):
return x*x
res3 = map(square, [1,2,3,4,5,6,7,8,9,10])
print('列表元素求平方:', list(res3))
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
def money(num):
return '¥'+str(num)
res4 = map(money, [1.9, 9.9, 19.9, 29.9, 39.9])
print('列表元素字符串处理:', list(res4))
# ['¥1.9', '¥9.9', '¥19.9', '¥29.9', '¥39.9']
(5)高阶函数 reduce()
- reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算。
from functools import reduce
def sum(x, y):
return x + y
res5 = reduce(sum, [1, 2, 3, 4, 5])
print('[1, 2, 3, 4, 5]求和:', res5) # 15
def deci(x, y):
return x*10 + y
res6 = reduce(deci, [1, 2, 3, 4, 5, 0, 0])
print('res6', res6) # 1234500
(6)高阶函数 filter()
- filter()函数,也接收一个函数参数和一个序列参数,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
def is_odd(n):
return n % 2 == 1
res7 = filter(is_odd, [1,2,4,5,6,9,10,15])
print('去除列表中的偶数:', list(res7)) # [1, 5, 9, 15]
def not_empty(s):
return s and s.strip()
res8 = filter(not_empty, ['A','','B','','','C'])
print('去除空串元素:', list(res8)) # ['A', 'B', 'C']
(7)高阶函数 sorted()
- sorted()函数就可以对 list 进行排序。
res9 = sorted([36, 5, -12, 9, -20])
print('sorted排序:', res9) # [-20, -12, 5, 9, 36]
- sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。
res10 = sorted([36, 5, -12, 9, -20], key=abs)
print('sorted排序:', res10) # [5, 9, -12, -20, 36]
res11 = sorted(['bob', 'about', 'Zoo', 'Credit'])
print('默认ASCII的大小比较排序:', res11)
# ['Credit', 'Zoo', 'about', 'bob']
- 根据ASCII码大小,'Z' < 'a',所以 Z 会排在 a 的前面。
res12 = sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
print('忽略大小写的排序:', res12)
# ['about', 'bob', 'Credit', 'Zoo']
res13 = sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
print('忽略大小写、反向排序:', res13)
# ['Zoo', 'Credit', 'bob', 'about']
2、高阶函数(函数作为返回值)
(1)函数作为返回值
- 高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax += n
return ax
return sum
f1 = lazy_sum(1,2,3,4,5)
res1 = f1()
print('1,2,3,4,5求和的结果是:', res1) # 15
- 在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
(2)闭包
- 当return返回的函数引用了当前函数的局部变量时,就产生了闭包。返回闭包时,要牢记一点:在返回函数中不要引用任何循环变量、或者后续会发生变化的变量。看如下这个反例:
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', f1()) # 9
print('调用f2', f2()) # 9
print('调用f3', f3()) # 9
- 说明:上述三个函数都返回9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。因此,在返回函数中不要引用任何会发生变化的局部变量。
3、匿名函数(lambda 关键字)
- 把函数当作参数传入时,有些时候,不需要显式地定义这个函数参数,直接传入匿名函数更方便。
res = map(lambda x: x*x, [1,2,3,4,5,6])
print('res', list(res)) # [1, 4, 9, 16, 25, 36]
- 关键字lambda表示匿名函数,冒号前面的x表示函数参数。匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。
- 匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数
f = lambda x: x*x
print('5的平方:', f(5)) # 25
- 还可以把匿名函数作为返回值返回。
def build(x,y):
return lambda : x*x + y*y
print('3和4的平方和:', build(3,4)()) # 25
- lambda 关键字表示匿名函数,冒号前的是参数,冒号后的是返回值。
4、装饰器(函数修饰)
(1)函数对象的 __name__
属性
- 函数是一个对象,函数对象可以被赋值给变量,所以通过变量也能调用该函数。
def now():
print('执行了')
f = now
now() # 执行了
f() # 执行了
print('__name__ ', now.__name__) # 'now'
print('__name__ ', f.__name__) # 'now'
(2)装饰器
- 在代码运行期间动态地增强函数功能,比如在函数调用前后自动打印日志等,这被称之为“装饰器”。本质上,decorator装饰器就是一个返回函数的高阶函数。
- 定义一个能够打印日志的装饰器
import functools
def log(fn):
@functools.wraps(fn) # 解决被装饰器修饰后的函数__name__发生变化的问题。
def wrapper(*args, **kw):
print('日志打印:call %s()' % fn.__name__)
return fn(*args, **kw)
return wrapper
- @ 语法使用装饰器,@ 语法相当于
now = log(now)
@log
def now():
print('函数打印:2020-04-10')
now()
# 日志打印:call now()
# 函数打印:2020-04-10
print('now的__name__', now.__name__) # 'now'
(3)给装饰器传入参数
- 如果装饰器本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,下面这个日志装饰器,且日志内容是动态的:
import functools
def log2(text):
def decorator(fn):
@functools.wraps(fn) # 解决被装饰器修饰后的函数__name__发生变化的问题。
def wrapper(*args, **kw):
print('日志打印:%s %s()' %(text, fn.__name__))
return fn(*args, **kw)
return wrapper
return decorator
- @ 语法使用装饰器,相当于
now2 = log2('自定义日志内容')(now)
@log2('自定义日志内容')
def now2():
print('函数打印:2020-04-10')
now2()
# 日志打印:自定义日志内容 now2()
# 函数打印:2020-04-10
print('now2的__name__', now2.__name__) # 'now2'
-
@functools.wraps(fn)
用于解决被装饰器修饰后的函数name发生变化的问题。 - decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。
5、偏函数(functools.partial)
- 在介绍函数参数的时候,我们知道通过设定参数的默认值,可以降低函数调用的难度。而偏函数(Partial function)也可以做到这一点。
- functools.partial 可以帮助我们创建一个偏函数,看下面的示例:
import functools
int2 = functools.partial(int, base=2)
print('转成十进制', int2('1000000')) # 64
print('转成十进制', int2('1010101')) # 85
print('转成十进制', int2('1010101', base=10)) # 1010101
- 简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。因此上述使用偏函数来给定默认参数,等价于下面的代码:
def int3(x, base=2):
# int() 可以给第二个参数base,用于指定按N进制进行转化
return int(x, base)
print('转成十进制', int3('1000000')) # 64
print('转成十进制', int3('1010101')) # 85
print('转成十进制', int3('1010101', base=10)) # 1010101
- 偏函数的作用总结:当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
参考资源:
1、廖雪峰Python教程
2、Python官方文档
END!!!
网友评论