先打个广告:欢迎关注我的公众号,参与 文史大挑战 趣味题目。使用方法见 这篇文章 。
正文开始:
本文综述 Python 函数的参数类型和捕获过程。parameter 的作用是捕获 argument,又叫做 参数处理机制(parameter handling mechanism),本文的主题围绕这一个捕获过程。
parameter 和 argument 的区别
在 这篇文章 ,解释了 parameter 和 argument 的概念上的区别。
parameter 出现在函数定义中;argument 出现在函数调用中。
可以用下面的例子解释:
# 定义
def f(a):
pass
# 调用
f(1)
这个例子中,定义函数时的 a
是 parameter, 而调用函数时的 1
是 argument.
参数的捕获
当函数被调用的时候,如何让用户提供的 argument 被正确的赋值给 parameter, 是从 argument 到 parameter 的映射。
因为这个映射是个从左往右的有序的动作,所以更形象的说,是一个:
- parameters 捕获 arguments 的过程
- argument values 填入 parameter slots 的过程
parameter 列表形成一个个的 slot,而 argument 包含若干个 value,通过一系列规定好的逻辑,将 value 捕获到 slot 中去,这就是参数处理机制(parameter handling mechanism),本文的主题围绕这一个捕获过程,系统的梳理一下参数处理机制。
通过位置捕获
最简单的捕获方式就是通过位置捕获,这和 shell 函数相同:
# 定义
def f(a, b):
pass
# 调用
f(1, 2)
这里的 a
、b
靠自身所处的位置捕获参数值,称为 Positional arguments. 这种通过位置对应获取参数的方式可以称为
- 隐式(implicit)的获取
- 匿名参数
参数值会按照位置一一对应到参数列表中的变量中去。如果提供的参数列表个数和定义的不符合,无论多还是少,都会抛出 TypeError
:
>>> def f(a,b):
... pass
...
>>> f(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required positional argument: 'b'
>>> f(1,2,3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes 2 positional arguments but 3 were given
>>>
位置参数的困难
当参数列表很长时,会遇到一个困难,就是需要逐个参数对应。比如
>>> def f(a,b,c,d,e,f,g):
... pass
...
>>> f(1,2,3,4,5,6,7)
>>>
如果想修改中间某个参数,需要先到参数列表中从左向右数出来参数的位置:
------>
f(a,b,c,d,e,f,g)
然后再到 arguments 列表里从左到右数出来参数的位置。
------>
f(1,2,3,4,5,6,7)
这种定位参数的方式会让人很累,造成可读性差、易出错。在隐式获取基础上,需要一种显示的获取(explicit capture),也就是赋值时指定变量名称。
通过名称捕获
名称捕获又叫关键字参数,Keyword arguments. 通过变量名直接指定参数值。
举个例子:
>>> def f(a,b,c,d,e,f,g):
... pass
...
>>> f(1,2,3,4,5,6,7)
>>> f(g=1,f=2,e=3,d=4,c=5,b=6,a=7)
>>>
如果要修改某个参数值,可以方便的找到参数名称。
关键字参数的优点是能通过变量名称了解参数值的含义,提高了可读性。
位置参数和关键字参数的混合
当有些参数以 位置参数形式提供,有些参数以 关键字参数 形式提供时,会遇到新问题:
如果 keyword argument 放置于 positional argument 前面,会抛出 SyntaxError
,因为如果列表前面提供了参数名称后,后面的参数就不能根据位置捕获。
举个例子:
>>> def f(a,b,c,d,e,f,g):
... pass
...
>>> f(g=1,f=2,3,4,5,b=6,a=7)
File "<stdin>", line 1
f(g=1,f=2,3,4,5,b=6,a=7)
^
SyntaxError: positional argument follows keyword argument
>>>
比如上面的例子里,参数值3
就很难说希望被哪个参数捕获。所以得到一条原则:
原则1:
基于这个原则,在设计函数时,需要把希望关键字参数放在参数列表后面。
位置参数和 关键字参数 两种参数属于简单的类型,合称 positional-or-keyword arguments
参数默认值
定义函数时,参数列表里可以为参数提供默认值,提供了默认值的参数是可选的,不具备默认值的参数是必选的。
因为可选参数有可能不提供,如果可选参数后面出现必选参数,就会出现混淆。
>>> def f(a,b=1,c):
File "<stdin>", line 1
def f(a,b=1,c):
^
SyntaxError: non-default argument follows default argument
>>>
上面的例子里,如果调用函数
f(1, 2)
就难以确定 2
是应该被 b
还是 c
捕获。
所以得到一条原则:
原则2:
可变长度位置参数
有一种情形,是不能预知 位置参数 的数量,比如 max()
函数:
>>> max('a')
'a'
>>> max('a','b','c')
'c'
>>>
使用 varargs 语法(*号,starred expression)将多个位置参数收集在一起,放入一个 parameter 中,这个 parameter 在函数内部作为一个 tuple 存在。
>>> def f(*a):
... print(a)
...
>>> f(1,2,3,4)
(1, 2, 3, 4)
>>>
可以理解为:参数 a
前的星号将参数列表 (1,2,3,4)
收集在一起,合成一个 tuple, 赋值给变量a
。这个过程叫 packing.
在定义函数时,参数名前的星号表示 packing;在调用函数时,参数名前的星号表示 unpacking:
>>> def f(*a):
... print(a)
...
>>> b='123'
>>> f(*b)
('1', '2', '3')
>>>
可变长度关键字参数
和 可变长度位置参数 类似,不能预知长度的 关键字参数 也可以用一个变量收集在一起,区别在于收集的结果不是 tuple 而是 dict, 而且使用两个星号。
>>> def f(**a):
... print(a)
...
>>> f(b=1, c=2)
{'b': 1, 'c': 2}
>>>
一样可以 unpacking
>>> f(**{'b':1,'c':2})
{'b': 1, 'c': 2}
>>>
注意参数名在字典中是以字符串形式存在。这个例子有个额外的好处:有些情况下,变量名不能方便的写在函数参数列表里(比如参数名里有空格),解决方法就是用 unpacking:
>>> def f(**a):
... for k,v in a.items():
... print(k,v,sep='->')
...
>>> f(**{'b d':1,'c':2})
b d->1
c->2
>>>
4种参数的两两组合
上面介绍了4种参数:
这4种参数存在4种两两组合场景,前两个参数的组合场景已经在 这一节 讨论。下面我们仔细观察另外3种场景下的参数定义。
普通位置参数 和 可变长度位置参数的组合
有这么一种场景,一些参数是固定的,另一些参数是不固定长度的,这样可以混合两种参数用法:
比如人的名字和携带的物品:
>>> def f(name, *inventory):
... print(name+': '+', '.join(inventory))
...
>>> f('Amy','water','food')
Amy: water, food
>>> f('Bob','car')
Bob: car
>>>
由于 packing 是将右边所有位置参数都收集在一起,所以带星号的参数不能出现在在普通的位置参数之前,比如(*a, b)
,不然后面的参数就会被收集进去。
在 Python 2.x 的时代,(*a, b)
这么定义函数是不合法的:
# Python 2.x
>>> def f(*a, b):
File "<stdin>", line 1
def f(*a, b):
^
SyntaxError: invalid syntax
>>>
在 Python 3.x 的时代,虽然允许了这种写法,但是*号表达式右边的参数必须以关键字形式提供,也就是(*a, b)
中的b
,这称为 Keyword-Only Arguments,另开一篇文章叙述。
普通位置参数 和 可变长度关键字参数的混合
类似上面一节的内容,
可以写出下面的函数定义:
>>> def f(name, **inventory):
... print(name+': '+', '.join([k+': '+v for k,v in inventory.items()]))
...
>>> f('Amy', food='bread', juice='apple')
Amy: food: bread, juice: apple
>>> f('Bob', car='jeep')
Bob: car: jeep
>>>
不允许普通位置参数写在 packing关键字参数后面:
将抛出 SyntaxError
>>> def f(**inventory, name):
File "<stdin>", line 1
def f(**inventory, name):
^
SyntaxError: invalid syntax
>>>
可变长度位置参数 和 可变长度关键字参数的混合
如果 位置参数数量和关键字参数数量都不能预知,那么就需要组合两种参数:
>>> def f(*names, **inventory):
... print(', '.join(names)+': '+', '.join([k+': '+v for k,v in inventory.items()]))
...
>>> f('Amy','Bob', car='jeep')
Amy, Bob: car: jeep
>>> f('Cindy', 'David',food='bread', juice='apple')
Cindy, David: food: bread, juice: apple
>>>
和 这一节相同的理由,可变长度位置参数 不能放在 可变长度关键字参数后面:
>>> def f(**inventory, *names):
File "<stdin>", line 1
def f(**inventory, *names):
^
SyntaxError: invalid syntax
>>>
结论
上面对 Python 函数的参数定义做了一番综述,概括下来有三类参数和三项原则:
三类参数
还有另外两种参数,在各自单独文章内介绍,分别是:
合计 五类参数
三项原则
位置参数
- 调用函数时,位置参数必须先于关键字参数提供。
- 定义函数时,必选参数必须先于可选参数提供。
- 定义函数时,普通的位置参数必须先于可变长度参数提供。
如果您对本文有疑问或者寻求合作,欢迎 联系邮箱 。邮箱已到剪贴板
精彩评论
本站 是个人网站,采用 署名协议 CC-BY-NC 授权。
欢迎转载,请保留原文链接 https://www.lfhacks.com/tech/python-function-arguments/ ,且不得用于商业用途。