1

Python 的 函数参数处理机制

 2 years ago
source link: https://www.lfhacks.com/tech/python-function-arguments/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文综述 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 的过程
capture.png

parameter 列表形成一个个的 slot,而 argument 包含若干个 value,通过一系列规定好的逻辑,将 value 捕获到 slot 中去,这就是参数处理机制(parameter handling mechanism),本文的主题围绕这一个捕获过程,系统的梳理一下参数处理机制。

通过位置捕获

最简单的捕获方式就是通过位置捕获,这和 shell 函数相同:

# 定义def f(a, b):    pass # 调用f(1, 2)

这里的 ab 靠自身所处的位置捕获参数值,称为 Positional arguments. 这种通过位置对应获取参数的方式可以称为

  • 隐式(implicit)的获取
positional.png

参数值会按照位置一一对应到参数列表中的变量中去。如果提供的参数列表个数和定义的不符合,无论多还是少,都会抛出 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. 通过变量名直接指定参数值。

keyword.png

举个例子:

>>> 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 ,因为如果列表前面提供了参数名称后,后面的参数就不能根据位置捕获。

wrongkwargs.png

举个例子:

>>> 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就很难说希望被哪个参数捕获。所以得到一条原则:

调用函数时,位置参数必须先于关键字参数提供。

基于这个原则,在设计函数时,需要把希望关键字参数放在参数列表后面。

rightkwargs.png

位置参数关键字参数 两种参数属于简单的类型,合称 positional-or-keyword arguments

参数默认值

定义函数时,参数列表里可以为参数提供默认值,提供了默认值的参数是可选的,不具备默认值的参数是必选的。

default.png

因为可选参数有可能不提供,如果可选参数后面出现必选参数,就会出现混淆。

>>> def f(a,b=1,c):  File "<stdin>", line 1    def f(a,b=1,c):                 ^SyntaxError: non-default argument follows default argument>>>
wrongdefault.png

上面的例子里,如果调用函数

f(1, 2)

就难以确定 2 是应该被 b 还是 c 捕获。

所以得到一条原则:

定义函数时,必选参数必须先于可选参数提供。

rightdefault.png

可变长度位置参数

有一种情形,是不能预知 位置参数 的数量,比如 max() 函数:

>>> max('a')'a'>>> max('a','b','c')'c'>>>
var-positional.png

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

arguments-unpacking.png

在定义函数时,参数名前的星号表示 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}>>>
var-keyword.png

一样可以 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->1c->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>>>
hybrid1.png

由于 packing 是将右边所有位置参数都收集在一起,所以带星号的参数不能出现在在普通的位置参数之前,比如(*a, b),不然后面的参数就会被收集进去。

hybrid2.png

在 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,另开一篇文章叙述。

普通位置参数 和 可变长度关键字参数的混合

类似上面一节的内容,

hybrid3.png

可以写出下面的函数定义:

>>> 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关键字参数后面:

hybrid4.png

将抛出 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>>>
hybrid5.png

这一节相同的理由,可变长度位置参数 不能放在 可变长度关键字参数后面:

>>> def f(**inventory, *names):  File "<stdin>", line 1    def f(**inventory, *names):                       ^SyntaxError: invalid syntax>>>

上面对 Python 函数的参数定义做了一番综述,概括下来有三类参数和三项原则:

还有另外两种参数,在各自单独文章内介绍,分别是:

合计 五类参数

调用函数时,位置参数必须先于关键字参数提供。
定义函数时,必选参数必须先于可选参数提供。
定义函数时,普通的位置参数必须先于可变长度参数提供。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK