目录
4.1 函数概述
有的时候,一段代码实现了某项功能,比如根据日期推算出星期几。程序里可能多处要用到这个功能,如果在所有需要用到这个功能的地方,都要把那段代码复制粘贴过来,那实在让人抓狂。更糟糕的是,如果发现那段代码有bug需要修正,或者需要改进一下让它变得更好,那么就要找出所有粘贴的地方再进行修改,那简直有一种“一失足成千古恨”的感觉。
稍微大一点的软件一般都是多个程序员合作完成的。不同的程序员实现不同的功能。如果一个程序员要使用另一个程序员开发的功能,就得向他索要源代码粘贴到自己的程序中,那是非常可怕的。如果别人写的代码用到的变量和自己的变量重名怎么办?
为了解决上述问题,程序设计语言需要有一种机制,将能够实现某一功能并需要在程序中多处使用的代码包装起来形成一个功能模块,即写成一个“函数”,当程序中需要使用该项功能时,只需写一条语句,调用实现该功能的“函数”即可。不同的程序员可以分别写不同的函数,组合起来形成一个大程序。
Python中定义一个函数的格式如下:
def函数名(参数1,参数2,……):
语句组(即“函数体”)
也可以没有参数:
def函数名():
语句组(即“函数体”)
语句组需要缩进。
调用函数的写法如下:
函数名(参数1,参数2,……)
对函数的调用也是一个表达式。函数调用表达式的值由函数内部的return语句决定。return语句语法如下:
return返回值
return语句的功能是结束函数的执行,并将“返回值”作为结果返回。“返回值”可以是常量、变量或复杂的表达式。如果return语句后面没有“返回值”,则返回值是None。None表示“啥也不是”,可以用它给变量赋值,也可以用它来写ifa!=None:这样的语句。
return语句作为函数的出口,可以在函数中多次出现。多个return语句的返回值可以不同。在哪个return语句结束函数的执行,函数的返回值就和哪个return语句里面的返回值相等。函数也可能直到执行完都没有碰到return语句,那样的话函数返回None。
下面是一个函数的示例:
defMax(x,y): #求x,y中最大的
if x > y:
return x
else:
return y
#函数到此结束
n = Max(4,6)
print(n,Max(20,n)) #>>6 20
print(Max("about","take")) #>>take
第1行:定义一个名为Max的函数,有两个参数x,y,其功能是返回x,y中最大的那个。函数中的语句组(函数体)需要缩进。函数体持续到第一条不再缩进的语句为止(该语句不属于函数)。因此,上面的Max函数,就到第5行为止。
在函数定义中出现的参数,如Max中的x,y,称为“形式参数”,简称“形参”。
定义一个函数,并不会导致执行它。上面的程序是从第7行开始执行的。Max(4,6)即以4、6作为参数,调用Max函数。调用函数时所给的参数,称为“实际参数”,简称“实参”。调用一个函数,导致程序进入函数内部执行,在本例中,Max(4,6)导致程序进入Max函数内部,即从第2行开始执行。函数执行到return语句,或者执行完函数的最后一条语句,函数调用就结束。如果函数执行中没有碰到return语句,函数的返回值是None。
函数被调用时,形参的值等于实参。因此,程序进入Max时,x的值是4,y的值是6,最终执行第5行returny返回6,因此表达式Max(4,6)的值就是6。
下面这个程序输出100以内的质数:
def IsPrime(n): #判断n是否是质数
if n <= 1 or n % 2 == 0 and n != 2:
return False
elif n == 2:
return True
else:
for i in range(3,n,2):
if n % i == 0:
return False
if i * i > n:
break
return True
def main():
for i in range(100):
if(IsPrime(i)):
print(i,end=" ")
main()
程序输出:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
判断一个数是否是质数,是个独立的功能,所以即便在上面的程序中只有一个地方用到,把它写成一个函数也非常必要。这样能够使程序清晰易懂。读到第15行,我们就知道这里是要判断i是否是质数,IsPrime里面的代码可以不用去看。如果没有函数,把IsPrime的那些代码都写在这里,那么还得看半天才能明白程序在做什么。。即便几十行的短程序,把其中独立的功能分离出来写成一个个函数,也大有好处—因为这样可以分别测试每个函数写得对不对,便于查错。
第13、17行:编写一个main函数,程序执行就是调用main函数,这是不错的程序设计风格。请注意,即便函数没有参数,调用时也要在函数名后面跟“()”。
本程序如果没有第17行,就不会有任何语句被执行。因为函数必须被调用才会执行。
函数之间可以互相调用。例如,上面的main函数就调用了IsPrime函数。
4.2 全局变量和局部变量
在所有函数外面定义(即首次赋值)的变量,称为全局变量。在函数内部定义的变量,称为局部变量。局部变量在定义它的函数的外部不能使用,因此不同函数中的同名局部变量不会互相影响。
函数中可以出现和全局变量同名的变量。假设它们都叫x,则:
(1)如果函数中没有对x赋值,则函数中的x就是全局变量x;
(2)如果函数中对x进行赋值,且没有特别声明,则在函数中全局变量x不起作用,函数中的x就是只在函数内部起作用的局部变量x;
(3)函数内部可以用globalx声明函数里的x就是全局变量x。
def f0():
print("x in f0:",x) #这个x是全局的x
def f1():
x = 8 #这个x是局部的x,不会改变全局的x
print("x in f1:",x)
def f2():
global x #说明本函数中的x都是全局的x
print("x in f2:",x)
x = 5
print("x in f2:",x)
def f3():
print("x in f3=",x) #执行到此会出错
x = 9 #局部的x
x = 4 #全局的x
f0() #>>x in f0: 4
f1() #>>x in f1: 8
print(x) #>>4 全局的x
f2()
#>>x in f2:4
#>>x in f2:5
print(x) #>>5
f3() #出错
本程序中的几个函数,都没有return语句。它们的返回值都是None。
程序是从第14行开始运行的。
第2行:此处的x看似没有定义,但是只要程序运行到这条语句时,x有定义即可。第14行定义了全局的x,于是当程序运行到第2行时,x便有了定义。
第12行:若调用f3(),执行到第12行时会产生运行时错误。因为在第13行对x进行赋值,x就被认为是f3函数内部的x,和全局的x没关系。那么在第12行,x还没有初始化就使用其值,就会产生RuntimeError。
第18行:产生的输出如第19、20行所示。第7行的globalx表明f2函数内的x都是全局的x。
从第2行可以看出,Python是解释执行的。不同于缩进不对齐、括号不匹配、变量名不合法之类程序尚未运行就会被PyCharm划红线标记出错的语法错误,变量或函数名没定义这样的运行时错误,只有在程序运行到那条出错语句的时候才会发生。例如:
defg():
fadsf()
hgjg=ffasdfa(),335543
print("hello")
上面这个程序,g函数中各个标识符似乎都没有定义。但是,只要不调用g函数,程序就不会出错,该程序会输出“hello”。如果调用了g函数,那么运行到g函数内部的时候,就会发生fadsf没定义的错误。
4.3 参数个数可变的函数
Python支持参数个数可变的函数,用法示例如下:
def f1(*args):
for x in args:
print(x,end = " ")
print("")
def f2(x,y,*n):
print(x,y,end = " ")
for k in n:
print(k,end = " ")
print("")
f1(1,'a',2,'b') #>>1 a 2 b
f2(1,2,3,4,5) #>>1 2 3 4 5
f2(1,2) #>>1 2 参数n也可以为空
第1行:定义函数时,最后一个形参可以写成"*x"的形式。x是个变量名,名字任意,代表一个元组。目前,可以将元组简单理解为元素不可修改的列表。在调用函数的时候,在x对应的实参位置可以写0到任意多个实参。这些实参都称为x的元素。如果x对应的位置没有实参,x就称为空元组。形式为*x的参数x,称为可变元组参数。
第10行:args对应的实参就是全部实参,进入f1函数,args就是包含全部实参的元组。
第11行:实参1和2对应于形参x,y,剩下的全部实参都被放到元组n中。
4.4 函数参数的默认值
Python的函数还允许有些参数有默认值,即调用的时候如果不给出这些参数,这些参数的值就自动取默认值。定义函数时,将参数写成x=y的形式,就意味着参数x的默认值是表达式y。用法示例如下:
def f(x, y = 1, z = 2):
print(x,y,z)
f(0) #>>0 1 2
f(0,100) #>>0 100 2
f(0,200,300) #>>0 200 300
f(0,z='a') #>>0 1 a
第3行:只给了参数x的值,y,z的值没给,那么y的值就是1,z的值就是2。
第4行:z的值没给,z即为默认值2。
第5行:所有参数的值都给了,参数的默认值就不起作用。
要注意,定义函数时,有默认值的参数,必须是最右边的连续若干个参数。调用函数时,如果少写了一些参数,Python会认为缺失的参数就是最右边的若干个参数,于是就用默认值替代这些参数。因此定义函数时,如下写法是不行的:
def f(x,y=1,z):
print(x,y,z)
也不能用f(10,,20)这样的方式来默认省略掉中间的参数。
但是如上面程序第6行那样,不给参数y的值,但指明参数z的值是'a',是可以的。
print函数就是典型的带默认值参数的函数。其end参数的默认值是"\n"(换行符),sep参数的默认值是""。所以使用print函数时,如果不指定这两个参数,输出就会以换行结尾,以空格作为各项的分隔符。
4.5 Python 的库函数
各种程序设计语言都会提供大量函数,可以直接在程序中使用,这些函数称为库函数。print就是一个库函数。前面程序中用到的abs、len、round,甚至int、str,都是库函数。Python中的常用库函数见表4.5.1。
函数 | 功能 |
int(x) | 把x转换成整数 |
float(x) | 把x转换成小数 |
str(x) | 把x转换成字符串 |
ord(x) | 求字符x的编码 |
chr(x) | 求编码为x的字符 |
abs(x) | 求x的绝对值 |
len(x) | 求序列x的长度(元素个数),如len("123")、len([2,3,4]) |
max(x) | 求序列x中的最大值。x可以是元组、列表、集合 |
min(x) | 求序列x中的最小值。x可以是元组、列表、集合 |
max(x1,x2,x3,...) | 求多个参数中最大的那个 |
min(x1,x2,x3...) | 求多个参数中最小的那个 |
type(x) | 返回变量x或表达式x的值的类型 |
exit() | 中止程序执行 |
dir(x) | 返回类x或对象x的成员函数名构成的列表 |
help(x) | 返回函数x或类x的使用说明。想查函数或类用法时用它很方便 |
部分库函数用法示例如下:
print(max(1,2,3)) #>>3
print(max([1,2,5,2]) ) #>>5
print(min("ab","cd","af")) #>>ab
print(type("hello")) #>><class 'str'>
print(type([1,2,3])) #>><class 'list'>
print(type("123") == str) #>>True
print(help(len))
exit() #程序中止
print("done") #不会执行
第7行:输出Python库函数len的使用说明。
Help on built-in function len in module builtins:
len(obj, /)
Return the number of items in a container.
读者可以自行尝试一下dir(str)、help(list)、dir(tuple)等的效果是什么。
第8行:exit()使得程序立即结束,因此第9行不会被执行。写一个稍微复杂点的程序,测试时只想测试前面部分,不想测试后面部分,那么在中间加个exit()挺方便的。
还有一种函数,叫作“成员函数”。比如,input().split()中的split,就是字符串的“成员函数”。当我们说“x是y的成员函数”时,意思就是,对于y类型的任何变量或者常量m,可以用m.x(......)的方式来调用成员函数x。例如,字符串有成员函数split(),因此"12345".split()是合法的;由于input()的返回值是个字符串,,所以input().split()也是合法的。成员函数,也称为“方法”(method),比如可以说“字符串有split方法”。
4.6 lambda 表达式
lambda表达式写法如下:
lambda参数1,参数2,…: 返回值
一个lambda表达式就是一个函数,它相当于如下函数:
def f(参数1,参数2,…):
return返回值
只不过lambda表达式代表的函数没有名字。示例程序如下:
add = lambda x,y : x + y #add的值就是一个参数为x,y,返回值为x+y的函数
print(add(5,4)) #>>9
square = lambda x: x*x
print(square(5)) #>>25
print((lambda x:x+1)(3)) #>>4
最后一行直接以3为参数调用lambda表达式。
4.7 高阶函数和闭包
在Python中,函数可以赋值给变量,也可以作为函数的参数和返回值。如果一个函数能接收函数作为参数,或其返回值是函数,这样的函数就称为高阶函数。
下面程序演示了函数作为参数的情况:
def square(x):
return x * x
def inc(x):
return x+1
def combine(f,g,x):
returnf(g(x))
print(combine(square,inc,4)) #>>25
print(combine(inc,square,4)) #>>17
第5行:combine函数的参数f和g都是函数,因此combine是高阶函数。
第7行:进入combine函数,f是square,g是inc,f(g(4))就是square(inc(4)),所以输出25。
第8行:f是inc,g是square,f(g(4))就是inc(square(4)),所以输出17。下面程序演示了函数作为函数返回值的情况:
下面程序演示了函数作为函数返回值的情况:
def square(x):
return x * x
def inc(x):
return x+1
def combineFunctions(f,g):
return lambda x: f(g(x))
def powerFunction(f,n):
def h(x):
for i in range(n):
x = f(x)
return x #x最终变为f(f(...f(x))),f写n次
return h
print(combineFunctions(square,inc)(4)) #>>25
print(powerFunction(inc,5)(1)) #>>6
print(powerFunction(square,4)(2)) #>>65536
第6行:返回一个函数,若称为k,则k(x)=f(g(x))。
第13行:combineFunctions(square,inc)的值是一个无名函数,若称为k,则k(x)=square(inc(x))。combineFunctions(square,inc)(4)就是k(4),所以输出25。
第8行:Python允许函数内部再定义函数。在一个函数f1内部定义的函数f2,和在f1内部定义的变量一样,在函数f1外部不可见,即不可以f2(...)的形式直接调用。f2称为“嵌套函数”,f1称为f2的“父函数”或“外围函数”。h就是在powerFunction内部定义的一个嵌套函数,powerFunction就是h的父函数。h的定义到第11行为止。powerFunction(f,n)返回h的一个“实例”。该实例是一个函数,若称之为k,则
k(x) = f(f(f....f(x)))) f一共写n次
因此第14行,powerFunction(inc,5)的返回值是h的一个实例,若称为k1,则k1(x)=inc(inc(inc(inc(inc(x)))))。k1(1)=6,所以输出6。
第15行:powerFunction(square,4)的返回值是h的另一个实例,称为k2,则k2(x)=square(square(square(square(x))))。k2(2)=2562,所以输出65536。
k1和k2都是powerFunction中的嵌套函数h的实例。二者的不同之处在于,实例k1中的f是inc,n是5,而实例k2中的f是square,n是4。f和n都来自h的父函数powerFunction的某次调用。即便powerFunction的调用已经结束,k1和k2还能各自维持不同的f和n值。像k1、k2这样的嵌套函数的实例,称为“闭包”(也有将h称为闭包,将k1、k2称为闭包h的不同实例的说法)。f和n这样,在嵌套函数中使用,但是来自于父函数,且在父函数调用结束后仍然能够存在的变量,称为自由变量。每个闭包各自有一套自由变量。
实际上,第6行的lambda表达式也是一个嵌套函数,本行也返回该嵌套函数的一个实例,即一个闭包。
下面是闭包的又一个例子:
def func(x):
def g(y):
nonlocal x #有了此行,才能在g中对x赋值
x += 1
return x+y
return g
f = func(10) #f是一个闭包,其自由变量x初值是10
print(f(4)) #>>15
print(f(5)) #>>17
g = func(20) #g是一个闭包,其自由变量x初值是20
print(g(4)) #>>25
print(g(5)) #>>27
第3行:在嵌套函数中若想对自由变量x赋值,则需声明该自由变量为nonlocal。否则第4行会导致RuntimeError。
可以看到,在闭包f和g的使用过程中,它们的自由变量x的值会变化。
4.8 生成器
Python中有一条很特殊的yield语句,有点类似于return语句,功能也是结束函数的执行并返回一个值。用法如下:
yield 返回值
如果一个函数中使用了yield语句,则这个函数就被称为“生成器函数”。生成器函数数的区别在于:调用生成器函数,并不会导致该函数被执行,而是会返回一个“生成器”(generator),此后就可以用for循环遍历该生成器。遍历时,生成器函数内部的代码才会被执行。
如果X是一个生成器,由某生成器函数F被调用时返回,则:
for i in X:
print(i)
会依次打印F中yield语句返回的结果,直到函数F通过return语句返回或执行完最后一条语句自然返回:
def test_yield(k): #被调用则返回生成器
yield 1
k += 1
yield 2
yield (1,2)
k += 1
yield k
return 100
for x in test_yield(10): #>>1,2,(1,2),12,
print(x,end=",")
程序输出结果是:
1,2,(1, 2),12,
第9行:testyield(10)的返回值就是一个生成器,此时testyield函数并未被执行。for循环执行时,test_yield函数从头开始执行,执行到第一个yield语句(第2行),函数返回,返回值是1。于是for循环的第一个x的值就是1。然后for循环继续,注意此时函数不是从头执行,而是接着从第3行往下执行,执行到第4行的yield语句,函数再次返回,且返回值为2,因此第二个x的值就是2。以此类推,第三个x的值是元组(1,2),第四个x的值是12。请注意k的值在函数执行过程中得以保持和延续。当for循环试图获取第五个x时,函数testyield往下执行到结束都没有碰到yield语句,于是for循环取不到第五个x,就此结束。第8行的100并不会被取为一个x值。
生成器可以用来实现无穷长的序列。当然,无穷长的序列是不可能事先生成好并存储的,但是可以做到需要这个序列有多少个元素,就生成多少个元素。例如下面的斐波那契数列生成器:
def fib(): #用于生成求斐波那契数列
a, b=0, 1
while True:
yield a
a, b=b, a+b
seq = fib() #seq是一个生成器
i = 0
for x in seq: #>>0,1,1,2,3,5,
print(x,end=",")
i += 1
if i > 5:
break
for x in seq: #>>8,13,21,34,55,
print(x,end=",")
i += 1
if i > 10:
break
输出结果:
0,1,1,2,3,5,8,13,21,34,55,
第13行:用for循环对生成器的遍历,是有记忆的,不是每次都从头开始。
注意:生成器函数一般不能写成递归形式的,因为调用生成器函数,并不会执行它,而是返回一个生成器,所以生成器函数无法递归调用自己。
Python有next(x)函数,用于取生成器x的下一个返回值。如果取不到,就会产生StopIteration异常:
def test_yield():
yield 1
yield 2
yield (1,2)
a = test_yield()
while True: #>>1,2,(1,2),
try:
print(next(a),end=",")
except StopIteration:
break