Python:函数的形参与实参

Posted by zhangshun on December 30, 2019

一、 形参与实参介绍

函数的参数分为形式参数和实际参数,简称形参和实参:

形参即在定义函数时,括号内声明的参数。形参本质就是一个变量名,用来接收外部传来的值。

实参即在调用函数时,括号内传入的值,值可以是常量、变量、表达式或三者的组合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#1: 实参是常量
res = func(1,2)

#2: 实参是变量
a = 1
b = 2
res = func(a,b)

#3: 实参是表达式
res = func(10*2,10*func(3,4))

#4: 实参可以是常量、变量、表达式的任意组合
a = 2
res = func(1,a,10*func(3,4))

在调用有参函数时,实参(值)会赋值给形参(变量名)。在Python中,变量名与值只是单纯的绑定关系,而对于函数来说,这种绑定关系只在函数调用时生效,在调用结束后解除。

二、 形参与实参的具体使用

2.1 位置参数

位置即顺序,位置参数指的是桉顺序定义的参数,需要从两个角度去看:

1.在定义函数时,按照从左到右的顺序依次定义形参,称为位置形参,凡是按照这种形式定义的形参都必须被传值。

1
2
3
4
def register(name,age,sex): #定义位置形参: name,age,sex,三者都必须被传值
	print('Name:%s Age:%s Sex:%s' %(name,age,sex))

register() #TypeError: 缺少3个位置参数

2.在调用函数时,按照从左到右的顺序依次定义实参,称为位置实参,凡是按照这种形式定义的实参会按照从左到右的顺序与形参一一对应。

1
2
register('lili',18,'male') #对应关系为: name='lili',age=28,sex='male'
Name:lili Age:18 Sex:male

2.2 关键字参数

在调用函数时,实参可以是key=value的形式,称为关键字参数,凡是按照这种形式定义的实参,可以完全不按照从左到右的顺序定义,但仍能为指定的形参赋值

1
2
register(sex='male',name='lili',age=18)
Name:lili Age:18 Sex:male

需要注意在调用函数时,实参也可以是按位置或关键字的混合使用,但必须保证关键字参数在位置参数后面,且不可以对一个形参重复赋值

1
2
3
register('lili',sex='male',age=18) #正确使用
register(name='lili',18,sex='male') #SyntaxError:关键字参数name='lili'在位置参数18之前
register('lili',sex='male',age=18,name='jack') #TypeError:形参name被重复赋值

2.3 默认参数

在定义函数时,就已经为形参赋值,这类形参称之为默认参数,当函数有多个参数时,需要将值经常改变的参数定义为位置参数,而将值改变较少的参数定义成默认参数。例如编写一个注册学生信息的函数,如果大多数学生的性别都为男,那完全可以将形参sex定义成默认参数

1
2
def register(name,age,sex='male'):
	print('Name:%s Age:%s Sex:%s' %(name,age,sex))

定义时就已经为参数sex赋值,意味着调用时可以不对sex赋值,这降低了函数调用的复杂度

1
2
3
4
5
register('tom',17)
Name:tom Age:17 Sex:male #大多数情况,无需为sex传值,默认为male

register('Lili',18,'female')
Name:Lili Age:18 Sex:female #少数情况,可以为sex传值female

需要注意:
1.默认参数必须放在位置参数之后
2.默认参数的值仅在函数定义阶段被赋值一次

1
2
3
4
5
6
7
x = 1
def foo(arg=x):
	print(arg)

x = 5 #定义阶段arg已被赋值为1,此处的修改与默认参数arg无任何关系
foo()
1

3.默认参数的值通常设为不可变类型

1
2
3
4
5
6
7
8
9
10
def foo(n,arg=[]):
	arg.append(n)
	return arg

foo(1)
[1]
foo(2)
[1,2]
foo(3)
[1,2,3]

每次调用是在上一次的基础上向同一列表增加值,修改如下

1
2
3
4
5
6
7
8
9
10
11
12
def foo(n,arg=None):
	if arg is None:
		arg = []
	arg.append(n)
	return arg

foo(1)
[1]
foo(2)
[2]
foo(3)
[3]

2.4 可变长度的参数(*与**的用法)

参数的长度可变指的是在调用函数时,实参的个数不固定,而在调用函数时,实参的定义无非是按位置或者按关键字两种形式,这就要求形参提供两种解决方案来分别处理两种形式的可变长度的参数

2.4.1 可变长度的位置参数

如果在最后一个形参名前加*号,那么在调用函数时,溢出的位置参数,都会被*接收,以元组的形式保存下来赋值给该形参

1
2
3
4
5
6
7
8
9
10
11
12
def foo(x,y,z=1,*args):
	print(x)
	print(y)
	print(z)
	print(args)

foo(1,2,3,4,5,6,7) #实参1、2、3按位置为形参x、y、z赋值,多余的位置实参4、5、6、7都被*接收,以元组的形式保存下来,赋值给args,即args=(4,5,6,7)

1
2
3
(4,5,6,7)

如果我们事先生成了一个列表,仍然是可以传值给*args的

1
2
3
4
5
6
7
8
9
10
def foo(x,y,*args):
	print(x)
	print(y)
	print(args)

L = [3,4,5]
foo(1,2,*L) # *L就相当于位置参数3,4,5,foo(1,2,*L)就等同于foo(1,2,3,4,5)
1
2
(3,4,5)

注意:如果在传入L时没有加*,那L就只是一个普通的位置参数了

1
2
3
4
foo(1,2,L)
1
2
([3,4,5],)

如果形参为常规的参数(位置或默认),实参仍可以是*的形式

1
2
3
4
5
6
7
8
9
def foo(x,y,z=3):
	print(x)
	print(y)
	print(z)

foo(*[1,2]) #等同于foo(1,2)
1
2
3

如果我们想要求多个值得和,*args就派上用场了

1
2
3
4
5
6
7
8
def add(*args):
	sum = 0
	for i in args:
		sum += i
	return sum

add(1,2,3,4,5)
15

2.4.2 可变长度的关键字参数

如果在最后一个形参名前加**号,那么在调用函数时,溢出的关键字参数,都会被**接收,以字典的形式保存下来赋值给该形参

1
2
3
4
5
6
7
def foo(x,**kwargs): #在最后一个参数kwargs前加**
	print(x)
	print(kwargs)

foo(y=2,x=1,z=3) #溢出的关键字实参y=2,z=3都被**接收,以字典的形式保存下来,赋值给kwargs
1
{'z':3,'y':2}

如果我们事先生成了一个字典,仍然可以传值给**kwargs的

1
2
3
4
5
6
7
8
9
10
def foo(x,y,**kwargs): 
	print(x)
	print(y)
	print(kwargs)

dict = {'a':1,'b':2} 
foo(1,2,**dict) # **dict就相当于关键字参数a=1,b=2,foo(1,2,**dict)就等同于foo(1,2,a=1,b=2)
1
2
{'a':1,'b':2}

注意:如果在传入dicts时没有加**,那dict就只是一个普通的位置参数了 foo(1,2,dict) # TypeError:函数foo只需要2个位置参数,但是传了3个 如果形参为常规参数(位置或默认),实参仍可以是**的形式

1
2
3
4
5
6
7
8
9
def foo(x,y,z=3):
	print(x)
	print(y)
	print(z)

foo(**{'x':1,'y':2})
1
2
3

如果我们要编写一个用户认证的函数,起初可能只基于用户名密码的验证就可以了,可以使用**kwargs为日后的扩展提供良好的环境,同时还保持了函数的简洁性

1
2
def auth(user,password,**kwargs):
	pass

2.5 命名关键字参数

在定义了**kwargs参数后,函数调用者就可以传入任意的关键字参数key=value,如果函数代码的执行需要依赖某个key,必须在函数内进行判断

1
2
3
4
5
6
7
def register(name,age,**kwargs):
	if 'sex' in kwargs:
		#有sex参数
		pass
	if 'height' in kwargs:
		#有height参数
		pass

想要限定函数的调用者必须以key=value的形式传值,Python3提供了专门的语法:需要在定义形参时,用*号作为一个分隔符,*号之后的形参称为命名关键字参数。对于这类参数,在函数调用时,必须按照key=value的形式为其传值,且必须被传值

1
2
3
4
5
6
def register(name,age,*,sex,height): #sex,height为命名关键字参数
	pass

register('lili',18,sex='male',height='1.8m') #正确使用
register('lili',18,'male','1.8m') # TypeError:未使用关键字的形式为sex和height传值
register('lili',18,height='1.8m') # TypeError:没有为命名关键字sex传值

命名关键字参数也可以有默认值,从而简化调用

1
2
3
4
5
def register(name,age,*,sex='male',height):
	print('Name:%s Age:%s Sex:%s Height:%s' %(name,age,sex,height))

register('lili',18,height='1.8m')
Name:lili Age:18 Sex:male Height:1.8m

需要强调的是:sex不是默认参数,height也不是位置参数,因为两者均在*之后,所以都是命名关键字参数,形参sex=’male’属于命名关键字参数的默认值,因而即便放到形参height之前也不会有问题。另外,如果形参中已经有一个*args了,命名关键字参数就不再需要一个单独的*作为分隔符了

1
2
3
4
5
def register(name,age,*args,sex='male',height):
	print('Name:%s Age:%s Args:%s Sex:%s Height:%s' %(name,age,args,sex,height))

register('lili',18,1,2,3,height='1.8m') # sex与height仍为命名关键字参数
Name:lili Age:18 Args:(1,2,3) Sex:male Height:1.8m

2.6 组合使用

综上所述所有参数可以任意组合使用,但定义顺序必须是:位置参数、默认参数、*args、命名关键字参数、**kwargs

可变参数*args与关键字参数**kwargs通常是组合在一起使用的,如果一个函数的形参为*args与**kwargs,那么代表该函数可以接收任何形式、任何长度的参数

1
2
def wrapper(*args,**kwargs):
	pass

在该函数内部还可以把接收到的参数传给另一个函数

1
2
3
4
5
6
7
8
def func(x,y,z):
	print(x,y,z)

def wrapper(*args,**kwargs):
	func(*args,**kwargs)

wrapper(1,z=3,y=2)
1 2 3

按照上述写法,在为函数wrapper传参时,其实遵循的是函数func的参数规则,调用函数wrapper的过程分析如下:
1.位置实参1被*接收,以元组的形式保存下来,赋值给args,即args=(1,),关键字实参z=3,y=2被**接收,以字典的形式保存下来,赋值给kwargs,即kwargs={‘y’:2,’z’:3}
2.执行func(*args,**kwargs),即func(*(1,),**{‘y’:2,’z’:3}),就等同于func(1,z=3,y=2)

提示: *args、**kwargs中的args和kwargs被替换成其它名字并无语法错误,但使用args、kwargs是约定俗成的。