我们在编写代码时,如果要调用别人写的函数,一般我们不需要关心这些函数是如何实现的,只需要关注如何使用这个函数。
那么怎么才能知道如何使用呢?也就是我们必须要知道这个函数需要哪些参数,参数都代表什么,哪些参数是必须写的,哪些是不用管的,最后关注其返回的结果如何,这样就能正确的使用该函数。
比如我们前面讲过的range函数:
range(10)
range(1,10)
range(1, 10, 2)
我们看在传参数的时候,数量不同会得到不同的结果。其实这就是 Python 在函数定义的时候提供的各种参数形式。这种形式非常灵活,且功能强大。
在 Python 中,有多种参数形式,分别是位置参数(必选参数),默认参数,可变参数和关键字参数(不同的教程叫法不太一样)。
位置参数
也叫做必选参数,也就是说在函数定义的时候声明的参数,在调用的时候就必须全部给予实参,并且必须按照参数定义的顺序赋值。
比如下例,我们都知道求圆的面积和周长必须要用到 半径 r 和圆周率 π ,现在实现一个函数,提供半径 r 和 圆周率 π,返回面积(πr
2)和周长(2πr
)
def circle(r, pi):
area = pi * r ** 2
perimeter = 2 * pi * r
return area, perimeter
当我们调用 circle() 函数的时候,必须同时提供 r 和 π 两个参数,否则就会报错,当然多了也不行。
>>> circle(4)
TypeError: circle() missing 1 required positional argument: 'pi'
也就是说参数不能多也不能少,必须按照函数定义的参数个数依次赋值,才能正常调用该函数。
>>> circle(4, 3.14)
(50.24, 25.12)
如果顺序反了就得不到你想到的结果了!
>>> circle(3.14, 4)
(39.4384, 25.12)
如果你非要反着用(不按顺序),请使用 参数名=参数值
的形式。
>>> circle(pi=3.14, r=4)
(50.24, 25.12)
对于没有任何标识的参数,我们就称之为 位置参数
默认参数
刚才的 circle() 函数,我们在用的时候必须同时传入 r 和 π 两个参数。但是我们发现 π 也就是圆周率,一般都是固定的用两位小数 3.14。
那么我们就没必要每次调用这个函数都要输这个 π,毕竟圆周率不是谁想改就能改的。但是偶尔我们需要使用小数点后面 4 位或者 6 位以求得更精确的面积和周长。那么如何实现呢?
对于函数的参数,绝大部分情况使用固定的值,在少数情况需要改该参数的值,那么我们可以设置为默认参数
简而言之,就是给参数赋一个默认值。这样当我们没有特殊需要的时候,该参数直接用默认值,而当你有特殊需要的时候,又可以通过提供新的值来实现你的需求。而且当你没有特殊需要的时候,还可以少传一个参数,使用起来更方便。
我们来改造一下上面的 circle() 函数:
def circle(r, pi=3.14): # 给参数赋一个默认值即可
area = pi * r ** 2
perimeter = 2 * pi * r
return area, perimeter
那么我们调用改造后的函数:
# 没有特殊需要的时候
>>> circle(4)
(50.24, 25.12)
# 精度要求更高的时候
>>> circle(4, 3.14159265358)
(50.26548245728, 25.13274122864)
是不是很方便!
灵活使用默认参数,会大大增加函数调用者的体验 :】
可变参数
参数收集
在某些情况下,我们需要输入不确定数量的参数(其实一般初级编程用不上),那么根据上面我们说的参数必须按照顺序和个数传入,那么这种情况要怎么处理呢?
在 Python 中提供了参数收集的机制,在函数定义时,指定可变参数(参数收集),在函数调用的时候就可以提供任意数量的参数。
比如,我们要写一个函数,用于保存任意多的用户姓名:
def collect_names(*name):
print('收集到的姓名:', name)
没错,就是在参数前面加个星号*
,在函数内部使用这个参数即可。我们看看结果是什么?
>>> collect_names('nemo', 'nano', 'tato', 'jim', 'kid') # 你想传多少都可以!
收集到的姓名: ('nemo', 'nano', 'tato', 'jim', 'kid')
从结果我们可以看出,通过参数收集到的任意参数,传入函数内部就变成了一个元组,元组嘛,当然可以存任意多的数据了。
当然收集到元组如何用,就看你的需求了!
除了上面的直接收集参数的形式,还有一种收集 参数名=参数值
的传参形式,而通过这种方式收集到的参数在内部构成一个字典。
如下例,写一个函数,收集一个人的信息。
def person(**attr):
print('用户的信息:', attr)
调用的结果:
>>> person(name='nemo', age=18, sex='男', height=180)
用户的信息: {'name': 'nemo', 'age': 18, 'sex': '男', 'height': 180}
收集到内部形成一个字典,然后你要操作就随你咯。比如:
def person(**attr):
for k in attr.keys():
print('用户的%s信息:%s' % (k, attr.get(k)))
>>> person(name='nemo', age=18, sex='男', height=180)
用户的name信息:nemo
用户的age信息:18
用户的sex信息:男
用户的height信息:180
也就是说你收集到了用户的信息后,根据你的需要从字典中提取数据并处理。
参数收集的逆向过程
其实上面讲的参数收集,在你日常编码过程中使用相对较少,反倒其逆向过程用得更多。
那什么是参数收集的逆向过程呢?
参数收集是在函数调用时,传入任意多的参数,在函数内部形式一个元组或字典。而逆向过程,是先有一个元组(或任意序列)或字典,在函数调用时传入该序列或字典。
那么这有何用处呢?可以简化函数调用,并增加函数的可读性。
首先我们看第一种参数收集的逆向过程(元组):
比如用上面的 circle 函数的例子,我们在一个数据文件中存储了多个需要求面积和周长的数据。然后提取出来后放在一个二维的元组中,我们需要调用该函数逐一运算。代码如下:
data = ((4, 3.14), (6, 3.1415), (11, 3.14))
for i in data:
circle(i[0], i[1])
好在我们这里只有两个参数,如果参数更多一些,比如5个6个,那么你要怎么写 fun(i[0], i[1], i[2]....)
编不下去了。。。急需一个更简单方便的方式,正好。。。
data = ((4, 3.14), (6, 3.1415), (11, 3.14))
for i in data:
circle(*i) # 在变量前加一个星号就搞定啦
我们来看一下,当 i 的值为(4, 3.14),那么这是一个元组,用星号能起什么作用呢?星号相当于拆解,把元组拆解成单个的元素,再分别按顺序赋值给 circle() 函数的各个参数。circle(*i) 等同于 circle(4, 3.14)。
同样字典形式的参数收集也可以逆向来用。这个就更有用啦。比如我们连接 mysql 数据的时候(这里我只举函数调用的例子,数据库的使用在 Python 基础学完后再深究),使用到的函数有很多的参数:
db = connect(host=127.0.0.1, port=3306, user='root', password='123456', db='happy') # 需要的话还有参数需要赋值
那么对于这种情况,我们可以写一个字典作为数据库连接的配置:
db_config = {
'host': '127.0.0.1',
'port': 3306,
'user': 'root',
'password': '123456',
'db': 'happy'
}
db = connect(**db_config)
这里两个星号的作用,把字典拆解为单个的 参数名=参数值
的形式分别赋值给函数的各个参数。
虽然代码看起来更多一些,但是可读性更强,维护起来也更容易些,可以把这个字典放在配置文件中维护,其他需要用到的地方直接引入这个字典即可。
关键字参数
最后还剩一个关键字参数,关键字参数在函数定义时是一种参数的限制条件。
定义为关键字参数的参数,在调用时,必须以
参数名=参数值
的形式赋值,不能直接写参数值。
def person(name, age, *, sex, height):
print('用户的姓名为:', name)
print('用户的年龄为:', age)
print('用户的性别为:', sex)
print('用户的身高为:', height)
上面例子中函数参数中的星号看到了么?星号后面的参数 sex
和 height
就是关键字参数了。
注意星号不用赋值也不算一个参数,星号属于一个标识用来区分普通参数和关键字参数。
在函数调用时,sex 和 height 两个关键字参数,必须以 sex='男' 和 height=180 这种形式赋值,否则就会报错。
>>> person('nemo', 18, '男', 180) # 提示只需要两个位置参数,但是给了4个
TypeError: person() takes 2 positional arguments but 4 were given
>>> person('nemo', 18) # 提示缺少两个关键字参数
TypeError: person() missing 2 required keyword-only arguments: 'sex' and 'height'
>>> person('nemo', 18, sex='男', height=180) # 只有这样才能正确调用
用户的姓名为: nemo
用户的年龄为: 18
用户的性别为: 男
用户的身高为: 180