定义函数的时候,我们把参数的名字和位置确定下来,函数的接口定义就完成了,对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了,函数内部的复杂逻辑被封装起来,调用者无需了解。
Python 的函数定义非常简单,但灵活度却非常大,除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码,本节我们就来学习函数的各种参数定义。
位置参数
- 位置参数也叫必传参数,也就是函数的实参的个数和位置必须和形参的个数和位置一致。说明:我们把函数定义时的参数叫做形参,调用函数时给的参数叫做实参。
def myfunc(name, age):
print(name)
print(age)
myfunc("ruhua", 18) # 实参 "ruhua" 对应形参 name,实参 18 对应形参 age
myfunc("ruhua", "zhaoritian", 18) # 错误,实参和形参的参数个数不匹配
- Python 是动态语言,在定义函数的时候没法给形参定义类型,所以我们需要对调用的函数参数类型说明要清楚,不然会得到错误的逻辑结果。
def myfunc(name, age):
print(name)
print(age)
myfunc(18, "ruhua") # 实参 18 对应形参形参 name,实参 "ruhua" 对应形参 age
默认参数
- 在函数定义时,如果给某个参数提供一个默认值,这个参数就变成了默认参数,不再是位置参数了。在调用函数的时候,我们可以给默认参数传递一个自定义的值,也可以使用默认值。
def girlsschool(name, sex="f"):
print(name)
print(sex)
girlsschool("wanghuahua")
girlsschool("ruhua", "m")
- 有多个默认参数时,调用的时候,既可以按顺序提供默认参数,也可以不按顺序提供部分默认参数,当不按顺序提供部分默认参数时,需要把参数名写上。
def girlsschool(name, age=18, sex="f"):
print(name)
print(age)
print(sex)
girlsschool("ruhua", sex="m", age=17)
- 使用默认参数时要确保位置参数在前,默认参数在后,否则 Python 的解释器会报错。其实大家可以思考一下原因(如果默认参数在位置参数前面,由于位置参数是按次序接收实参的,所有默认参数在实参中必须给出,这样的话默认参数就没法默认了)。
def girlsschool(age=18, name): # 函数参数定义错误
pass
def _girlsschool(name, age=18): # ok
pass
默认参数详解
- 从一个例子说起,对于以下代码,我们预期的结果是:调用
func()
,L 的值是 [2],再次调用func()
,L 的值还是 [2],而结果是:[2],[2, 2]。
def func(L=[]):
L.append(2)
print(L)
func()
func()
- 我们都知道函数的默认参数也是局部变量,但 Python 的函数在第一次使用默认参数的时候,该默认参数指向的内存就确定了,这与其他语言不同(在其他语言中,默认参数每次调用都要重新分配内存),我们打印出默认参数 L 的 id 发现两次调用都是一样。
def func(L=[]):
print(id(L))
L.append(2)
print(L)
func()
func()
- 我们通过在函数体内定义一个局部变量,来看下 Python 解释器的行为,我们发现两次调用 L 指向的内存确实不是一个(第一次结果为 [2], 第二次结果为 [2]),但是打印出的内存地址确是一样的。
def func():
L = []
print(id(L))
L.append(2)
print(L)
func()
func()
- 导致上面结果的原因是内存分配机制是一样的,我们可以通过下面的代码来证明,通过下面的代码,我们发现第一次调用 func 函数中 L 的 id 和 mylist 的 id 是一样的,和第二次调用 func 函数中 L 的 id 并不一样。
def func():
L = []
print(id(L))
L.append(2)
print(L)
func()
mylist = []
print(id(mylist))
func()
- 对于默认参数是变量的情况出现的坑,我们可以通过修改默认参数的值为常量来避免。
def func(L=None):
if not isinstance(L, list):
L = []
L.append(2)
print(L)
func()
func()
- 为什么要设计 str,None 这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改, 这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁, 同时读一点问题都没有,所以我们在编写程序时,如果默认参数可以设计一个不变对象,那就尽量设计成不变对象。
可变参数
- 在 Python 函数中,实参个数可以是不固定的,就是我们调用函数时传入的实参个数是可变的(可以是任意个数,还可以是 0 个),可变参数的形参要写成 *变量名 的形式。
def func(*arg):
print(arg)
func()
func(1)
func(1, 2, 3)
- 对于定义可变参数的函数,我们调用函数时,无论传入的实参参数类型是什么,个数是多少个(包括 0 个), Python 解释器都会把对应的实参组成一个 tuple 传给形参。
def func(*arg):
print(arg)
func() # 0 个实参,arg 被组成 ()
func(1, 2) # 2 个实参,arg 被组成 (1, 2)
func("hello") # 1 个实参,arg 被组成 ("hello",)
func(1, [1, 2]) # 2 个实参,arg 被组成 (1, [1, 2])
func((1, 2)) # 1 个实参,arg 被组成 ((1, 2),)
func({1: 2}) # 1 个实参,arg 被组成 ({1: 2},)
- 如果传入的实参类型是集合(str, list, tuple, dict, set), Python 允许在实参前面加一个 * 号,此时 Python 解释器则会把实参中的所有成员(字典是键)展开。
def func(*arg):
print(arg)
# 解释器会把 *"hello" 展开为 'h','e','l','l','o',实参变为('h','e','l','l','o')
func(*"hello")
# 解释器会把 *[1, 2] 展开为 1, 2,实参变为(1, 1, 2)
func(1, *[1, 2])
# 解释器会把 *(1, 2) 展开为 1, 2,实参变为(1, 2)
func(*(1, 2))
# 解释器会把 *{1: "1", 2: "2"} 展开为 1, 2 ,实参变为(1, 2)
func(*{1: "1", 2: "2"})
- 如果可变参数在位置参数或默认参数的前面,就变成了命名关键字参数(下面学习)。
def girlsschool(*sex, name): # 命名关键字参数(下面讲解定义)
pass
def _girlsschool(*sex, age=18): # 命名关键字参数(下面讲解定义)
pass
def _girlsschool(*sex, age, name="ruhua"): # 命名关键字参数(下面讲解定义)
pass
def _girlsschool(*sex, age=18, name): # 命名关键字参数(下面讲解定义)
pass
def _girlsschool(name, *sex, age=18): # 命名关键字参数(下面讲解定义)
pass
def _girlsschool(name="ruhua", *sex, age): # 命名关键字参数(下面讲解定义)
pass
关键字参数
- 可变参数允许你传入 0 个或任意个参数,我们知道这些可变参数在函数调用时自动组装为一个 tuple。而关键字参数允许你传入 0 个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个 dict。
def func(**arg):
print(arg)
func()
func(name="ruhua", age=18)
- 如果传入的实参类型是 dict,Python 允许在实参前面加一个 ** 号,此时 Python 解释器则会把字典类型的实参转为 key=value 的形式。
def func(**arg):
print(arg)
stuents_dict = {"name": "ruhua", "age": 18}
# 解释器会把 **stuents_dict 转为 name="ruhua", age=18,最后的实参为(name="ruhua", age=18)
func(**stuents_dict)
# 解释器会把 **stuents_dict 转为 name="ruhua", age=18,最后的实参为(sex="f", name="ruhua", age=18)
func(sex="f", **stuents_dict)
- 要确保关键字参数在可变参数和默认参数和位置参数前面,否则 Python 的解释器会报错。
def girlsschool(name, age=18, **beauty, *sex): # 函数参数定义错误
pass
def _girlsschool(name, age=18, *sex, **beauty): # ok
pass
命名关键字参数
- 命名关键字参数是在 Python 3 中新增加的一种语法,它和关键字参数
**kw
不同,命名关键字参数需要一个特殊分隔符*
,*
后面的参数被视为命名关键字参数。例如,只接收 sex 和 lover 作为关键字参数,这种方式定义的函数如下。
def girlsschool(name, age=18, **beauty, *sex): # 函数参数定义错误
pass
def _girlsschool(name, age=18, *sex, **beauty): # ok
pass
- 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符
*
了。
def Human(name, age, *args, sex, lover):
print(name, age, args, sex, lover)
Human('ruhua', 18, sex='f', lover='tangbohu')
- 命名关键字参数必须传入参数名,这和位置参数不同,如果没有传入参数名,Python 解释器则报异常。
def Human(name, age, *, sex, lover):
print(name, age, sex, lover)
Human('ruhua', 18, 'f', 'tangbohu') # 必须要指定参数名 sex 和 lover
- 命名关键字参数可以有默认值,从而简化调用,给命名关键字参数设置默认值和给默认参数设置默认值的规则不一样,命名关键字参数不限制默认值的顺序。
def Human(name, age, *, sex, lover):
print(name, age, sex, lover)
Human('ruhua', 18, 'f', 'tangbohu') # 必须要指定参数名 sex 和 lover
各种类型参数组合使用
- 在 Python 中定义函数,可以用位置参数,默认参数,可变参数和关键字参数,这 4 种参数都可以一起使用,或者只用其中某些,但是请大家养成一个好习惯,尽量不要使用命名关键字参数,参数定义的顺序最好按照:必选参数,默认参数,可变参数和关键字参数的写法。
def func(a, b="ok", *c, **d):
pass
- 参数组合混用的时候,如果形参的默认参数后面有可变参数,传给默认参数的实参最好不要指定名称。
def girlsschool(age=18, *sex):
print(age)
print(sex)
girlsschool(age=19, "f") # 错误
本节重要知识点
- Python 中默认参数的实现机制。
- 可变参数和关键字参数的实现机制
- 参数组合混用注意事项。
作业
为什么我们经常用到的第三方库的函数喜欢用 func(arg,
*kwarg)
的形式来定义函数参数。
阅读原文:
函数参数详解www.birdpython.com![688f0315d5dffde10a38d15ee5ff6bd5.png](https://i-blog.csdnimg.cn/blog_migrate/471a26951b9f1a64f59430d73f2cb9dd.png)