更新了第11课和第12课的文档
parent
73659c60e7
commit
a47f917331
Binary file not shown.
After Width: | Height: | Size: 288 KiB |
|
@ -126,7 +126,7 @@ print(add(c=50, a=100, b=200))
|
|||
|
||||
#### 可变参数
|
||||
|
||||
接下来,我们还可以实现一个对任意多个数求和的`add`函数,因为Python语言中的函数支持可变参数,所谓可变参数指的是在调用函数时,可以向函数传入0个或任意多个参数。将来我们以团队协作开发的模式做商业项目时,可能需要去设计一个函数给其他人使用,但我们又不知道函数的调用者会向该函数传入多少个参数,这个时候可变参数就可以派上用场。下面的代码演示了用可变参数实现对任意多个数求和的`add`函数。
|
||||
接下来,我们还可以实现一个对任意多个数求和的`add`函数,因为Python语言中的函数可以通过星号表达式语法来支持可变参数。所谓可变参数指的是在调用函数时,可以向函数传入0个或任意多个参数。将来我们以团队协作的方式开发商业项目时,很有可能要设计函数给其他人使用,但有的时候我们并不知道函数的调用者会向该函数传入多少个参数,这个时候可变参数就可以派上用场。下面的代码演示了用可变参数实现对任意多个数求和的`add`函数。
|
||||
|
||||
```Python
|
||||
"""
|
||||
|
@ -137,7 +137,7 @@ Author: 骆昊
|
|||
"""
|
||||
|
||||
|
||||
# 在参数名前面的*表示args是一个可变参数
|
||||
# 用星号表达式来表示args可以接收0个或任意多个参数
|
||||
def add(*args):
|
||||
total = 0
|
||||
# 可变参数可以放在for循环中取出每个参数的值
|
||||
|
|
|
@ -102,7 +102,7 @@ print(len('goodbye, world')) # 14
|
|||
|
||||
#### 索引和切片
|
||||
|
||||
如果希望从字符串中取出某个字符,我们可以对字符串进行索引运算,运算符是`[n]`,其中`n`是一个整数,假设字符串的长度为`N`,那么`n`可以是从`0`到`N-1`的整数,其中`0`是字符串中第一个字符的索引,而`N-1`是字符串中最后一个字符的索引,通常称之为正向索引;在Python中,字符串的索引也可以是从`-1`到`-N`的整数,其中`-1`是最后一个字符的索引,而`-N`则是第一个字符的索引,通常称之为负向索引。
|
||||
如果希望从字符串中取出某个字符,我们可以对字符串进行索引运算,运算符是`[n]`,其中`n`是一个整数,假设字符串的长度为`N`,那么`n`可以是从`0`到`N-1`的整数,其中`0`是字符串中第一个字符的索引,而`N-1`是字符串中最后一个字符的索引,通常称之为正向索引;在Python中,字符串的索引也可以是从`-1`到`-N`的整数,其中`-1`是最后一个字符的索引,而`-N`则是第一个字符的索引,通常称之为负向索引。注意,因为**字符串是不可变类型**,所以**不能通过索引运算修改字符串中的字符**。
|
||||
|
||||
```Python
|
||||
s = 'abc123456'
|
||||
|
@ -121,6 +121,8 @@ print(s[2], s[-7]) # c c
|
|||
print(s[5], s[-4]) # 3 3
|
||||
```
|
||||
|
||||
需要提醒大家注意的是,在进行索引操作时,如果索引越界(正向索引不在`0`到`N-1`范围,负向索引不在`-1`到`-N`范围),会引发`IndexError`异常,错误提示信息为:`string index out of range`(字符串索引超出范围)。
|
||||
|
||||
如果要从字符串中取出多个字符,我们可以对字符串进行切片,运算符是`[i:j:k]`,其中`i`是开始索引,索引对应的字符可以取到;`j`是结束索引,索引对应的字符不能取到;`k`是步长,默认值为`1`,表示从前向后获取相邻字符的连续切片,所以`:k`部分可以省略。假设字符串的长度为`N`,当`k > 0`时表示正向切片(从前向后获取字符),如果没有给出`i`和`j`的值,则`i`的默认值是`0`,`j`的默认值是`N`;当`k < 0`时表示负向切片(从后向前获取字符),如果没有给出`i`和`j`的值,则`i`的默认值是`-1`,j的默认值是`-N - 1`。如果不理解,直接看下面的例子,记住第一个字符的索引是`0`或`-N`,最后一个字符的索引是`N-1`或`-1`就行了。
|
||||
|
||||
```Python
|
||||
|
@ -175,6 +177,26 @@ print(s[::-1]) # 654321cba
|
|||
print(s[::-2]) # 642ca
|
||||
```
|
||||
|
||||
#### 循环遍历
|
||||
|
||||
如果希望从字符串中取出每个字符,可以使用`for`循环对字符串进行遍历,有两种方式。
|
||||
|
||||
方式一:
|
||||
|
||||
```Python
|
||||
s1 = 'hello'
|
||||
for index in range(len(s1)):
|
||||
print(s1[index])
|
||||
```
|
||||
|
||||
方式二:
|
||||
|
||||
```Python
|
||||
s1 = 'hello'
|
||||
for ch in s1:
|
||||
print(ch)
|
||||
```
|
||||
|
||||
### 字符串的方法
|
||||
|
||||
在Python中,我们可以通过字符串类型自带的方法对字符串进行操作和处理,对于一个字符串类型的变量,我们可以用`变量名.方法名()`的方式来调用它的方法。所谓方法其实就是跟某个类型的变量绑定的函数,后面我们讲面向对象编程的时候还会对这一概念详加说明。
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
## 第010课:函数和字符串的应用
|
||||
|
||||
前面的两节课,我们介绍了函数和字符串。接下来我们通过一些案例来为大家讲解函数和字符串的应用。
|
||||
前面两节课,我们介绍了函数和字符串。在讲解今天的内容之前,先来回答一个可能会让大家感到费解的问题:为什么字符串类型(`str`)可以通过调用方法的方式进行操作,而之前我们用到的数值类型(如`int`、`float`)却没有可以调用的方法。在Python中,数值类型是标量类型,也就是说这种类型的变量没有可以访问的内部结构;而字符串类型是一种结构化的、非标量类型,所以才会有一系列的方法可供调用。如果对这一点感到困惑,那就继续学习吧,等学习完面向对象编程的知识后,你就能找到这些问题的答案了。
|
||||
|
||||
接下来我们通过一些案例来为大家讲解函数和字符串的应用。
|
||||
|
||||
### 一些案例
|
||||
|
||||
|
|
|
@ -0,0 +1,277 @@
|
|||
## 第011课:常用数据结构之元组和列表
|
||||
|
||||
在开始本节课的内容之前,我们先给大家一个编程任务,将一颗色子掷6000次,统计每个点数出现的次数。这个任务对大家来说应该是非常简单的,我们可以用1到6均匀分布的随机数来模拟掷色子,然后用6个变量分别记录每个点数出现的次数,相信大家都能写出下面的代码。
|
||||
|
||||
```Python
|
||||
import random
|
||||
|
||||
f1 = 0
|
||||
f2 = 0
|
||||
f3 = 0
|
||||
f4 = 0
|
||||
f5 = 0
|
||||
f6 = 0
|
||||
for _ in range(6000):
|
||||
face = random.randint(1, 6)
|
||||
if face == 1:
|
||||
f1 += 1
|
||||
elif face == 2:
|
||||
f2 += 1
|
||||
elif face == 3:
|
||||
f3 += 1
|
||||
elif face == 4:
|
||||
f4 += 1
|
||||
elif face == 5:
|
||||
f5 += 1
|
||||
else:
|
||||
f6 += 1
|
||||
print(f'1点出现了{f1}次')
|
||||
print(f'2点出现了{f2}次')
|
||||
print(f'3点出现了{f3}次')
|
||||
print(f'4点出现了{f4}次')
|
||||
print(f'5点出现了{f5}次')
|
||||
print(f'6点出现了{f6}次')
|
||||
```
|
||||
|
||||
看看上面的代码,相信大家一定觉得它非常的“笨重”和“丑陋”,更可怕的是,如果要统计掷2颗或者更多的色子统计每个点数出现的次数,那就需要定义更多的变量,写更多的分支结构。讲到这里,相信大家一定想问:有没有办法用一个变量来保存多个数据,有没有办法用统一的代码对多个数据进行操作?答案是肯定的,在Python中我们可以通过容器类型的变量来保存和操作多个数据,我们首先为大家介绍列表(list)这种新的数据类型。
|
||||
|
||||
### 定义和使用列表
|
||||
|
||||
在Python中,**列表是由一系元素按特定顺序构成的数据序列**,这样就意味着定义一个列表类型的变量,**可以保存多个数据**,而且**允许有重复的数据**。跟上一课我们讲到的字符串类型一样,列表也是一种结构化的、非标量类型,操作一个列表类型的变量,除了可以使用运算符还可以使用它的方法。
|
||||
|
||||
在Python中,可以使用`[]`字面量语法来定义列表,列表中的多个元素用逗号进行分隔,代码如下所示。
|
||||
|
||||
```Python
|
||||
items1 = [35, 12, 99, 68, 55, 87]
|
||||
items2 = ['Python', 'Java', 'Go', 'Kotlin']
|
||||
```
|
||||
|
||||
需要说明的是,列表是一种可变数据类型,也就是说列表可以添加元素、删除元素、更新元素,这一点跟我们上一课讲到的字符串有着鲜明的差别。字符串是一种不可变数据类型,也就是说对字符串做拼接、重复、转换大小写、修剪空格等操作的时候会产生新的字符串,原来的字符串并没有发生任何改变。
|
||||
|
||||
#### 列表的运算符
|
||||
|
||||
和字符串类型一样,列表也支持拼接、重复、成员运算、索引和切片,对此我们不再进行赘述,请大家参考下面的代码。
|
||||
|
||||
```Python
|
||||
items1 = [35, 12, 99, 68, 55, 87]
|
||||
items2 = [45, 8, 29]
|
||||
|
||||
# 列表的拼接
|
||||
items3 = items1 + items2
|
||||
print(items3) # [35, 12, 99, 68, 55, 87, 45, 8, 29]
|
||||
|
||||
# 列表的重复
|
||||
items4 = ['hello'] * 3
|
||||
print(items4) # ['hello', 'hello', 'hello']
|
||||
|
||||
# 列表的成员运算
|
||||
print(100 in items3) # False
|
||||
print('hello' in items4) # True
|
||||
|
||||
# 获取列表的长度(元素个数)
|
||||
size = len(items3)
|
||||
print(size) # 9
|
||||
|
||||
# 列表的索引
|
||||
print(items3[0], items3[-size]) # 35 35
|
||||
items3[-1] = 100
|
||||
print(items3[size - 1], items3[-1]) # 100 100
|
||||
|
||||
# 列表的切片
|
||||
print(items3[:5]) # [35, 12, 99, 68, 55]
|
||||
print(items3[4:]) # [55, 87, 45, 8, 100]
|
||||
print(items3[-5:-7:-1]) # [55, 68]
|
||||
print(items3[::-2]) # [100, 45, 55, 99, 35]
|
||||
```
|
||||
|
||||
值得一提的是,由于列表是可变类型,所以通过索引操作既可以获取列表中的元素,也可以更新列表中的元素。对列表做索引操作一样要注意索引越界的问题,对于有`N`个元素的列表,正向索引的范围是`0`到`N-1`,负向索引的范围是`-1`到`-N`,如果超出这个范围,将引发`IndexError`异常,错误信息为:`list index out of range`。
|
||||
|
||||
#### 列表元素的遍历
|
||||
|
||||
如果想逐个取出列表中的元素,可以使用`for`循环的,有以下两种做法。
|
||||
|
||||
方法一:
|
||||
|
||||
```Python
|
||||
items = ['Python', 'Java', 'Go', 'Kotlin']
|
||||
|
||||
for index in range(len(items)):
|
||||
print(items[index])
|
||||
```
|
||||
|
||||
方法二:
|
||||
|
||||
```Python
|
||||
items = ['Python', 'Java', 'Go', 'Kotlin']
|
||||
|
||||
for item in items:
|
||||
print(item)
|
||||
```
|
||||
|
||||
讲到这里,我们可以用列表的知识来重构上面“掷色子统计每个点数出现次数”的代码。
|
||||
|
||||
```Python
|
||||
import random
|
||||
|
||||
counters = [0] * 6
|
||||
for _ in range(6000):
|
||||
face = random.randint(1, 6)
|
||||
counters[face - 1] += 1
|
||||
for face in range(1, 7):
|
||||
print(f'{face}点出现了{counters[face - 1]}次')
|
||||
```
|
||||
|
||||
上面的代码中,我们用`counters`列表中的六个元素分别表示1到6的点数出现的次数,最开始的时候六个元素的值都是`0`。接下来用随机数模拟掷色子,如果摇出1点`counters[0]`的值加`1`,如果摇出2点`counters[1]`的值加`1`,以此类推。大家感受一下,这段代码是不是比之前的代码要简单优雅很多。
|
||||
|
||||
### 列表的方法
|
||||
|
||||
和字符串一样,列表类型的方法也很多,下面为大家讲解比较重要的方法。
|
||||
|
||||
#### 添加和删除元素
|
||||
|
||||
```Python
|
||||
items = ['Python', 'Java', 'Go', 'Kotlin']
|
||||
|
||||
# 使用append方法在列表尾部添加元素
|
||||
items.append('Swift')
|
||||
print(items) # ['Python', 'Java', 'Go', 'Kotlin', 'Swift']
|
||||
# 使用insert方法在列表指定索引位置插入元素
|
||||
items.insert(2, 'SQL')
|
||||
print(items) # ['Python', 'Java', 'SQL', 'Go', 'Kotlin', 'Swift']
|
||||
|
||||
# 删除指定的元素
|
||||
items.remove('Java')
|
||||
print(items) # ['Python', 'SQL', 'Go', 'Kotlin', 'Swift']
|
||||
# 删除指定索引位置的元素
|
||||
items.pop(0)
|
||||
items.pop(len(items) - 1)
|
||||
print(items) # ['SQL', 'Go', 'Kotlin']
|
||||
|
||||
# 清空列表中的元素
|
||||
items.clear()
|
||||
print(items) # []
|
||||
```
|
||||
|
||||
需要提醒大家,在使用`remove`方法删除元素时,如果要删除的元素并不在列表中,会引发`ValueError`异常,错误消息是:`list.remove(x): x not in list`。在使用`pop`方法删除元素时,如果索引的值超出了范围,会引发`IndexError`异常,错误消息是:`pop index out of range`。
|
||||
|
||||
#### 元素位置和次数
|
||||
|
||||
列表类型的`index`方法可以查找某个元素在列表中的索引位置;因为列表中允许有重复的元素,所以列表类型提供了`count`方法来统计一个元素在列表中出现的次数。请看下面的代码。
|
||||
|
||||
```Python
|
||||
items = ['Python', 'Java', 'Java', 'Go', 'Kotlin', 'Python']
|
||||
|
||||
# 查找元素的索引位置
|
||||
print(items.index('Python')) # 0
|
||||
print(items.index('Python', 2)) # 5
|
||||
# 注意:虽然列表中有'Java',但是从索引为3这个位置开始后面是没有'Java'的
|
||||
print(items.index('Java', 3)) # ValueError: 'Java' is not in list
|
||||
```
|
||||
|
||||
再来看看下面这段代码。
|
||||
|
||||
```Python
|
||||
items = ['Python', 'Java', 'Java', 'Go', 'Kotlin', 'Python']
|
||||
|
||||
# 查找元素出现的次数
|
||||
print(items.count('Python')) # 2
|
||||
print(items.count('Go')) # 1
|
||||
print(items.count('Swfit')) # 0
|
||||
```
|
||||
|
||||
#### 元素排序和反转
|
||||
|
||||
列表的`sort`操作可以实现列表元素的排序,而`reverse`操作可以实现元素的反转,代码如下所示。
|
||||
|
||||
```Python
|
||||
items = ['Python', 'Java', 'Go', 'Kotlin', 'Python']
|
||||
|
||||
# 排序
|
||||
items.sort()
|
||||
print(items) # ['Go', 'Java', 'Kotlin', 'Python', 'Python']
|
||||
# 反转
|
||||
items.reverse()
|
||||
print(items) # ['Python', 'Python', 'Kotlin', 'Java', 'Go']
|
||||
```
|
||||
|
||||
### 列表的生成式
|
||||
|
||||
在Python中,列表还可以通过一种特殊的字面量语法来创建,这种语法叫做生成式。我们给出两段代码,大家可以做一个对比,看看哪一种方式更加简单优雅。
|
||||
|
||||
通过`for`循环为空列表添加元素。
|
||||
|
||||
```Python
|
||||
# 创建一个由1到9的数字构成的列表
|
||||
items1 = []
|
||||
for x in range(1, 10):
|
||||
items1.append(x)
|
||||
print(items1)
|
||||
|
||||
# 创建一个由'hello world'中除空格和元音字母外的字符构成的列表
|
||||
items2 = []
|
||||
for x in 'hello world':
|
||||
if x not in ' aeiou':
|
||||
items2.append(x)
|
||||
print(items2)
|
||||
|
||||
# 创建一个由个两个字符串中字符的笛卡尔积构成的列表
|
||||
items3 = []
|
||||
for x in 'ABC':
|
||||
for y in '12':
|
||||
items3.append(x + y)
|
||||
print(items3)
|
||||
```
|
||||
|
||||
通过生成式创建列表。
|
||||
|
||||
```Python
|
||||
# 创建一个由1到9的数字构成的列表
|
||||
items1 = [x for x in range(1, 10)]
|
||||
print(items1) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
|
||||
# 创建一个由'hello world'中除空格和元音字母外的字符构成的列表
|
||||
items2 = [x for x in 'hello world' if x not in ' aeiou']
|
||||
print(items2) # ['h', 'l', 'l', 'w', 'r', 'l', 'd']
|
||||
|
||||
# 创建一个由个两个字符串中字符的笛卡尔积构成的列表
|
||||
items3 = [x + y for x in 'ABC' for y in '12']
|
||||
print(items3) # ['A1', 'A2', 'B1', 'B2', 'C1', 'C2']
|
||||
```
|
||||
|
||||
下面这种方式不仅代码简单优雅,而且性能也优于上面使用`for`循环和`append`方法向空列表中追加元素的方式。可以简单跟大家交待下为什么生成式拥有更好的性能,那是因为Python解释器的字节码指令中有专门针对生成式的指令(`LIST_APPEND`指令);而`for`循环是通过方法调用(`LOAD_METHOD`和`CALL_METHOD`指令)的方式为列表添加元素,方法调用本身就是一个相对耗时的操作。对这一点不理解也没有关系,记住“**强烈建议用生成式语法来创建列表**”这个结论就可以了。
|
||||
|
||||
### 嵌套的列表
|
||||
|
||||
Python语言没有限定列表中的元素必须是相同的数据类型,也就是说一个列表中的元素可以任意的数据类型,当然也包括列表。如果列表中的元素又是列表,那么我们可以称之为嵌套的列表。嵌套的列表可以用来表示表格或数学上的矩阵,例如:我们想保存5个学生3门课程的成绩,可以定义一个保存5个元素的列表保存5个学生的信息,而每个列表元素又是3个元素构成的列表,分别代表3门课程的成绩。但是,一定要注意下面的代码是有问题的。
|
||||
|
||||
```Python
|
||||
scores = [[0] * 3] * 5
|
||||
print(scores) # [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
|
||||
```
|
||||
|
||||
看上去我们好像创建了一个`5 * 3`的嵌套列表,但实际上当我们录入第一个学生的第一门成绩后,你就会发现问题来了,我们看看下面代码的输出。
|
||||
|
||||
```Python
|
||||
# 嵌套的列表需要多次索引操作才能获取元素
|
||||
scores[0][0] = 95
|
||||
print(scores) # [[95, 0, 0], [95, 0, 0], [95, 0, 0], [95, 0, 0], [95, 0, 0]]
|
||||
```
|
||||
|
||||
我们不去过多的解释为什么会出现这样的问题,如果想深入研究这个问题,可以通过[Python Tutor](<http://www.pythontutor.com/visualize.html>)网站的可视化代码执行功能,看看创建列表时计算机内存中发生了怎样的变化,下面的图就是在这个网站上生成的。建议大家不去纠结这个问题,现阶段只需要记住不能用`[[0] * 3] * 5]`这种方式来创建嵌套列表就行了。那么创建嵌套列表的正确做法是什么呢,下面的代码会给你答案。
|
||||
|
||||
```Python
|
||||
scores = [[0] * 3 for _ in range(5)]
|
||||
scores[0][0] = 95
|
||||
print(scores) # [[95, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
|
||||
```
|
||||
|
||||
![](res/embedded-list.png)
|
||||
|
||||
在讲完下节课的知识点后,我们会把这个案例写得更为完整一些,实现录入5个学生3门课程的成绩,统计出每个学生和每门课程的平均分。
|
||||
|
||||
### 简单的总结
|
||||
|
||||
这节课我们先不做总结,等学完下节课的元组之后,我们对列表和元组的使用做一个简单的总结。
|
||||
|
||||
> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问,群号:**789050736**。
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
## 第012课:常用数据结构之元组
|
||||
|
||||
上一节课为大家讲解了Python中的列表,它是一种容器型数据类型,我们可以通过定义列表类型的变量来保存和操作多个元素。当然,Python中容器型的数据类型肯定不止列表一种,接下来我们为大家讲解另一种非常重要的容器型数据类型,它的名字叫元组(tuple)。
|
||||
|
||||
### 定义和使用元组
|
||||
|
||||
在Python中,元组也是多个元素按照一定的顺序构成的序列。元组和列表的不同之处在于,元组是不可变类型,这就意味着元组类型的变量一旦定义,其中的元素不能再添加或删除,而且元素的值也不能进行修改。定义元组使用`()`字面量语法,元组类型支持的运算符跟列表是一样。下面的代码演示了元组的定义和运算。
|
||||
|
||||
```Python
|
||||
# 定义一个三元组
|
||||
t1 = (30, 10, 55)
|
||||
# 定义一个四元组
|
||||
t2 = ('骆昊', 40, True, '四川成都')
|
||||
|
||||
# 查看变量的类型
|
||||
print(type(t1), type(t2)) # <class 'tuple'> <class 'tuple'>
|
||||
# 查看元组中元素的数量
|
||||
print(len(t1), len(t2)) # 3 4
|
||||
|
||||
# 通过索引运算获取元组中的元素
|
||||
print(t1[0], t1[-3]) # 30 30
|
||||
print(t2[3], t2[-1]) # 四川成都 四川成都
|
||||
|
||||
# 循环遍历元组中的元素
|
||||
for member in t2:
|
||||
print(member)
|
||||
|
||||
# 成员运算
|
||||
print(100 in t1) # False
|
||||
print(40 in t2) # True
|
||||
|
||||
# 拼接
|
||||
t3 = t1 + t2
|
||||
print(t3) # (30, 10, 55, '骆昊', 40, True, '四川成都')
|
||||
|
||||
# 切片
|
||||
print(t3[::3]) # (30, '骆昊', '四川成都')
|
||||
```
|
||||
|
||||
一个元组中如果有两个元素,我们就称之为二元组;一个元组中如果五个元素,我们就称之为五元组。需要提醒大家注意的是,`()`表示空元组,但是如果元组中只有一个元素,需要加上一个逗号,否则`()`就不是代表元组的字面量语法,而是改变运算优先级的圆括号,所以`('hello', )`和`(100, )`才是一元组,而`('hello')`和`(100)`只是字符串和整数。我们可以通过下面的代码来加以验证。
|
||||
|
||||
```Python
|
||||
# 空元组
|
||||
a = ()
|
||||
print(type(a)) # <class 'tuple'>
|
||||
# 不是元组
|
||||
b = ('hello')
|
||||
print(type(b)) # <class 'str'>
|
||||
c = (100)
|
||||
print(type(c)) # <class 'int'>
|
||||
# 一元组
|
||||
d = ('hello', )
|
||||
print(type(d)) # <class 'tuple'>
|
||||
e = (100, )
|
||||
print(type(e)) # <class 'tuple'>
|
||||
```
|
||||
|
||||
### 元组的应用场景
|
||||
|
||||
讲到这里,相信大家一定迫切的想知道元组有哪些应用场景,我们给大家举几个例子。
|
||||
|
||||
#### 例子1:打包和解包操作。
|
||||
|
||||
当我们把多个用逗号分隔的值赋给一个变量时,多个值会打包成一个元组类型;当我们把一个元组赋值给多个变量时,元组会解包成多个值然后分别赋给对应的变量,如下面的代码所示。
|
||||
|
||||
```Python
|
||||
# 打包
|
||||
a = 1, 10, 100
|
||||
print(type(a), a) # <class 'tuple'> (1, 10, 100)
|
||||
# 解包
|
||||
i, j, k = a
|
||||
print(i, j, k) # 1 10 100
|
||||
```
|
||||
|
||||
在解包时,如果解包出来的元素个数和变量个数不对应,会引发`ValueError`异常,错误信息为:`too many values to unpack`(解包的值太多)或`not enough values to unpack`(解包的值不足)。
|
||||
|
||||
```Python
|
||||
a = 1, 10, 100, 1000
|
||||
# i, j, k = a # ValueError: too many values to unpack (expected 3)
|
||||
# i, j, k, l, m, n = a # ValueError: not enough values to unpack (expected 6, got 4)
|
||||
```
|
||||
|
||||
有一种解决变量个数少于元素的个数方法,就是使用星号表达式,我们之前讲函数的可变参数时使用过星号表达式。有了星号表达式,我们就可以让一个变量接收多个值,代码如下所示。需要注意的是,用星号表达式修饰的变量会变成一个列表,列表中有0个或多个元素。还有在解包语法中,星号表达式只能出现一次。
|
||||
|
||||
```Python
|
||||
a = 1, 10, 100, 1000
|
||||
i, j, *k = a
|
||||
print(i, j, k) # 1 10 [100, 1000]
|
||||
i, *j, k = a
|
||||
print(i, j, k) # 1 [10, 100] 1000
|
||||
*i, j, k = a
|
||||
print(i, j, k) # [1, 10] 100 1000
|
||||
*i, j = a
|
||||
print(i, j) # [1, 10, 100] 1000
|
||||
i, *j = a
|
||||
print(i, j) # 1 [10, 100, 1000]
|
||||
i, j, k, *l = a
|
||||
print(i, j, k, l) # 1 10 100 [1000]
|
||||
i, j, k, l, *m = a
|
||||
print(i, j, k, l, m) # 1 10 100 1000 []
|
||||
```
|
||||
|
||||
需要说明一点,解包语法对所有的序列都成立,这就意味着对字符串、列表以及我们之前讲到的`range`函数返回的范围序列都可以使用解包语法。大家可以尝试运行下面的代码,看看会出现怎样的结果。
|
||||
|
||||
```Python
|
||||
a, b, *c = range(1, 10)
|
||||
print(a, b, c)
|
||||
a, b, c = [1, 10, 100]
|
||||
print(a, b, c)
|
||||
a, *b, c = 'hello'
|
||||
print(a, b, c)
|
||||
```
|
||||
|
||||
现在我们可以反过来思考一下函数的可变参数,可变参数其实就是将多个参数打包成了一个元组,可以通过下面的代码来证明这一点。
|
||||
|
||||
```Python
|
||||
def add(*args):
|
||||
print(type(args), args)
|
||||
total = 0
|
||||
for val in args:
|
||||
total += val
|
||||
return total
|
||||
|
||||
|
||||
add(1, 10, 20) # <class 'tuple'> (1, 10, 20)
|
||||
add(1, 2, 3, 4, 5) # <class 'tuple'> (1, 2, 3, 4, 5)
|
||||
```
|
||||
|
||||
#### 例子2:交换两个变量的值。
|
||||
|
||||
交换两个变量的值是编程语言中的一个经典案例,在很多编程语言中,交换两个变量的值都需要借助一个中间变量才能做到,如果不用中间变量就需要使用比较晦涩的位运算来实现。在Python中,交换两个变量`a`和`b`的值只需要使用如下所示的代码。
|
||||
|
||||
```Python
|
||||
a, b = b, a
|
||||
```
|
||||
|
||||
同理,如果要将三个变量`a`、`b`、`c`的值互换,即`b`赋给`a`,`c`赋给`b`,`a`赋给`c`,也可以如法炮制。
|
||||
|
||||
```Python
|
||||
a, b, c = b, c, a
|
||||
```
|
||||
|
||||
需要说明的是,上面并没有用到打包和解包语法,Python的字节码指令中有`ROT_TWO`和`ROT_THREE`这样的指令可以实现这个操作,效率是非常高的。但是如果有多于三个变量的值要依次互换,这个时候没有直接可用的字节码指令,执行的原理就是我们上面讲解的打包和解包操作。
|
||||
|
||||
#### 例子3:让函数返回多个值。
|
||||
|
||||
有的时候一个函数执行完成后可能需要返回多个值,这个时候元组类型应该是比较方便的选择。例如,编写一个找出列表中最大值和最小的函数。
|
||||
|
||||
```Python
|
||||
def find_max_and_min(items):
|
||||
"""找出列表中最大和最小的元素
|
||||
:param items: 列表
|
||||
:return: 最大和最小元素构成的二元组
|
||||
"""
|
||||
max_one, min_one = items[0], items[0]
|
||||
for item in items:
|
||||
if item > max_one:
|
||||
max_one = item
|
||||
elif item < min_one:
|
||||
min_one = item
|
||||
return max_one, min_one
|
||||
```
|
||||
|
||||
上面函数的`return`语句中有两个值,这两个值会组装成一个二元组然后返回。所以调用`find_max_and_min`函数会得到这个二元组,如果愿意也可以通过解包语法将二元组中的两个值分别赋给两个变量。
|
||||
|
||||
### 元组和列表的比较
|
||||
|
||||
这里还有一个非常值得探讨的问题,Python中已经有了列表类型,为什么还需要元组这样的类型呢?这个问题对于初学者来说似乎有点困难,不过没有关系,我们先抛出观点,大家可以一边学习一边慢慢体会。
|
||||
|
||||
1. 元组是不可变类型,**不可变类型更适合多线程环境**,因为它降低了并发访问变量的同步化开销。关于这一点,我们会在后面讲解多线程的时候为大家详细论述。
|
||||
|
||||
2. 元组是不可变类型,通常**不可变类型在创建时间和占用空间上面都优于对应的可变类型**。我们可以使用`sys`模块的`getsizeof`函数来检查保存相同元素的元组和列表各自占用了多少内存空间。我们也可以使用`timeit`模块的`timeit`函数来看看创建保存相同元素的元组和列表各自花费的时间,代码如下所示。
|
||||
|
||||
```Python
|
||||
import sys
|
||||
import timeit
|
||||
|
||||
a = list(range(100000))
|
||||
b = tuple(range(100000))
|
||||
print(sys.getsizeof(a), sys.getsizeof(b)) # 900120 800056
|
||||
|
||||
print(timeit.timeit('[1, 2, 3, 4, 5, 6, 7, 8, 9]'))
|
||||
print(timeit.timeit('(1, 2, 3, 4, 5, 6, 7, 8, 9)'))
|
||||
```
|
||||
|
||||
3. Python中的元组和列表是可以相互转换的,我们可以通过下面的代码来做到。
|
||||
|
||||
```Python
|
||||
# 将元组转换成列表
|
||||
info = ('骆昊', 175, True, '四川成都')
|
||||
print(list(info)) # ['骆昊', 175, True, '四川成都']
|
||||
# 将列表转换成元组
|
||||
fruits = ['apple', 'banana', 'orange']
|
||||
print(tuple(fruits)) # ('apple', 'banana', 'orange')
|
||||
```
|
||||
|
||||
### 简单的总结
|
||||
|
||||
**列表和元组都是容器型的数据类型**,即一个变量可以保存多个数据。**列表是可变数据类型**,**元组是不可变数据类型**,所以列表添加元素、删除元素、清空、排序等方法对于元组来说是不成立的。但是列表和元组都可以进行**拼接**、**成员运算**、**索引和切片**这些操作,就如同之前讲到的字符串类型一样,因为字符串是字符按一定顺序构成的序列,在这一点上三者并没有本质区别。我们**推荐大家使用列表的生成式语法来创建列表**,它很好用,也是Python中非常有特色的语法。
|
||||
|
||||
> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问,群号:**789050736**。
|
Loading…
Reference in New Issue