文章目录
前言
简单罗列一下知识体系
一、数学运算
单个 *
用于乘法运算;
两个 **
表示幂运算
>>> 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章节来看,不过仍然有如下区别
- ‘*num’,作为参数传入函数中时,num数据类型是元组
- ‘*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'
"""