python学习笔记(六)函数式编程

函数式编程
函数是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()求绝对值

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值