第5章 函 数 函数(Function)是执行计算的命名语句序列。将一段代码封装为函数并在需要的位 置进行调用,不仅可以实现代码的重复利用,更重要的是可以保证代码的完全一致。 5.1 函数的定义和使用 1.函数的定义 函数定义(definition)的语法形式: def 函数名([形参列表]): #[]表示可选,即一个函数可以没有形参 ''' docstring ''' # 函 数的功能说明,也就是文档字符串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)。 60 Python 程序设计教程 图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)。 下面定义一个fib()函数,该函数能够输出任意指定范围内的斐波那契(Fibonacci) 数列: >>> def fib(n): '''Print a Fibonacci series up to n.''' 第5 章 函数 61 a, b = 0, 1 while a >> 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 while a >> f100 = fib2(100) #调用fib2()函数 >>> f100 #输出结果 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] 62 Python 程序设计教程 5.2 函数的参数类型 在函数的定义中,形参列表的一般形式为 <非可选参数>, …, <可选参数>=<默认值>, … 1.非可选参数 没有给出默认值(defaultvalue)的参数都是非可选参数。在定义函数时,非可选参数 必须出现在可选参数的前面。如在下面定义的person()函数中,参数name就是一个非 可选参数。在调用函数时,必须给非可选参数赋值。 2.可选参数 带有默认值的参数都是可选参数。在定义函数时已经给可选参数指定了默认值。因 此在调用一个函数时,如果没有给可选参数提供值,那么该函数就会使用其默认值。如在 下面定义的person()函数中,gender和age都是可选参数,因此在调用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()函数的定义中出现的三个参数都是关键字 参数。使用关键字参数可以很方便地给形参赋值,而无须记住各个参数在函数定义中出 现的先后顺序。 4.可变长度参数 在Python语言中,可变长度参数有两种类型:一种是用单星操作符“*”定义的;另 一种是用双星操作符“**”定义的。拥有第一种可变长度参数的函数能够接收任意数量的 实参,这些实参被封装成一个元组: def multiply(*args): #arg 代表argument(参数) 第5 章 函数 63 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的值为元组('nice','to','meet', 'you! ')。 程序的输出结果如下: Jim, nice to meet you! 再看一个例子: >>> def concat(*args, sep="/"): return sep.join(args) 上述代码中的参数sep为可选参数,也是关键字参数。 >>> concat("earth", "mars", "venus") 'earth/mars/venus' 在上述代码中,可选参数sep取默认值“/”,可变长度参数args的值为元组('earth', 'mars','venus')。 >>> concat("earth", "mars", "venus", sep=".") 'earth.mars.venus' 在上一行的函数调用中,可变长度参数args的值为元组('earth','mars','venus'),并 且为可选参数sep指定了新值“.”。 如果在函数形参列表的最后有一个用双星操作符“**”定义的可变长度参数,那么该 函数能够接收任意数量的实参,而且这些实参被封装成一个字典: def print_values(**kwargs): for key, value in kwargs.items(): print("The value of {} is {}".format(key, value)) 64 Python 程序设计教程 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): # **kwargs① 必须出现在*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('Tim') 执行上述代码后,形参name得到的值为Tim,gender和age取默认值,也就是 gender的值为male,age的值为20。再看下面几种函数调用: person('Tim', 'female') #n ame 为Tim,gender 为female,age 取默认值20 person('Smith', 'male', 30) #n ame 为Smith,gender 为male,age 为30 再强调一次:必须给非可选参数指定参数值。综上可知:按位置给形参赋值时,实 参的出现顺序非常重要。除了按位置给形参赋值外,还可以通过指定参数名的方式给形 参赋值,这种方式叫作按关键字赋值。如下面的函数调用: person(name='Tim', gender='female') person(gender='female', name='Tim') person(age=22, gender='female', name='Tim') ① kwargs代表keywordarguments,关键字参数。 第5 章 函数 65 很显然,按关键字赋值的优点是不用考虑实参出现的先后顺序。 也可以将这两种赋值方式混合使用: 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=', ') 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) 66 Python 程序设计教程 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, 4, 5] 类似地,字典可以使用双星操作符“**”进行解包以便传递关键字参数: >>> 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 lambda函数 可以使用lambda关键字创建小型匿名函数,如: lambda x, y: x+y 该函数返回两个参数的和,可以像普通函数那样使用lambda函数。注意,lambda函 数在形式上只能是一个表达式。 >>> add_one = lambda x: x + 1 >>> add_one(1) 2> >> f = lambda x, y=1: x * y >>> f(2, 3) 6> >> def make_incrementor(n): 第5 章 函数 67 return lambda x: x + n #lambda 函数是该函数的返回值 >>> f = make_incrementor(42) >>> f(0) #相当于计算0+42 42 >>> f(1) #相当于计算1+42 43 上述make_incrementor()函数的返回值是一个lambda函数。 5.5 变量的作用域 变量在程序中起作用的范围称为变量的作用域。在不同作用域内出现的同名变量, 相互之间是互不影响的。根据变量在程序中所处的位置和作用范围,可将变量分为局部 变量(localvariable)和全局变量(globalvariable)。 局部变量是指在函数内部(包括函数头)定义的变量,其作用范围仅限于函数内部(包 括函数头),当函数退出时该变量将不复存在。 >>> def multiply(x, y=10): # 形参x 和y 是局部变量 z = x * y #z 是局部变量 return z >>> s = multiply(5, 2) >>> print(s) 10 >>> print(z) #变量z 未定义 Traceback (most recent call last): File "", line 1, in print(z) NameError: name 'z' is not defined >>> print(x) #变量x 未定义 Traceback (most recent call last): File "", line 1, in print(x) NameError: name 'x' is not defined 全局变量是指在所有函数的外部定义的变量,它在程序执行的整个过程都有效。全 局变量在函数内部使用时,需要在使用之前用关键字global进行声明(declaration),语法 格式如下: global <全局变量> >>> n = 2 #定义一个全局变量n 并赋初值 >>> def multiply(x, y = 10): global n #声明n 是一个全局变量 68 Python 程序设计教程 return x * y * n #使用全局变量n >>> s = multiply(5, 2) >>> print(s) #s = x * y * n = 5 * 2 * 2 20 在函数内部出现的变量,如果没有使用关键字global进行声明,那么即使它的名字 与全局变量名相同,也不是全局变量。 >>> n = 2 #定义一个全局变量n 并赋初值 >>> def multiply(x, y=10): n = x * y #此处n 不是全局变量,尽管它与全局变量名相同 return n >>> s = multiply(5, 2) >>> print(s) #s = x * y = 5 * 2 10 >>> print(n) #n 的值没有改变,仍然是2 而不是10 2 5.6 小 结 本章学习了函数的定义、函数的调用、函数的返回值、函数的参数类型、函数参数的赋 值方式、参数解包、lambda函数、变量的作用域。在函数体的第一行要注意添加文档字符 串,方便其他编程人员了解函数的功能和使用方法。 练习题5 1.Python中定义函数的关键字是。 2.在函数内部使用全局变量之前,需要使用关键字进行声明。 3.如果函数中没有return语句或者return语句不带任何返回值,那么该函数的返回 值为。 4.已知函数的定义如下,那么表达式func(1,2,3,4)的值为。 def func(*p): return sum(p) 5.已知函数的定义如下,那么表达式demo(3,5,'+')的值为。 def demo(x, y, op): return eval(str(x) + op + str(y))