引言
这篇文章介绍python中函数的基本知识。
文章目录
0×5.递归函数
0×6.生成器函数
0×7.高阶函数
a.map和reduce
b.filter
c.sorted
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
而在一些没有循环的编程语言中,这个算法是用函数的递归来实现的,例如:
01
#!/usr/bin/env python
02
#coding=utf-8
03
#--------
04
def fact(n):
05
"""n!=n*fact(n-1)!"""
06
if n==1:
07
return 1
08
return n*fact(n-1)
09
10
print(fact(3))
运行这段程序会和循环得到相同的结果,这段递归函数的执行流程如下:
fact(3)
3*fact(3-1)
3(2fact(2-1))
3(21)
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)]
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。