第二模块第12章 函数的参数

目录:

一 形参与实参介绍
二 形参与实参的具体使用
  2.1 位置参数
  2.2 关键字参数
  2.3 默认参数
  2.4 可变长度的参数(*与**的用法)
    2.4.1 可变长度的位置参数
    2.4.2 可变长度的关键字参数
  2.5 命名关键字参数(了解)
  2.6 组合使用(了解)

本博客参考的博文:

https://zhuanlan.zhihu.com/p/108907210

一 形参与实参介绍

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

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

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

#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中,变量名与值只是单纯的绑定关系,而对于函数来说,这种绑定关系只在函数调用时生效,在调用结束后解除。

 综上,

形参与实参的关系:

  1. 在调用阶段, 实参(变量值)会绑定给形参(变量名)

  2. 这种绑定关系只能在函数体内使用

  3. 实参与形参的绑定关系在函数调用时生效, 函数调用结束后解除绑定关系

二 形参与实参的具体使用

形参: 位置形参  默认形参

   位置形参和默认形参混用注意事项:

    1. 默认参数必须在位置参数之后
    2. 默认参数的值仅在函数定义阶段被赋值一次, 准确地说被赋予的是值的内存地址

    3. 虽然默认值可以被指定为任意数据类型, 但是不推荐使用可变类型, 以确保函数调用结果靠谱(函数最理想状态: 函数的调用只跟函数本身有关, 不受外界代码的影响)

实参: 位置实参   关键字实参

  位置实参和关键字实参混用注意事项:

    1. 位置实参必须放在关键字实参前

    2. 不能为同一个形参重复传值

2.1 位置参数

定义: 按照从左到右的顺序依次定义的参数称为位置参数, 位置参数包括位置形参和位置实参

位置形参的特点: 必须被传值, 多一个不行, 少一个也不行

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

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

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


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被重复赋值

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

需要注意:

1. 默认参数必须在位置参数之后
2. 默认参数的值仅在函数定义阶段被赋值一次, 准确地说被赋予的是值的内存地址

3. 虽然默认值可以被指定为任意数据类型, 但是不推荐使用可变类型

>>> 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]
# 默认参数的值仅在函数定义阶段被赋值一次, 准确地说被赋予的是值的内存地址, python中所有的传递都是内存地址的传递, 或引用的传递
示例1:
m = 2 def func(x, y=m): # 将y与m的内存地址绑定 print(x, y) m = 333 func(1)

示例2:
m = [1111,]
def func(x, y=m):
    print(x, y)
m.append(333)
func(1)
# 结果: 1 [1111, 333]

示例3:
# 如果参数中需要有可变类型, 可通过以下方式解决:
def func(x, y, z, l = None):
if l is None: # 注意, 所有None都对应一个id, 在判断时用is和==都可以, 但是规范的用法是is.
l = []
l.append(x)
l.append(y)
l.append(z)
print(l)
func(1, 2, 3)
new_l = [0]
func(1, 2, 3, new_l)

2.4 可变长度的参数

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

2.4.1 可变长度的位置参数

*形参名: 用来接收溢出的位置实参, 溢出的位置实参会被*保存成元祖的格式, 然后赋值给紧跟其后的形参名.

*后跟的可以是任意名字, 但是约定俗成应该是args.

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

>>> 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 可变长度的关键字参数

**形参名: 用来接收溢出的关键字实参, **会将溢出的关键字实参保存成字典格式, 然后赋值给紧跟其后的形参名.

**后跟的可以是任意名字, 但是约定俗成的应该是kwargs.

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

>>> 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 
...

*在实参中的使用:

实参中带*, 先将*后的值打散成位置实参

# 形参和实参中都带*
def func(x, y, *args):
    print(x, y, args)
func(1, 2, [3, 4, 5]) # 结果: 1 2 ([3, 4, 5],)
func(1, 2, *[3, 4, 5]) # 结果: 1 2 (3, 4, 5)
func(1, *'hello') # 结果: 1 h ('e', 'l', 'l', 'o')
func(1, *(2, 3)) # 结果: 1 2 (3,)

**在实参中的使用:

**可以用在实参中, **后跟的只能是字典, 实参中带**, 先将**后的值打散成关键字实参

def func(x, y, **kwargs):
    print(x, y, kwargs)
func(1, 2, **{'a': 'aa', 'b': 'bb'}, z = 3) # 结果: 1 2 {'a': 'aa', 'b': 'bb', 'z': 3}
func(*{'a': 'aa', 'b': 'bb'}) # 结果: a b {}
# func(1, *{'a': 'aa', 'b': 'bb'}) 会报错, 因为最终生成3个位置实参

def func(x, y, **kwargs):
    print(x, y, kwargs)
func(**{'y':1, 'x':2, 'a':3, 'b':4}) # 结果: 2 1 {'a': 3, 'b': 4}

总结: *和**都可以用在形参和实参中, *用在形参中接收的是溢出的位置实参, 生成元祖, **用在形参中接收的是溢出的关键字实参, 生成字典. *用在实参中会将可迭代对象打散, 形成位置实参, **用在实参中会将字典打散, 形成关键字实参. *和**用在实参中时, *适用于可迭代对象, **适用于字典.

混用*和**:  *args必须在**kwargs之前

def index(x, y):
    print(x, y)
def wrapper(*args, **kwargs):
    index(*args, **kwargs) # 参数格式为1, 2或x=1, y=2
'''
在wrapper(*args, **kwargs)中:
    *args: *接收传来的位置实参, 形成元祖, 将其赋值给args; 
    **kwargs: **接收传来的关键字实参, 形成字典, 将其赋值给kwargs.
在index(*args, **kwargs)中:
    *args: *将args元祖中的元素打散, 生成位置实参;
    **kwargs: **将kwargs字典中的元素打散, 生成关键字实参.
'''
# 注意, 采用什么方式给wrapper传参, index中接收的参数就是什么格式, 所以在给wrapper传参时, 要遵循index的形参格式
wrapper(1, 2)
wrapper(x = 1, y = 2)

2.5 命名关键字参数(了解)

'''
命名关键字参数
在定义函数时, *后定义的参数, 如下所示, 称之为命名关键字参数
特点:
    1. 命名关键字实参必须按照key=value的形式为其传值

'''
def func(x, y, *, a, b):  # 其中, a和b称之为命名关键字参数, 其中*也可以是*args
    print(x, y)
    print(a, b)
func(1, 2, a=3, b=4)  # 结果: 1 2 3 4


def func(x, y, *args, a, b):  # 其中, a和b称之为命名关键字参数, 其中*也可以是*args
    print(x, y)
    print(args, a, b)
func(1, 2, 3, a=4, b=5)  # 结果: 1 2 (3,) 4 5

# 为命名关键字参数赋默认值
def func(x, y, *, a=111, b):
    print(x, y, a, b)
func(1, 2, b=3)  # 结果: 1 2 3 111

 

在定义了**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

 

2.6 组合使用(了解)

形参混用的顺序: 位置形参,  默认形参,  *args,  命名关键字形参,   **kwargs

实参混用的顺序: 位置实参或*可迭代对象, 关键字实参或**{}, 只要确保位置实参在关键字实参前面即可

综上所述, 所有参数可任意组合使用,但定义顺序必须是:位置参数、默认参数、*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是约定俗成的。

 

第二模块第12章 函数的参数

上一篇:django的View和drf的APIView源码解析


下一篇:Visual Studio 2017 注册码