Python ——*号的常用用法


前言

  简单罗列一下知识体系
在这里插入图片描述

一、数学运算

  单个 *​​​ 用于乘法运算;
  两个 ​​​** ​​ 表示幂运算

>>> 2*4
>>> 8
>>> 2**4
>>> 16

二、制造重复序列

  序列与整数n相乘,相当于重复n次此序列。这里所指的序列包括字符串、元组和列表等。

>>> # 字符串与整数n相乘
>>> 'abc' * 3
'abcabcabc'
>>> # 列表与整数n相乘
>>> ['abc', 'xyz'] * 3
['abc', 'xyz', 'abc', 'xyz', 'abc', 'xyz']
>>> # 元组与整数n相乘
>>>> ('abc', 'xyz') * 3
('abc', 'xyz', 'abc', 'xyz', 'abc', 'xyz')
>>>

三、解包、赋值与重组

3.1 解包

3.1.1 列表、元组、字符串与集合解包

  我们使用*可以直接对列表、元组、字符串与集合解包,使得数组变为一个个的值,不再成为一个组合。

>>> print('未解包列表:', ['a', 2, 3], '|', '解包后的列表:', *['a', 2, 3])
未解包列表: ['a', 2, 3] | 解包后的列表: a 2 3
>>> print('未解包元组:', ('a', 2, 3), '|', '解包后的元组:', *('a', 2, 3))
未解包元组: ('a', 2, 3) | 解包后的元组: a 2 3
>>> print('未解包集合:', {'a', 2, 3}, '|', '解包后的集合:', *{'a', 2, 3})
未解包集合: {2, 'a', 3} | 解包后的集合: 2 a 3  # 集合为无序序列,所以无法保证输出顺序
>>> print('未解包字符串:', 'abc', '|', '解包后的字符串:', *'abc')
未解包字符串: abc | 解包后的字符串: a b c
>>>

3.1.2 字典的解包

  ***都可以对字典进行解包,但是最终解出的结果是不一样的。
  *作为字典类型数据的前缀,它的key将会被解包。

>>> print('未解包字典:', {'a': 1, 'b': 2, 'c': 3}, '|', '解包后的字典:', *{'a': 1, 'b': 2, 'c': 3})
未解包字典: {'a': 1, 'b': 2, 'c': 3} | 解包后的字典: a b c  # 这里只输出了字典的key值
>>> 

  **作为字典类型数据的前缀,它的键值对将会被解包。

>>> dict_example = {'a': 1, 'b': 2, 'c': 3}
>>> print('{a},{b},{c}'.format(**dict_example))
1,2,3
>>>

  可以看成**dict_example相当于'a'=1, 'b'=2, 'c'=3。这样就好理解print的输出结果了。

3.2 赋值

3.2.1 列表、元组与字符串赋值

  在给*valuename进行赋值时,会自动将一个列表、元组、字符串赋值给valuename,对于前后的其他变量,解释器将给它们单独赋值,特别注意的,如果只有一个*变量,需要写上逗号,表示它是一个列表在接收赋值。
  无论原来的类型是什么类型,valuename赋值后都是列表类型
  基本赋值方法:

>>> *a, = 1, 2, 3  # 给单独一个'*'变量赋值时必须要加',', 直接写成*a会提示语法错误
>>> print(a)
[1, 2, 3]
>>>
>>> tuple_instance = ('a', 'b', 'c')
>>> *b, = tuple_instance
>>> print(b)
['a', 'b', 'c']  # 我们将元组赋值给了*b但最后仍然输出的是列表
>>>

  我们知道python有a, b = 1, 2这种赋值表达式,把1赋值给a,把2赋值给b。那么当我们不清楚等号后有多少个常数的时候,我们可以用如下方式来赋值。

numbers = [1, 2, 3, 4, 5, 6]
*a, b = numbers
print(a)  # a = [1, 2, 3, 4, 5]
print(b)  # b = 6
a, *b, = numbers
print(a)  # a = 1
print(b)  # b = [2, 3, 4, 5, 6]
a, *b, c = numbers
print(a)  # a = 1
print(b)  # b = [2, 3, 4, 5]
print(c)  # c = 6
str1 = 'python'
s1,*s2,s3 = str1
print(s1)  # p
print(s2)  # ['y', 't', 'h', 'o']
print(s3)  # n
t = ('a', 'b', 'c', 'd', 'e')
t1,*t2,t3 = t
print(t1)  # a
print(t2)  # ['b', 'c', 'd']
print(t3)  # c

  由于*args被赋值后,args为列表类型,我们可以将任意多个值存储其中。
  在3.1.1章节中我们还提到了集合,但是由于集合是无序列表无法被确定赋值,因为从上面的例子可以看出*a, b = numbers这种是有序序列赋值,最后一个值赋值给b,其他赋值给*a

3.2.2 字典赋值

**kwargs无法像3.2.1章节那样赋值,没有**kwargs = ...此种写法。不过我们可以在定义函数中用到:

def date_of_birth(**kwargs):
    print('我的名字是{}, 生日信息如下:'.format(kwargs['name']))  # 可以在此处直接调用某一个字典值
    for key, value in kwargs.items():
        print(f'{key}: {value}')
        
date_of_birth(year=2001, day=10, month=8, name='Tom') 

  就像3.2.1章节里面说的**kwargs解包后为键值对,那么也可以将键值对反向打包赋值给**kwargs使kwargs成为字典,传给函数内部。

第三章总结:理解好这章节我认为非常重要,方便后面我们理解python函数中任意变参的写法,如下:

def foo(*args, **kwargs):
    pass

3.3 重组

3.3.1 列表、元组、字符串和集合重组

  可以通过*号先将列表解包,然后在通过括号将元素重新组织起来

>>> A = [1, 2, 3]
>>> B = (4, 5, 6)
>>> C = {7, 8, 9}
>>> D = 'python'
>>> L = [*A, *B, *C, *D]
>>> print(L)
[1, 2, 3, 4, 5, 6, 8, 9, 7, 'p', 'y', 't', 'h', 'o', 'n']
>>> A = ('1', '2', '3')
>>> B = ('a', 'b', 'c')
>>> C = {*A, *B}
>>> print(C)
{'2', '1', 'b', '3', 'a', 'c'}
>>>

  甚至在连接的过程中还可以加入其他的值:

>>> my_list_1 = [1, 2, 3]
>>> my_list_2 = [10, 20, 30]
>>> num= 'union'
>>> merged_list = [*my_list_1, num, *my_list_2]
>>> print(merged_list)
[1, 2, 3, 'union', 10, 20, 30]
>>>

3.3.2 字典重组

  与上一章节类似,仍然是先解包再重组,只不过字典解包需要用**

>>> dict1 = {'a': 1, 'b': 2}
>>> dict2 = {'c': 3, 'd': 4}
>>> dict_union1 = {**dict1, **dict2, 'e': 5}
>>> print(dict_union1)
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
>>> dict_union2 = dict(**dict1, **dict2)
>>> print(dict_union2)
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>>

  需要注意的是字典类型的key值具有唯一性。如果两个具有相同key的字典解包重组会被覆盖或者报错,尽量不要这样做。

>>> dict1 = {'a': 1, 'b': 2}
>>> dict2 = {'a': 3, 'd': 4}
>>> dict_union = {**dict1, **dict2, 'e': 5}
>>> print(dict_union)
{'a': 3, 'b': 2, 'd': 4, 'e': 5}  # 后面的dict2覆盖前面的值
>>>
>>> dict_union2 = dict(**dict1, **dict2)  # 这种情况会报错
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: dict() got multiple values for keyword argument 'a'
>>>

四、函数变参

  我们常用的函数基本都有固定参数个数,但是有些函数我们不知道会传进去几个参数,或者说我们需要传100个参数进去,我们也不太可能给函数定义100个参数,而可变参数就可以很好的解决这个问题。比较典型的例子就是Python的装饰器。

4.1 位置变参

  举个例子,你有一个函数,用来实现两个数求和

def add(num1, num2):
    return num1 + num2

print(add(1,2)) # 3

  上面这个函数只能接收固定个数(两个)的参数,那如果我想要求任意数量的值的和该怎么办?
  可以在形参部分加一个 *

def add(*num):
    print(type(num))  # <class 'tuple'>
    sum = 0
    for i in num:
        sum += i
    return sum

#使用任意数量的参数来调用函数
print(add(1, 2, 3, 4))  # 10

  在函数定义的时候形参前面加一个 * 号,就可以用任意数量的参数来调用函数。这种形式与3.2.1章节的赋值很像。
  那如果函数在定义的时候就已经固定了形参的个数

def add(num1, num2, num3):
    return num1 + num2 + num3

我们想要传递一个列表参数(假设这个列表有三个元素),可以这么调用函数。

my_list = [1, 2, 3]
add(my_list[0], my_list[1], my_list[2])

  我们可以对照3.1.1章节讲到的,直接用解包的方式传入变量。这样就可以很方便。

my_list = [1, 2, 3]
add(*my_list)

注:
此章节我们可以对照3.1.1和3.2.1章节来看,不过仍然有如下区别

  1. ‘*num’,作为参数传入函数中时,num数据类型是元组
  2. ‘*num’,作为被赋值的变量来看时,num数据类型是列表

4.2 关键字变参

  对于位置参数,你调用函数写入实参的顺序要与形参顺序一致,才能一对一赋值。

def add(num1, num2, num3):
    return num1 + num2 + num3

print(add(1,2,3)) # 6

  上面add(1,2,3)中虽然是一对一赋值的,但是由于几个值的作用相同,都作为求和对象。所以实参顺序不管如何变,结果都是一样的。我们可以不用理会。甚至可以用*num去替代num1, num2, num3

  但是下面这个函数不行

def date_of_birth(year, month, day):
    print(f'Year: {year}, Month:{month}, Day:{day}')

date_of_birth(2001, 8, 10)  # Year: 2001, Month:8, Day:10

  上面函数2001,8,10的顺序不能乱,顺序不对最后的年月日输出就会有问题,而且可读性比较差。如果我们要改成上述*形式更不知道函数写的是什么了。

def date_of_birth(*date):
    print(f'Year: {date[0]}, Month:{date[1]}, Day:{date[2]}')

date_of_birth(2001, 8, 10)  # Year: 2001, Month:8, Day:10

  我们如下这样书写和调用可读性比较高,而且不必在意书写顺序:

def date_of_birth(year, month, day):
    print(f'Year: {year}, Month:{month}, Day:{day}')

date_of_birth(year=2001, month=8, day=10)  # Year: 2001, Month:8, Day:10
date_of_birth(year=2001, day=10, month=8)  # Year: 2001, Month:8, Day:10

  接下来我们又遇到了跟上一章节同样的问题,如果我们想加入一个参数name那么我们就要重写函数。

def date_of_birth(name, year, month, day):
    print(f'Name:{name}, Year: {year}, Month:{month}, Day:{day}')

date_of_birth(year=2001, day=10, month=8, name='Tom')  # Name:Tom, Year: 2001, Month:8, Day:10

  我们可以利用**把传进来的键值对实参打包成字典。

def date_of_birth(**kwargs):
    print('我的名字是{}, 生日信息如下:'.format(kwargs['name']))  # 可以在此处直接调用某一个字典值
    for key, value in kwargs.items():
        print(f'{key}: {value}')
        
date_of_birth(year=2001, day=10, month=8, name='Tom') 

  输出:

我的名字是Tom, 生日信息如下:
year: 2001
day: 10
month: 8
name: Tom

  这样我们就可以向此函数加入任意个关键字实参。比如:再加一个年龄age = 18
  其实我们对这样还是不满意,我们可以把一个字典解包当成参数传给我们的函数:

tom_info = {'year':2001, 'day':10, 'month':8, 'name':'Tom'}

def date_of_birth(**kwargs):
    print('我的名字是{}, 生日信息如下:'.format(kwargs['name']))
    for key, value in kwargs.items():
        print(f'{key}: {value}')

date_of_birth(**tom_info)  # 这里就相当于先对实参tom_info解包,再赋值给形参**kwargs, 使得kwargs为被赋值后的字典类型数据

  与上面的输出是一样的

4.3 任意变参

  按照惯例,当我们定义的函数接收不定数量的参数时,我们一般采用以下函数定义形式:

def foo(*args, **kwargs):
    pass

注意:位置参数一定要放在关键字参数之前,下面这个声明是错误的:

def save_ranking(**kwargs, *args):
    ...

  这里可以结合商量个章节所说使用,只不过把其揉合在了一起,可以接收任意个位置参数关键字参数

4.4 星号在函数中的其他用法

4.4.1 不允许使用位置参数

def function(*, keyword_arg):
    ...

  这样写,* 的后面无法传入位置参数(只能传入关键字参数),如果传入会报错

>>> def function(*, keyword_arg):
...     print(keyword_arg)
...
>>> function(1, 2, 3, keyword_arg=4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: function() takes 0 positional arguments but 3 positional arguments (and 1 keyword-only argument) were given
>>>

  上面告诉你function() takes 0 positional arguments

4.4.2 只允许传入固定数量的位置参数和关键字参数

def function(*, keyword_arg):
    ...

  这样写,* 后面无法传入位置参数(只能传入关键字参数),我们要注意后面这个词,反之如果是前面呢?

>>> def function(pos1, pos2, *, keyword_arg):
...     print(pos1, pos2, keyword_arg)
...
>>> function(1, 2, keyword_arg=4)  # 此种调用方法可以正常输出
1 2 4
>>> function(1, 2, 4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: function() takes 2 positional arguments but 3 were given
>>>

  第一次调用可以正常输出,第二次调用告诉你function() takes 2 positional arguments but 3 positional arguments只有两个位置参数,而你却给了三个,最后的4需要写成关键字参数形式。
  总的来说,*为分水岭,前面是位置参数,后面是关键字参数。

4.4.3 不允许传入关键字参数

  如果我们想强制只使用位置参数,使用 /号来实现

def only_positional_arguments(arg1, arg2, /):
    pass

  如果你传递关键字参数,会发生报错

only_positional_arguments(arg1=1, arg2=2)
"""
TypeError: only_positional_arguments() got some positional-only arguments passed as keyword arguments: 'arg1, arg2'
"""

参考文章

  1. Python 中星号(*)的用法小结
  2. Python中的星号*还能这么用你知道吗
  • 25
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值