4.10 函数¶
在之前章节的学习中,其实我们已经接触过了函数,如list()、print()、range()等,这些都是Python内部自带的函数。 在Python解释器中输入“dir(__builtins__)”可以查看Python所有的内置函数,其数量与你使用的Python版本有关。
通过使用这些函数你发现了什么?我们只需输入一行代码就可以完成一个复杂的功能。例如使用print函数可以将数据格式化输出、使用list函数自动生成列表等。
可以看出,使用函数可以将原本很复杂的一连串代码抽象为一行代码,这在编写大型程序时很有用,提高了程序的易读性。
4.10.1 函数的定义¶
在Python中,使用def语句可以定义一个函数,下面以一个简单的加法运算为例来介绍def语句:
1 2 3 4 5 6 | >>> def add(x,y):
... z = x + y
... return z
...
>>> print(add(1,5))
6
|
本例中的第1行-第3行定义了一个名为add的函数,它的入口参数有两个x和y,返回值(return)为二者的和。调用add函数并用print将其打印出来, 其结果与预期相符。
调用函数时一定会有返回值么?不一定。
1 2 3 4 5 6 | >>> def add(x,y):
... z = x + y
... print(z)
...
>>> add(1,5)
6
|
在本例中第6行输出的“6”是由add函数内部的print打印得到,而非return得到。
4.10.2 函数的入口参数¶
在4.10.1中add(x,y)里的x和y就是add函数的入口参数,它们的值可以被外部的程序修改。当然,并不是所有的函数都具有入口参数。
1 2 3 4 5 | >>> def Hello():
... print('Hello World!')
...
>>> Hello()
Hello World!
|
在使用带有入口参数的函数之前,我们需要先了解一个概念——实参和形参。
1. 实参和形参¶
以add函数为例,x和y属于形参,而1和5则属于实参。它们的区别是什么?形参是一个没有具体值的变量,可以理解为数学公式中的未知数(x,y),在函数未被调用时, Python不会给形参分配内存;而实参就是一个有着具体值的数据,它实际存在着。在调用函数时,实参与形参的位置和数据类型要一一对应, 不能将字符串类型的实参赋给数字类型的形参,否则Python解释器会报TypeError类型的错误。
2. 位置实参¶
前面用到的函数中的参数都是位置实参,它要求实参与形参的位置要一一对应。用add函数我们无法直观地体会到这种限制, 下面用div函数来直观地介绍什么是位置实参:
1 2 3 | >>> def div(x,y):
... z = x / y
... return z
|
定义div函数,返回值为z(x/y)。
1 2 3 4 | >>> print(div(1,4))
0.25
>>> print(div(4,1))
4.0
|
将输入的实参“1、4”的顺序调换后,其计算结果也截然不同。因此,在使用位置实参时,请注意输入的实参顺序要与形参的顺序保持一致。
3. 关键字实参¶
当函数的入口参数有很多时,使用位置实参就不那么方便,这时,就可以使用关键字实参。与位置实参不同的是, 关键字实参可以让你在调用函数时,不必关心实参与形参的对应关系,你可以直接在调用函数时将实参和形参联系在一起。
1 2 3 | >>> def div(x,y):
... z = x / y
... return z
|
依然使用之前的div函数,这次,让我们使用关键字实参来调用它。
1 2 3 4 | >>> print(div(x = 1,y = 4))
0.25
>>> print(div(y = 4,x = 1))
0.25
|
在这里,实参的顺序无关紧要,因为Python可以正确地将实参与形参对应起来。关键字实参让你可以不必关心实参的顺序,还指出了各个实参的用途。
4. 默认值¶
对于形参而言,我们也可以事先为它设置一个默认值。
1 2 | >>> def f(a, b=2, c=3):
... print(a, b , c)
|
在此,将形参b的默认值设为2,c的默认值设为3,调用该函数:
1 2 3 4 5 6 | >>> f(1)
1 2 3
>>> f(1,b = 1)
1 1 3
>>> f(1,c = 0,b = 1)
1 1 0
|
从输出结果中可以看出,在调用该函数时,若提供了实参,则使用指定的实参,若无实参传入,则使用形参的默认值。
在调用时,也可能会出现以下情况:
1 2 3 | >>> f(b = 1,0)
File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
|
该异常指的是位置实参位于关键字实参后面,这是一种语法错误,为了避免该错误,我们可以这样做:
1 2 | >>> f(b = 1,a = 0)
0 1 3
|
可以看出,位置实参只能位于关键字实参的前面。当然,以下定义也是不被允许的:
1 2 3 4 5 | >>> def f(a=1, b, c=3):
... print(a, b , c)
...
File "<stdin>", line 1
SyntaxError: non-default argument follows default argument
|
从该异常反馈中,我们可以得出无默认值的形参不能被放在有默认值的形参后面。
5. “*”和“**”¶
当def函数定义语句的参数前出现了“*”或“**”时,它们表示可以接收的参数个数可变。“*”将参数解释为元组类型,“**”将参数解释为字典类型。
1 2 3 4 5 6 7 | >>> def all_the_args(*args, **kwargs):
... print( args )
... print( kwargs )
...
>>> all_the_args(1,2,3,x=4,y=5)
(1, 2, 3)
{'x': 4, 'y': 5}
|
可以看出,“1,2,3”被传入“*args”中,被转化为一个元组,关键字参数“x=4,y=5”被传入“**kwargs”中,被转为一个字典。
在调用函数时使用“*”或“**”,它的用法与函数定义中的用法相反,它将把参数的组合解包,将组合中的各个项依次作为函数的实参传入形参中。
1 2 3 4 5 6 7 8 9 10 11 | >>> def sum(x,y,z):
... s = x + y + z
... print(s)
>>> li = [1,2,3]
>>> sum(*li)
6
>>> tup = (1,2,3)
>>> sum(*tup)
6
|
使用“*”可以把列表和元组内的项解包出来并作为sum函数的入口参数,当然,列表或元组内项的个数应与函数的入口参数个数相同。 同样,“**”也可以对字典进行同样的解包操作。
1 2 3 | >>> dic = {'x': 1,'y': 2,'z': 3}
>>> sum(**dic)
6
|
这里需要注意的一点是,字典的键要和函数中形参的名称相同,否则,Python无法确定实参和形参的对应关系。
4.10.3 作用域¶
什么是作用域呢?作用域与变量密切相关,变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。 对于函数内的变量而言,它们的作用域只局限于该函数,因此,这种变量被称为局部变量,他不能被函数外部的程序调用, 而在函数外部程序中定义的变量被称为全局变量,全局变量可以在整个py文件中被调用。
下面是一个简单的例子:
1 2 3 4 | >>> s = 0
>>> def sum(x,y,z):
... s = x + y + z
... return s
|
第1行定义的变量s是全局变量,而第3行定义的变量s是局部变量,它只在函数sum()中存在。在实际编程中,请避免这种命名方式, 这会影响程序的可读性,带来不必要的麻烦。
观察其调用结果:
1 2 3 4 | >>> sum(1,2,3)
6
>>> s
0
|
显然,全局变量s的值并没有被修改。该如何将函数内的局部变量定义为全局变量呢?可以使用global语句声明全局变量。
1 2 3 4 5 6 7 8 9 | >>> def sum(x,y,z):
... global s
... s = x + y + z
... return s
...
>>> sum(1,2,3)
6
>>> s
6
|
使用global语句声明变量s为全局变量,变量s的值可以被外部程序调用。
4.10.4 小结¶
本节主要介绍了函数的定义、入口参数、变量的作用域。函数以抽象的方式,用一行代码表示一个代码块的功能,化繁为简,使得编程更加自然。
在下一节中将讲到类,它会告诉你为什么Python是一门面向对象编程的语言。