Python 中星号(* 或 **)的动态参数详解

发布时间 2023-03-27 20:12:22作者: konglingbin

*参数与**参数是Python参数中的重点知识,他们都被称为可变参数(任意参数),我们经常会在代码中看到*args、**kwargs

函数的两种参数类型:

 Python的函数的输入参数有两种类型,一种是位置参数(positional argument),一种是关键字参数(keyword argument)。

所谓positional argument位置参数,是指用相对位置指代参数。关键字参数(keyword argument),见名知意使用关键字指代参数。位置参数或者按顺序传递参数,或者使用名字,自然使用名字时,对顺序没有要求。

作为函数定义时:收集未匹配参数组成tuple或dict对象

1、*参数收集所有未匹配的位置参数组成一个tuple对象,局部变量args指向此tuple对象

2、**参数收集所有未匹配的关键字参数组成一个dict对象,局部变量kwargs指向此dict对象

  1.  def temp(*args,**kwargs):
  2.  pass

作为函数调用时:解包功能

1、*参数用于解包tuple对象的每个元素,作为一个一个的位置参数传入到函数中

2、**参数用于解包dict对象的每个元素,作为一个一个的关键字参数传入到函数中 

  1.  my_tuple = ("wang","yuan","wai")
  2.  temp(*my_tuple)
  3.  #---等同于---#
  4.  temp("wangyuan","yuan","wai")
  1.  my_dict = {"name":"wangyuanwai","age":32}
  2.  temp(**my_dict)
  3.  #----等同于----#
  4.  temp(name="wangyuanwai",age=32)

二、   *args 例子

这些基本概念暂时不理解很正常,完全理解需要一个过程……接下来的几个 *args 例子会说明这些概念, 来帮助学习!!

1)包含两个位置参数的函数print_str

  1.  def print_str(first, second):
  2.  print(first)
  3.  print(second)

只传1个参数调用print_str()函数,会发生什么呢?

  1. In [31]: print_str("hello")
  2.   TypeError: print_str() missing 1 required positional argument: 'second'

TypeError:解释器在控制台告知print_str()函数需要2个参数,而你只为print_str()函数传入了1个参数!

思考:怎么修改print_str()函数为即可接受一个参数、也可接受两个参数、甚者接受数量不定的更多参数呢?

2)修改print_str()函数可接受一个参数、也可接受数量不定的参数

将print_str()函数的最后一个参数修改为可变参数*second

  1.  def print_str(first, *second):
  2.  print(first)
  3.  print(second)

此时我们再传一个参数调用print_str()函数,看看这次发生什么?

  1.  print_str("hello")
  2.  hello

这次不再报错,传入的第一个字符串参数"hello"打印出来了,没有传入参数的*second则打印的是一个tuple对象的字符串表示形式,即一个括号"()"  。 注意:()表示含有0个元素的tuple对象!

思考:为什么second变量变成一个tuple对象了?我们继续向下学习!

3)再做一个实验,为print_str()函数传入四个参数…会发生什么?

  1.  In [35]: print_str("hello","美女","小猫","青蛙")
  2.  hello
  3.  ('美女', '小猫', '青蛙')

第一个参数“hello”,正常打印在第一行……

第二个参数"美女",第三个参数“小猫”,第四个参数“青蛙”在函数的内部被组装进1个新的tuple对象中,而这个新的tuple对象会赋值给变量second,此时局部变量second指向了一个tuple对象

说明:函数调用时传入的参数,会按照从左到右的顺序依次在函数中使用,最左侧的参数先由位置参数first使用(匹配),剩下的所有未匹配的参数会被自动收集到1个新的tuple对象中,而局部变量second会指向这个新的tuple对象

注意:*参数只收集未匹配的位置参数

4)调用print_str()函数时,直接传入一个 *参数会发生什么?

  1.  def print_str(first, *second):
  2.  print(first)
  3.  print(second)

控制台调用:

  1.  In [38]: numbers_strings = ("1","2","3","4","5")
  2.  ...: print_str(*numbers_strings) # 注意这里的*numbers_strings
  3.  1
  4.  ('2', '3', '4', '5')

说明:*numbers_strings出现在函数调用时,称为解包(一个“*”字符后面紧挨着1个tuple对象),numbers_strings自身是一个tuple对象,所以也称为元组的解包,tuple中的元素解包成一个一个的位置参数传入到函数中,所以才有下面两个语句的相等性!

print_str(*numbers_strings) 

等同于

print_str("1","2","3","4","5")  

5)未定义可变参数的函数被调用时,传入*参数会发生什么呢?

  1.  def print_str(first, second):
  2.  print(first)
  3.  print(second)

控制台调用:

  1. In [40]: numbers_strings = ("1","2")
  2.  ...: print_str(*numbers_strings)
  3.  1
  4.  2

print_str(*numbers_string)

等同于

print_str("1","2")

元组解包的过程中会将每一个元素依次放入到位置参数,这说明元组的解包功能的如下特点:

1、可以在可变参数中使用

2、也可以在未定义可变参数的函数上使用

元组解包功能是完全独立的一个功能

再次说明:*参数,出现在函数的不同的位置上时,具备不同的功能

1、当*参数出现在函数定义时,表示可变参数

2、当*参数出现在函数调用时,则表示解包功能

注意:解包tuple的时候,tuple的元素数量要与函数的位置参数总数一致

三、**kwargs例子

1)函数定义中,参数名称前有两个**

  1.  def printStr(**anything):
  2.  print(anything)

传入两个关键字参数调用printStr函数,看看发生什么?

  1.  In [42]: printStr(first = 5, second = 100)
  2.  {'first': 5, 'second': 100}

打印结果为dict对象的字符串形式,为什么anything成为dict了?

说明:函数调用时,传入的关键字参数有匹配的位置参数时,则位置参数优先使用(匹配)这些关键字参数,剩余所有未使用(未匹配)的关键字参数会在函数内组装进一个dict对象中,组装后dict对象会赋值给变量名anything,此时局部变量anything指向一个dict对象

注意:**参数只收集未匹配的关键字参数

2)函数调用时使用字典解包功能(dict对象前加**)

  1.  def printStr(first, **dict):
  2.  print(str(first) + "\n")
  3.  print(dict)

控制台调用:

  1.   printDic = {"name": "tyson", "age":"99"}
  2.  ..: printStr(100, **printDic)
  3.  100
  4.  {'name': 'tyson', 'age': '99'}
  5.  #等同于
  6.  In [45]: printDic = {"name": "tyson", "age":"99"}
  7. ..: printStr(100, name = "tyson", age = "99")
  8.  100
  9.  {'name': 'tyson', 'age': '99'}

说明:函数调用时,在一个dict对象的前面,添加**,表示字典的解包,它会把dict对象中的每个键值对元素,依次转换为一个一个的关键字参数传入到函数中。

四、总结

Python语法中,当*参数和**参数同时出现在函数定义的参数列表中时,说明参数列表可接受任意数量的参数,它们都统称为可变参数。

函数定义时

1、*args表示可接受任意个(包含0个)位置参数,当函数调用时,所有未使用(未匹配)的位置参数会在函数内自动组装进一个tuple对象中,此tuple对象会赋值给局部变量args

2、**kwargs表示可接受任意个(包含0个)关键字参数,当函数调用时,所有未使用(未匹配)的关键字参数会在函数内组装进一个dict对象中,此dict对象会赋值给局部变量kwargs

注意:函数定义时,二者同时存在,一定需要将*args放在**kwargs之前

函数调用时

1、*args表示解包元组对象中的每个元素作为位置参数传入到被调用函数中

2、**kwargs表示解包字典对象中的每个元素作为关键字参数传入到被调用函数中

注意事项
1、可变参数,可以传数量不定的多个参数,包括0个参数

2、可变参数,必须定义在普通参数(也称位置参数、必选参数、选中参数等名称)以及默认值参数的后面,这是因为可变参数会收集所有【未匹配】的参数,如果将可变参数定义在前面,那么普通参数与默认值参数就无法匹配到传入的参数,因为全都收集到可变参数中了。

  1. def printStr(普通参数,默认值参数name="王员外",*参数,**参数):
  2.  pass

3、*参数必须定义在**参数的前面

  1.  def printStr(普通参数,*参数,**参数):
  2.  pass

4、调用包含*args参数的函数时,不要直接传入一个tuple对象,如果传入的是一个tuple对象,那么这个tuple对象只会成为未匹配的,函数内组装的tuple对象中一个元素而已。我们可以将tuple对象的元素使用元组解包语法传入,解包语法:*tuple。

  1.  temp = (1,2,3,4,5)
  2.  def my_first(*args):
  3.  print(args)
  4. my_first(temp) #temp只算一个参数,除非你有这个需求
  5. my_first(*temp) #OK

5、调用包含**kwargs参数的函数时,不要直接传入一个字典对象,一个字典对象只算一个参数,此时会报错,因为一个dict对象不符合关键字参数的语法规范,字典对象可以使用字典解包语法,解包语法: **dict

  1.  my_book = {"first":"小当家", "seoncd": "我是baby"}
  2.  def my_blood(**kwargs):
  3.  print(kwargs)
  4.  
  5. my_blood(my_book) #作为一个字典对象传入
  6.  my_blood(**my_book) #一个一个的关键字参数传入

6、*参数的变量名,一般使用变量名args,只是建议,你想叫啥名都行,它只是局部变量名

7、**参数的变量名,一般使用变量名kwargs,只是建议,你想叫啥名都行,它也是个局部变量名

 参考:https://www.jb51.net/article/206158.htm

  https://blog.csdn.net/cadi2011/article/details/84871401?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-84871401-blog-113512343.235%5Ev27%5Epc_relevant_recovery_v2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-84871401-blog-113512343.235%5Ev27%5Epc_relevant_recovery_v2&utm_relevant_index=2

https://blog.csdn.net/cadi2011/article/details/84871401?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-84871401-blog-113512343.235%5Ev27%5Epc_relevant_recovery_v2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-84871401-blog-113512343.235%5Ev27%5Epc_relevant_recovery_v2&utm_relevant_index=2