python基础-函数的参数
一 形参与实参介绍
函数的参数分为形式参数和实际参数,简称形参和实参:
形参即在定义函数时,括号内声明的参数。形参本质就是一个变量名,用来接收外部传来的值。
实参即在调用函数时,括号内传入的值,值可以是常量、变量、表达式或三者的组合:
#1:实参是常量 res=my_min(1,2) #2:实参是变量 a=1 b=2 res=my_min(a,b) #3:实参是表达式 res=my_min(10*2,10*my_min(3,4)) #4:实参可以是常量、变量、表达式的任意组合 a=2 my_min(1,a,10*my_min(3,4))
在调用有参函数时,实参(值)会赋值给形参(变量名)。在Python中,变量名与值只是单纯的绑定关系,而对于函数来说,这种绑定关系只在函数调用时生效,在调用结束后解除。
二 形参与实参的具体使用
2.1 位置参数
位置即顺序,位置参数指的是按顺序定义的参数,需要从两个角度去看:
- 在定义函数时,按照从左到右的顺序依次定义形参,称为位置形参,凡是按照这种形式定义的形参都必须被传值
def register(name,age,sex): #定义位置形参:name,age,sex,三者都必须被传值 print('Name:%s Age:%s Sex:%s' %(name,age,sex)) register() #TypeError:缺少3个位置参数
- 在调用函数时,按照从左到右的顺序依次定义实参,称为位置实参,凡是按照这种形式定义的实参会按照从左到右的顺序与形参一一对应
def register(name,age,sex): #定义位置形参:name,age,sex,三者都必须被传值 print('Name:%s Age:%s Sex:%s' %(name,age,sex)) register() #TypeError:缺少3个位置参数
# 形参与实参的关系: # 1、在调用阶段,实参(变量值)会绑定给形参(变量名) # 2、这种绑定关系只能在函数体内使用 # 3、实参与形参的绑定关系在函数调用时生效,函数调用结束后解除绑定关系 # 二 形参与实参的具体使用 # 2.1 位置参数:按照从左到右的顺序依次定义的参数称之为位置参数 # 位置形参:在函数定义阶段,按照从左到右的顺序直接定义的"变量名" # 特点:必须被传值,多一个不行少一个也不行 # def func(x,y): # print(x,y) # func(1,2,3) # func(1,) # 位置实参:在函数调用阶段, 按照从左到有的顺序依次传入的值 # 特点:按照顺序与形参一一对应 # func(1,2) # func(2,1)
2.2 关键字参数
在调用函数时,实参可以是key=value的形式,称为关键字参数,凡是按照这种形式定义的实参,可以完全不按照从左到右的顺序定义,但仍能为指定的形参赋值
>>> register(sex='male',name='lili',age=18) Name:lili Age:18 Sex:male
需要注意在调用函数时,实参也可以是按位置或按关键字的混合使用,但必须保证关键字参数在位置参数后面,且不可以对一个形参重复赋值
>>> 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被重复赋值
# 关键字实参:在函数调用阶段,按照key=value的形式传入的值 # 特点:指名道姓给某个形参传值,可以完全不参照顺序 # def func(x,y): # print(x,y) # func(y=2,x=1) # func(1,2) # 混合使用,强调 # 1、位置实参必须放在关键字实参前 # func(1,y=2) # func(y=2,1) # 2、不能能为同一个形参重复传值 # func(1,y=2,x=3) # func(1,2,x=3,y=4)
2.3 默认参数
在定义函数时,就已经为形参赋值,这类形参称之为默认参数,当函数有多个参数时,需要将值经常改变的参数定义成位置参数,而将值改变较少的参数定义成默认参数。例如编写一个注册学生信息的函数,如果大多数学生的性别都为男,那完全可以将形参sex定义成默认参数
>>> def register(name,age,sex='male'): #默认sex的值为male ... print('Name:%s Age:%s Sex:%s' %(name,age,sex)) ...
定义时就已经为参数sex赋值,意味着调用时可以不对sex赋值,这降低了函数调用的复杂度
>>> register('tom',17) #大多数情况,无需为sex传值,默认为male Name:tom Age:17 Sex:male >>> register('Lili',18,'female') #少数情况,可以为sex传值female Name:Lili Age:18 Sex:female
需要注意:
- 默认参数必须在位置参数之后
- 默认参数的值仅在函数定义阶段被赋值一次
>>> x=1 >>> def foo(arg=x): ... print(arg) ... >>> x=5 #定义阶段arg已被赋值为1,此处的修改与默认参数arg无任何关系 >>> foo() 1
- 默认参数的值通常应设为不可变类型
def foo(n,arg=[]): arg.append(n) return arg foo(1) [1] foo(2) [1, 2] foo(3) [1, 2, 3]
每次调用是在上一次的基础上向同一列表增加值,修改如下
def foo(n,arg=None): if arg is None: arg=[] arg.append(n) return arg foo(1) [1] foo(2) [2] foo(3) [3]
# 默认形参:在定义函数阶段,就已经被赋值的形参,称之为默认参数 # 特点:在定义阶段就已经被赋值,意味着在调用阶段可以不用为其赋值 # def func(x,y=3): # print(x,y) # # # func(x=1) # func(x=1,y=44444) # def register(name,age,gender='男'): # print(name,age,gender) # # register('三炮',18) # register('二炮',19) # register('大炮',19) # register('没炮',19,'女') # 位置形参与默认形参混用,强调: # 1、位置形参必须在默认形参的左边 # def func(y=2,x): # pass # 2、默认参数的值是在函数定义阶段被赋值的,准确地说被赋予的是值的内存地址 # 示范1: # m=2 # def func(x,y=m): # y=>2的内存地址 # print(x,y # m=3333333333333333333 # func(1) # 示范2: # m = [111111, ] # # def func(x, y=m): # y=>[111111, ]的内存地址 # print(x, y) # # m.append(3333333) # func(1) # 3、虽然默认值可以被指定为任意数据类型,但是不推荐使用可变类型 # 函数最理想的状态:函数的调用只跟函数本身有关系,不外界代码的影响 # m = [111111, ] # # def func(x, y=m): # print(x, y) # # m.append(3333333) # m.append(444444) # m.append(5555) # # # func(1) # func(2) # func(3) # def func(x,y,z,l=None): # if l is None: # l=[] # l.append(x) # l.append(y) # l.append(z) # print(l) # func(1,2,3) # func(4,5,6) # new_l=[111,222] # func(1,2,3,new_l)
2.4 可变长度的参数(与*的用法)
参数的长度可变指的是在调用函数时,实参的个数可以不固定,而在调用函数时,实参的定义无非是按位置或者按关键字两种形式,这就要求形参提供两种解决方案来分别处理两种形式的可变长度的参数
2.4.1 可变长度的位置参数
如果在最后一个形参名前加号,那么在调用函数时,溢出的位置实参,都会被接收,以元组的形式保存下来赋值给该形参
>>> def foo(x,y,z=1,*args): #在最后一个形参名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的
>>> 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就只是一个普通的位置参数了
>>> foo(1,2,L) #仅多出一个位置实参L 1 2 ([1, 2, 3],)
如果形参为常规的参数(位置或默认),实参仍可以是*的形式
>>> def foo(x,y,z=3): ... print(x) ... print(y) ... print(z) ... >>> foo(*[1,2]) #等同于foo(1,2) 1 2 3
如果我们想要求多个值的和,*args就派上用场了
>>> def add(*args): ... res=0 ... for i in args: ... res+=i ... return res ... >>> add(1,2,3,4,5) 15
2.4.2 可变长度的关键字参数
如果在最后一个形参名前加号,那么在调用函数时,溢出的关键字参数,都会被接收,以字典的形式保存下来赋值给该形参
>>> 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的
>>> def foo(x,y,**kwargs): ... print(x) ... print(y) ... print(kwargs) ... >>> dic={'a':1,'b':2} >>> foo(1,2,**dic) #**dic就相当于关键字参数a=1,b=2,foo(1,2,**dic)等同foo(1,2,a=1,b=2) 1 2 {'a': 1, 'b': 2}
注意:如果在传入dic时没有加**,那dic就只是一个普通的位置参数了
>>> foo(1,2,dic) #TypeError:函数foo只需要2个位置参数,但是传了3个
如果形参为常规参数(位置或默认),实参仍可以是**的形式
>>> def foo(x,y,z=3): ... print(x) ... print(y) ... print(z) ... >>> foo(**{'x':1,'y':2}) #等同于foo(y=2,x=1) 1 2 3
如果我们要编写一个用户认证的函数,起初可能只基于用户名密码的验证就可以了,可以使用**kwargs为日后的扩展供良好的环境,同时保持了函数的简洁性。
>>> def auth(user,password,**kwargs): ... pass ...
# I:*形参名:用来接收溢出的位置实参,溢出的位置实参会被*保存成元组的格式然后赋值紧跟其后的形参名 # *后跟的可以是任意名字,但是约定俗成应该是args # def func(x,y,*z): # z =(3,4,5,6) # print(x,y,z) # func(1,2,3,4,5,6) # def my_sum(*args): # res=0 # for item in args: # res+=item # return res # # res=my_sum(1,2,3,4,) # print(res) # II: *可以用在实参中,实参中带*,先*后的值打散成位置实参 # def func(x,y,z): # print(x,y,z) # # # func(*[11,22,33]) # func(11,22,33) # # func(*[11,22]) # func(11,22) # # l=[11,22,33] # func(*l) # III: 形参与实参中都带* # def func(x,y,*args): # args=(3,4,5,6) # print(x,y,args) # func(1,2,[3,4,5,6]) # func(1,2,*[3,4,5,6]) # func(1,2,3,4,5,6) # func(*'hello') # func('h','e','l','l','o') 可变长度的关键字参数 # I:**形参名:用来接收溢出的关键字实参,**会将溢出的关键字实参保存成字典格式,然后赋值给紧跟其后的形参名 # **后跟的可以是任意名字,但是约定俗成应该是kwargs # def func(x,y,**kwargs): # print(x,y,kwargs) # # func(1,y=2,a=1,b=2,c=3) # II: **可以用在实参中(**后跟的只能是字典),实参中带**,先**后的值打散成关键字实参 # def func(x,y,z): # print(x,y,z) # func(*{'x':1,'y':2,'z':3}) # func('x','y','z') # func(**{'x':1,'y':2,'z':3}) # func(x=1,y=2,z=3) # 错误 # func(**{'x':1,'y':2,}) # func(x=1,y=2) # func(**{'x':1,'a':2,'z':3}) # func(x=1,a=2,z=3) # III: 形参与实参中都带** # def func(x,y,**kwargs): # print(x,y,kwargs) # func(y=222,x=111,a=333,b=444) # func(**{'y':222,'x':111,'a':333,'b':4444}) # 混用*与**:*args必须在**kwargs之前 # def func(x,*args,**kwargs): # print(args) # print(kwargs) # # func(1,2,3,4,5,6,7,8,x=1,y=2,z=3) def index(x, y, z): print('index=>>> ', x, y, z) def wrapper(*args, **kwargs): # args=(1,) kwargs={'z':3,'y':2} index(*args, **kwargs) # index(*(1,),**{'z':3,'y':2}) # index(1,z=3,y=2) wrapper(1, z=3, y=2) # 为wrapper传递的参数是给index用的 # 原格式---》汇总-----》打回原形
2.5 命名关键字参数
在定义了**kwargs参数后,函数调用者就可以传入任意的关键字参数key=value,如果函数体代码的执行需要依赖某个key,必须在函数内进行判断
>>> def register(name,age,**kwargs): ... if 'sex' in kwargs: ... #有sex参数 ... pass ... if 'height' in kwargs: ... #有height参数 ... pass ...
想要限定函数的调用者必须以key=value的形式传值,Python3提供了专门的语法:需要在定义形参时,用作为一个分隔符号,号之后的形参称为命名关键字参数。对于这类参数,在函数调用时,必须按照key=value的形式为其传值,且必须被传值
>>> 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没有为命名关键字参数height传值。
命名关键字参数也可以有默认值,从而简化调用
>>> 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了,命名关键字参数就不再需要一个单独的*作为分隔符号了
>>> 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
# 命名关键字参数(了解) # 命名关键字参数:在定义函数时,*后定义的参数,如下所示,称之为命名关键字参数 # 特点: # 1、命名关键字实参必须按照key=value的形式为其传值 # def func(x,y,*,a,b): # 其中,a和b称之为命名关键字参数 # print(x,y) # print(a,b) # # # func(1,2,b=222,a=111) # 示例 # def func(x,y,*,a=11111,b): # print(x,y) # print(a,b) # # func(1,2,b=22222)
2.6 组合使用
综上所述所有参数可任意组合使用,但定义顺序必须是:位置参数、默认参数、args、命名关键字参数、*kwargs
可变参数*args与关键字参数kwargs通常是组合在一起使用的,如果一个函数的形参为*args与kwargs,那么代表该函数可以接收任何形式、任意长度的参数
>>> def wrapper(*args,**kwargs): ... pass ...
在该函数内部还可以把接收到的参数传给另外一个函数(这在4.6小节装饰器的实现中大有用处)
>>> 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被接收,以元组的形式保存下来,赋值给args,即args=(1,),关键字实参z=3,y=2被*接收,以字典的形式保存下来,赋值给kwargs,即kwargs={'y': 2, 'z': 3}
- 执行func(args,kwargs),即func((1,),* {'y': 2, 'z': 3}),等同于func(1,z=3,y=2)
提示: *args、**kwargs中的args和kwargs被替换成其他名字并无语法错误,但使用args、kwargs是约定俗成
# 2. 组合使用(了解) # 形参混用的顺序:位置新参,默认形参,*args,命名关键字形参,**kwargs # def func(x,y=111,*args,z,**kwargs): # print(x) # print(y) # print(args) # print(z) # print(kwargs) # # 实参混用的顺序: def func(x,y,z,a,b,c): print(x) print(y) print(z) print(a) print(b) print(c) # func(111,y=222,*[333,444],**{'b':555,'c':666}) # func(111,y=222,333,444,b=555,c=666) # func(111,*[333,444],a=222,**{'b':555,'c':666}) # func(111,333,444,a=222,b=555,c=66) # func(111,*[333,444],**{'b':555,'c':666},a=222,) func(111,3333,4444,b=555,c=666,a=222) # func(1) # func(x=1) # func(1,x=1) # func(*'hello') # func(**{}) # func(*'hell',**{})