函数式编程
函数是python内建支持的一种封装
函数时编程是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量。因此,任意一个函数,只要输入时确定的,输出就是确定的。这种纯函数没有副作用。而允许使用变量的程序设计语言。由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出
特点:允许把函数本身作为参数传入另一个函数,还允许返回一个函数
高阶函数
函数本身也可以赋值给变量,即变量可以指向函数
函数名也是变量
变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
def add(x,y,f):
return f(x)+f(y)
print(add(-5,6,abs))
参数x,y,f分别接收-5,6和abs
编写高阶函数,就是让函数的参数能够接收别的函数
map/reduce
map()函数能够接收两个参数,一个是函数,一个是iterable。map将传入的函数一次作用到序列的每个元素,并把结果作为新的iterator返回
def f(x):
return x*x
r=map(f,[1,2,3,4])
print(list(r))
map()传入的第一个参数是f,即函数对象本身,由于结果r是一个iterator,iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list
map()作为高阶函数,事实上它把运算规则抽象了,因此,不但乐意计算简单的求方,还可以计算任意复杂的函数。
把list所有数字转为字符串
list(map(str,[1,2,34,5,6])
输出
['1','2','34','5','6']
reduce
reduce把一个函数作用在一个序列[x1,x2,x3,…]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,效果:
reduce(f,[x1,x2,x3,x4])=f(f(f(x1,x2),x3),x4)
把序列变成整数
from functools import reduce
def fn(x,y):
return x*10+y
reduce(fn,[1,3,5,7])
输出
1357
配合map()函数使用
from functools import reduce
D={'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}
def srtint(s):
def fn(x,y):
return x*10+y
def char2num(s):
return D[s]
return reduce(fn,map(char2num,s))
可以用lambda函数简化
filter()
用于过滤序列
和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,fiflter()把传入的函数一次作用域每个元素,然后根据返回值是turehi是false决定保留还是丢弃该元素
在list中,删掉偶数,保留奇数
def is_odd(n):
return n%2==1
list(filter(is_odd,[1,2,3,4,5])
把一个序列中的空字符串删掉
def not_empty(s):
return s and s.strip()
list(filter(not_empty,['a',' ',none,'b']
输出
['a','b']
filter这个高阶函数,关键在于正确实现一个筛选
filter()函数返回的是一个iterator,一个惰性序列,要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list
用filter求素数
计算素数的一个方法是埃氏筛法
埃氏筛法:
首先列出从2开始的所有自然数,构造一个序列
去序列第一个数2,一定是素数,用2把序列的2的倍数筛掉
去新序列的第一个数3,一定是个素数,然后用3把序列的3的倍数筛掉
取新序列的第一个数5,然后用5把序列的5的倍数筛掉
不断筛下去,就会能得到所有的素数
用python实现
首先构造一个从3开始的数列
def _odd_iter():
n=1
while True:
n=n+2
yield n
然后定义一个筛选函数:
def _not_divisible(n):
return lambda x:x%n>0
最后定义一个生成器,不断返回下一个素数:
def primes():
yield 2
it=_odd_iter()
while True:
n=next(it)
yield n
it=filter(_not_divisible(n),it)
这个生成器先返回第一个素数2,然后,里游泳filter()不断产生筛选后的新序列
由于primes()也是一个无线序列,所以调用时需要设置一个退出循环的条件:
for n in primes():
if n<1000:
print (n)
else:
break
sorted 排序算法
排序也是在程序中经常用到的算法。排下序的核心是比较两个元素的大小。
sorted()函数可以对list进行排序
sorted([36,12,34,56,0])
此外,sorted()函数也是一个高阶函数,还可以接收一个key函数来实现自定义的排序
按绝对值大小排序:
sorted([35,-2,-89],key=abs)
key指定的函数将作用于list的每个元素上,并根据key函数返回的结果进行排序。
然后sored()函数按照返回的结果进行排序之后,并按照对应关系返回list相应的元素
字符串排序
在默认情况下,对字符串排序,是按照ASCII的大小比较的。(大写字母在小写字母前面)
如果进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
返回函数
高阶函数除了可以接受函数作为参数外,还可以吧函数作为结果值返回
可变参数求和(通常情况)
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,2,34,5)
>f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
调用函数f时才真正计算求和的结果
在函数lazy_sum中又定义了函数sum,并且,内部函数宿命可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包”的程序结构拥有极大的威力。
在调用lazy_sum时,每次调用都会返回一个新的函数,即使传入相同的参数。
闭包
注意到返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。所以,闭包用起来简单,不好实现。
返回的函数并没有立刻执行,而是直到调用了f()才执行。
def count():
fs=[]
for i in range(1,4):
def f():
return i*i
fs.append(f)
return fs
f1,f2,f3=count()
每次循环,都创建了一个新的函数。然后把创建的三个函数返回
由于返回的函数引用了变量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))
return fs
匿名函数
在传入函数时,不需要显式的定义函数,直接传入匿名函数
lambda x:x*x
实际上就是:
def f(x):
return x*x
装饰器
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
函数对象有一个__name__属性,可以拿到函数的名字
>def now():
print('2021')
>f=now
>f()
2021
>now.__name__
'now'
>f.__name__
'now'
假设增强now()函数的功能,比如在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为‘装饰器’
本质上,装饰器就是一个返回函数的高阶函数。所以,要定义一个能打印日志的装饰器,可以定义:
def log(func):
def wrapper(*args,**kw):
print('call %s():' % func.__name__)
return func(*args,**kw)
return wrapper
log是一个装饰器,所以接受一个函数作为参数,返回一个函数。
借助@语法,把装饰器置于函数的定义处:
@log
def now():
print('2021')
调用now()函数,不仅会运行now()函数本身,还会再运行now()函数钱打印一行日志,输出如下:
call now():
2020
由于log()是一个装饰器,返回一个函数,所以,原来的now函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
wrapper()函数的参数定义是(*args,**kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,在紧接着调用原始函数。
如果装饰器本身需要传入参数,就需要编写一个返回装饰器的高阶函数。
比如,要自己定义log的文本:
def log(text):
def decorator(func):
def wrapper(*args,**kw):
print('%s %s():' % (text,func.__name__))
return func(*args,**kw)
return wrapper
return decorator
这个三层嵌套的decorator用法:
@log('execute')
def now():
print('2021')
执行结果:
>now()
execute now():
2021
和两层嵌套的decorator(装饰器)相比,三层嵌套的效果:
now=log('execute')(now)
首先执行log(‘execute’)返回的时decorator函数,再调用返回的函数,参数时now函数,返回值最终是wrapper函数
函数也是对象,他有__name__
等属性,但是去看经过装饰器装是之后的函数,他们的__name__
已经从原来的’now’变成了’wrapper’
因为返回的wrapper()函数的名字就是’wrapper’,所以需要把原始函数__name__
等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。
直接用内置的functools.wraps
完整的装饰器写法:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args,**kw):
print('call %s():'% func.__name__)
return func(*args,**kw)
return wrapper
再定义wrapper()的前面加上@functools.wraps(func)即可
偏函数
functools模块提供的偏函数功能,和数学意义上的偏函数不一样。
在参数中,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。
int()函数可以把字符串转换为整数,当今传入字符串时,int()函数默认按十进制转换
但int()函数还提供额外的base参数,默认值为10。如果传入base参数就可以做n进制的转换
>int('123',base=8) //按8进制转换
假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,可以定义一个int2()的函数,默认把base=2传进去:
def int2(x,base=2):
return int(x,base)
functools.partial就是帮助我们创建一个偏函数,不需要自己定义int2()可以直接使用:
import functools
int2=functools.partial(int,base=2)
直接调用int2就可以
总结就是functools.partial的作用就是,把一个函数的某些参数给固定住(设置默认值)返回一个新的函数,调用这个新函数会更简单
设置的时候,只是把base的参数默认值设置为了2,也可以在函数调用时传入其他值
创建偏函数时,实际上可以接受函数对象 ,*args和**kw这三个参数
函数:
abs()求绝对值