python学习笔记(四)---函数式编程
一、高阶函数
①map/reduce
a.两个函数的区别
1.从参数方面来讲:
map()函数:
map()包含两个参数,第一个是参数是一个函数,第二个是序列(列表或元组)。其中,函数(即map的第一个参数位置的函数)可以接收一个或多个参数。
reduce()函数:
reduce() 第一个参数是函数,第二个是 序列(列表或元组)。但是,其函数必须接收两个参数。
2.从对传进去的数值作用来讲:
map()是将传入的函数依次作用到序列的每个元素,每个元素都是独自被函数“作用”一次;
reduce()是将传人的函数作用在序列的第一个元素得到结果后,把这个结果继续与下一个元素作用(累积计算),最终结果是所有的元素相互作用的结果。
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9} def str2int(s): def fn(x, y): return x * 10 + y def char2num(s): return DIGITS[s] return reduce(fn, map(char2num, s))
print(str2int('4564687'))
还可以进一步用lambda函数简化成以下的代码
from functools import reduce DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9} def char2num(s): return DIGITS[s] def str2int(s): return reduce(lambda x, y: x * 10 + y, map(char2num, s))
print(str2int('15488'))
b.lambda函数的普及
python 使用 lambda 来创建匿名函数。
- lambda只是一个表达式,函数体比def简单很多。
- lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
- lambda函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。
- 虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
语法:
lambda [arg1 [,arg2,.....argn]]:expression
用法:
def sum(x,y): return x+y s = lambda x, y : x + y print(sum(4, 6)) # 输出10 print(s(4, 6)) # 输出10 效果一样 b = lambda x, y, z : (x + 4) * y - z print(b(1, 2, 3)) # 输出 (1+4) * 2 - 3 == 7 # 注 : 不带参数时返回的是 lambda函数的地址 一个函数对象 print(b) # 此时输出是函数b的地址 格式如:<function <lambda> at 0x0000000002093E18>
练习1:
利用map()
函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:['adam', 'LISA', 'barT']
,输出:['Adam', 'Lisa', 'Bart']
:
def normalize(name): result=name[0].upper()+name[1:].lower() return result #常规方式 L1 = ['adam', 'LISA', 'barT'] L2=[] for i in L1: temp=normalize(i) L2.append(temp) print (L2) #map方式map就省掉了for循环去取每一个的过程 L3 = list(map(normalize, L1)) print (L3)
练习2:
Python提供的sum()
函数可以接受一个list并求和,请编写一个prod()
函数,可以接受一个list并利用reduce()
求积:
from functools import reduce def prod(L): return reduce(lambda x,y:x*y,L) L1=[2,3,4] print(prod(L1))
c. index()方法 和 find()方法
index() 方法检测字符串中是否包含子字符串 str ,如果指定 beg(开始) 和 end(结束) 范围,则检查是否包含在指定范围内,该方法与 python find()方法一样,只不过如果str不在 string中会报一个异常。影响后面程序执行
find() 方法检测字符串中是否包含子字符串 str ,如果指定 beg(开始) 和 end(结束) 范围,则检查是否包含在指定范围内,
如果包含子字符串返回开始的索引值,否则返回-1。不影响后面程序执行
index()方法如果字符串存在str会抛出异常例子。
user_name = ['xiaolei','xiaoman','lixia'] pass_word = ['123','456','789'] username = input('username:').strip() password = input('password:').strip() if username in user_name and password == pass_word[user_name.index(username)]: print(f"登录成功,欢迎您:{username}") else: print("错误!") 若输入:username == xiaolei user_name.index(username) == 0 所以:password == pass_word[0] == 123
find()
str1='python is on the way' str2='on' str3='nice' print(str1.index(str2)) #不在字符串str1中 print(str1.find(str3)) #从索引1开始检测,检测长度为3 print(str1.find(str2,1,3))
练习3:
利用map
和reduce
编写一个str2float
函数,把字符串'123.456'
转换成浮点数123.456
:
from functools import reduce def str2float(s): def fn(x, y): return x * 10 + y def char2num(s): return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s] return reduce(fn, map(char2num, s.replace(".",""))) s="1234.567" if s.find(".")!=-1: print('str2float(\'%s\') ='%s, str2float(s)/pow(10,(len(s)-s.find(".")-1))) else: print('str2float(\'%s\') ='%s, str2float(s))
②filter
Python内建的filter()
函数用于过滤序列。
和map()
类似,filter()
也接收一个函数和一个序列。和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
用filter()
这个高阶函数,关键在于正确实现一个“筛选”函数。
注意到filter()
函数返回的是一个Iterator
,也就是一个惰性序列,所以要强迫filter()
完成计算结果,需要用list()
函数获得所有结果并返回list。
例如,在一个list中,删掉偶数,只保留奇数,可以这么写:
def is_odd(x): return x%2==1 print(list(filter(is_odd,[1,2,3,4,5,6,7])))
python的strip()函数
一、默认去掉空格
str.strip() : 去除字符串两边的空格
str.lstrip() : 去除字符串左边的空格
str.rstrip() : 去除字符串右边的空格
注:此处的空格包含'\n', '\r', '\t', ' '
二、去掉指定字符串
str.strip('do') :去除字符串两端指定的字符
str.lstrip('do') :用于去除左边指定的字符
str.rstrip('do') :用于去除右边指定的字符
把一个序列中的空字符串删掉,可以这么写:
def not_empty(s): return s and s.strip() list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))
练习:打印素数
#1、先构建一个从3开始的奇数列 当前是生成器并且是无限序列 def _odd_iter(): n=1 while True: n=n+2 yield n #2、筛选函数 def _not_divisible(n): return lambda x:x%n>0 #3、生成器返回下一个素数,这个生成器先返回第一个素数2,然后, #利用filter()不断产生筛选后的新的序列。 #由于primes()也是一个无限序列,所以调用时需要设置一个退出循环的条件: def primes(): yield 2 it = _odd_iter() # 初始序列 while True: n = next(it) # 返回序列的第一个数 yield n it = filter(_not_divisible(n), it) # 构造新序列 #4、打印1000以内的素数: for n in primes(): if n < 1000: print(n) else: break
练习:打印回数
#计算数值的位数 def places(n): i=0 while n>1: n=n/10 i=i+1 return i #测试1 print(places(1234546)) #is_palindrome函数返回bool值,是否为回数 def is_palindrome(n): #将数值的每一位保存在列表中, L = [] num=places(n) for i in range(1, num+ 1): L.append(n % 10) n = int(n / 10) #cs表示数值中相等数字的个数,如12321,相等数字的个数为2 cs=0 for i in range(int(num/2)): if L[i]==L[-1-i]: cs=cs+1 else: cs=cs+0 return cs==int(num/2) #测试2 print(is_palindrome(12321)) print(is_palindrome(567834)) #测试3 print(list(filter(is_palindrome,[12312,12321,5678765,218,472839420])))
③sorted
排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个dict呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。
Python内置的sorted()
函数就可以对list进行排序:
sorted()
函数也是一个高阶函数,它还可以接收一个key
函数来实现自定义的排序,例如按绝对值大小排序:
#1.按绝对值大小进行排序 #key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序 print(sorted([36, 5, -12, 9, -21], key=abs)) #[5, 9, -12, -21, 36]
#2.字符串排序 #情况一 ACII码排序 默认情况下,对字符串排序,是按照ASCII的大小比较的,由于'Z' < 'a',结果,大写字母Z会排在小写字母a的前面。 sorted(['bob', 'about', 'Zoo', 'Credit']) #['Credit', 'Zoo', 'about', 'bob']
#情况二 直接忽略大小写比字母排列顺序的大小 sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower) #['about', 'bob', 'Credit', 'Zoo']
#情况三 逆向排序 sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) #['Zoo', 'Credit', 'bob', 'about']
练习:假设我们用一组tuple表示学生名字和成绩: L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)],分别按名字和分数排序
#请用sorted()对上述列表按名字排序 def by_name(t): return t[0] L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)] print(sorted(L, key=by_name)) ##请用sorted()对上述列表按分数排序 def by_score(t): return t[1] print(sorted(L, key=by_score)) #L不变
二、返回函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。我们来实现一个可变参数的求和。通常情况下,求和的函数是这样定义的
#1.需要马上计算出结果 def calc_sum(*args): ax = 0 for n in args: ax = ax + n return ax print(calc_sum(1,2,3,4,5)) #2.不需要马上计算出结果 可定义返回函数,这时候返回的是一个函数,要计算结果除非在后面加上参数 def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum print(lazy_sum(1,2,3,4,5)) #3.计算结果的方式 print(lazy_sum(1,2,3,4,5)()) f=lazy_sum(1,2,3,4,5) print(f())
#4.每次调用都会返回一个新函数,就算传入的是相同的参数,python 3中使用operator判断2个对象是否相等,即使调用一致两个对象也是不等的,f1()
和f2()
的调用结果互不影响。
import operator
f1 = lazy_sum(1, 3, 5, 7, 9)
f2 = lazy_sum(1, 3, 5, 7, 9)
print(operator.eq(f1,f2))
在这个例子中,我们在函数lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
闭包:
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
因为每个函数调用都是独立的个体会各自不互相干扰,所以最后函数出来的结果都会输出最终的结果,如下输出的结果不会是 1 4 9 而都是最后的结果 9 9 9
注意到fs.append(f)这里,fs列表里面,添加的是函数,而不是值,也就是打印三次后,fs=[ f(),f(),f() ]
而最后打印的时候,才去计算这三个f()的值是多少,这时候已经进行了三次循环,所以这三个f()都等于9
只需要稍微改一下,改成fs.append(f()),那么最后的结果就是1,4,9了,因为这一次,fs列表保存的不是函数,而是值
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())
def count(): def f(j): def g(): return j*j return g fs = [] for i in range(1, 4): fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f() return fs f1, f2, f3 = count() print(f1(),f2(),f3())
利用闭包返回一个计数器函数,每次调用它返回递增整数
# 闭包实现计数器 # 方法1 '''def createCounter(): s=[] def counter(): s.append('a') return len(s) return counter ''' # 方法2 '''def createCounter(): a=0 def counter(): nonlocal a a=a+1 return a return counter ''' #方法3 def createCounter(): def g(): n=1 while True: yield n n=n+1 gg=g() def counter(): return next(gg) return counter print(createCounter()())
一个函数可以返回一个计算结果,也可以返回一个函数。
返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。
三、匿名函数
1 匿名的目的就是要没有名字,给匿名函数赋给一个名字是没有意义的
2 匿名函数的参数规则、作用域关系与有名函数是一样的,冒号前面为函数的参数
3 匿名函数的函数体通常应该是 一个表达式,该表达式必须要有一个返回值,返回值就是return的结果不需要再写return
4 用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
def f(x): return x * x
可写成如下:
list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
#匿名函数赋值到一个变量,利用当前的变量,利用变量调用当前函数 f = lambda x: x * x f(5) #匿名函数作为返回值调用 def build(x, y): return lambda: x * x + y * y
lambda匿名函数的应用:**max,min,sorted,map,reduce,filter**
求工资最高的人:max
salaries={ 'egon':3000, 'alex':100000000, 'wupeiqi':10000, 'yuanhao':2000 } def get(k): return salaries[k] print(max(salaries,key=get)) #'alex' print(max(salaries,key=lambda x:salaries[x])) info = [ {'name': 'egon', 'age': '18', 'salary': '3000'}, {'name': 'wxx', 'age': '28', 'salary': '1000'}, {'name': 'lxx', 'age': '38', 'salary': '2000'} ] max(info, key=lambda dic: int(dic['salary'])) max([11, 22, 33, 44, 55])
求工资最低的人:min
salaries={ 'egon':3000, 'alex':100000000, 'wupeiqi':10000, 'yuanhao':2000 } print(min(salaries,key=lambda x:salaries[x])) # 'yuanhao' info=[ {'name':'egon','age':'18','salary':'3000'}, {'name':'wxx','age':'28','salary':'1000'}, {'name':'lxx','age':'38','salary':'2000'} ] min(info,key=lambda dic:int(dic['salary']))
把薪资字典,按照薪资的高低排序sort
salaries={ 'egon':3000, 'alex':100000000, 'wupeiqi':10000, 'yuanhao':2000 } alaries=sorted(salaries) # 默认按照字典的键排序 print(salaries) # salaries=sorted(salaries,key=lambda x:salaries[x]) #默认是升序排 alaries=sorted(salaries,key=lambda x:salaries[x],reverse=True) #降序 print(salaries) info=[ {'name':'egon','age':'18','salary':'3000'}, {'name':'wxx','age':'28','salary':'1000'}, {'name':'lxx','age':'38','salary':'2000'} ] l=sorted(info,key=lambda dic:int(dic['salary']))
map 映射, 循环让每个元素执行函数,将每个函数执行的结果保存到新的列表中
v1 = [11,22,33,44] result = map(lambda x:x+100,v1) # 第一个参数为执行的函数,第二个参数为可迭代元素. print(list(result)) # [111,122,133,144] names=['alex','wupeiqi','yuanhao','egon'] res=map(lambda x:x+'_NB' if x == 'egon' else x + '_SB',names) print(list(res))
reduce , 对参数序列中元素进行累积.
import functools v1 = ['wo','hao','e'] def func(x,y): return x+y result = functools.reduce(func,v1) print(result) # wohaoe result = functools.reduce(lambda x,y:x+y,v1) print(result) # wohaoe from functools import reduce l=['my','name','is','alex','alex','is','sb'] res=reduce(lambda x,y:x+' '+y+' ',l) print(res) #my name is alex alex is sb
filter , 按条件筛选.
result=filter(lambda x:x > 2,[1,2,3,4]) print(list(result)) v1 = [11,22,33,'asd',44,'xf'] # 一般做法 def func(x): if type(x) == int: return True return False result = filter(func,v1) print(list(result)) # [11,22,33,44] # 简化做法 result = filter(lambda x: True if type(x) == int else False ,v1) print(list(result)) # 极简做法 result = filter(lambda x: type(x) == int ,v1) print(list(result)) names=['alex_sb','wxx_sb','yxx_sb','egon'] res=filter(lambda x:True if x.endswith('sb') else False,names) res=filter(lambda x:x.endswith('sb'),names) print(list(res)) #['alex_sb', 'wxx_sb', 'yxx_sb'] ages=[18,19,10,23,99,30] res=filter(lambda n:n >= 30,ages) print(list(res)) #[99, 30] salaries={ 'egon':3000, 'alex':100000000, 'wupeiqi':10000, 'yuanhao':2000 } res=filter(lambda k:salaries[k] >= 10000,salaries) print(list(res)) #['alex', 'wupeiqi']
四、装饰器
装饰器语法体现了Python中函数是第一公民,函数是对象、是变量,可以作为参数、可以是返回值,非常的灵活与强大。
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
抽离出大量与函数功能本身无关的雷同代码并继续重用,装饰器的作用就是为已经存在的对象添加额外的功能。
需求一:不带参数的装饰器
记录下函数的执行日志
import logging
def foo():
print('i am foo')
logging.info("foo is running")
其他函数如bar( )也有记录函数执行日志的需求,为了减少代码重复,可用一个单独的函数,专门处理日志 ,日志处理完之后再执行真正的业务代码
import logging #独立出来的函数 def use_logging(func): logging.warning("%s is running" % func.__name__) func() #进行函数的调用 def bar(): print('i am bar') use_logging(bar)
这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行bar(),但是现在不得不改成use_logging(bar)。那么有没有更好的方式的呢?当然有,答案就是装饰器。
def use_logging(func): def wrapper(*args, **kwargs): logging.warn("%s is running" % func.__name__) return func(*args, **kwargs) return wrapper def bar(): print('i am bar') bar = use_logging(bar) bar()
函数use_logging就是装饰器,它把执行真正业务方法的func包裹在函数里面,看起来像bar被use_logging装饰了。在这个例子中,函数进入和退出时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。
@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作
import logging def use_logging(func): def wrapper(*args, **kwargs): logging.warning("%s is running" % func.__name__) return func(*args) return wrapper @use_logging def foo(): print("i am foo") @use_logging def bar(): print("i am bar") bar()
这样我们就可以省去bar = use_logging(bar)这一句了,直接调用bar()即可得到想要的,如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。提高了程序的可重复利用性,并增加了程序的可读性。
需求二:带参数的装饰器
带参数的装饰器,比如@use_logging,该装饰器唯一的参数就是执行业务的函数,装饰器的语法允许我们在调用时,提供其他参数,比如@decorator(a)
import logging def use_logging(level): def decorator(func): def wrapper(*args,**kwargs): if level == 'warn': logging.warning("%s is running" % func.__name__) return wrapper return decorator @use_logging(level='warn') def foo(name='foo'): print("i am %s" % name) foo()
上面的use_logging是允许带参数的装饰器,它实际上是对原有装饰器的一个函数封装,并返回一个装饰器,可以理解为一个含有参数的闭包,
当我们使用@use_logging(level="warn")调用的时候,python能够发现这一层的封装,并把参数传递到装饰器的环境中
需求三:类装饰器
相比函数装饰器,类装饰器具有灵活度大,高内聚,封装性等优点。使用类装饰器还可以依靠内部的\_\_call\_\_方法,当使用@形式将装饰器附加到函数上时,就会调用此方法
def logged(func): def with_logging(*arg,**kwargs): print(func.__name__+"was called") return func(*args,**kwargs) return with_logging
调用
@logged #使用@logged完成等价于f=logged(f),所以在当前这个函数这边显示的内容不会再是这个内容而是引用的装饰器内的内容 def f(x): """does some math""" return x+x*x print( f.__name__ ) # prints 'with_logging' print (f.__doc__) # prints None
Python装饰器(decorator)在实现的时候,有一些细节需要被注意。例如,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变)。
这样有时候会对程序造成一些不便,例如笔者想对flask框架中的一些函数添加自定义的decorator,添加后由于函数名和函数的doc发生了改变,对测试结果有一些影响。(变成装饰器内的函数 就是装饰后的样子)
所以,Python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。写一个decorator的时候,最好在实现之前加上functools的wrap,它能保留原有函数的名称和docstring。
会出现报错:Python报错:NameError: name ‘wraps‘ is not defined
@wraps(func)
NameError: name ‘wraps’ is not defined
解决方法:
(1)安装functiontools:pip install functiontools
(2)@wraps(func) --> @functools.wraps(func)
import loggingfrom functools import wraps def logged(func): @wraps(func) def with_logging(*arg,**kwargs): print(func.__name__+"was called") return func(*args,**kwargs) return with_logging @logged #使用@logged完成等价于f=logged(f),所以在当前这个函数这边显示的内容不会再是这个内容而是引用的装饰器内的内容 def f(x): """does some math""" return x+x*x print( f.__name__ ) # prints 'f' print (f.__doc__) # prints 'does some math'
需求四:内置装饰器
@staticmathod、@classmethod、@property
装饰器的顺序
@a
@b
@c
def f ():
等效于
f = a(b(c(f)))
五、偏函数
Python的 functools
模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。
在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。举例如下:
int()
函数可以把字符串转换为整数,当仅传入字符串时,int()
函数默认按十进制转换:
>>> int('12345') 12345
但int()
函数还提供额外的base
参数,默认值为10
。如果传入base
参数,就可以做N进制的转换:
>>> int('12345', base=8) 5349 >>> int('12345', 16) 74565
假设要转换大量的二进制字符串,每次都传入int(x, base=2)
非常麻烦,于是,我们想到,可以定义一个int2()
的函数,默认把base=2
传进去:
def int2(x, base=2): return int(x, base)
这样,我们转换二进制就非常方便了:
>>> int2('1000000') 64 >>> int2('1010101') 85
functools.partial
就是帮助我们创建一个偏函数的,不需要我们自己定义int2()
,可以直接使用下面的代码创建一个新的函数int111
:
import functools def int111(x,base=2): return int(x,base) print(int('100',2))
#返回值是4
functools.partial
就是帮助我们创建一个偏函数的,不需要我们自己定义int2()
,可以直接使用下面的代码创建一个新的函数int2
:
import functools int2 = functools.partial(int, base=2) print(int('100',2))
#返回值是4
functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。可以理解为是一个有默认值的函数,但是用一句话更加简单