人工智能开发语言:Python
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.6 函数

在Python语言中,函数是一组相关联的、能够完成特定任务的语句模块,分内置函数和用户自定义函数两类。内置函数是系统自带的函数,开发者只要按照接口调用即可;自定义函数是第三方或开发者自行开发的函数。本节主要介绍自定义函数。

3.6.1 函数定义

Python的函数由函数头和函数体组成,其格式如下:

Python函数定义必须以def关键词开始,def标志着函数头部的开始,函数名称(function_name)是函数头的一部分,它的命名要符合前面讲述的标识符命名规则;函数名称后跟随着一对小括号,括号里面是函数的参数(parameters),参数是可选的,括号后面紧跟着一个冒号(:),标志着函数头的结束和函数体的开始。

文档描述(docstring)是函数体中可选的部分,如果出现,必须出现在函数体的第一行。文档描述以连续的三个单(双)引号开始,也以连续的三个单(双)引号结束,这样可以在多行显示,呈现更多的内容。

statement(s)是函数体语句块,格式必须符合代码缩进的层级要求。

另外还有函数返回语句(return),它是可选的,其作用是返回一个数据给调用者。如果return后没有参数或一个函数体根本就没有return语句,则相当于返回None。

下面是一个简单的自定义函数:

函数定义时的参数称为形式参数(formal parameters),简称形参,而被调用时传递给函数的实际数据值称为实际参数(actual arguments),简称为实参。很显然,实参是变化的,形参是不变的。假如函数有多个形参,则用逗号隔开;形参是不需要说明参数类型的;形参表可以为空,表示此函数是个无参函数。

3.6.2 函数调用

一般把调用其他函数的函数称为调用函数(或主调函数),被调用的函数称为被调函数。一个函数既可以是主调函数,也可以是被调函数。下面通过例子展示函数调用的方式:

输出结果如下:

这里需要关注的是第25行,在函数定义的时候,紧随函数头后面的注释部分为docstring。docstring是通过函数内置的__doc__属性来访问的,访问格式如下:

注意函数名称function_name后没有小括号()。docstring是对函数的一个简要说明,虽然是可选的,但是建议开发者尽量加以利用。

3.6.3 参数传递

3.6.3.1 参数传递方式

Python语言的数据类型有不可变类型和可变类型两种,因此参数也分可变类型和不可变类型。像数值类型、字节串类型、元组类型等都属于不可变类型,不可变类型变量的特点是被重新赋值后会在内存中生成一个新的对象,原有变量不变。而像列表类型、字典类型等都属于可变类型,即变量在被重新赋值后,本身指向的内存地址并没有变动,只是其内部数据被修改了。

Python对不可变类型参数的传递采用“值传递(pass by value)”方式。当函数被调用时,系统会为形式参数分配独立的内存空间,并用实际参数值初始化对应的形式参数,这样就把实际参数的值传递给了形式参数。在值传递方式中,实际参数和形式参数各自占有自己的内存空间,参数只能由实际参数向形式参数传递,不论被调函数对形式参数内容作何修改,对相应的实际参数都没有影响。

Python对可变类型参数的传递采用“地址传递(pass by address)”方式。当函数被调用时,系统不会为形参分配新的空间,只是把实参的内存地址传给被调函数。这样如果在函数内对形参内容做了修改,会影响到实参。

首先看一个不可变类型参数传递的实例:

输出结果为:

可以看到,虽然在函数changeAge()中修改了参数age的值,但是对实际的参数age并没有任何影响。

我们对上面的程序稍作修改,把age作为列表变量(可变类型),来看一下传递可变类型参数会有什么不同:

输出结果为:

从输出结果可知,调用后原来的age内容发生了变化,也就是说,函数changeAge()中对形参age的修改会影响到实参age。

3.6.3.2 参数调用方式

Python函数的参数根据调用方式不同分为四种:

① 默认参数;

② 位置参数;

③ 关键字参数;

④ 变长参数

其中默认参数是指在定义函数时直接给这个参数赋一个值,这样在函数调用时,如果没有实参传入,就使用这个值作为默认值。

注意:除了可变长参数,默认参数之后不能出现非默认参数。

根据以上参数分类,函数可按下述方式定义:

pos_args:位置参数(positional arguments)

kw_args:关键字参数(keyword arguments)

tuple_grp_nonkw_args:以“*”开始,元组非关键字变长参数。

dict_grp_kw_args:以“**”开始,字典关键字变长参数。

原则上,以上四种参数都可以省略,如果出现,建议按照上述顺序编写。

1)位置参数

调用函数时,根据函数的参数位置(顺序)来传递参数,一般把位置参数放在最前面。

2)关键字参数

调用函数时通过“键=值”形式指定实参,其中“键”是形参名称,“值”是实参值。使用关键字参数允许函数调用时参数的顺序与声明时不一致,Python 解释器能够用参数名称匹配参数值。这种方式可以让函数更加清晰,容易使用,同时也消除了参数的顺序要求(位置参数)。

注意:函数调用时,关键字参数一定要出现在位置参数之后,否则会导致解释出错。

举例如下:

输出结果为:

3)元组非关键字变长参数

开发者可能需要设计一个形参个数不定的函数,以增加函数的灵活性,这时变长参数就派上用途了。与位置参数、关键字参数不同,变长参数在声明时甚至可以不用命名(没有名称),即使有名称,也只是一个名义上的占位符。在Python中,变长参数包括元组非关键字变长参数和字典关键字变长参数两种。

元组非关键字变长参数以“*”开头,在一个函数定义中,最多只能有一个这样的参数。

在调用函数时,所有未命名的实参变量会以元组(tuple)的形式传入。举例如下:

输出结果为:

4)字典关键字变长参数:

字典关键字变长参数是以“**”开始的参数,在一个函数定义中,最多只能有一个这样的参数。如果存在,只能放在所有其他参数的后面,即只能放在最后。

在调用函数时,所有没有对应上的关键字参数(实参)都会以字典(dict)的形式传入。

举例如下:

输出结果为:

其输出结果是以字典(dict)形式输出,而不是元组(tuple)形式了。

3.6.3.3 参数小结

位置参数使用时,所传入参数的位置必须与定义函数时参数的位置相同。

关键字参数使用时,对位置顺序没有要求。

元组变长参数可接受任意数量的位置参数,最多只能有一个,可出现在任何位置。不过一旦这种参数出现,后面的参数只能作为关键字参数使用。

字典变长参数可接受任意数量的关键字参数,它必须是最后一个参数,而且最多只能有一个。

默认参数的赋值只会在函数定义的时候绑定一次,不会再被修改。

同一个参数是不允许多次赋值的。

3.6.4 变量的作用域和生命周期

3.6.4.1 什么是作用域和生命周期

所谓一个变量的作用域,是指可以访问这个变量的程序代码段。对于一个函数的参数以及定义在函数内部的变量,由于它们只能在函数内部使用,对函数外部是不可见的,所以其作用域是函数的代码范围内,故称为局部变量;相对应地,独立于任何函数的变量称为全局变量。

所谓一个变量的生命周期,是指变量在内存中存续的时间,是一个时间范围。对于在函数内部定义的变量,其生命周期与函数执行周期一致,一旦函数退出(返回主调函数),所有内部定义的变量也就失效(在内存中销毁)。所以,函数不会记得以前被调用时内部变量的值。

请看下面的例子:

输出结果为:

可以看出,x变量初始为20(第6行),即使通过myfun()函数改变了其内部的x变量值为10(第3行),它也改变不了函数外部定义的x的值(第8行)。这是因为无论它们的名称是否相同,它们都有不同的作用域。并且myfun()函数内部的x的生命周期在函数返回时即告结束。

在Python中,只有模块(module)、类(class)以及函数[def、lambda(匿名函数)]才会引入新的作用域,其他的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说在这些语句内定义的变量,外部也可以访问。

3.6.4.2 global 和 nonlocal关键字

局部变量可以直接访问全局变量,但是只能读取,不能修改。如果要实现对全局变量的修改,要用global和nonlocal关键字,下面以实际例子说明:

输出结果为:

global的作用是告诉函数updateRevenue():变量totalRevenue已经在外部进行了定义,本函数使用的是外部的totalRevenue,不要生成一个新变量了。

nonlocal与global的区别在于:nonlocal一定用在嵌套作用域内,声明为nonlocal的变量是在嵌套作用域外层定义的变量,而不是全局变量。请看下面的例子:

输出结果为:

如果不加第10行,则输出结果的第三行就变为“现在的总统是:Barack Hussein Obama”。

3.6.5 匿名函数

所谓匿名函数,是指不以def语句定义的没有名称的函数,它在使用时临时声明、立刻执行,且只能调用一次,不能被反复调用,其优点是执行效率高。

Python使用lambda来创建匿名函数,一般函数体只包含一个表达式语句,其语法格式为:

匿名函数主体是一个表达式,而不是一个代码块,所以能实现的业务逻辑非常有限。

匿名函数拥有自己的命名空间,可以访问嵌套层的变量(无需nonlocal关键字),但是不能访问全局变量。

举例如下:

3.6.6 有益的编码风格

一个格式良好的源程序代码可以大大提高代码的可读性,使人能够轻松理解开发者的思路。

现在越来越多的开发者开始遵循PEP 8(PEP:Python Enhancement Proposals)中倡导的编程风格。按照PEP 8中的建议编写代码,能够提高程序的可读性,减轻视觉疲劳,所以建议大家好好遵循。下面节选了部分重要内容。

每层级使用4个空格缩进,不要使用tab键。4个空格缩进处于小缩进(可以嵌套更深)和大缩进(更易读)之间,是一个很好的折中;tab键容易引起混乱,不建议使用,更不建议Tab键和空格混合使用。

每行代码长度建议不超过79个字符;如果一行代码太长,分成多行。

使用空行来分隔函数、类以及函数体内的语句块。

注释尽可能写在一行。

不要忘记docstring。

在运算符前后以及逗号之后使用空格,但在包围结构(如小括号、中括号、大括号等)的包围符号内侧不使用空格,例如:a = f(1, 2) + g(3, 4)。

对类及函数的命名建议始终保持统一命名方式。对类的命名建议使用大驼峰拼写方式(CamelCase),即多个单词直接拼在一起,每个单词的首字母大写,其余字母小写;对函数或类函数的命名采用小写字母与下划线拼接的形式,注意一定要把self作为类函数的第一个参数的名称。

如果系统有可能在国际上使用,编码尽量采用utf-8或ASCII,这会在任何情况下都运行得非常好。

尽量不要在标识符命名时使用非ASCII码字符。