形参与实参
什么是形参
#==== 什么是形参 ====
def func(x, y, z): #形参
pass
在定义函数阶段定义的参数称之为形式参数,简称形参,相当于变量名。
什么是实参
#==== 什么是实参 ====
def func(x, y, z): #形参
passfunc("实参1", "实参2", "实参3")
在调用函数阶段传入的值称为实际参数,简称实参。相当于"变量值"的内存地址。(Ps:Python中的所有传值都是传递的内存地址,因此Python中的传值也被称为引用传值。)
实参与形参的关系
1.在调用阶段,实参("变量值的内存地址引用")会绑定给形参("变量名")
2.这种绑定关系只能在函数体内使用
3.实参与形参的绑定关系在函数调用时生效,函数调用结束后解除绑定关
#==== 实参与形参的关系 ====
x = 1y= 2z= 3
#函数体内的形参与函数体外的参数没有任何关系
deffunc(x, y, z):
x+= y +zprint(x) #6
func(x, y, z)print(x) #1
形参定义方式
位置形参
在函数定义阶段,按照从左到右的顺序直接定义的"变量名"被称为位置形参,它的特点是必须被传值,多一个少一个也不行。
#==== 位置形参 ====
#当使用位置形参时传递参数要注意形参实参一一对应
deffunc(x, y, z):print(x, y, z) #1 2 3
func(1, 2, 3)
默认形参
默认形参的值是在函数定义阶段就被赋予的,准确的说是赋予形参了一个默认的值的内存地址。当调用时可以不对该形参进行传值,如不对该形参进行传值则使用默认的值,反之则使用传入的实参值,需要注意在定义时默认形参必须放在位置形参后面,否则会抛出异常。
#==== 默认形参 ====
#重复实用性较高的一些参数设置为默认形参
def func(name, age, gender="male"):print(name, age, gender) #YunYa 18 male
func("YunYa", 18) #如果要传递默认形参,需要放在位置形参后面
实参传值方式
位置传参
上面一直在使用位置传参的方式,在函数调用阶段,按照从左至右的顺序依次传入值的方式被称为位置传参,特点是按照实参摆放的顺序与形参一一对应进行传值。来看一段错误示范:
#==== 位置传参 ====
#位置形参与传递实参没有一一对应,抛出异常
def func(name, age, gender="male"):print(name, age, gender)
func("YunYa")#TypeError: func() missing 1 required positional argument: 'age'
关键字传参
在函数调用阶段,按照key=value的形式传入值的方式被称为关键字传参,可以打破顺序为形参进行传值。注意:若使用关键字传参需注意必须跟在位置传参的后面。(也是个人比较推崇的一种传值方式)。
#==== 关键字传参 ====
def func(name, age, gender="male"):print(name, age, gender)
func(age=18, name="YunYa") #可以不依照位置传参
func("Jack", gender="female", age=17) #关键字传参放在后面
多参函数的形参与实参传值
位置传参与 *
*是一种语法,args是一种约定俗称。如果定义一个函数时不知道使用者要用位置传参的方式传入多少个实参,可使用*args来进行接收所有多余的被传入的实参,这些多余的被传入实参会被打包成元组类型。(args只是形参名,但是按照约定俗称如果要接收多个实参的形参名一般命名为args)
#==== *args 的使用 ====
def func(x, y, *args):print("x -->", x)print("y -->", y)print("args -->", args)#实参 1 和 2 会根据位置传参依次被形参x,y所接收(内存地址)。剩下的所有通过位置传参的实参全部被args所接收。
func(1, 2, 3, 4, 5, "6", [7, 8, 9])#==== 执行结果 ====
"""x --> 1
y --> 2
args --> (3, 4, 5, '6', [7, 8, 9])"""
关键字传参与 **
**也是一种语法。kwargs则是后面形参名的约定俗称。如果定义一个函数时不知道使用者要用关键字传参的方式传入多少个实参,可使用**kwargs来进行接收所有多余的被传入的实参,这些多余的被传入实参会被打包成字典类型。
#==== **kwargs 的使用 ====
def func(name, age, **kwargs):print("name -->", name)print("age -->", age)print("kwargs -->", kwargs)#实参yuya,18通过位置传参会被name和age所接受(内存地址),剩下的所有关键字传参的实参全部被kwargs所接收。
func("yunya", 18, gender="male", height="1.92", body_weight="180Kg")#==== 执行结果 ====
"""name --> yunya
age --> 18
kwargs --> {'gender': 'male', 'height': '1.92', 'body_weight': '180Kg'}"""
实参中 * 的使用
*可以用在实参中,实参中带*,先将*后面的值(一般为容器类型,不包含字典类型)拆分为实参后再以位置传值的方式进行传入。
#==== 实参中 * 的使用 ====
deffunc(x, y, z):print("x --->", x)print("y --->", y)print("z --->", z)
func(*["a", "b", "c"])#==== 执行结果 ====
"""x ---> a
y ---> b
z ---> c"""
实参中 ** 的使用
**也可以用在实参中,实参中带**,先将**后面的值(必须为字典类型)拆分为key=value的形式后再以关键字传值的方式进行传入。
#==== 实参中 ** 的使用 ====
deffunc(x, y, z):print("x --->", x)print("y --->", y)print("z --->", z)
func(**{"x": 1, "y": 2, "z": 3})#==== 执行结果 ====
"""x ---> 1
y ---> 2
z ---> 3"""
*args 与 **kwargs的组合使用
其实关于实参传值的方式就两种,一种是位置传参,一种是关键字传参。所以一个函数如果定义了*args与**kwargs后则代表它能够接受足够多的参数。需要注意的是*args必须定义在**kwrags之前,否则会抛出异常。
#==== *args 与 **kwargs 的组合使用 ====
def wrapper(*args, **kwargs):print(args) #(1, 2, 3)
print(kwargs) #{'name': 'Yunya', 'age': 18}
wrapper(1, 2, 3, name="Yunya", age=18)
扩展:命名关键字参数
我们可以指定某些形参的传值方式必须为关键字传参。那么这些被规定死了传值方式的形参则被称为命名关键字参数,它同位置形参,默认形参是同一级别的。我们先来看一下下面的代码:
#==== 命名关键字参数 ====
#在一些IDE中看起来可能会提示语法错误,其实是没有问题的#请记住在 * 后的既不是位置参数也不是默认参数它叫命名关键字参数
#x是位置形参 y是默认形参 a是由默认值的命名关键字参数,b是没有默认值的命名关键字参数
def func(x, y=2, *, a=3, b):print("x -->", x)print("y -->", y)print("a -->", a)print("b -->", b)
func(1, 2)#TypeError: func() missing 1 required keyword-only argument: 'b'#缺少一个关键字参数 "b"
我们来看一下默认形参的报错提示是什么:
def func(y=2, x):pass
#SyntaxError: non-default argument follows default argument#语法错误:不能将非默认参数跟在默认参数后面
由于命名关键字参数的应用场景十分少见,Pycharm也会在某些时候进行一些错误的提示:
#==== 命名关键字参数 ====
#在一些IDE中看起来可能会提示语法错误,其实是没有问题的#请记住在 * 后的既不是位置参数也不是默认参数它叫命名关键字参数
#x是位置形参 y是默认形参 a是由默认值的命名关键字参数,b是没有默认值的命名关键字参数
def func(x, y=2, *, a=3, b):print("x -->", x)print("y -->", y)print("a -->", a)print("b -->", b)
func(1, a=3, b=4) #如果y要使用默认参数,则不指定y,后面紧跟的是命名关键字参数,故只能使用关键字传参
#==== 执行结果 ====
"""x --> 1
y --> 2
a --> 3
b --> 4"""
扩展:定义形参顺序及传入实参顺序
注意:以下场景在实际开发中不可能遇到。但是这里还是做一个补充。
#==== 形参定义顺序 ====
#位置形参 -> 默认形参 -> *args -> 命名关键字参数 -> **kwargs
#注意:默认形参是一个尴尬的点,放在之前的话每次都需要使用默认的形参,放在之后又变成了命名关键字参数
def func(x, y, z="默认", *args, a="命名", b, **kwargs):pass
#==== 实参传入顺序 ====
#只需要遵循位置传参在前,关键字传参在后的原则,怎么操作都不会出现异常
deffunc(a, b, c, d, e, f):passfunc("A", *["B", "C"], d="D", **{"e": "E", "f": "F"})#拆分后的传递#func("A", "B", "C", d="D", e="E", f="F")
扩展:函数嵌套与*和**传参
这里要提前说一下,下面这个实例是为了给装饰器打基础,装饰器算是Python基础里比较难的一个点。它包含函数嵌套使用与*和**的多重传参,这里不说那么多。直接上个代码图,看懂了就可以关闭本页面了。
#==== 函数嵌套与*和**的组合使用 ====
deffunc(x, y, z):print(x, y, z) #1 v1 v2
def wrapper(*args, **kwargs):
func(*args, **kwargs) #实参用 * 和 ** 。拆分开, * 是位置传值,**是关键字传值.
#args (1) kwargs {"y":"v1","z":"v2"} 拆分成 ---> func(1,y="v1",z="v2") 最终传入样式
wrapper(1, y="v1", z="v2")
扩展:传参类型提示
#==== 传参类型提示 ====
def func(x: str, y: int, z: list) -> """返回str类型""":return "hello,world"
print(func.__annotations__)"""Python3.5之后新增了类型提示功能。在输入参数的时候会显示一定的提示,
注意:这只是一种提示,并不会真正限制你传参时的数据类型。"""
#==== 执行结果 ====
"""{'x': , 'y': , 'z': , 'return': '返回str类型'}"""