函数的参数

形参与实参

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

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

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

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

形参实参的使用

位置参数

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

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

关键字参数

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

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

默认参数

# 在定义函数时,就已经为形参赋值,这类形参称之为默认形参
def register(name, age, sex='male'):
    pass

# 在函数调用时,可以不需要为默认参数赋值,这降低了函数调用的复杂度
register('jack', 18)	        # sex默认为'male'
register('jane', 19,'female')	#sex赋值为'female'


#注意:
	- 默认形参必须在位置形参之后
	- 默认参数的值仅在函数定义阶段被赋值一次
    - 默认形参的值通常应设为不可变类型

可变长度的参数

参数的长度可变指的是在调用函数时,实参的个数可以不固定;

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

可变长度的位置参数 *args

# 如果在最后一个形参名前加*号,那么在调用函数时,溢出的位置实参都会被接收,以元组的形式保存下来赋值给该形参
def foo(x,y,z=1, *args):
    print(x,y,z)
    print(args)
    
foo(1,2,3,4,5,6,7)
# output:
1,2,3
(4,5,6,7)

补充:*的用法(调用)

#1 如果我们事先生成了一个列表,仍然是可以传值给*args的
def foo(x, y, *args):
    print(x,y)
    print(args)

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


#2 如果在传入L时没有加*,那L就只是一个普通的位置参数了
def foo(x, y, *args):
    print(x,y)
    print(args)
    
    
l1 = [3,4,5]
foo(1,2,l1)			# 仅多出一个位置实参l1
# output:
1,2
([3,4,5], )


#3 如果形参为常规的参数(位置或默认),实参仍可以是*的形式
def foo(x, y, z=3):
    print(x,y,z)
    
l1 = [1,2]
foo(*l1)		# 等同于foo(1,2)

#output:
1,2,3

练习:求多个数的和

def total_sum(*args):
    total_sum = 0
    for i in args:
        total_sum += i
    return total_sum

res = total_sum(1,2,3,4,5)   # res=15

可变长度的关键字参数 **kwargs

# 如果在最后一个形参名前加**号,那么在调用函数时,溢出的关键字参数都会被接收,以字典的形式保存下来赋值给该形参
def foo(x, **kwargs):
    print(x)
    print(kwargs)
    
foo(1, y=2, z=3)	# 溢出的关键字实参y=2,z=3都被**接收,以字典的形式保存下来,赋值给kwargs
#output:
1
{'z':3, 'y':2}

补充:**的用法(调用)

#1 如果我们事先生成了一个字典,仍然是可以传值给**kwargs的
def foo(x, y, **kwargs):
	print(x, 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)
#output:
1 2
{'a': 1, 'b': 2}

#2 注意:如果在传入dic时没有加**,那dic就只是一个普通的位置参数了
foo(1,2,dic) #TypeError:函数foo只需要2个位置参数,但是传了3个

#3 如果形参为常规参数(位置或默认),实参仍可以是**的形式
def foo(x,y,z=3):
	print(x, y, z)
    
foo(**{'x':1,'y':2}) 	# 等同于foo(y=2,x=1)
# output:
1 2 3

强调强调强调

*args **kwargs 当形参时:分别接收溢出的位置参数和关键字参数,存放在元祖和字典中

*args **kwargs 当实参时:分别解压出位置参数和键值对参数赋值给对应的形参

命名关键字参数

# 在定义了**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没有为命名关键字参数sex传值

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

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')
# output:
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仍为命名关键字参数
# output:
Name:lili,Age:18,Args:(1, 2, 3),Sex:male,Height:1.8m

组合使用

综上所述所有参数可任意组合使用,但定义顺序必须是:

位置参数、默认参数、*args、命名关键字参数、**kwargs

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

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

def func(x, y , z):
    print(x, y, z)

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

wrapper(1, z=3, y=2)
# output:
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)

补充思考

参数按传参方式可以分两种:位置参数和关键字参数

位置参数被赋值的方式:顺序赋值或 *identifier(identifier是可迭代对象)

关键字参数被赋值的方式:有标识符的参数(name='age')或字典前加**

# 位置赋值
complex(3, 5)
complex(*(3, 5))	# 等价于 complex(3, 5)

# 关键字赋值
complex(real=3, imag=5)
complex(**{'real': 3, 'imag': 5})	

PEP448:函数可以接收任意数量的 *identifier 或者 **identifier 的解压赋值,传参顺序:

​ 位置参数、*identifier、关键字参数、**identifier

# 小测试
def f(a, b):
    print(a, b)

f(*(2,), 3)		# 这种传参虽然不会报错,但推荐使用: f(3, *(2,3))
posted @ 2020-03-15 14:00  the3times  阅读(346)  评论(0编辑  收藏  举报