函数
当编写的代码出现有规律的重复时,这个时候就要考虑定义函数,将这些代码提取定义成一个函数,方便调用。
Python 提供许多内置函数,可以根据需要调用相应的函数实现想要的功能。同样 Python 也能够灵活地自定义函数。
调用函数
介绍如何定义函数前,先讲下如何调用函数。
Python 提供许多内置函数,这些函数都是可以直接调用的。包括前面的篇幅,其实也有调用函数实现一些功能。这里大致讲下函数的调用。
举个例子,求绝对值的函数 abs()
,这个函数只接受一个参数。函数的使用可以查看官方文档:
http://docs.python.org/3/library/functions.html#abs
也可以在交互式命令行下通过 help(abs)
来查看 abs
函数的帮助信息。
现在尝试调用 abs()
函数:
>>> abs(-1)
1
>>> abs(1)
1
因为 abs
只接受一个参数,若是传入的参数个数不为 1,会报 TypeError
的错误,信息中也会给出错误的提示:
>>> abs(-1, 1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: abs() takes exactly one argument (2 given)
这里表示,abs()
函数只接受一个参数,但是却提供了两个参数。
abs()
的参数接收的是数值,若是传入的类型错误,同样也会报错:
>>> abs('1')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'str'
这里给出的错误提示表示 str
不是可接受的类型。
这就是函数调用,同时也需要注意的一些地方。要了解函数调用需要的几个参数,以及参数的类型。
定义函数
现在讲一下函数的定义,函数定义要使用 def
语句,后面紧跟的是函数名,括号,包含在括号内的参数以及冒号,然后在缩进块编写函数体,如果有返回值的话,则使用 return
语句返回。
还是以求绝对值为例,这里自定义函数实现:
def my_abs(num):
if num >= 0:
return num
else:
return -num
当执行 return
时,函数即为执行完毕,直接返回结果。例子中,简单使用了条件判断,函数内部可以通过使用条件判断以及循环,实现非常复杂的逻辑。
有些函数并没有 return
语句。但是函数执行完毕后同样会返回结果,只是结果为 None。所以 return None
,也可以写为 return
。
现在我们尝试调用自己编写的函数 my_abs
:
>>> my_abs(1)
1
>>> my_abs(-1)
1
>>> my_abs('a')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in my_abs
TypeError: '>=' not supported between instances of 'str' and 'int'
这里会看到当参数接受字符串的时候,出现的错误与 Python 提供的 abs
出错信息不一样。
这里我们添加对参数的检查,只允许整数和浮点数类型的参数。可以用 isinstance()
函数实现数据类型检查。
>>> def my_abs(num):
... if not isinstance(num, (int, float)):
... raise TypeError("bad operand type")
... if num >= 0:
... return num
... else:
... return -num
...
再次尝试调用该函数:
>>> my_abs('1')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in my_abs
TypeError: bad operand type
这样,看起来就更像 abs()
函数。
pass 语句
有时候,可能并没有想好要编写怎样的函数,但是想先定义,在后续补充代码。这个时候,pass 语句就派上用场。
pass 语句能够做占位符使用,可以在函数体中先将这个语句放进来,让编写的其他代码运行起来。等到后续要补充的时候再去掉 pass 语句进行修改。例如:
def no_idea():
pass
这里如果函数体内没有这个 pass 语句,运行就会提示有语法错误。
返回多个值
上面提及的自定义函数,包括带 pass 语句的空函数(返回的是 None),返回的是一个值。
为了可以返回多个值,可以直接返回一个元组,例如:
>>> def my_func():
... return 1, 2, 3
...
>>> a, b, c = my_func()
>>> a
1
>>> b
2
>>> c
3
虽然上面的函数看上去是返回了多个值,但实际上是先创建一个元组然后返回的。
其实我们使用的是逗号来生成一个元组,而不是用括号。例如:
>>> a = (1,2) # 带括号
>>> a
(1, 2)
>>> b = 1,2 # 不带括号
>>> b
(1, 2)
所以,这前面定义返回多值的函数,其实返回的是元组。若是赋值给单个变量,那么这个变量其实就是返回的那个元组本身:
>>> d = my_func()
>>> d
(1, 2, 3)
函数参数
位置参数
先定义一个函数:
>>> def func(a, b):
... print('a = {},b = {}'.format(a, b))
...
>>> func(1,2)
a = 1,b = 2
在这里,就是 a
和 b
,两个参数都是位置参数,调用的时候,依次将两个值按照顺序依次赋值给参数 a
和 b
。
默认参数
延用上面的例子,在这里,我们调用的时候只赋值给 a:
>>> func(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func() missing 1 required positional argument: 'b'
这里错误提示:调用参数缺少一个位置参数 b
。
这个时候,就可以考虑使用默认参数:
>>> def func(a, b=2):
... print('a = {}, b = {}'.format(a, b))
...
>>> func(1)
a = 1, b = 2
可以看到当调用 func(1)
时,就相当于调用了 func(1, 2)
。
这里有些地方需要注意:
- 必选的参数一定要放在默认参数前面,否则会报错。
- 默认参数必须指向不变对象
第一点比较好理解,如果默认参数在必选参数前面,由于传入的值会按照位置给参数赋值。例如下面的错误示例(下面这种写法仅做错误示范,不建议如此编写。当然这样编写也会报错):
def func(a=1, b):
return a, b
假设我们调用的时候,以这样的逻辑想,现在 a
有默认值,只给 b
赋值就可以了。但是赋值会按照位置赋值。比如调用 func(2)
,其实这里是将 2
赋值给了 a
,替代了原来的 1
,最终 b
还是缺少赋值。
第二点,必须指向不变对象,千万不能像下面这样编写代码:
def func(a, b=[]):
....
这样写的话,后面会遇到很多的麻烦。b
作为可修改的对象,这些修改会影响到下次调用这个函数的默认值。比如:
>>> def func(a, b=[]):
... print(b)
... return b
...
>>> x = func(1)
[]
>>> x.append(2)
>>> x.append('wow!')
>>> x
[2, 'wow!']
>>> func(1)
[2, 'wow!']
可以看到当再次调用 func(1)
的时候,结果已经变了,这个并不是原来的初衷。
如果真的需要使用列表,最好先使用 None
,然后在函数里面用相应的逻辑检查。
当然测试 None 的时候要需要注意,使用 is
操作符是非常重要的。
def func(a, b=None):
if not b: # 不建议这样写,建议用 b is None:代替
b = []
上面不建议使用 if not b:
,如果这样的话,None 会被当成是 False,而长度为 0 的字符串,列表,元组,字典等都可能被当成 False。这一点也要注意。
可变参数
上面提及的函数都是固定数量的位置参数,为了实现一个能接受任意数量的位置参数的函数,可以使用一个 *
参数。例如:
>>> def func(*nums):
... sum = 0
... for num in nums:
... sum += num
... return sum
...
>>> func(1,2)
3
>>> func(1,2,3)
6
这样就实现了可以传入任意数量的参数的位置参数。
关键字参数
若是要实现接受任意数量的关键字参数,可以使用 **
开头的参数。比如:
>>> def info(name, age, **kw):
... print('name:', name, 'age:', age, 'other:', kw)
...
>>> info('大梦三千秋', 0)
name: 大梦三千秋 age: 0 other: {}
>>> info('大梦三千秋', 0, city='Guangzhou')
name: 大梦三千秋 age: 0 other: {'city': 'Guangzhou'}
>>> info('大梦三千秋', 0, city='Guangzhou', gender='male')
name: 大梦三千秋 age: 0 other: {'city': 'Guangzhou', 'gender': 'male'}
这样就实现了传入任意个数的关键字参数。这里,关键字参数允许传入 0 个或任意个参数名的参数,函数内部会自动组成一个 dict,如上示例。
命名关键字参数
命名关键字参数,其实是将关键字参数放到某个 *
参数后面或者单个 *
后面,例如:
>>> def info(name, age, *, city):
... print(name, age, city)
...
>>> info('大梦三千秋', 0, city='Guangzhou')
大梦三千秋 0 Guangzhou
这里注意调用方式,一定要传入参数名,若是没有传入参数名,则会报错:
>>> info('大梦三千秋', 0, 'Guangzhou')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: info() takes 2 positional arguments but 3 were given
上面的提示中说只需要 2 个位置参数,但是传入的参数有 3 个。这时因为缺少参数名 city
,解释器将 3 个参数都视为位置参数,但 info
函数只接受 2 个位置参数。
参数顺序
这里提及到的参数有位置(必选)参数、默认参数、可变参数、关键字参数和命名关键字参数。但是需要注意参数的定义顺序:
必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
虽然上面的参数都可以组合在一起,但是不建议这样做,这样会导致可理解性变差。