第5章 函 数 函数(function)是执行计算的命名语句序列。将一段代码封装为函数并在需要的位置 进行调用,不仅可以实现代码的重复利用,更重要的是可以保证代码的完全一致。 5.1 函数的定义和使用 1.函数的定义 函数定义(definition)的语法形式: def 函数名([形式参数列表]): #[]表示可选,即一个函数可以没有形式参数 ''' docstring①''' #函数的功能说明,也就是文档字符串 函数体中的语句 Python使用关键字def定义函数。关键字def后面有一个空格,然后是函数名,接下来 是一对圆括号,圆括号里面是形式参数(formalparameter),圆括号后面是一个冒号和换行, 最后是必要的注释以及函数代码。定义函数时需要注意如下几个问题: (1)一个函数即使不需要接收任何参数,也必须保留一对圆括号; (2)括号后面的冒号必不可少; (3)函数体(包括注释部分)相对于def关键字必须向右缩进一定数量的空格。 下面定义一个add()函数,该函数接收两个形式参数x1和x2。 def add(x1, x2): #函数头 '''Return the sum of x1 and x2.''' return x1 +x2 在上述定义的add()函数中,其函数体的第1行是注释,也就是文档字符串docstring。 在Python语言中有些工具利用docstring自动生成联机文档,使得用户可以方便地、交互式 地浏览程序代码,如图5-1所示。编写代码时额外添加文档字符串docstring是一种好的编 程实践,请注意培养这样的好习惯。 ① docstring代表文档字符串(documentationstring)。 教学课件 第5章 函数 71 图5-1 函数定义中的文档字符串 在图5-1中,使用add()函数的_ _doc_ _属性可以查看在该函数的定义中添加的文档字 符串,也可以使用Python的内置函数help()查看add()函数的使用帮助。另外,在调用 add()函数时输入左括号,IDLE等集成开发环境就会立即弹出该函数的使用帮助信息。 2.函数的调用(call) 函数定义完毕并不能自动运行,只有被调用时才能运行。下面的代码用整数1和2调 用add()函数,该函数的返回值被赋值给变量result。 result =add(1, 2) print(result) 程序的运行结果: 3 上述调用add()函数时使用的整数1和2是实际参数(actualparameter),简称实参;而 在函数头使用的参数是形式参数,简称形参。形参没有具体的值,形参的值来自实参。 3.函数的返回值(returnvalue) 通常,定义一个函数是希望它能够返回一个或多个计算结果,这在Python语言中是通 过关键字return来实现的。无论return语句出现在函数的什么位置,一旦被执行,它都会 立即结束函数的执行过程。如果在函数的定义中没有出现return语句或者执行了不返回 任何值的return语句,Python解释器就认为该函数以returnNone语句结束,即返回一个 空值(None)。下面定义的3个函数demo(x,y),它们的返回值都是None。 def demo(x, y): x +y #没有return 语句 def demo(x, y): Return #return 语句不返回任何值 def demo(x, y): return None 72 Python程序设计教程(第2版) 下面定义一个fib()函数,该函数能够输出任意指定范围内的斐波那契(Fibonacci) 数列①: >>>def fib(n): '''Print a Fibonacci series up to n.''' a, b =0, 1 while a <n: print(a, end=' ') #输出a 的值后,接着输出一个空格 a, b =b, a+b #元组赋值 print() #输出一个空行 >>>fib(20) #将20 作为实参调用fib()函数 上述代码的输出结果: 0 1 1 2 3 5 8 13 可以将一个函数名赋值给另外一个变量,使得该变量也可以作为函数使用: >>>f =fib #这是一种通用的重命名机制 >>>f(20) 0 1 1 2 3 5 8 13 由于上述定义的fib()函数中没有return语句,因此Python解释器会自动返回一个 空值。 >>>print(fib(5)) #注意对比fib(5)与print(fib(5))的输出结果 0 1 1 2 3 None #fib(5)不输出此行 再看一个例子: >>>def demo(): pass >>>print(demo()) None 修改上述定义的fib()函数,使其返回一个由斐波那契数组成的列表,而不是在该函数 中打印该数列: >>>def fib2(n): '''Return a list containing the Fibonacci series up to n.''' result =[] a, b =0, 1 ① 斐波那契数列的前两项为0和1,从第3项开始,每一项都等于前两项之和,如0,1,2,3,5,…。 程序源码 第5章 函数 73 while a <n: result.append(a) a, b =b, a+b return result >>>f100 =fib2(100) #调用fib2()函数 >>>f100 #输出结果 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] 5.2 函数的参数类型 在函数的定义中,形参列表的一般形式为 <必选参数>, …, <可选参数>=<默认值>, … 1.必选参数 没有给出默认值(defaultvalue)的参数都是必选参数。在定义函数时,必选参数必须出 现在可选参数的前面。例如,在下面定义的demo()函数中,参数x就是一个必选参数。在 调用函数时,必须给必选参数赋值。 def demo(x, y=5): pass 2.可选参数 带有默认值的参数都是可选参数。在定义函数时已经给可选参数指定了默认值。因此 在调用一个函数时,如果没有给可选参数提供值,那么该函数就会使用其默认值。例如,在 上面定义的demo()函数中,y就是可选参数,因此在调用demo()函数时,可以不给可选参 数y提供新值。下面再定义一个函数person()。 def person(name, gender='male', age=20): print('name:', name) print('gender:', gender) print('age:', age) 可以使用以下三种方式调用上述定义的person()函数: (1)仅给必选参数name赋值,如person('Tim'); (2)给必选参数name和可选参数gender赋值,如person('Tim',f'emale'); (3)给所有参数赋值,如person('Smith','male',30)。 3.关键字参数 拥有参数名的参数都是关键字参数(这里所说的关键字不是指Python解释器内部定 义并使用的关键字,如if等)。在person()函数的定义中出现的3个参数都是关键字参数。 使用关键字参数可以很方便地给形参赋值,而无须记住各个参数在函数定义中出现的先后 顺序。下面是调用demo()函数的各种方式。 74 Python程序设计教程(第2版) demo(3) #正确,x 的取值3,y 的取值5 demo(x=3) #正确,x 的取值3,y 的取值5 demo(3, y=4) #正确,x 的取值3,y 的取值4 demo(y=4, x=3) #正确,x 的取值3,y 的取值4 demo(y=4, 3) #错误 4.可变长度参数 在Python语言中,可变长度参数有两种类型:一种是用单星操作符“*”定义的;另一 种是用双星操作符“**”定义的。拥有第一种可变长度参数的函数能够接收任意数量的实 参,这些实参被封装成一个元组: def multiply(*args): #arg 代表argument(参数) z =1 for arg in args: z *=arg print(z) 下面调用multiply()函数。 multiply(4, 5) #输出结果为20 multiply(10, 9) #输出结果为90 multiply(2, 3, 4) #输出结果为24 在可变长度参数的前面,可能会出现必选参数: def demo(name, *args): #可变长度参数args 前有一个必选参数name print(name, end=', ') for arg in args: print(arg, end=' ') demo('Jim', 'nice', 'to', 'meet', 'you!') 在上一行的函数调用中,name的取值为Jim,args的值为元组(n'ice',t'o','meet',y' ou! ')。 程序的输出结果如下。 Jim, nice to meet you! 再看一个例子: >>>def concat(*args, sep="/"): return sep.join(args) 上述代码中的参数sep为可选参数,也是关键字参数。 >>>concat("a", "b", "c") 'a/b/c' 第5章 函数 75 在上述代码中,可选参数sep 取默认值“/”,可变长度参数args的值为元组("a", "b","c")。 >>>concat("one", "two", "three", sep=".") 'one.two.three' 在上一行的函数调用中,可变长度参数args的值为元组("one","two","three"),并 且为可选参数sep指定了新值“.”。 如果在函数形参列表的最后有一个用双星操作符“**”定义的可变长度参数,那么该 函数能够接收任意数量的实参,而且这些实参被封装成一个字典: def print_values(**kwargs): for key, value in kwargs.items(): print("The value of {} is {}".format(key, value)) print_values(my_name="Tom", your_name="Tim") 上述代码的执行结果: The value of my_name is Tom The value of your_name is Tim 注意:在函数的定义中,双星操作符“**”必须出现在单星操作符“*”的后面。 下面定义一个demo()函数。 def demo(*args, **kwargs): #**kwargs18 必须出现在*args 的后面 for arg in args: print(arg, end=' ') print() #输出一个回车换行 for key, value in kwargs.items(): print(key, '=>', value) 下面调用demo()函数。 demo('hello', 'world', name='Tom', age=30) 程序的输出结果如下。 hello world name =>Tom age =>30 上述四种参数类型相互之间并不是互斥的关系,如可选参数同时也是关键字参数。 5.函数参数的赋值方式 为方便读者阅读,再次给出person()函数的定义: 程序源码 76 Python程序设计教程(第2版) def person(name, gender='male', age=20): print('name:', name) print('gender:', gender) print('age:', age) 在调用函数时,如果不使用参数名给形参赋值,那么将按照实参出现的顺序依次给对应 位置上的形参赋值,这种方式叫作按位置赋值。如下面的函数调用。 person('Tim') 执行上述代码后,形参name得到的值为Tim,gender和age取默认值,也就是gender 的取值为male,age的取值为20。再看下面几种函数调用。 person('Tim', 'female') #name 为Tim,gender 为female,age 取默认值20 person('Smith', 'male', 30) #name 为Smith,gender 为male,age 为30 再强调一次:必须给必选参数指定参数值。综上可知:按位置给形参赋值时,实参的 出现顺序非常重要。除了按位置给形参赋值外,还可以通过指定参数名的方式给形参赋值, 这种方式叫作按关键字赋值。如下面的函数调用。 person(name='Tim', gender='female') person(gender='female', name='Tim') person(age=22, gender='female', name='Tim') 很显然,按关键字赋值的优点是不用考虑实参出现的先后顺序。 也可以将这两种赋值方式混合使用: person('Sue', gender='female') 实参Sue按位置给形参name赋值,而实参female按关键字给gender赋值。 注意:当混合使用这两种赋值方式时,一定要保证按位置赋值出现在按关键字赋值的 前面。下面的函数调用是错误的。 person(gender='female', 'Sue') 在上述的函数调用中,按位置赋值应出现在前面,即person(S' ue',gender=f'emale')。 再看两个错误的函数调用方法: person('Gorge', name='Gorge') #为同一个参数指定重复的值 person(weight=150) #使用未知的形参weight 有时只能采用按关键字赋值的方式给形参赋值,如下面定义的函数。 def demo(*args, name): #可变长度参数args 后面有一个必选参数name print(name, end=', ') 第5章 函数 77 for arg in args: print(arg, end=' ') 假如按位置赋值,也就是采用下面的方式调用demo()函数,读者可以试一试能否得到 期望的输出结果。 demo('nice', 'to', 'meet', 'you!', 'Jim') 此时应该按关键字赋值: demo('nice', 'to', 'meet', 'you!', name='Jim') 5.3 参数解包 有时实参已存储在列表、元组等数据容器中,但是函数调用却需要单独的位置参数,这 时就需要使用单星操作符“*”将实参从这些数据容器中解包(unpacking)出来: >>>def demo(x, y, z): print(x+y+z) >>>lt =[1, 2, 3] #列表lt >>>demo(*lt) 6 >>>tu =(1, 2, 3) #元组tu >>>demo(*tu) 6 >>>st ={1, 2, 3} #集合st >>>demo(*st) 6 实参为字典时: >>>dt ={1:'a', 2:'b', 3:'c'} >>>demo(*dt) #默认使用字典的键 6 >>>demo(*dt.values()) #明确指定使用字典的值 abc >>>list(range(3, 6)) [3, 4, 5] >>>args =[3, 6] >>>list(range(*args)) #解包列表,得到实参3 和6 [3, 4, 5] #执行结果相同 类似地,字典可以使用双星操作符“**”进行解包以便传递关键字参数: 78 Python程序设计教程(第2版) >>>def person(name, gender='male', age=20): print('name:', name) print('gender:', gender) print('age:', age) >>>dt ={"name": "Tom", "gender": "male", "age": 40} >>>person(**dt) name: Tom gender: male age: 40 5.4 递归函数 简单地说,算法是解决问题的方法与步骤。递归算法(recursivealgorithm)的核心思想 是分治策略。分治,是“分而治之”(divideandconquer)的意思。分治策略将一个复杂问题 反复分解为两个或更多个相同的,或相似的子问题,直到这些子问题可以直接求解,最后将 子问题的解合并起来,就能得到原问题的解,如图5-2所示。 图5-2 分治策略 在Python语言中,分治策略是通过递归函数实现的。一个函数在其函数体内调用它 自身,这种函数叫作递归函数。递归函数由终止条件和递归条件两部分构成。下面定义一 个计算阶乘的函数factorial(n)。 def factorial(n): #factorial 是阶乘的意思 if n <=1: #终止条件 return 1 else: return n * factorial(n -1) #递归条件 print(factorial(5)) 第5章 函数 79 上述代码的输出结果: 120 调用上述定义的factorial(n)函数计算5的阶乘,执行过程如下所示。 factorial(5) = 5 * factorial(4) = 5 * 4 * factorial(3) = 5 * 4 * 3 * factorial(2) = 5 * 4 * 3 * 2 * factorial(1) = 5 * 4 * 3 * 2 * 1 = 120 定义计算斐波那契数列的递归函数fib(n): def fib(n): """ 计算斐波那契数列,参数n 为数列的第n 项""" if n in [0, 1]: return n else: return fib(n-1) +fib(n-2) for i in range(10): print(fib(i), end=" ") 上述代码的执行结果: 0 1 1 2 3 5 8 13 21 34 5.5 lambda 函数 可以使用lambda关键字创建小型匿名函数,如: lambda x, y: x +y 该函数返回两个参数之和,可以像普通函数那样使用lambda函数。注意,lambda函数 在形式上只能是一个表达式。 >>>add_one =lambda x: x +1 >>>add_one(1) 2 用普通函数实现上述匿名函数的功能: def add_one(x): return x +1