引言
这篇文章介绍python中函数的基本知识。
文章目录
0×8.返回函数
0×9.匿名函数
0×10.函数装饰器
0×11.偏函数
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
#--------
#VALUE!
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
#--------
#VALUE!
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
#--------
#VALUE!
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,否则会返回传入的最大值
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。