在编码过程中常常看到python使用几行代码就可以搞定几十行代码所具有的功能,优雅至极,实用至极,但非常抽象。本篇介绍几个常用的高级函数,实际上也是python进阶过程中所需要了解的内容,如装饰器、列表推导式、yield生成器等,有了这些知识做基础,后续会进步更多。同时提笔非常推荐一个pythontutor代码执行过程可视化工具,,Visualize Python, Java, JavaScript, C, C++, Ruby code execution :
那我们就开始这些高级函数内容吧:
匿名函数lambda
匿名函数就是函数不命名,但能实现函数的功能,我们来对比一下:
def mul(x,y): #定义一个函数mul,实现两个变量的乘积
return x*y
print(mul(2,3)) #调用函数mul,传入x为2,y为3,返回结果输出为6
上述使用def命名函数,完成两个数的乘积并返回结果;
mul = lambda x,y:x*y: #定义一个lambda匿名函数,实现两个变量的乘积,返回一个函数类
print(mul(2,3)) #调用函数类mul,传入x为2,y为3,返回结果输出为6
使用lambda匿名函数也可以达到一样的效果,而且代码还相对简洁。它的特点为:(1)不用函数def声明,没有函数名字,直接使用lambda声明。(2)格式为:lambda 参数1,参数2.... : 表达式, 参数与表达式之间使用冒号 : 分隔。(3)不需要return语句,返回的是表达式的值。(4)调用时将lambda函数赋给一个变量,然后就可以正常如函数一样使用了,包括传递参数和获取返回值。
高阶函数map
map为python内置的一个高阶函数,其用法为map(function,iterable),即第一个参数为function,第二个为可迭代的对象,包括列表、元组、字典、字符串等,返回的是一个map对象,如果想获取其中的数据,可以使用list或者for循环。如将上面的匿名函数作为其参数,可以快速完成一个列表数据的运算:
number=[2,3,4,5]
result=map(lambda x:x+3,number) #使用map函数对列表数进行处理print(type(result)) #打印结果为<map object at 0x00000179A41BBF98>,为map对象
num=list(result) #使用list将map对象打印出来
print(num) #显示结果[5,6,7,8]
不过注意的是,这个list(result)在map处理后只能用一次,因为迭代器的原因,指针无法回退,所以如果再使用一次list(result),就只能得到空值。
map函数中除了那个lambda函数外,还也可以传入多个可迭代参数,
number1=[2,3,4,5]
number2=[20,30,40,50]
result=map(lambda x,y:x+y,number1,number2) #使用map函数对列表数进行处理,传入了两个列表参数
print(type(result)) #打印结果为<map object at 0x00000179A41BBF98>,为map对象
num=list(result) #使用list将map对象打印出来
print(num) #显示结果[22,33,44,55]
代码中注释了在map函数第一部分为lambda函数,用于两个变量相加,第二部分就传入了两个列表number1,number2,从结果可以看出,最终是实现了两个列表的相加。也就是将两个列表作为了参数传递进了lambda函数。
高阶reduce函数
reduce与map函数一样,也属于高阶函数,其原型为reduce(function,sequence),作用是用function对序列进行累计操作,返回的是一个累计值。累计操作不是计数操作,而是对列表里第一个数、第二个数传入function里处理,如function函数为lamda x,y:x+y,即对数x和y相加操作,接下来是使用该结果与第三个数相加,这样来调用function。如下例子:
from functools import reduce
number1=[2,3,4,5]
result=reduce(lambda x,y:x+y,number1) #使用reduce函数对列表数进行处理
print(result) #显示结果14
高阶filter函数
Filter函数与map()函数用法类似,不过它可以用来过滤元素的迭代函数,它的函数原型是:filter(function,itearable)。同样有一个function作为参数,来处理后面的可迭代对象。filter处理后返回的是一个filter对象,可以使用list或for循环来取出内容。值得注意的是,因为涉及到filter处理,所以要求function参数的返回值必须为bool型,即真或假,当返回值为真的时候,就依据function中的条件对后面的可迭代对象进行过滤操作。
number1=[2,3,4,5]
result=filter(lambda x:x%2==0,number1) #使用filter函数对列表数进行处理
print(list(result)) #显示结果 [2,4]
生成器zip函数
zip函数用于将可迭代对象作为参数,将对象中的元素打包成一个个元组,并返回一个zip对象,然后可以使用list来显示。同时还可以将返回的zip对象使用dict来生成字典对象。不过如果两个参数长度不同时,按短的进行处理。
number1=['A','B','C','D']
number2=[80,85,89,90]
result=zip(number1,number2) #使用zip函数对列表数进行处理
print(type(result)) #返回结果:<class 'zip'>
#print(list(result)) #生成元组构成的列表 [('A', 80), ('B', 85), ('C', 89), ('D', 90)]
print(dict(result)) #生成字典 {'A': 80, 'B': 85, 'C': 89, 'D': 90}
enumerate函数
class enumerate:
"""
enumerate(iterable[, start]) -> iterator for index, value of iterable
"""
def __init__(self, iterable, start=0):
pass
enumerate函数属于python内置方法,接收一个可迭代对象,如列表,字典等,返回一个包括迭代对象索引和值对应元组,索引默认起始值为0.例如:
num=['cao','jian','hua']
for index,value in enumerate(num):
print(index,value)
最后输出包括索引和值:
0 cao
1 jian
2 hua
一行代码典范之一:三元表达式
三元表达式:也称为if / else的紧凑表达式,例如:
def max(x,y): #比大小函数
if x>y: return x
else: return y
print(max(10,20)) #结果为20
如果采用三元表达式,直接一行代码:
def max(x,y): #比大小函数
return x if x>y else y #三元表达式
print(max(10,20)) #结果为20
三元表达式左边是条件为真时返回的值,中间是判断条件,右边是条件假返回的值。
一行代码典范之二:列表推导式
列表推导式很常用,可以快速的从循环获取数据里解脱出来,原来要新建一个列表,然后使用循环获取数据然后追加到新列表里得过程用一行代码就可以完成:
number=[2,3,4,5,6]
newNumber=[] #新建一个列表
for i in number:
newNumber.append(i) #循环追加到新建列表里
print(newNumber) #生成结果也为[2,3,4,5,6]
使用列表推导式直接一行代码: newNumber=[i for i in number]。总结一下列表推导式,其格式为:[表达式 for 变量 in 列表 if 条件] ,其中if条件是可选的。再举例如下:
number=[2,3,4,5,6,7,8]
newNumber=[] #新建一个列表
for i in number:
if i%2==0: #如果能被2整除
newNumber.append(i) #就把这个数循环追加到新建列表里
print(newNumber) #生成结果也为[2,4,6,8]
#使用列表推导式
newNumber=[i for i in number if i%2==0]
print(newNumber) #生成结果也为[2,4,6,8]
生成器函数yield
前面已经介绍了一个生成器函数zip。这里再介绍一个常用的yield函数。从格式上来说生成器函数和普通函数很相似,只有一点的区别。那就是生成器函数不需要return,而是用一个新的关键字yield。在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。调用一个生成器函数,返回的是一个迭代器对象。如下例子:
def fibonacci(n): # 生成器函数 - 斐波那契
a, b, counter = 0, 1, 0
while True:
if (counter > n):
return
yield a
a, b = b, a + b
counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
while True:
try:
print (next(f), end=" ") #循环使用迭代器的next方法,获取f的值
except StopIteration:
pass
如上斐波那契数列函数,使用f = fibonacci(10) 时,f就是一个迭代器,需要使用next函数来访问,next(f)获得值,如果想获取所有的值,就需要使用循环依次读取next(f)。当然这里关注的不光是迭代器next读值,还有一个yield生成器使用后,执行过程。我们使用pythontutor可视化工具来看整个执行过程,使用的时候就是把上述代码拷贝到http://www.pythontutor.com/visualize.html#mode=edit 中,然后点击visualize execution按钮,然后可以一步步的看执行过程。这里张贴两张图说明一下。如下,当程序第一次进入函数fibonacci(n)里的While True后,a的值为0,b的值为1,n的值为10,由于if条件不成立,就进入yield a语句执行,yield a的作用如右边红字所示,就是return value,返回当前a 的值为0,此时程序会进入print(next(f),end="")语句就会打印出来0,这一步就体验到了yield的返回作用。
上述图步骤返回0后,跳出循环,进入下一个next(f)执行过程,此时程序会直接到yield a语句那,此时a,b,counter的值都是上一步的值,也就是a还是0,b为1,然后执行yield a的下一句:a,b=b,a+b,此时a就为1,b也是1,count增加到1,然后判断counter是否大于10,又遇到yield a,返回a的值,也就是本次next(f)的值就是返回的1。返回1后,后面的语句不执行,直接跳出循环,进入下一个next(f)的执行过程,依次这样。
函数闭包
函数闭包本质是函数的嵌套和高阶函数。我们来看看要实现函数闭包要满足什么条件(缺一不可):1)必须嵌套函数; 2)内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量——内部函数引用外部变量 ;3)外部函数必须返回内嵌函数——必须返回那个内部函数。比较绕,不好理解啊。举个例子,如图中。foo()函数里嵌套一个add函数,而add函数里引用了外部的num变量,且返回的为num变量,最终foo函数返回值为add函数。图中程序里f=foo(),此时foo为一个函数对象,f为其实例,然后f(1),将1传递给foo对象,接收1的是其内部add函数,返回的值为2。这一步好像还可以理解。如果我们再传入一个n值,此时就可以看到其方便程度了。
第二步,我们传入一个n值为10,此时看一下运行过程:
很显然,第二步传入n为10,得到的结果为12,就是使用了第一次n为1的结果。也就是说闭包的作用为:可以保持程序上一次运行后的状态然后继续执行。我们看到生成器和函数闭包都是使得程序执行更快,执行效率高,不过过程就是要按规则来组织代码执行。
函数装饰器
上面介绍的函数闭包也是为了引入函数装饰器的使用。类似上面的函数闭包其实难懂,非常抽象。装饰器反而见得较多。装饰器就是在不改变其他代码的情况下给其他函数增加一些功能的函数,也称语法糖。
首先定义一个以函数作为参数的函数:
def aaa(fun): #fun为一个函数参数
def bbb():
print("start test......")
fun() #执行fun函数
print("end of test......")
return bbb #返回函数名参数
然后可以这样使用这个aaa函数,
@aaa #使用@注解符号将上述定义的aaa函数放在ccc函数上面
def ccc():
print(" now you can see me...")
ccc() #执行ccc函数
上述执行结果为:
start test......
now you can see me....
end of test......
此时aaa函数就可以用为装饰器函数,使用注解符号@标明。在执行过程中相当于将ccc函数作为其参数传递进到aaa函数内部,也就是替代fun()函数语句,因此最终打印的就是上述三条语句。
现在我们增加点变量给ccc函数,同时在装饰器函数里也要给定对应的参数变量:
def aaa(fun): #fun为一个函数参数
def bbb(x,y): #bbb接收两个变量参数传递
print("start test......")
fun(x,y) #执行fun函数
print("end of test......")
return bbb
@aaa #将aaa作为装饰器函数实用
def ccc(x,y): #将ccc函数传递给装饰器函数参数
print( x+y)
ccc(3,5) #传递两个实际值给x和y变量
执行后结果为:
start test......
8
end of test......
不过上述例子里,ccc函数有两个变量,所以在装饰器bbb函数也得给两个变量,便于传递到其内部函数fun中进行执行。装饰器函数一般需要给成通用型,因此在装饰器内部的bbb函数一方面可以将bbb更名,一方面参数给成不定长度参数,如下:
def aaa(fun): #fun为一个函数参数
def func(*args,**kwargs): #更改名为func,参数给成不定长参数
print("start test......")
fun(*args,**kwargs) #接收不定长参数
print("end of test......")
return func
@aaa #使用装饰器作用于ccc函数
def ccc(x,y):
print("结果为:",(x+y))
ccc(3,5) #调用ccc函数获得执行结果
上述式中不定长参数命名为*args,**kwargs, *args为元组方式参数,**kwargs为字典方式参数,也就是函数在使用这些不定长参数时的数据结构组织方式。
这类装饰器的作用就是在大量需要重复使用某一个函数的场景,而且整个业务流程都需要这个函数参与其中时效果很好,避免代码重复使用,简化代码。例如网站授权方面的应用:通常在某个软件或者网站访问时需要授权才能进入,而且许多模块都需要登陆授权后才能访问,这个授权函数是每个模块场景应用都需要的,我们不可能每个模块开启时都做一遍登陆授权。因此可以使用装饰器来搞定这个事情。如下代码:
def Auth_login(fun): #编写一个鉴权函数,用于登陆验证
def func(*args,**kwargs):
if username=='admin'and userpwd == 'admin123': #设定用户名和密码正确时才执行fun函数
fun(*args,**kwargs)
else:
print("您还没有登陆!") #用户名和密码不正确时无法进入相关业务
return func
@Auth_login #加一个装饰器函数,对Trade场景做是否登陆验证
def Trade(*args):
print("您可以开启交易了")
@Auth_login #加一个装饰器函数给购物车做验证
def Cargo(*args):
print("点击查询购物车。。")
username,userpwd = input('请输入您的账号:').split(',') #从键盘输入用户名和密码
Trade(username,userpwd) #进入Trade场景业务,如果鉴权不对,会提示没有登陆,否则打印可以开启交易
Cargo(username,userpwd) #进入Cargo场景业务,同样如果鉴权通过,则打印点击查询购物车
日志是另外一个频繁使用装饰器的例子。我们希望的是在每个业务处理的时候都给出监控日志,这个监控日志函数肯定是需要大量使用,这样对整个系统开发可以开展状态和进程监控。如下示范案例:
from functools import wraps
def logit(fun):
@wraps(fun)
def func(*args,**kwargs):
print(fun.__name__+" is running!")
return fun(*args,**kwargs)
return func
@logit
def Trade():
print("now let us begin to trade module now...")
@logit
def Account():
print("now let us begin to account module now...")
Trade()
Account()
运行后输出结果为:
Trade is running!
now let us begin to trade module now...
Account is running!
now let us begin to account module now...
这样可以把过程简单记录下来,如果哪里有问题,就可以返回去查询。我们也可以创建一个类来构建装饰器,修改一下上述的日志代码:
class logit():
def __init__(self,fun): #初始化函数里调入外部fun函数
self._fun=fun
print(fun.__name__+" is running!")
def __call__(self,*args): #__call__函数在类为装饰器时自动调用
return self._fun(*args)
@logit #使用logit类装饰器,自动调用__call__函数
def Trade():
print("now let us begin to trade module now...")
@logit
def Account():
print("now let us begin to account module now...")
Trade()
Account()
最后结果打印如下:
Trade is running!
now let us begin to trade module now...
Account is running!
now let us begin to account module now...