探索 *args

总的需求:初识 args

从一个小的需求开始——连加函数

可能的需求::我想写一个做多个数字的加法的函数,但是我并不确定有多少个数需要相加。

一般来讲可以使用 for 循环来满足这个需求,在运用这种方法的时候首先需要构造一个列表,在列表里输入我们需要相加的数字。

def my_sum(my_integers_list):
	result = 0;
	for x in my_integers_list:
		result += x
	return result

list_of_integers = [1, 2, 3]
print(my_sum(list_of_integers))

除此之外还可能想到使用 递归1 的方法构造相同功能的函数,与上一种方法相同的是仍然要构造一个存放等待相加的数的列表作为参数。

def my_sum(my_integers_list):  
    if len(my_integers_list) == 1:  
        return my_integers_list[0]  
    else:  
        return my_integers_list[0] + my_sum(my_integers_list[1:])  
  
  
my_integers_list = [1, 2, 3]  
print(my_sum(my_integers_list))

还有没有其他的方法实现相同的功能,但是更加方便? *args 为我们提供了这种可能,准确的说是 *args 中的 * 为我们提供了这种可能,因为 args 可以替换为替他我们想要的名称,比如我想输入的参数是整数,那我就将其命名为 *integers 或者其他任何你喜欢的名字。

def my_sum(*integers):
	result = 0
	for x in integers:
		result += x
	return result

print(my_sum(1, 2, 3))

连加函数引出的需求——认识解压操作符

这种构造方法在功能上和以上两种方法相同,它的优越性在于可以输入任意数量的整数而不必构造数列。以上,一个小小的需求得到了满足。与此同时一个新的疑问产生了: * 做了什么?这是一个新的需求: 我想了解一下实现这种方法的原理。为了满足这个需求,我们需要先了解一些概念:

  1. 解压缩操作符:* 就是一个解压缩操作符,它的作用是将迭代对象中的元素释放出来2
  2. 可迭代对象:可以一次性返回所有元素,并且可以被 for 循环迭代的对象,例如 列表元组字典 等等。3

列表 为例,看一下解压缩操作符的作用效果,首先创建一个可迭代对象——列表,然后按照解压缩操作符的语法规则将其作用于可迭代对象:

my_list = [1, 2, 3]
print(my_list)
print(*my_list)
# 输出的结果为:
[1, 2, 3]
1 2 3

再来以 元组 为例,看一下解压缩操作符的作用效果:

my_tuple = 1, 2, 3
print(my_tuple)
print(*my_tuple)
# 输出的结果为:
(1, 2, 3)
1 2 3

在这个过程中我很好奇 *my_list*my_tuple
的数据类型是什么,从输出的值来看,1 2 3 是三个孤立的整数,无论如何可以确定的是 *my_list*my_tuple 肯定不再是 可迭代对象 ,我们可以用 type 函数试一下:

my_tuple = 1, 2, 3
print(type(*my_tuple))
# 输出结果为:
…… 省略 ……
TypeError: type.__new__() argument 1 must be str, not int

无法显示其类型,暂时不去追究这个问题,还记得之前的第三个关于* 解压缩操作符的连加函数吗,我们传入的参数是 1, 2, 3 ,它们的整体是一个元组,也就是一个可迭代对象,这正是解压缩操作符可以操作的对象,一个直观的想法是我们在调用函数时输入的参数 1, 2, 3 等同于定义函数时的形式参数 *integers ,也就是满足等式 *integers = 1, 2, 3

def my_sum(*integers):
	result = 0
	for x in integers:
		result += x
	return result

print(my_sum(1, 2, 3))

但事实并非如此,我们可以做一个实验来验证这个等式的错误:

>>> *integers = 1, 2, 3
Traceback (most recent call last):
  …… 省略 ……
  File "<input>", line 1
SyntaxError: starred assignment target must be in a list or tuple

解压操作符引出的需求——解释错误提示

这里的错误提示是理解 解压缩操作符 * 的用法的关键,它写到 “星号(解压缩操作符)的指派对象必须位于一个列表或者元组中” ,下面用几个例子来解释这句话的含义,首先了解一下一下概念:

  1. 元组的打包:指派多个元素到一个元组中,例如 t = (1, 2, 3) .4
  2. 元组的解压:指派元组中的元素到多个变量中,例如 (a, b, c) = (1, 2, 3) 4
元组的打包
常规元组形式

一般来讲,元组打包的方式有两种,可以是 t = (1, 2, 3) 这种标准形式,也可以 f = 1, 2, 3 这种便捷形式:

>>> t = (1, 2, 3)
>>> f = 1, 2, 3
>>> t
(1, 2, 3)
>>> f
(1, 2, 3)
>>> type(t)
<class 'tuple'>
>>> type(f)
<class 'tuple'>

除了这些常规形式的元组外还要特别留意一下一些特殊形式的元组,比如 空元组单元素 元组,尤其是后者,它关乎我们对 解压缩操作符 用法的正确理解。

特殊元组形式——空元素和单元素
# 空元素
>>> a = ()
>>> type(a)
<class 'tuple'>

# 单个元素
>>> b = (2)
>>> b
2
>>> tpye(b)
<class 'int'>

>>> c = ('str')
>>> c
'str'
>>> type(c)
<class 'str'>

观察发现当输入元素为单个元素是变量的类型是元素的原始类型,而不是元组!按照正常的逻辑应该存在单元素元组,事实也正是如此,它的实现需要借助一个看似多余但却十分关键的标记:, 逗号
如果不借助 逗号b = (2) 等价于 b = 2 , c = ('str') 等价于 c = 'str' ,而加上 逗号 之后变量的性质就会变得不同,在这里可以使用便捷写法,去掉圆括号() 后可以得到同样的结果。

>>> a = (2,)
>>> a
(2,)
>>> type(a)
<class 'tuple'>

>>> c = ('str')
>>> c
('str',)
>>> type(c)
<class 'tuple>
元组的解压
对应位置赋值解压

上文中已经提到了一种解压方式,可以将其理解为在对应位置将值赋给相应的变量。

>>> t = 1, 2, 3
>>> a, b, c = t
>>> a
1
>>> b
2
>>> c
3

这里指的留意的是 a, b, c 中的每一个元素都位于元组中,而这是一种非常一般的元组形式,我们仍然要特别留意一些特殊情况,那就是单元素元组情况。

>>> t = 1,
>>> a = t
>>> a
(1,)

似乎没什么特别的地方,事实也确实如此,但是当我们联系到 解压缩操作符 的时候,它会变得非同寻常,接下来我们要来认识一种新的 解压 方式:

含有星号变量的解压
>>> a, *b, c = 1, 2, 3, 4
>>> a
1
>>> *b
……
SyntaxError: can't use starred expression here
>>> b
[2, 3]
>>> c
4

第一行中,赋值符号的右侧是一个用便捷方式写的元组,同样左侧也是这样,容易发现这是一个元组的解压操作,也就是将元组中的元素指派给多个变量,这些变量就是左侧的这些变量,需要注意的是 *b 并不是一个变量,b 才是,从代码中关于 b 的运算中可以得出这个结论。

可以观察到左侧的变量数和右侧的元组元素个数并不相同,变量更少一些,也就是说如果一一对应的话右侧会多出一个元素 4 ,但从实验结果中可以看出并没有发生这种情况,止于这中间发生了什么我暂时还不知道,只是容易得出这个结论:解压缩操作符使变量 b 容纳了剩余的元组元素,并构成了一个列表。我们可以更多的实验来深入了解解压缩操作符的运作方式:

>>> *a, *b, c = 1, 2, 3, 4 
……
SyntaxError: multiple starred expressions in assignment
# 同时使用多个解压缩操作符会出现语法错误提示。

>>> a, *b, c = 1, 2
>>> a
1
>>> b
[]
>>> c
2
# 可以推测没有解压缩操作符的变量会优先被赋值

基于对这种解压方式的理解,我们来讨论关于单元素元组的特殊解压情况,首先回忆一下我们在 解压操作符引出的需求——解释错误提示 这个标题下遇到的错误提示:“星号(解压缩操作符)的指派对象必须位于一个列表或者元组中” ,我们当时的需求是将元组中的整数元素 1, 2, 3 赋值给变量 integerts ,如今我们已经澄清了一个重要的区分:真正的变量不是 *integers 而是 integers ,在赋值的过程中给我们遇到上面提到的错误提示,它提示我们要将 *integers 放在一个元组或者列表中,而不是放在其他任何位置,那我们原本放在那里了呢?*integers = 1, 2, 3 ,回顾我们之前提到的关于 单元素元组 的打包的相关讨论,可以得出这样的结论:*integers 并没有位于元组中或者列表里,它只是一个孤立的变量。回顾打包单元素元组的方法,我们只需要在 *integers 的后面添加一个 逗号 就可以了,当然我们也可以让他处于一个列表中:

# 这是一种便捷但是有着奇怪长相的表达式
>>> *integers, = 1, 2, 3
>>> integers
[1, 2, 3]

# 也可以这样表示,左侧是一个标准的单元素元组形式
>>> (*integers,) = 1, 2, 3
>>> integers
[1, 2, 3]

# 也可以将 *integers 置于列表中
>>>[*integers] = 1, 2, 3
>>>integers
[1, 2, 3]

>>> type(integers)
<class 'list'>

这时候再来看我们最初的那个连加函数,结合我们对 元组含有星号变量的解压 以及 解压缩操作符 的理解,不难得出这个结论:我们输入一个元组 1, 2, 3 ,然后会有一步解压操作,也就是将元组中的元素值指派给多个变量,如果变量数和元素个数相等,那么将会是一一对应的赋值,如果变量中存在 含有星号操作符的变量 ,那么没有星号的变量会先被赋值,含有星号的变量最后被赋值,而在这里只含有 带星号的变量 ,所以所有的值都会被赋给该变量,并保存为列表格式。

def my_sum(*integers):
	result = 0
	for x in integers:
		result += x
	return result

print(my_sum(1, 2, 3))

关于 args 的讨论就先到这,kwargsargs 类似,后面再说。


  1. 参考《Python 数据结构和算法分析》——布拉德利 • 米勒 戴维 • 拉努姆 这本书的第4章 ↩︎

  2. https://realpython.com/python-kwargs-and-args/#unpacking-with-the-asterisk-operators ↩︎

  3. https://www.pythonlikeyoumeanit.com/Module2_EssentialsOfPython/Iterables.html ↩︎

  4. https://levelup.gitconnected.com/improve-your-python-coding-tuple-packing-and-unpacking-44bd92daab31 ↩︎ ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值