形参与实参
什么是传参
将需要被处理的对象传递给函数的行为,被称为传参。
形参介绍
在定义函数时,函数需要使用1个或者多个标识符来接收到外部传递进的对象。
这个标识符就是形参:
def func(x, y, z):
"""
:param x: 形参x
:param y: 形参y
:param z: 形参z
:return: None
"""
...
可以理解为形参是函数(工厂)的预留位置,你将“原材料”放在这些位置上才能被工厂所接收到。
实参介绍
在调用函数时,外部向函数传入的对象则被称为实参。
def func(x, y, z):
"""
:param x: 形参x
:param y: 形参y
:param z: 形参z
:return: None
"""
...
# 传入实参1,2,3
func(1, 2, 3)
可以理解为实参是待加工的“原材料”, 需要将这些“原材料”放到指定的工厂预留位置上(形参)才能被工厂所接收。
两者关系
由于Python中一切皆引用,故实参传递给形参的也就是同一个对象,这种传参被称为引用传参。
2者总结关系有3点:
- 在函数调用时,实参将对象传递给形参后,对象的引用计数会+1
- 在函数内部,可以通过形参访问到该对象
- 形参与对象的绑定关系在函数调用结束后就会被取消掉
下面示例将验证形参和实参引用的都是同1个对象:
x = 1
def func(x):
print(id(x))
func(x)
print(id(x))
# 4518684720
# 4518684720
形参定义
位置参数
函数定义时从左至右依次排列的没有特殊意义的形参被称为位置形参,位置形参必须被对象传入,多一个少一个都不行。
如果传入的不够、或者传入的过多,将会抛出异常:
def func(x, y, z):
...
传少了:
func(1)
# TypeError: func() missing 2 required positional arguments: 'y' and 'z'
传多了:
func(1, 2, 3, 4)
# TypeError: func() takes 3 positional arguments but 4 were given
默认参数
默认形参是指可以不用被传递对象的形参,它拥有1个默认的对象。
-
如果调用函数时没有传入对象,则使用该默认的对象
-
如果调用函数时传入了1个对象,则使用接收到的对象
需要注意在定义默认形参时必须放在位置形参后面,否则会抛出异常。
def func(x, y, z=3):
print(x, y, z)
func(1, 2)
func("A", "B", "C")
# 1 2 3
# A B C
传参方式
位置传参
如果是位置形参,则实参传入对象时必须严格按照形参的位置顺序、形参的总体数量进行传入。
如果传入的不够、或者传入的过多,将会抛出异常:
def func(x, y, z):
...
传少了:
func(1)
# TypeError: func() missing 2 required positional arguments: 'y' and 'z'
传多了:
func(1, 2, 3, 4)
# TypeError: func() takes 3 positional arguments but 4 were given
关键字传参
使用位置传参时,我们要牢记形参的位置,这样比较麻烦。
可以直接通过 key = value 的形式进行传入,避免了需要记住形参位置的繁琐步骤,但是关键字传参必须放置在位置传参后面,也是个人比较推崇的一种传参方式:
def func(x, y, z=3):
print(x, y, z)
func(y=2, x=1, z=3)
func(1, z=3, y=2)
# 1 2 3
# 1 2 3
*与**语法使用
*的形参接收
*语法用在形参上,代表该形参可以接收任意数量的位置传参对象。
该形参将会变成一个元组形式,接收所有的位置传参对象,这种形参必须定义在位置形参与默认形参的后面:
def func(x, y=2, *args):
print(x, y, args)
func(1, 2, 3, 4, 5, 6, 7, 8, 9)
# 1 2 (3, 4, 5, 6, 7, 8, 9)
**的形参接收
**语法用在形参上,代表该形参可以接收任意数量的关键字传参对象。
该形参将会变成一个字典形式,接收所有的关键字传参对象,这种形参必须定义在位置形参与默认形参以及*形参的后面:
def func(x, y=2, *args,**kwargs):
print(x, y, args, kwargs)
func(1, 2, 3, 4, 5, 6, 7, 8, 9, a=ord("a"), b=ord("b"), c=ord("c"))
# 1 2 (3, 4, 5, 6, 7, 8, 9) {'a': 97, 'b': 98, 'c': 99}
*的实参传入
通过*语法,我们可以将1个容器序列根据位置传参的方式把其中的数据项对象传递给形参,如下所示:
def func(x, y, z):
print(x, y, z)
func(*(1, 2, 3))
# 1 2 3
**的实参传入
通过**语法,我们也可以将1个字典根据关键字传参的方式把字典中的value对象传递给形参,如下所示:
def func(x, y, z):
print(x, y, z)
func(**{"x": 1, "y": 2, "z": 3})
# 1 2 3
*args与**kwargs
*形参通常命名为*args,这是一种约定的俗称。
同理,**形参则被命名为**kwargs。
指定传入方式
形参中,如果存在一个*名字的特殊标识,则该标识不用传递对象。
该标识是规定了哪些形参必须是接收位置传入的对象、哪些形参是必须接收关键字传入的对象,如下所示:
def func(a, b=2, *, c=3, d): # ❶
print(a, b, c, d)
func(1, d=4)
❶:在 *左边的都是位置传入,在*右边的都是关键字传入,在*右边的参数也被称为命名关键字参数
- a:位置传入,不是默认参数,则必须通过位置传参的方式传入对象
- b:位置传入,是默认参数,可以不用通过位置传参的方式传入对象
- c:关键字传入,不是默认参数,则必须通过关键字传参的方式传入对象
- d:关键字传入,是默认参数,可以不用通过关键字传参的方式传入对象
形参的定义顺序
形参的定义顺序如下所示:
位置形参 -> 默认形参 -> *args -> **kwargs
示例演示:
def func(a, b, c="c", *args, **kwargs):
print("""
position params: {} {}
default params: {}
* params:{}
** params:{}
""".format(a, b, c, args, kwargs))
func("A", "B", "C", 1, 2, 3, 4, 5, k1="v1", k2="v2")
# position params: A B
# default params: C
# * params:(1, 2, 3, 4, 5)
# ** params:{'k1': 'v1', 'k2': 'v2'}
实参的传入顺序
实参的传入顺序牢记位置传参在前,关键字传参在后即可。
或者统一使用关键字传参,示例如下:
def func(a, b, c, d, e, f):
pass
func("A", *["B", "C"], d="D", **{"e": "E", "f": "F"})
# eq:
# func("A", "B", "C", d="D", e="E", f="F")
嵌套调用中*与**
当函数2作为函数1的一层外包装,我们想调用函数2的时候实际上是调用的函数1,并且参数也要完整的传递过去,该怎么做呢?
Ps:这其实是装饰器的一个前瞻知识点
def func(x, y, z):
print(x, y, z)
def wrapper(*args, **kwargs):
func(*args, **kwargs) # ❶ ❷
wrapper(1, y="v1", z="v2")
# 1 v1 v2
❶:实参用 * 和 ** 拆分开, * 是位置传参,**是关键字传参
❷:args = (1),kwargs = {“y”:“v1”,“z”:"v2”},然后再通过解构进行传递给func函数,func相当于接收了 func(1, y=“v1”, z=“v2”)
函数标注
Python3.5之后新增了函数标注(function annotation),即类型提示功能如下所示,仅做提示,并不会限制传递参数时的类型:
def add(x: int or float, y: int or float) -> int or float: # ❶
print(x)
print(y)
return x + y
❶:->代表返回值,返回int或者float类型