[Python]-8-函数
引言
这篇文章介绍python中函数的基本知识。
文章目录
- 0×1.自定义函数
- 0×2.给函数添加文档说明
- 0×3.函数的作用域
- 0×4.带参数的函数
- 0×5.递归函数
- 0×6.生成器函数
- 0×7.高阶函数
- a.map和reduce
- b.filter
- c.sorted
- 0×8.返回函数
- 0×9.匿名函数
- 0×10.函数装饰器
- 0×11.偏函数
0×1.自定义函数
在任何编程语言中,函数的作用都是相同的,它可以将一些需要重复执行的语句组合起来,方便在需要的时候调用他们,python中使用def关键字来创建一个函数,请看下面的实例:
#!/usr/bin/env python3 #coding=utf-8 dect={"one":1,"two":2,"three":3} #创建函数,函数名为in_dect,中括号中为空代表不接收传入参数,函数执行过程中遇到return会结束执行,将return后的值返回给调用者 #------ def in_dect(): """根据字典传入键返回值""" try: count=dect[num] except KeyError as err: count=0 return count #------ #将num设置成dect字典中不同的键,在print中调用刚才创建的函数,in_dect()函数会根据num键返回对应的值,如果传入的键不存在,则触发KeyError异常,返回0 num="one" print(in_dect()) num="two" print(in_dect()) num="xxx" print(in_dect()) #程序输出 1 2 0
0×2.给函数添加文档说明
我们可以使用"三引号"给每个函数添加一段功能说明,在python中,通过"函数名.__doc__"能够打印出这段说明文字,请看下面的实例:
#!/usr/bin/env python3 #coding=utf-8 #------ def sayhello(): """打印出hello world""" """www.qingsword.com""" print("hello world!") #------ sayhello() #直接调用函数 print(sayhello.__doc__) #打印出函数中的说明信息 #程序输出 hello world! 打印出hello world #注意上面的输出,__doc__只能够打印出函数中第一个"三引号"所包含的说明信息
0×3.函数的作用域
函数中的变量名称和全局中的变量名称可以相同,函数的变量名称只作用与函数内部,请看下面的实例:
#!/usr/bin/env python3 #coding=utf-8 #------ def re_list(): lst=[1,2,3] return lst #------ lst=["a","b","c"] print(lst,re_list()) #在上面的程序中,re_list()函数中包含了一个变量名称lst,全局中也有一个相同名称的变量,但两者互不干扰,函数中的变量的作用域仅限函数本身,当函数被调用时创建,函数执行完成后被回收,下面是程序输出 ['a', 'b', 'c'] [1, 2, 3]
0×4.带参数的函数
可以在自定义的函数括号中定义变量来接收传入数据,请看下面的实例:
#!/usr/bin/env python3 #coding=utf-8 #------ def re_dect_value(dect,k): return dect[k] #------ d={"a":1,"b":2,"c":3} k="a" #调用函数时,将d和k传递给函数,函数定义时,括号中包含了两个参数,这相当于将函数参数中的dect指向了d所引用的内存区域的数据(列表数据),k同理,所以这个函数的return语句返回的值,实际上就等价与d[k] print(re_dect_value(d,k)) #程序输出: 1
可以使用判断语句来判断传递给函数的参数的数据类型,通过数据类型判断调用不同的函数区块执行,如果不携带参数,则使用默认值所指向的区块执行,请看下面的实例:
#!/usr/bin/env python3 #coding=utf-8 #choose_def函数,用于确定传入的数据是哪种类型,根据不同类型调用不同函数执行,如果不传入参数,则使用默认值(即typ="default",idx=0) def choose_def(typ="default",idx=0): if type(typ)==type([]): print(c_dect(typ,idx)) elif type(typ)==type(""): print(c_str(typ,idx)) #------ def c_dect(d,idx): return d[idx] #------ def c_str(s,idx): return s[idx] #------ lst=[1,2,3,4] s="www.qingsword.com" i=2 choose_def(lst,i) choose_def(s,i) choose_def() #程序输出 3 w d #在上面的程序中,最后一次调用选择函数时没有携带参数,python会使用定义函数时设定的默认值(如果有),程序判断出"default"是一个字符串,所以调用字符串函数c_str(),因为默认的idx是0,所以打印出"default"第一个字符d #像choose_def()这种携带默认值参数的函数,如果我们只想修改其中一个参数,其他的参数按照默认值,可以使用下面的方法调用 choose_def(idx=3) #这样的调用方法仅指定了idx的值,typ并没有设定,它将使用默认值
除了使用类型判断外,还可以使用字符串判断,通过每个类型判断所返回的字符串,来判断传入的参数属于那种类型,对上面的程序中的choose_def函数做出一点点修改,执行结果相同:
#将type()判断后返回的值转化成字符串,与对应的字符串值比较 def choose_def(typ="default",idx=0): if str(type(typ))=="<class 'list'>": print(c_dect(typ,idx)) elif str(type(typ))=="<class 'str'>": print(c_str(typ,idx))
当传递了一个错误的数据给函数时,除了使用try捕获异常外,还能够使用raise关键字来主动抛出异常,修改choose_def()函数,在末尾添加下面的语句:
def choose_def(typ="default",idx=0): if str(type(typ))=="<class 'list'>": print(c_dect(typ,idx)) elif str(type(typ))=="<class 'str'>": print(c_str(typ,idx)) else: raise TypeError("错误的参数:%s"%typ) #向函数传递一个错误的参数,整数 choose_def(123) #当程序执行if判断时发现没有类型能够匹配传入的整型,所以跳转到else执行下面的raise语句,当raise语句被执行时,会立刻终止程序的执行,并抛出一个异常,这个异常是我们主动抛出的,下面是这个异常的显示信息,其中的中文内容是我们自定义的 builtins.TypeError: 错误的参数:123
再来看一个实例,创建一个函数,接受两个数值,返回这两个数值相加后的结果:
#!/usr/bin/env python3 #coding=utf-8 #------如果输入的字符不是数值,if判断会匹配到else从而主动抛出一个异常 def a_plus(x,y): """返回x+y的值""" if is_Num(x) and is_Num(y): return float(x)+float(y) else: raise TypeError("输入的数值错误:%s,%s"%(x,y)) #------用来判断输入的是否为数值数据,不论是浮点数,整数或科学计数法,使用float()转换时都不会报错,而其他的字符就会引发ValueError异常返回False,如果不报错,返回True def is_Num(x): """判断键盘输入的是否为数字""" try: float(x) except ValueError: return False else: return True #------ x=input("第一个数:") y=input("第二个数:") print("相加结果:%.2f"%a_plus(x, y)) #程序输出 第一个数:1.2 第二个数:1.3333 相加结果:2.53 第一个数:abc 第二个数:111 builtins.TypeError: 输入的数值错误:abc,111
除了能够返回单个值外,函数还能一次返回多个值,在返回多个值的时候,这些值实际上是以元组的形式返回的,例如:
#!/usr/bin/env python3 def my_def(x,y,z): """返回多个值的函数""" return x,y,z #可以使用一个变量接受这些返回值,实际上x就指向了一个元组 x=my_def(1,2,3) print(x) #也可以用多个变量分别指向元组中的单个元素 a,b,c=my_def(1,2,3) print(a) print(b) print(c) #程序输出 (1, 2, 3) 1 2 3
Ps:可能有点朋友会问,那么如果函数没有return语句呢?在没有return语句的函数执行完成后,返回一个None。
最后给出一个二元一次方程组求解的实例:
#!/usr/bin/env python3 #coding=utf-8 import math def quadratic(a,b,c): """二元一次方程组公式法求解""" #当传入的为一个非数值数据时,可以使用异常捕获try,或下面的方法抛出异常 if not isinstance(a,(int,float)) or not isinstance(b,(int,float)) or not isinstance(c,(int,float)): raise TypeError("错误的参数传入,a,b,c只能是整数或小数:%s,%s,%s"%(a,b,c)) #求出判别式 disc=b**2-4*a*c #判别式的三种情况 if disc<0: return "方程无解" elif disc==0: return "方程有唯一解:"+str(-b/2*a) else: #math模块的sqrt()函数用于开方 result1=(-b+math.sqrt(disc))/2*a result2=(-b-math.sqrt(disc))/2*a return "方程两个解分别为:%s,%s"%(str(result1),str(result2)) print(quadratic(4,8,4)) print(quadratic(2,6,4)) print(quadratic(2,3,3)) #程序输出 方程有唯一解:-16 方程两个解分别为:-4.0,-8.0 方程无解 #如果输入一个错误的值,就会抛出错误信息 print(quadratic("a",3,3)) TypeError: 错误的参数传入,a,b,c只能是整数或小数:a,3,3
0×5.递归函数
递归函数有点类似于循环,简单的讲,就是函数在运行过程中再次调用自身,通过这种调用来执行一些类似循环一样的工作,下面来看一个数学中阶乘的算法:
我们知道一个数的阶乘可以写成:
n!=1×2×3×...×(n-1)×n=n×(n-1)!
例如,5的阶乘:
5!=1×2×3×4×5=5×4!
这种算法用循环能够很轻松的实现,例如:
#!/usr/bin/env python #coding=utf-8 #-------- def fact(n): """计算n!""" s=1 while n>1: s*=n n-=1 return s #计算出3的阶乘 print(fact(3)) #程序输出 6
而在一些没有循环的编程语言中,这个算法是用函数的递归来实现的,例如:
#!/usr/bin/env python #coding=utf-8 #-------- def fact(n): """n!=n*fact(n-1)!""" if n==1: return 1 return n*fact(n-1) print(fact(3))
运行这段程序会和循环得到相同的结果,这段递归函数的执行流程如下:
fact(3)
3*fact(3-1)
3*(2*fact(2-1))
3*(2*1)
3*2
6
递归函数虽然好用,但在python中使用递归函数容易引发"栈溢出"(这在我们传递一个比较大的数据时,比如999的阶乘),处理"栈溢出"的一种方法是使用"尾递归",但Python解释器并没有提供支持"尾递归"的优化,如果python支持"尾递归"优化,那么上面的递归函数可以更改成下面的样子:
#!/usr/bin/env python #coding=utf-8 #-------- def fact(n): """计算n!""" return fact_a(n,1) #-------- def fact_a(n,result): if n==1: return result return fact_a(n-1,n*result) print(fact(6))
在上面的fact_a(n,result)函数中使用了"尾递归"优化,在return时调用函数自身,并且return语句中不包含表达式,只有函数本身,n-1与n*result两个参数会在调用函数之前被计算出来,在支持尾递归优化的编程语言中,这种调用方式使得栈不会增长,所以也能很好的避免"栈溢出"(但Python中却没有提供这种功能,所以Python中使用递归函数要谨慎)。
0×6.生成器函数
"生成器函数"有点类似前面介绍过的"列表生成器",每次迭代这个生成器函数时,才执行算法计算出下一个值,请看下面的实例:
#首先来看一个正常的函数,传入一个数值num,打印出num位数的斐波那契数列 #!/usr/bin/env python #coding=utf-8 def fibo(num): #同a=0 b=1 n=0 a,b,n=0,1,0 while n<num: print(b) #同a=b b=a+b a,b=b,a+b n+=1 return "End" print(fibo(5)) #程序输出,此时fibo还只是普通函数 1 1 2 3 5 End #将上面的普通函数改成"生成式函数"仅需要一步,将while中的print(b)替换成yield b,如下 #!/usr/bin/env python #coding=utf-8 def fibo(num): a,b,n=0,1,0 while n<num: yield b a,b=b,a+b n+=1 return "End" print(fibo(5)) #程序输出 <generator object fibo at 0x7fea773fdc50> #想要迭代这个生成式中的值,可以使用next()函数,或使用for循环 for x in fibo(5): print(x) #输出 1 1 2 3 5 #可能大家会发现一个问题,迭代后,根本没有执行fibo最后的return语句,这是因为生成式函数在迭代的时候,遇到yield就返回,不再执行后面的内容,而再次访问这个生成式的时候,会接着上次yield返回的位置往下执行,如果想要获得生成式函数的return返回值,需要使用next()函数去迭代其内容,然后捕获StopIteration异常,这个return就包含在这个异常的value里,例如: #!/usr/bin/env python #coding=utf-8 def fibo(num): a,b,n=0,1,0 while n<num: yield b a,b=b,a+b n+=1 return "End" f=fibo(5) while True: try: print(next(f)) except StopIteration as erro: print(erro.value) break #程序输出 1 1 2 3 5 End
来看一个利用生成器函数生成杨辉三角的实例:
#!/usr/bin/env python #coding=utf-8 def triangles(): L=[1] while True: yield L L.append(0) L=[L[i-1]+L[i] for i in range(len(L))] x=triangles() print(next(x)) print(next(x)) print(next(x)) print(next(x)) print(next(x)) #程序输出 [1] [1, 1] [1, 2, 1] [1, 3, 3, 1] [1, 4, 6, 4, 1]
0×7.高阶函数
如果一个函数可以接收另一个函数作为参数,这种函数就叫做"高阶函数",例如:
#!/usr/bin/env python #coding=utf-8 #abs()是python内置的求绝对值的函数 print(abs(-23)) #变量可以指向函数,这个变量就是这个函数的一个引用 a=abs print(a(-24)) #函数名本身也是变量,如果将内置函数名指向另外一个数值,那么它将不再能够计算绝对值,如果此时再用abs计算绝对值,就会报错 abs=233 print(abs) #删除abs的指向,恢复默认的指向 del abs print(abs(-25)) #自定义一个高阶函数,将abs函数传递给这个函数,用于计算|x|+|y| #-------- def xandy(x,y,f): """高阶函数,返回|x|+|y|""" return f(x)+f(y) print(xandy(-10,-20,abs)) #程序输出 23 24 233 25 30
通过这个实例,我们得出几个结论:python中所有函数名都是变量,并且可以改变他们的指向(虽然不推荐这样做),变量可以指向函数,高阶函数能够接收另一个函数作为参数。
下面就来看几个python内置的高阶函数。
a.map和reduce
首先来看map()函数,这个函数接收两个值:一个是函数,一个是可迭代类型(Iterable);map()将传入的函数作用于后面的可迭代类型中的每个值,请看下面的实例:
#!/usr/bin/env python #coding=utf-8 #-------- def square(x): """返回x的平方""" return x*x L=[1,2,3,4,5] #map第一个参数是一个函数,第二个参数是一个可迭代类型,map会读取列表L中的每个值,传递给square函数,最后使用list函数将map对象转换成列表输出 print(list(map(square,L))) #程序输出 [1, 4, 9, 16, 25]
再来看一个利用map函数将数值列表转换成字符串列表的实例:
#!/usr/bin/env python #coding=utf-8 L=[1,2,3,4,5] print(list(map(str,L))) #程序输出 ['1', '2', '3', '4', '5'] #使用循环可以达到同样的效果,但代码看起来却没有使用高阶函数那么简洁 #!/usr/bin/env python #coding=utf-8 L0=[1,2,3,4,5] L1=[] for a in L0: L1.append(str(a)) print(L1)
再来看reduce函数,这个函数也接收两个值,第一个为函数,第二个参数也是一个可迭代类型,与map不同的是,reduce的第一个参数必须是可以接收两个传入参数的函数,reduce的工作原理如下:
reduce(f, [a, b, c, d]) = f(f(f(a, b), c), d)
reduce把一个函数作用在一个序列 [a, b, c, d,...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算。
下面来看一个将字符串类型(仅包含整型的字符串)转换成整型的实例:
#!/usr/bin/env python #coding=utf-8 from functools import reduce #-------- def str2int(x): """将传入的字符串整型转换成整型""" def char2num(a): """将单个字符转换成整型""" return {"0":0,"1":1,"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9}[a] def tonum(a,b): """返回整数""" return a*10+b return reduce(tonum,map(char2num,x)) print(type(str2int("10086"))) #输出<class 'int'> #虽然没有添加异常处理模块,但我们已经利用map函数与reduce函数完成了一个int()函数原型,假设Python没有提供int()函数,你完全可以自己写一个把字符串转化为整数的函数,就像上面这样
b.filter
和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素,例如:
#实例一:返回奇数列表 #!/usr/bin/env python #coding=utf-8 #-------- def is_odd(x): """如果x为奇数返回True,偶数返回False""" return x%2==1 L=[1,2,3,4,5,6,7,8] print(list(filter(is_odd,L))) #程序输出 [1, 3, 5, 7] #实例二:去除列表中的空字符串 #!/usr/bin/env python #coding=utf-8 #-------- def is_sp(x): """strip()函数能去除数据前后空格,如果是空字符去除空格后,return返回的就为False,任何非空字符都返回True""" return x.strip() L=[" ","a","b"," ","c"] print(list(filter(is_sp,L))) #程序输出 ['a', 'b', 'c'] #实例三:输出1000以内所有回数(回数如121,131,前后对称) #!/usr/bin/env python #coding=utf-8 #-------- def is_back(x): """判断是否为回数,将传入数字转换成字符串然后使用分片反转,将反转的数字重新转换成整型并与原数比较,[::-1]如果步长为正表示从前往后取,步长为负表示从后往前""" return x==int(str(x)[::-1]) print(list(filter(is_back,range(1,1001))))
c.sorted
Python内置的sorted()函数可用于对可迭代类型(列表等)进行排序,请看下面的实例:
#默认情况下,数字从小到大,字母先大写后小写按照字母表顺序排列 >>> L0=[1,-10,9,-2,23,-9] >>> print(sorted(L0)) [-10, -9, -2, 1, 9, 23] >>> L1=["B","a","C","d","b","X"] >>> print(sorted(L1)) ['B', 'C', 'X', 'a', 'b', 'd'] #sorted是一个高阶函数,可通过key关键字传递一个函数,首先使用这个函数作用于列表的每一个元素,然后再按照默认排序,本例使用abs函数将列表每个元素都转换成正整数,然后按照sorted的默认排序从小到大,确定位置后,再将原列表对应的数字放到对应的位置上 >>> print(sorted(L0,key=abs)) [1, -2, 9, -9, -10, 23] #将列表中的字母全部转换成小写排序 >>> print(sorted(L1,key=str.lower)) ['a', 'B', 'b', 'C', 'd', 'X'] #使用reverse参数可以将排序反转 >>> print(sorted(L1,key=str.lower,reverse=True)) ['X', 'd', 'C', 'B', 'b', 'a']
再来看一个实例,使用自定义函数对列表进行排序:
#!/usr/bin/env python #coding=utf-8 L = [('John', 66), ('Qingsword', 96), ('George', 76), ('Lifa', 83)] #-------- def by_name(L): """按名字排序""" return L[0].lower() def by_score(L): """按分数排序""" return L[1] print(sorted(L,key=by_name)) print(sorted(L,key=by_score)) #程序输出 [('George', 76), ('John', 66), ('Lifa', 83), ('Qingsword', 96)] [('John', 66), ('George', 76), ('Lifa', 83), ('Qingsword', 96)]
0×8.返回函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回,被返回的函数并没有立刻执行,直到被调用时才执行其中的内容,请看下面的实例:
#!/usr/bin/env python #coding=utf-8 #-------- def calc_sum(*n): """立即计算并返回传入的所有参数之和""" s=0 for x in n: s+=x return s #-------- def lazy_sum(*n): """将n_sum函数返回给调用者,只有当访问函数时才计算所有参数之和""" def n_sum(): s=0 for x in n: s+=x return s return n_sum f1=calc_sum(1,2,3,2,1) f2=lazy_sum(1,2,3,2,1) #f1接收的是calc_sum函数的返回结果 print(f1) #f2接收的只是一个返回函数n_sum print(f2) #访问n_sum函数时才计算结果 print(f2()) #程序输出 9 <function lazy_sum.<locals>.n_sum at 0x7fc0410cae18> 9
将函数作为返回值容易出现一些小问题,当返回函数中的值调用了外部的一些变量的时候,如果外部的变量发生改变,那么可能得到意料之外的结果,例如:
#!/usr/bin/env python #coding=utf-8 #-------- def count(): L = [] for i in range(1, 4): def fx(): return i*i L.append(fx) return L f1, f2, f3 = count() print(f1()) print(f2()) print(f3()) #这段程序count()执行后会在L列表中生成三个函数[fx,fx,fx],将这三个函数赋予三个变量f1,f2,f3,我们期望的函数执行结果应该是1,4,9,但实际上print输出三个函数的结果都是9,这是因为,在我们调用f1(),f2(),f3()之前,并不会去计算i*i的结果,而在我们调用这三个函数时,count()函数早已执行完成,for循环后i的值是3,所以f1(),f2(),f3()全部返回9 #如果期望得到1,4,9这样的结果,就应该将i参数在for执行的时候传递给返回函数,让传递过去的参数保存在那个函数实例中,这样调用他们时才会得到对应的结果,就像下面这样 #!/usr/bin/env python #coding=utf-8 #-------- def count(): def f(i): def fx(): return i*i return fx L = [] for i in range(1, 4): L.append(f(i)) return L f1, f2, f3 = count() print(f1()) print(f2()) print(f3()) #L.append(f(i))每次执行时,都会向f()传递当前的i值,这个i值被保留在当前f()实例中,而f()本身返回一个函数fx(),所以i*i并没有立刻计算,count执行完成后L列表中仍然保存的是三个返回的fx函数,直到他们被调用时,才分别计算每个fx函数中i的乘积
0×9.匿名函数
匿名函数使用关键字lambda定义,例如:
#!/usr/bin/env python #coding=utf-8 #-------- def print_website(): """返回网站地址""" return "www.qingsword.com" #-------- def squ(x): """求x的平方""" return x*x a=lambda:"www.qingsword.com" b=lambda x:x*x print(print_website()) print(squ(12)) print(a()) print(b(11)) #程序输出 www.qingsword.com 144 www.qingsword.com 121 #在上面这个小程序中,前面两个是正常def定义的函数,下面的a和b是使用lambda定义的匿名函数,两者功能完全相同 #匿名函数的语法如下,参数列表就相当于def函数名称括号里面的部分,返回值就等同于def函数的return部分: lambda 参数列表:返回值 #再来看个匿名函数实例,接收两个参数返回这两个参数的和 #!/usr/bin/env python #coding=utf-8 s=lambda x,y:x+y print(s(12,6)) #程序输出 18
理解了匿名函数的工作原理后,前面很多的函数都可以改成匿名函数的形式让程序更加简洁,下面是使用匿名函数配合map和reduce函数完成将字符串转换成浮点数的一个实例:
#!/usr/bin/env python #coding=utf-8 from functools import reduce def str2float(s): return reduce(lambda x,y:x*10+y,map(int,s[:s.find(".")]))+\ reduce(lambda x,y:x/10+y,map(int,s[:s.find("."):-1]))/10 print(str2float("520.1314")) #输入一个字符串浮点数,将输出一个数值浮点数,下面拆分一下这个程序的return部分 #reduce(lambda x,y:x*10+y,map(int,s[:s.find(".")])) #这一部分完成字符串小数点前的部分到整数的转换 #s[:s.find(".")]切片将取出第一个字符到"."位置(本例是3),也就是0,1,2这三个索引位置的字符(即"520") #然后使用map将这三个字符转换成int形式,并且保存成可迭代类型 #reduce将逐个读取上一步中map得到的那个结果集,并使用匿名函数进行处理,本例为首先取出5和2得到52,再取出0得到520 #reduce(lambda x,y:x/10+y,map(int,s[:s.find("."):-1]))/10 #这一部分完成字符串小数点后面的一部分到浮点数的转换 #唯一需要解释一下的就是s[:s.find("."):-1] #这个切片从-1的位置(末尾数字)开始,倒序读取(末尾的-1代表倒序,如果是-2代表倒序并且读取间隔为2),直到"."位置 #这一部分最后会得到0.1314 #两部分相加得到最终结果
匿名函数也可以用作返回函数,例如:
#!/usr/bin/env python #coding=utf-8 def a(x,y): return lambda :x+y b=a(1,2) print(b) print(b()) #程序输出,只有当访问b()时才计算x+y的值 <function a.<locals>.<lambda> at 0x7f1c808a5d90> 3
0×10.函数装饰器
有时候我们需要在函数运行前执行一些操作,但又不能更改原函数,这个时候就能够用到"函数装饰器",所谓的装饰器,实际上就是一个函数,它接收原函数作为传入参数,然后返回另外一个函数,这个返回的函数在原函数执行前,将执行一系列我们设定的操作,然后再执行原函数,请看下面的实例:
#!/usr/bin/env python #coding=utf-8 import time #-------- #log函数返回一个wrapper函数,返回函数被调用前不会被执行,返回函数被执行后会打印出传入函数的名称(fx.__name__),并且在返回时调用传入函数 def log(fx): """接收一个传入函数并返回一个函数的装饰器""" def wrapper(*args,**kw): print("Execute %s()"%fx.__name__) return fx(*args,**kw) return wrapper #-------- #在原函数前,使用@符号添加装饰器函数,这相当于在这个位置执行了一句now=log(now),now函数名称变量被重新指向了log函数,原函数now被当成参数传递给了log函数,这段语句执行后相当于now="log函数返回的wrapper()函数",程序执行now()就等价与执行log中的wrapper()函数 @log def now(): """打印当前时间""" print(time.ctime(time.time())) print("End") now() #程序输出 Execute now() Sat Sep 10 09:07:28 2016 End #上面的wrapper函数是能够接收任意数量的参数的,将程序更改成下面的样子 #!/usr/bin/env python #coding=utf-8 import time #-------- def log(fx): """接收一个传入函数并返回一个函数的装饰器""" def wrapper(*args,**kw): print("Execute %s()"%fx.__name__) return fx(*args,**kw) return wrapper #-------- @log def now(*x,**kw): """打印当前时间""" print(time.ctime(time.time())) print(x) now("www.qingsword.com") #程序输出 Execute now() Sat Sep 10 09:25:45 2016 ('www.qingsword.com',) #执行now()就等同于执行wrapper(),因为@log等价与now=log(now),而log()返回wrapper(),就相当于now=wrapper(),wrapper()能够接收任意参数,所以传入参数"www.qingsword.com"将被传递到return fx(*args,**kw)的fx函数参数中
在上面的实例中,装饰器本身只能接收被装饰的函数作为传入参数,如果想要装饰器本身也能接收参数,需要更改程序如下:
#!/usr/bin/env python #coding=utf-8 import time #-------- def log(text): """接收传入参数的装饰器""" def decorator(fx): """接收一个传入函数并返回一个函数""" def wrapper(*args,**kw): print("%s %s()"%(text,fx.__name__)) return fx(*args,**kw) return wrapper return decorator #-------- @log("Execute") def now(*x,**kw): """打印当前时间""" print(time.ctime(time.time())) print(x) now("www.qingsword.com") #程序输出 Execute now() Sat Sep 10 09:33:42 2016 ('www.qingsword.com',) #@log("Execute")等同于now=log("Execute")(now),首先调用log传入参数"Execute",log函数会返回decorator函数,这个函数接收一个传入参数,本例将被装饰的now函数传递给了它,decorator函数执行后再返回一个wrapper()函数,后面的步骤和上一个实例就一样了
上面的实例中,如果我们在程序末尾添加一个打印调用函数名称的语句:
...... now("www.qingsword.com") print(now.__name__) #程序输出 wrapper #这是因为装饰器将原函数now包装成了wrapper,now变量指向的其实是wrapper函数,这有时会带来不小的麻烦,那么如何让程序名称输出为原函数的名称,而不是装饰器的名称呢?更改如下 #!/usr/bin/env python #coding=utf-8 import time import functools #-------- def log(text): """接收传入参数的装饰器""" def decorator(fx): """接收一个传入函数并返回一个函数""" #在wrapper上面也添加一个装饰器,传入原函数,这条语句执行后,会将返回的wrapper函数中的__name__字段替换成fx.__name__,也就是原函数的名称 @functools.wraps(fx) def wrapper(*args,**kw): print("%s %s()"%(text,fx.__name__)) return fx(*args,**kw) return wrapper return decorator #-------- @log("Execute") def now(*x,**kw): """打印当前时间""" print(time.ctime(time.time())) print(x) now("www.qingsword.com") print(now.__name__) #程序输出 Execute now() Sat Sep 10 09:43:12 2016 ('www.qingsword.com',) now
最后来看一个完整的实例,要求装饰器在原函数执行前输出"begin call",在原函数执行后输出"end call",然后根据传入装饰器的参数判断是使用带参数的装饰器还是普通装饰器,请看下面的实例:
#!/usr/bin/env python #coding=utf-8 import time import functools #-------- def log(TxtOrFx): #判断传入的是函数还是字符串 if type(TxtOrFx)==type(""): def decorator(fx): @functools.wraps(fx) def wrapper(*args,**kw): print("Begin call") print("%s %s()"%(TxtOrFx,fx.__name__)) fx(*args,**kw) print("End call") return wrapper return decorator else: @functools.wraps(TxtOrFx) def wrapper(*args,**kw): print("Begin call") print("Execute %s()"%(TxtOrFx.__name__)) TxtOrFx(*args,**kw) print("End call") return wrapper #-------- @log("Execute") def now(*x,**kw): """打印当前时间""" print(time.ctime(time.time())) print(x) now("www.qingsword.com") print(now.__name__) #程序输出,如果将@log("Execute")替换成@log,同样将得到下面的输出结果,但两者所执行的代码块不一样 Begin call Execute now() Sat Sep 10 10:14:58 2016 ('www.qingsword.com',) End call now
0×11.偏函数
偏函数是functools模块的一个叫做partial的函数提供的功能,这个函数接收的第一个参数是一个函数,后面依次是要传递给这个函数的参数,请看下面的实例:
#!/usr/bin/env python #coding=utf-8 import functools #-------- def divi(x,y): """返回x%y""" return x%y #第一个偏函数,10作为传递给divi函数的第一个参数,相当于默认参数divi(x=10,y),这个时候使用d0只要传递给他第二个参数y的值,就能完成运算 d0=functools.partial(divi,10) print(d0(3)) #d1定义的偏函数自带了两个默认参数,调用 d1的时候就不需要传入参数了,相当于divi(10,6) d1=functools.partial(divi,10,6) print(d1()) #将字符串"520"转换成整数520 f1=functools.partial(int,"520") print(f1()) #int接受一个关键字参数base来定义传入给int的字符串是多少进制,本例等同于将传入给int的字符串当成二进制转换成十进制,调用的时候如果只专递一个字符串,默认当做二进制处理,也可以手动传入一个base值,比如base=16,传入的字符串会当做16进制处理 f2=functools.partial(int,base=2) print(f2("1110")) print(f2("1110",base=16)) #程序输出 1 4 520 14 4368 #再例如求最大值的偏函数 #!/usr/bin/env python #coding=utf-8 import functools max2=functools.partial(max,100) print(max2(10,20,30,200)) #程序输出 200 #max函数接受多个传入值,相当于max(*args),我们在定义max2的时候,提前预设了一个传入值,这相当于max(100,*args),如果调用max2传入的值比100小,那么直接会返回100,否则会返回传入的最大值