吃透Python-第 7 章 列表

吃透Python-第 7 章 列表

列表是表示数据集合的数据结构,是 Python 中不可或缺的重要概念。

列表
元素和构成元素
运算符 []
list 函数
解包列表
索引运算符 [] 和索引表达式
分片运算符 [:] 和分片表达式
列表的赋值
复制列表
浅复制与深复制
搜索列表
扩展列表和插入、删除元素
遍历列表
反转列表
使用列表实现矩阵
列表解析式
扁平序列和容器序列
数组 array
字节序列 bytes

7-1 列表

归拢分散的多个变量更易于程序处理,为此 Python 引入了列表。本节,我将介绍列表的基础知识。

列表的必要性
  我们来思考如何统计学生的考试分数。代码清单 7-1 是一个读取了 5 个人的分数,然后计算总分和平均分的程序。

在这里插入图片描述

如图 7-1a 所示,程序给每个学生的分数都分配了一个变量,分别为 tensu1、tensu2 等,因此,程序的两处涂色部分分别重复了 5 行几乎相同的代码。

在这里插入图片描述

图 7-1 变量与列表

现在我们考虑对该程序做出以下修改。

1使人数可变

本程序中人数固定为 5 人。修改程序后,程序在运行时会从键盘读取人数,并计算总分和平均分。

2查看或修改某个学生的分数

添加查看或修改某个学生分数的功能。

3计算最低分和最高分

添加计算最低分和最高分的功能(当然,按照的方式改变人数后也能计算)。

4对分数进行排序

按照升序或降序排列分数。

实际上,扩充代码清单 7-1 并做出前述修改是不可能的事情,我们必须从根本上改变程序的实现方法。

首先,要将各个学生的分数归拢至一处进行处理。这可以通过图 b中名为列表的数据结构实现。

列表是对象的“储藏室”,存储的各个变量称为元素。每个元素都有一个索引,从前往后依次为 0, 1, 2, …。这一点与上一章介绍的字符串相同。

▶ 与字符串一样,列表也可以使用负数索引,也可以通过分片表达式取出部分元素。我们会在后面学习这些内容。

因为生成列表时可以任意指定元素个数,所以 1 可以轻易实现。而且,列表生成后也能轻易增加或减少元素个数。

因为第 3 个元素 tensu[2] 和第 4 个元素 tensu[3] 可以使用基于索引运算符的索引表达式访问(读写),所以 2 也可以轻易实现。

像这样,使用索引表达式可以自由访问各个元素,所以 3 和 4 也能轻易实现。

在处理数据集合时,可以说一定会用到列表。

列表的元素可以是 int 型或 float 型等任意类型。不仅如此,列表中也可能存在不同类型的元素,列表元素本身也可以是列表(甚至下一章介绍的元组和字典也可以是列表元素)。

列表虽然用起来很方便,但不容易让人理解。本章我们会逐步学习列表的相关内容。

理解列表的内部结构
  大家可能现在就想使用列表来编写程序,请少安勿躁。我们先来学习一下列表的内部结构。

在其他编程语言中,数组是“单纯的变量集合”。在 Python 中,列表对数组进行了“升级”,它是“高性能的数据容器(储藏室)”。

下页展示了列表的部分特性。

① 列表是“高性能的数据储藏室”,是可变的 list 型对象。

② 存储的元素是对象的引用,所以元素(引用的对象)可以是任意类型,即所有元素的类型没有必要相同,元素本身也可以是列表。

③ 列表中的元素是有顺序的。

④ 使用索引表达式可以访问任意元素(对值进行读写)。

⑤ 使用分片表达式可以连续或按照一定周期取出特定范围内的元素。

⑥ 可以轻易遍历列表中的元素。

⑦ 元素的数量没有限制。列表生成后可以随意扩展和缩减。

⑧ 列表中也可以没有元素,这种列表称为空列表。空列表的逻辑值是 False。

⑨ 列表提供了排序和反转等许多方便的功能。

图 7-2 展示了列表 x 和列表 y 的内部机制。

在这里插入图片描述

图 7-2 列表的内部机制

我们观察此图来加深对列表的理解。

① 红色点线和蓝色点线包围的部分是列表(list 型的对象)。x 和 y 是分别绑定了对应列表(引用列表)的变量(名字)。

② x 中元素(引用的对象)的类型按从前(左侧)往后的顺序分别为 int 型、int 型、int 型、float 型、列表型和字符串型。

③ 元素之间是有顺序的。在该图中,左侧是开头,右侧是末尾。

④ x 的各个元素可以按从前往后的顺序分别用索引表达式 x[0]、x[1]、x[2]、x[3]、x[4] 和 x[5] 访问。

⑤ x[1]~x[3] 这一连串元素可以通过分片表达式 x[1:4] 取出(作为新的列表)。

⑥ 通过从左至右访问元素,程序可以轻易地从前往后逐一遍历列表中的元素。

⑦ 各种运算符和方法可以用来增加或减少列表的元素总数。这些运算符和方法简化了列表的各种处理,包括在列表末尾添加元素、在列表的任意位置插入元素和删除列表中的任意元素等。

⑧ y 是空列表。即使没有元素,y 也是一个列表(与空字符串也是一个字符串相同)。

⑨ 列表提供了获取元素总数的便利功能。与字符串一样,程序可以通过 len(x) 和 len(y) 获取元素总数(分别是 6 和 0)。除此以外,列表还提供了排序、搜索、反转等功能。

下面我们通过例 7-1 体验一下上面讲解的内容(现阶段不理解上述所讲内容也没关系)。

在这里插入图片描述

现在我们继续学习列表的内容。

生成列表
  使用列表前必须先生成列表。现在我们来学习列表的生成方法。

使用运算符 [] 生成列表

在运算符 [] 中以逗号隔开各个元素会生成包含这些元素的新列表。另外,如果 [] 中没有元素就会生成空列表。

我们通过示例程序来进行确认。

list01 = []                    # []
list02 = [1, 2, 3]             # [1, 2, 3]
list03 = ['A', 'B', 'C', ]     # ['A', 'B', 'C']

▶ 右侧的注释是生成的列表的内容。程序 chap07/list_construct.py 生成并输出了从 list01~list13 的列表。

像 list03 和 list04 这样,在最后一个元素后面放置逗号“,”有以下好处。

纵向排列的元素在外观上更平衡。
便于在末尾添加和删除元素。
在这里插入图片描述

▶ 代码在 ()、[] 和 {} 中可以自由地换行(3-3 节),list04 利用了这一特性。

使用 list 函数生成列表

使用内置函数 list 可以生成包含各种类型对象(字符串和元组等)的列表。

另外,在不传递实参的情况下调用 list() 会生成空列表。

在这里插入图片描述

使用 list 函数转换 range 函数生成的数列(可迭代对象),由此生成由特定范围的整数值构成的列表。

list10 = list(range(7))            # [0, 1, 2, 3, 4, 5, 6]
list11 = list(range(3, 8))         # [3, 4, 5, 6, 7]
list12 = list(range(3, 13, 2))     # [3, 5, 7, 9, 11]

指定元素总数生成列表

可以通过关键语句 [None] 生成“元素总数确定,但元素的值不确定”的列表。

列表 [None] 只有一个元素 None,重复 n 次 [None] 后可以生成一个元素总数为 n 且所有元素都是 None 的列表。元素总数为 5 时生成的列表如下。

# 生成一个元素总数为5且元素都为空的列表
list13 = [None] * 5                    # [None, None, None, None, None]

与字符串相同,列表可以使用乘法运算符 * 进行重复。

▶ 除此之外,使用 str.split 方法也可以分割字符串并生成列表(6-2 节)。这一点在前面的章节中介绍过。

分别生成的列表之间是否具有同一性
在这里插入图片描述

图 7-3 分别生成的列表

即使所有元素的值都相等,如果列表是分别生成的,它们的实体也各不相同(图 7-3)。

在这里插入图片描述

例 7-2 通过运算符 is 来判断 lst1 和 lst2 是否具有同一性(标识值是否相等)。当然,结果为 False。

▶ [1, 2, 3, 4, 5] 是使用运算符 [] 生成新列表的表达式,并不是所谓的“字面量”。

另外,虽然列表的元素是对象的引用,但在图 7-3 中,引用对象的值作为元素使用。后面的内容也会用到这样的简化图。

列表的赋值
  即使使用列表(引用列表的变量)进行赋值操作(例 7-3),列表的元素也不会被复制。这是因为在赋值时,复制的是引用而不是值本身,这一点在第 5 章介绍过。

在这里插入图片描述

赋值后 lst2 引用的对象变成了 lst1 引用的列表(图 7-4)。也就是说,lst2 和 lst1 引用了同一个实体(列表)。

在这里插入图片描述

图 7-4 列表的赋值

因此,如果通过 lst1 使用索引表达式(或分片表达式)修改元素的值,lst2 获取的元素值也会被修改。

▶ 使用索引表达式和分片表达式修改元素值的相关内容会从第 169 页开始讲解。

列表的运算
  与字符串相同,我们可以针对列表使用多种运算符。

使用加法运算符 + 合并列表,使用乘法运算符 * 重复列表
  我们可以使用加算运算符 + 合并列表。左操作数的列表与右操作数的列表合并后生成新的列表(例 7-4)。

在这里插入图片描述

我们可以使用乘法运算符 * 重复列表(前述内容也用到了 *)(例 7-5)。

在这里插入图片描述

使用比较运算符进行比较
  比较运算符可以用来判断列表之间的大小关系和等价性。以下示例的判断结果均为真。

[1, 2, 3]     == [1, 2, 3]
[1, 2, 3]     <  [1, 2, 4]
[1, 2, 3, 4]  <= [1, 2, 3, 4]
[1, 2, 3]     <  [1, 2, 3, 5]
[1, 2, 3]     <  [1, 2, 3, 5]  <  [1, 2, 3, 5, 6]   # 视为用and结合

▶ 从第一个元素开始按顺序比较,如果元素的值相等,则比较下一个元素。

如果某个列表的元素较大,程序则判定该列表较大。另外,像最后两个示例那样,开头的 [1, 2, 3] 相同,其中一方的元素总数较大时,程序会判定元素总数较大的列表更大。

在使用比较运算符 == 和 != 进行判断时,程序会对所有元素的等价性进行判断。这种判断方式与前面提到的运算符 is 不同。我们通过代码清单 7-2 进行确认。

在这里插入图片描述

如果使用 print 函数输出列表,程序就会打印输出用 [] 包围的元素,元素间用逗号隔开。

使用 len 函数获取元素总数
  列表的元素总数(长度)可以通过 len 函数获取(与字符串相同)(例 7-6)。

在这里插入图片描述

如果元素本身就是列表(或者元组、集合),则该元素计数为 1。此时,元素中包含的元素不计入总数(即 x 中包含的 [32, 55] 计为一个元素)。

使用 min 函数和 max 函数获取最小值和最大值
  第 3 章介绍的内置函数 min 和 max 也可应用于列表。使用这些函数可以轻松获取列表中元素的最大值和最小值。

▶ 请通过代码清单 7-14 进行学习。

判断空列表
  没有元素的空列表,其逻辑值为假(第 52 页),因此,程序如果可以根据 x 是否为空列表进行不同的处理,就能实现以下功能。

if x:
    # x不为空列表时执行的代码组
else:
    # x为空列表时执行的代码组

当然,我们也可使用 not x 作为判断表达式。

▶ 此时,两个代码组的顺序颠倒。

解包列表
  在使用赋值语句时,如果在等号左边放置多个变量,在等号右边放置列表,则可以统一取出列表中的元素,然后将它们分别赋给变量(例 7-7)。

在这里插入图片描述

在本例中,列表 x 的元素被取出至 a、b 和 c 中。

像这样从单一的列表(或元组等)中取出多个元素的值并将其拆散的过程称为解包(unpack)。

▶ 下一章将对解包进行讲解。

使用索引表达式访问元素
  我在上一章讲解字符串时提到了索引,它是访问列表中各个元素的关键。如图 7-5 所示,在列表中,索引在使用方法上与字符串一致。

在这里插入图片描述

图 7-5 列表和索引

索引表达式
  我们在使用列表时,可以在索引运算符 [] 中指定整数索引(例 7-8),这一点与字符串相同。请通过图 7-5 所示列表进行确认。

在这里插入图片描述

想必大家能理解示例前半部分取出 x[2] 和 x[-3] 的值并执行打印输出操作的程序代码。

现在请看之后对 x[-4] 进行赋值的代码。与字符串不同,在对列表赋值时,索引表达式可以放在等号的左边。赋值后,原本值为 int 型的 x[-4] 变成了 float 型,即该元素的值从整数 44 变成了浮点数 3.14。

当然,因为赋值时复制的是引用而不是值,所以 x[ -4] 的引用对象从 int 型对象 44 变为 float 型对象 3.14。

之后,程序指示打印输出 x[7],但由于 7 不是正确的索引,所以程序产生了错误。另外,对 x[7] 进行赋值也产生了错误。在等号左边使用索引表达式访问不存在的元素时,程序无法添加新的元素。

使用分片表达式访问元素
  我在上一章介绍字符串时提到了分片。在列表中也可以通过分片连续或按一定周期取出列表中的元素,组成新的列表。

使用分片表达式取出元素
  在列表中使用分片表达式的形式与字符串中的相同。

在这里插入图片描述

首先,我们来通过例 7-9 确认一下分片表达式的基本使用方法(这也是对前面介绍的字符串相关内容的复习)。

在这里插入图片描述

在列表中指定 i、j 和 k 的规则与字符串中的指定规则相同。

如果 i 和 j 大于 len(s),则程序认为 i 和 j 等于 len(s)。

与索引不同,即使分片指定了正确范围以外的值,程序也不会产生错误。

如果 i 省略或为 None,则程序认为 i 等于 0。

如果 j 省略或为 None,则程序认为 j 等于 len(s)。
  当然,在省略 i、j、k 中的多个时,使用规则也与字符串的相同。规则总结如下。

s[:] 所有元素 s[:] [11, 22, 33, 44, 55, 66, 77]
s[:n] 前 n 个元素 s[:3] [11, 22, 33]
s[i:] 从 s[i] 到末尾的元素 s[3:] [44, 55, 66, 77]
s[-n:] 后 n 个元素 s[-3:] [55, 66, 77]
s[::k] 每隔 k-1 个取出一个元素 s[::2] [11, 33, 55, 77]
s[::-1] 反向输出所有元素 s[::-1] [77, 66, 55, 44, 33, 22, 11]
▶ 当 n 大于元素总数时,取出所有元素。

通过对分片表达式赋值来替换元素
  在列表中使用分片表达式的方法与字符串中的相同。不过,与字符串不同的是,列表的分片表达式可以放在等号左边进行赋值。利用该特性可以轻松替换列表的一部分元素。

请通过例 7-10 进行确认。

在这里插入图片描述

另外,当替换对象的元素总数不同时,列表的元素总数会发生变化。请通过例 7-11 进行确认。

在这里插入图片描述

列表 x 的元素总数从 7 变成了 8。

搜索列表
  Python 提供了许多搜索列表的方法。

使用运算符 in 和运算符 not in 进行判断
  上一章介绍了成员运算符 in 和 not in,这些运算符用来判断某个字符串中是否包含了其他字符串(表 6-1)。

这些运算符也适用于列表。因为它们是比较运算符(表 3-5)的一种,所以可以连续使用。请通过例 7-12 进行确认。

在这里插入图片描述

归属判断表达式 a in b in c 被程序解释为 a in b and b in c。

▶ 请注意 [1, 2] 是 c 所包含的元素,但并不是 d 所包含的元素。这是因为 d 的第一个元素是 [1, 2, 3],而不是 [1, 2]。

该运算符不仅可用于列表,还可用于下一章介绍的元组。

使用 index 方法进行判断
  使用 index 方法进行判断的调用方式与字符串的相同。

在这里插入图片描述

如果列表 list[i:j] 包含 x,则程序返回包含的 x 中最小的索引。

参数 i 和参数 j 可以省略,其表示的含义与分片中使用的索引相同。但是,与 list[ i: j].index(x) 不同,list.index(x, i, j) 没有复制数据,而且其返回的值是相对于整个序列的索引,而不是相对于分片的索引。

另外,如果在 list 中未发现 x,程序就会抛出 ValueError 异常。

使用 count 方法统计出现次数
  使用 count 方法进行判断的调用方式与字符串的相同。

在这里插入图片描述

返回列表 list 中 x 的出现次数。

请通过例 7-13 确认上述两个方法的运行过程。

在这里插入图片描述

专栏 7-1 列表的内部机制

不同编程语言提供了不同形式的链表,链表通过指针或引用串起所有元素(图 7C-1 所示为链表的内部机制)。链表的优点是可以快速在任意位置插入元素或删除元素,缺点是比(所有元素在存储空间上连续存储的)数组占用更多的存储空间且执行速度慢

在这里插入图片描述

图 7C-1 链表的内部机制(不是 Python 的列表)

Python 的列表不是链表。在内部实现上,列表以数组的形式将所有元素连续存储在存储空间上。因此,它的执行速度并不是很慢。

另外,即使逐一添加或插入元素,程序内部也不会每次都申请或释放存储空间,因为程序事先获取了比实际所需的最少存储空间更多的存储空间。

扩展列表
  我们可以轻易在列表中增加或减少元素。现在我来介绍扩展列表的方法。

添加元素(append 方法)
在这里插入图片描述

图 7-6 添加元素

如例 7-14 所示,x.append(v) 用于在列表 x 后添加一个元素 v(图 7-6)。

在这里插入图片描述

另外,如果指定 v 为列表,程序就会将列表作为一个元素添加到原列表中(例 7-15)(请注意本例与使用 extend 方法的示例间的不同)。

在这里插入图片描述

添加列表(增量赋值 += 和 extend 方法)
  我们可以使用增量赋值 x += y 或 x.extend(y) 在列表 x 后添加列表 y(图 7-7)。

在这里插入图片描述

图 7-7 添加列表

在使用增量赋值的情况下,代码编写起来更简洁(例 7-16)。

在这里插入图片描述

重复列表(乘法运算符 * 和增量赋值 *=)
  执行字符串和整数相乘的表达式 ‘Python’ * 3 可得到 ‘PythonPythonPython’。与此相同,列表和整数相乘后会得到重复的列表(图 7-8)。

在这里插入图片描述

图 7-8 重复列表和执行增量运算

执行增量赋值 *= 后,等号左边的列表更新为执行重复后得到的列表(例 7-17)。
在这里插入图片描述

赋值和增量赋值的不同
  增量赋值 += 用于在列表自身后添加列表,增量赋值 *= 用于更新原列表为不断重复自身后得到的列表。

一般来说,赋值“x = x ★ y”和增量赋值“x ★ = y”的不同点是在赋值的情况下,等号左边 x 的求值次数是两次,而增量赋值是一次。这一点在第 4 章提到过。

还有一个较大的不同点是,在增量赋值的情况下,可以进行就地(in-place)运算。

列表的增量赋值可以实现就地运算。因此,程序在赋值时不会生成新的对象并进行赋值,而是会修改赋值目标对象本身的内容。

在这里插入图片描述

图 7-9 对列表进行赋值和对列表进行增量赋值的不同

我们来通过例 7-18 确认赋值与增量赋值的不同(图 7-9)。

在这里插入图片描述

因为在直接进行赋值时生成了新的列表,所以赋值前后 x 的标识值不同。

另一方面,增量赋值进行了就地运算。因为列表自身进行了更新,所以赋值前后 x 的标识值没有发生变化。

要点 不能使用 x = x + y 对列表 x 和列表 y 进行就地合并,要使用 x.extend(y) 或 x += y。

▶ 讲解这些细节的目的是帮助大家理解可变对象的本质,而非让大家拘泥于细节。

另外,在学习第 252 页的内容时需要用到该知识点。

插入元素和删除元素
  现在我们来学习如何在列表中插入元素和删除元素。

▶ 删除表示从列表中删除已有元素。

插入元素(insert 方法和分片操作)
  如例 7-19 所示,x.insert(i, v) 表示将 v 赋给 x[i] 且之后的元素往后移(图 7-10)。

在这里插入图片描述

图 7-10 插入元素

另外,如果 i 不是正常范围的数值,元素就会插入列表末尾。

在这里插入图片描述

分片也可以实现插入操作。

▶ 只有在插入的元素 v 是不可变对象时,才可以使用 x[i:i] = [v] 插入元素。

删除任意值的元素(remove 方法)
在这里插入图片描述

图 7-11 删除特定值的元素

如例 7-20 所示,x.remove( v) 从列表 x 中删除值为 v(如果有多个则删除第一个)的元素,并前移之后的所有元素(图 7-11)。

在这里插入图片描述

当列表中不存在删除对象时,程序会产生 ValueError 异常。为了避免程序出现错误,我们有必要进行以下处理。

提前使用运算符 in 检查对象是否存在。
进行异常处理(第 12 章)。
删除指定元素(pop 方法)

在这里插入图片描述

图 7-12 删除指定元素

如例 7-21 所示,x.pop(i) 在删除元素 x[i] 的同时会返回该元素的值(图 7-12)。

在这里插入图片描述

另外,如果不给 pop 传递参数,程序会认为参数为 -1。

于是,程序就从列表 x 中删除了最后一个元素 x[-1](图 7-13)。

在这里插入图片描述

图 7-13 删除最后一个元素

如果用不到要删除的元素值,则使用后文讲解的 del 语句,而不是 pop 方法。

使用 del 语句删除任意元素或元素群
  第 5 章讲解了与赋值相对应的 del 语句。del 语句可以用来从列表中删除特定元素或分片(例 7-22)。

在这里插入图片描述

最开始的 del 语句使用索引表达式 x[2] 删除了一个元素,第二个 del 语句使用分片表达式 x[2:4] 删除了两个元素。最后一个 del 语句使用分片表达式 x[:] 删除了所有元素。

删除所有元素(clear 方法)
在这里插入图片描述

图 7-14 删除所有元素

如例 7-23 所示,x.clear() 用于删除列表的所有元素,使 x 成为空列表(图 7-14)。

在这里插入图片描述

如例 7-22 所示,使用 del x[:] 可以删除所有元素。我们也可以通过 x = [],将空列表赋给 x 来清空 x(x 的引用对象更新为空列表)。

删除列表
  前面,我讲解了如何在列表中插入元素或删除元素。如果要删除列表 x 本身,则使用 del x。

可迭代对象和遍历列表
  列表的遍历方法与字符串的遍历方法相同。现在我们来编写程序对列表进行遍历。

代码清单 7-3 … 提前通过 len 函数获取元素总数,然后从 0 开始循环至元素总数 -1
代码清单 7-4 … 使用 enumerate 函数成对取出索引和元素,然后进行循环
代码清单 7-5 … 与代码清单 7-4 相同,不过它是从 1 开始计数的
代码清单 7-6 … 如果不需要索引值,则使用 in 按从前往后的顺序取出元素
  以上程序分别对应于用于遍历字符串的代码清单 6-3、代码清单 6-8、代码清单 6-9 和代码清单 6-11。

在这里插入图片描述

因为列表是可迭代对象,所以最后一个程序可以从列表 x 中逐一取出元素到 i 中。可迭代是一种支持从前往后逐一取出元素的结构(8-3 节)。

▶ range 函数生成的数列也是可迭代对象。

逆序进行遍历
  像上一章讲解的那样,如果按从后往前的顺序进行遍历,程序会使用 reversed(x) 或 x[::-1] 作为遍历对象,而不是 x。

遍历元组和遍历集合
  按下面的方式修改前面程序中给 x 赋值的代码,于是,程序变为对元组进行遍历。

在这里插入图片描述

▶ 本书可供下载的代码清单文件中包含上述修改后的程序文件。

chap07/tuple01.py、chap07/tuple02.py、chap07/tuple03.py、chap07/tuple04.py

另外,按下面的方式修改代码清单 7-6 中给 x 赋值的代码,于是,程序变为对集合进行遍历(chap07/set01.py)。

x = {'John', 'George', 'Paul', 'Ringo'}

反转列表
  reverse 方法可以就地反转列表中元素的排列顺序(例 7-24)。

在这里插入图片描述

生成反转的列表
  上一章讲解的 reversed(x) 生成了反转 x 的元素排列顺序的可迭代对象。具体来说就是下面这样。

列表 x 本身没有发生变化,程序生成了新的对象。
生成的对象不是列表,而是可迭代对象。
  如果要获得某个列表的元素排列顺序反转后所形成的列表,可以将 reversed 函数生成的可迭代对象作为参数传递给 list 函数,生成新的列表(转换为列表)。具体如例 7-25 所示。

在这里插入图片描述

使用列表处理成绩
  本章开头以代码清单 7-1 为例进行了讲解,分析了程序使用多个单一的变量表示学生分数的局限性,进而指出使用列表的必要性。

代码清单 7-7 基于列表对代码清单 7-1 进行了修改。另外,人数并没有固定为 5,程序可以通过读取键盘输入来增加人数。

在这里插入图片描述

首先,程序读取学生人数到变量 number 中。

代码 1生成了元素总数为 number 且所有元素都为 None 的列表(第 166 页)。

代码 2 一边将用于计数的变量 i 的值从 0 递增到 number-1,一边读取分数到 tensu[i]。

▶ 在提示输入时,程序输出的是 i+1 的值而不是 i 的值,比如 i 等于 0 时会输出“第 1 名的分数:”。

代码 3 用于计算总分。首先将 total 初始化为 0。然后,将用于计数的变量 i 的值从 0 递增到 number-1,同时将 tensu[i] 的值添加到 total 中。

由于人数可变,程序可以通过索引访问每个学生的分数,所以程序的灵活性大幅提升。修改后的程序代码也更加精练。

代码清单 7-8 使用 enumerate 函数改写了代码2 中用于读取分数的 for 语句。执行该程序后,改写后的代码 3正常运行,而代码 3 在运行时程序产生错误。

在这里插入图片描述

程序产生错误的原因是 i 和 point 的数据来源 (i, point) 是从列表中取出的重新生成的数据对。由于 point 复制于 tensu[0] 和 tensu[1],所以即使在 point 中写入值,原本的 tensu[0] 和 tensu[1] 还是 None。

因此,后面的代码 3 把从 tensu[0] 中取出的 None 与整数 total 相加。这就是程序产生错误的原因。

▶ 如果不能很好地理解上述内容,不妨在学习完第 207 页的知识点后再返回来看这部分内容。

另一方面,计算总分的代码 只读取了列表中元素的值,并没有将值放在等号左边。因此,我们可以使用 enumerate 函数实现该 for 语句。具体如代码清单 7-9 所示。

在这里插入图片描述

即使没有索引值,也可以计算元素总和。因此,这里我们不使用 enumerate 函数,只使用 in。代码清单 7-10 是修改后的程序。

在这里插入图片描述

修改后的代码更加清晰,但仍有改进空间。如果给内置函数 sum` 传递列表(或元组等)作为参数,就能计算出所有元素值的总和。

使用 sum 函数的程序如代码清单 7-11 所示。

在这里插入图片描述

▶ sum 函数接收的参数是(包含列表在内的各种)可迭代对象,该函数可以计算可迭代对象内元素的总和。代码清单 4-3 介绍了如何使用 while 语句计算 1 到 n 的总和,现在我们可以使用下面的表达式调用函数进行计算。

sum(range(1, n + 1))    # 计算1到n的总和

读取键盘输入和添加元素
  因为前面的程序读取的是从键盘输入的学生人数(列表的元素总数),所以如果在输入具体分数时元素总数未知,则该程序无法使用。

现在修改代码使程序不断读取从键盘输入的分数,直至收到停止读取的信号(具体来说就是输入 ‘End’)。具体如代码清单 7-12 所示。

在这里插入图片描述

首先,程序生成空列表 tensu。

程序中的 while 语句会无限循环,不断读取字符串。如果读取的字符串 s 是 ‘End’,则程序执行 break 语句后会强行结束 while 语句。

当读取的字符串 s 不是 ‘End’ 时,程序会通过 int 函数将 s 转换为整数值,并将其添加到列表 tensu 的末尾。

▶ 变量 number 被初始化为 0。程序每次读取整数值后 number 都会递增,所以读取的分数个数(与列表 tensu 的元素总数一致)始终正确。

列表元素的最大值和最小值
  下面讲解如何计算最低分和最高分。不过对读者来说,直接思考如何计算列表元素的最大值和最小值可能难度较大,所以我们先思考如何计算变量 a、b、c 等的最大值。如下所示。

maximum = a                   # 暂定最大值为a
if b > maximum: maximum = b   # 如果b大于maximum,则暂定最大值为b
if c > maximum: maximum = c   # 如果c大于maximum,则暂定最大值为c
if d > maximum: maximum = d   # 如果d大于maximum,则暂定最大值为d
#--- 以下相同 ---#

首先,将 a 的值赋给变量 maximum。然后,如果后继变量大于 maximum,则将该变量的值赋给 maximum。重复此步骤。

在上述步骤中,将变量 a、b、c 等分别替换为 tensu[0]、tensu[1]、tensu[2] 等后,代码会变成下面这样。

maximum = tensu[0]
if tensu[1] > maximum: maximum = tensu[1]
if tensu[2] > maximum: maximum = tensu[2]
if tensu[3] > maximum: maximum = tensu[3]
#--- 以下相同 ---#

我们可以看出,如果元素总数为 n,则需执行 n-1 次 if 语句。因此,我们可以用下面的方法计算(元素总数为 number 的)列表 tensu 的最大值。

maximum = tensu[0]
for i in range(1, number):
    if tensu[i] > maximum: maximum = tensu[i]

另外,比较运算符 > 被 < 替换后,程序可以计算最小值。代码清单 7-13 对代码清单 7-12 进行了修改,可以计算出最低分和最高分。

在这里插入图片描述

为了熟练掌握列表的使用方法,我们必须能迅速编写出计算最小值和最大值的程序。

但在实际编程中,我们一般会调用内置函数 min 和 max 来计算最小值和最大值(代码清单 7-14)。

在这里插入图片描述

使用列表实现矩阵
  本章前半部分提到列表的元素也可以是列表。利用该特性可以实现由行和列构成的列表。

▶ 这种结构在许多编程语言中称为二维数组。

我们以一个 2 行 3 列的二维列表为例进行思考。该列表的生成方式如下。

x = [[1, 2, 3], [4, 5, 6]]         # 2行3列的二维列表

虽然二维列表的生成方式很简单,但要深入理解其含义并不容易。

像第 5 章提到的那样,变量只是绑定到对象(引用对象)的名字。

如图 7-15 所示,该列表的内部结构非常复杂。

在这里插入图片描述

图 7-15 2 行 3 列的二维列表的内部结构

▶ 各变量和索引表达式的含义如下。

x 是对列表的引用,该列表由 x[0] 和 x[1] 这 2 个元素构成。
x[0] 是对列表的引用,该列表由 x[0][0]、x[0][1] 和 x[0][2] 这 3 个元素构成。
x[0][0] 是对单一的 int 型对象 1 的引用
x[0][1] 是对单一的 int 型对象 2 的引用
x[0][2] 是对单一的 int 型对象 3 的引用
x[1] 是对列表的引用,该列表由 x[1][0]、x[1][1] 和 x[1][2] 这 3 个元素构成。
x[1][0] 是对单一的 int 型对象 4 的引用
x[1][1] 是对单一的 int 型对象 5 的引用
x[1][2] 是对单一的 int 型对象 6 的引用
 另外,图中 x、x[0] 和 x[0][0] 等所在方框存放的是引用对象的地址。

如图所示,我们可以使用索引表达式 x[0] 和 x[1] 访问列表 x 中的两个元素。这两个元素各自引用了一个列表。两个列表的元素(的引用对象)分别是 1、2、3 和 4、5、6。它们是“元素的元素”(的引用对象)。为了便于理解,我们称呼“元素的元素”为构成元素。

索引表达式使用了两次索引运算符 [] 来表示(引用)构成元素。按从前往后的顺序依次并列显示索引表达式及其引用的值就是下面这样。

x[0][0]     x[0][1]     x[0][2]     x[1][0]     x[1][1]     x[1][2]
   1           2           3           4           5           6

图 7-16 只汇总显示了两个索引的值和构成元素的值。

在这里插入图片描述

图 7-16 2 行 3 列的二维列表的逻辑结构

该图展示了 2 行 3 列的列表的逻辑结构。也就是说,当我们听到“2 行 3 列的列表”时,可以将其想象成图 7-16 那样。

两个索引值分别表示以下含义。

靠前的索引…该值表示行号(0 和 1)
靠后的索引…该值表示列号(0、1 和 2)

▶ 这里考虑了所有行的列数相等的情况。如果各行的列数不相等,就会得到下面这种不规则的二维列表。

x = [[1, 2, 3], [4, 5], [6, 7, 8]]

上文介绍了在构成元素的值已知的情况下生成二维列表的方法。

在生成列表时,如果元素总数已知而具体的值未知,则需要使用下面的方法生成列表(第 165 页和第 166 页介绍的列表生成方法的应用示例)。

# 生成所有元素都为None的2行3列的列表
x = [None] * 2
for i in range(2):
    x[i] = [None] * 3

我们来创建一个读取行数和列数后,计算两个矩阵的和的程序。具体如代码清单 7-15 所示。

在这里插入图片描述

a、b 和 c 都是行数为 height、列数为 width 的矩阵。图 7-17 为运行示例中构成元素的值。

在这里插入图片描述

图 7-17 2 行 3 列的矩阵的加法运算

代码 1 和代码 2 在生成矩阵 a 和矩阵 b 的同时读取构成元素的值。

代码 3 在生成矩阵 c 的同时计算矩阵 a 与矩阵 b 的和。程序不断将 a[i][j] 与 b[i][j] 的和赋给 c[i][j],也就是将 a 和 b 中索引相同的构成元素之和赋给 c 中同一索引的构成元素。

▶ 图中蓝色部分是将 a[1][1] 与 b[1][1] 之和赋给 c[1][1] 的过程。所有的构成元素都进行了同样的加法运算。

复制列表
  本书第 167 页的例 7-3 提到了在使用 = 进行列表间的赋值时,等号右边的引用被复制到了左边(将左边的名字绑定到右边的列表)(图 7-18 )。

现在介绍复制列表本身的方法(图 7-18 )。

使用 copy 方法复制列表
  copy 方法用于生成并返回新复制的列表(例 7-26)。

在这里插入图片描述

复制 lst1 为 lst2 的方法如下。

lst2 = lst1.copy()

使用 list 函数复制列表
  前面讲解的 list 函数用于将传递的参数转换为新的列表,并返回该列表(例 7-27)。

在这里插入图片描述

将列表传递给该函数后,函数会返回复制的列表。如下所示。

lst2 = list(lst1)

使用索引表达式复制列表
  使用索引表达式 [:] 可以取出所有元素,生成新的列表(例 7-28)。

在这里插入图片描述

因此,我们可以使用以下方式复制列表。

lst2 = lst1[:]

在这里插入图片描述

图 7-18 列表的赋值和列表的复制

浅复制和深复制
  前面讲解了复制列表的方法。在复制列表时,如果列表的元素仍是列表,就会产生问题。我们来看例 7-29。

在这里插入图片描述

执行复制操作后,如果 x[0][1] 的值变为 9,则 y[0][1] 的值也变为 9。这是因为程序以浅复制(shallow copy) 的方式对列表进行了复制。

如图 7-19 a 所示,在浅复制的过程中,列表中的所有元素均被复制。此时,复制的元素为 x[0] 和 x[1]。

因为 x[0] 的引用对象和 y[0] 的引用对象相同,所以 x[0][1] 的引用对象和 y[0][1] 的引用对象也相同。

为了避免这样的问题出现,复制必须执行到构成元素这一级。这种复制称为深复制(deep copy)。

可以使用 copy 模块内的 deepcopy 函数进行深复制。我们来看例 7-30。

在这里插入图片描述

运行结果与预想的一致。

如图b 所示,列表的元素与构成元素均被复制。因此,x[0][1] 被赋值为 9(x[0][1] 的引用对象从 2 更新为 9)后,y[0][1] 的值没有发生变化(y[0][1] 的引用对象没有更新)。

要点 使用 copy.deepcopy 函数对列表进行深复制,可以复制多维列表的元素和构成元素。

▶ 上面的示例对二维列表进行了验证。deepcopy 函数也可以正确复制三维以上列表的构成元素。

在这里插入图片描述

图 7-19 列表的浅复制和深复制

7-2 列表解析式

本节,我们会学习列表解析式的相关内容。列表解析式的作用是以简洁的代码快速生成列表。

列表解析式
  首先思考一下列表 [1, 2, 3, 4, 5, 6, 7] 的生成方法。图 7-20 展示了 3 种方法。

在这里插入图片描述

图 7-20 生成元素值为 1~7 的列表

上一节已经介绍过图中的各个生成方法了。使用各方法生成列表的过程如下。

a :生成空列表,然后使用 append 方法逐一添加元素。

b :使用 for 语句实现与 a 相同的步骤。

c :将 range 函数返回的数列(可迭代对象)转换为列表。

在实际编程中,一般不使用上述方法。比较常用的是列表解析式(list comprehension),因为列表解析式的代码比较简洁且执行效率高。

使用列表解析式生成列表的形式如下。

[ 表达式 for 元素 in 可迭代对象 ]          # 列表解析式(形式 1)

将列表解析式套用到图 7-20 的例子中则为 [n for n in range(1, 8)]。请通过例 7-31 进行确认。

在这里插入图片描述

使用列表解析式生成列表的过程如图 7-21 所示。

在这里插入图片描述

图 7-21 使用列表解析式生成列表

图 a是列表 [1, 2, 3, 4, 5, 6, 7] 的生成过程,图 b 是列表 [0, 1, 2, 3, 4, 5, 6] 的生成过程。

图 a和图 b所示程序在执行 for 语句(蓝色底纹部分)的循环后,依次排列每次循环的表达式(n 或 n-1)(橘色底纹部分)的值,生成列表。

现在来编写应用示例。首先,生成元素值为 1~7 各数的平方的列表。我们来看例 7-32。

在这里插入图片描述

下面生成一个字符串列表,该列表的元素是奇数与“+”拼接和偶数与“-”拼接的字符串,如 ‘1+’、‘2-’、‘3+’、‘4-’ 等。例 7-33 是使用 f 字符串生成列表的示例。

在这里插入图片描述

专栏 7-2 解析式

在数学上,集合有外延和内涵两种解析方法。比如,元素为 1、2、3、4、5、6、7 的集合可以用以下方法进行解析。

外延解析:{1, 2, 3, 4, 5, 6, 7}
内涵解析:{x | x 是小于 8 的正整数 }
  Python 的解析式是内涵解析,它包括本章讲解的列表解析式,以及下一章讲解的字典解析式和集合解析式。

生成列表时使用的列表解析式还有更加复杂的形式。比如下面这样。

[ 表达式 for 元素 in 可迭代对象 if 判断表达式 ]          # 列表解析式(形式 2)

调用这种形式的列表解析式后,程序仅在 for 语句循环中的判断表达式成立时取出元素。

例 7-34 生成了仅由 1~7 中的偶数构成的列表。我们来看一下列表的生成过程(图 7-22)。

在这里插入图片描述

运行结果与预想的一致。

在这里插入图片描述

图 7-22 使用列表解析式生成列表(使用 if 语句)

嵌套的解析式
  for 语句一般可以嵌套。同样,解析式的 for 语句也可以嵌套。

请看例 7-35。

在这里插入图片描述

图 7-23a 展示了例 7-35 生成列表的过程。在外层(前侧)的 for 语句中,变量 i 的值从 1 递增到 2,在内层(后侧)的 for 语句中,j 的值从 1 递增到了 3。在此过程中,程序列出 i * 10 + j 的值并生成相应列表。

上例仅仅使用了二重循环,下面我们试着编写一个程序来生成一个列表元素仍是列表的二维列表(例 7-36)。

在这里插入图片描述

在解析式中插入解析式,构成了二重解析式。

▶ 在图 a 中,单个解析式中插入了二重 for 语句。

仔细阅读代码我们就能发现内层的 [] 由后侧的 for j in range(1, 4) 语句控制。图 b 是例 7-36 生成列表的过程。

在例 7-36 的二重解析式结构中,外侧(后方)for 语句在变量 j 从 1 递增到 3 的同时循环执行内层代码,内层(前方)的代码 [ i * 10 + j for i in range(1, 3) ] 执行后生成列表。

这意味着,内层代码 [ i * 10 + j for i in range(1, 3) ] 生成列表后,该列表被外层 for 语句当作元素生成了列表,因此程序生成了二重列表。

在这里插入图片描述

图 7-23 使用列表解析式生成列表(嵌套)

理解上述内容后,应该就能编写程序生成 n 行 n 列的单位矩阵(行号和列号相等的构成元素的值为 1,其余元素为 0)了。该程序如例 7-37 所示。

在这里插入图片描述

例 7-37 的程序生成了 4 行 4 列的单位矩阵。

7-3 扁平序列

字符串和列表之类的数据序列分为扁平序列和容器序列。本节对扁平序列进行介绍。

扁平序列和容器序列
  上一章讲解的字符串和本章讲解的列表有许多相似点,也有许多不同点。元素是否为对象本身是区分二者的关键点。

按照这种观点对元素序列的序列类型(表 5-2)进行分类就是下面这样。

扁平序列(flatsequence)

扁平序列是对同一内置类型的元素直接进行排列的结构。比如字符串型(str 型)的元素是字符,如图 7-24 a所示,元素连续分布在存储空间上。

字符串型(str 型)、字节序列型(bytes 型)、字节数组型(bytearray 型)、memoryview 型和 array.array 型。

容器序列(containersequence)

容器序列是对对象的引用进行排列的结构。

在使用容器序列时,元素(的引用对象)的类型没有必要都相同。图 b 为列表型的示例,各元素,即各对象的引用,连续分布在存储空间上。

▶ 在图 7-24 中,1、2、3、‘abc’、5.7 和 6 等引用对象看似排列在一起,实则分散存储在各处。

列表型(list 型)、元组型(tuple 型)和 collections.deque 型。

扁平序列的结构很简单,占用的存储空间也比较少,处理速度较快。本节会简单介绍一下扁平序列。

▶ 本节之后的内容可以先跳过。但是,第 13 章的内容会用到这里的知识点,届时大家仍需回顾本节内容。

在这里插入图片描述

图 7-24 扁平序列和容器序列

数组型(array 型)
  array 模块提供的数组 array 是可变的扁平序列,序列中的所有元素均由相同类型的数值构成。元素的类型由表 7-1 中的类型代码指定。

表 7-1 数组 array 的类型代码

在这里插入图片描述

这些元素类型与 C 语言(以及其他许多编程语言)的数据类型兼容。修饰整数型的 signed 表示“有符号的”,unsigned 表示“无符号的”。

比如,当数据占 2 字节时,各类型可以表示的值的范围如下所示。

有符号整数(signed int):-32 768~32 767
无符号整数(unsigned int):0~65 535
  数组型虽然表示的数值范围有限,但它也有很多优点,包括占用的存储空间较少、运算速度快、与其他语言的兼容性较好等。

除了元素对象的类型有限制,数组型和列表型在操作方面(大致)相同。请通过例 7-38 中元素类型为 signed int 的数组进行确认。

在这里插入图片描述

▶ 如果要编写程序进行科学计算,我推荐使用名为 NumPy 的程序库,而不是上文介绍的 array。

字节序列型(bytes 型)
  计算机中的数据使用 0 和 1 的序列进行表示。所谓的文本数据可以解释为字符,非文本数据一般称为二进制数据。

字节序列(bytes)以每 8 位为一组表示二进制数据。字节序列对象的类型是 bytes 型,它的每个元素都是 1 字节(十进制数的 0 到 255)的不可变扁平序列。

字节序列字面量
  字符串可以用字面量表示。同样,字节序列也可以用字面量表示。

字节序列字面量(bytes literal)用单引号“'”或双引号“"”包围字符序列并以字符 b 或 B 作为前缀。如下所示。

b'ABC'      # 字节序列字面量
b"XYZ"      # 字节序列字面量

因为每个元素是 1 字节,所以包括 ASCII 码在内,可使用的字符编码为 0~255(专栏 7-3)。当然,中文字符不能使用。

▶ 与字符串字面量相同,我们可以使用 ‘’’ 和 “”" 包围字节序列字面量。另外,如果前缀是 br 而不是 b,表示的就是原始字节序列(rawbytes)。与原始字符串(第 15 页)相同,在使用 br 时,转义序列解释为字面量本身的字符。

字节序列的操作
  Python 提供了各种用于处理字节序列的方法,这些方法与字符串的方法类似。请看下面的示例。

x = b'ABC'
y = x.replace(b'B', b'X')     # x中包含的'B'替换为'X'后生成相应的字节序列

执行上述代码后,y 赋值为 b’AXC’。

另外,与字符串相同,索引和分片表达式也可用于字节序列。

字节序列和字符串相互转换
  bytes 函数用于显式生成字节序列。

bytes([source[, encoding[, errors]]])

以对象 source 为基础生成字节序列。如果 source 省略,函数则生成空字节序列。另外,encoding 参数用于指定编码,该参数可以省略(省略时,encoding 默认为 ‘utf-8’)。参数 errors 用于指定存在不可转换的字符时的操作,该参数可以省略。

▶ 专栏 13-1 将对编码和’utf-8’进行讲解。

比如,对字符串 ’ 汉字 ’ 来说,如果编码不同,其在程序内部(存储空间上的值)的表示方式也不同。

以下示例将字符串 ’ 汉字 ’ 转换为字节序列(以字符串为基础生成字节序列)。

x = bytes('汉字', 'utf-8')    # 从字符串生成字节序列

请通过例 7-39 确认转换后字节序列的内容(每个字节以十六进制的转义序列表示)。

在这里插入图片描述

使用 decode 方法可以进行逆转换,也就是将字节序列转换为字符串。请看以下逆转换(以字节序列为基础生成字符串)的示例。

y = x.decode('utf-8')          # 从字节序列生成字符串

在上例中,y 引用了字符串’汉字’。

字节数组型(bytearray 型)
  bytearray 型的字节数组可以称得上字节序列的可变版本。bytearray 型的字节数组与字节序列在操作方式上相同,只是 bytearray 型的字节数组可变。

专栏 7-3 字符编码和ASCII码

我们人根据外形和发音分辨字符,而计算机根据每个字符被赋予的字符编码进行识别。

字符有多种编码赋予方式。在各种字符编码中,最基本的编码是 1963年在美国制定的 ASCII 码。

表 7C-1 ASCII 码的编码表

在这里插入图片描述

7C-1 是 ASCII 码的编码表。空栏表示该编码无对应字符。此外,表中行与列的 0 ~ F 是用十六进制数表示的各位的值。

比如:

字符 ‘R’ 的编码是十六进制数 52
字符 ‘g’ 的编码是十六进制数 67
  也就是说,该表中的字符编码是2位的十六进制数且范围是 00 ~ 7F(在十进制数的情况下为 0 ~ 127)。

数字字符 ‘1’ 的字符编码是十六进制数 31(也就是十进制数 49),而不是 1。

请不要混淆数字字符和数值。

ASCII 码可表示的字符数较少,无法表示中文字符。在表示中文字符时,一般使用的字符编码是 UTF-8(专栏 13-1)。

总结
容器序列的元素是对象的引用。容器序列有多种类型,包括列表型和元组型等。
列表是可变的 list 型可迭代对象。列表的元素是对象的引用,元素以直线结构排列。
空列表没有元素,其逻辑值为假,非空列表的逻辑值为真。
调用运算符 [] 和 list 函数可以生成列表。
列表中的各个元素可以使用由索引运算符 [] 构成的索引表达式访问。
列表的元素总数(长度)可以通过 len 函数获得。
分片表达式由分片运算符 [:] 构成,使用分片表达式可以连续或按一定周期取出列表元素并生成相应列表。
使用比较运算符可以判断列表元素值的大小关系和等价性。
元素总数为 n 但元素值不确定的列表可以通过调用 [None] * n 生成,元素值(元素的引用对象)可以在列表生成之后确定。
可以使用运算符 in、运算符 not in、index 方法和 count 方法搜索列表。
使用 append 方法可以给列表添加单个元素,使用运算符 += 和 extend 方法可以在列表后添加列表,使用运算符 * 可以重复列表。
可以就地对列表进行增量赋值。

在这里插入图片描述

可以使用 insert 方法在列表中插入元素,使用 remove 方法和 pop 方法删除列表中的元素,使用 clear 方法删除列表中的所有元素。

使用 reverse 方法可以就地反转列表元素的排列顺序。
二维数组可以实现所有元素都是列表的列表。
使用 copy 方法、list 函数和分片表达式 [:] 可以实现对列表的浅复制。
如果复制多重列表时要复制列表中元素的引用对象,则可以使用 copy.deepcopy 函数进行深复制。
使用列表解析式生成列表比通过循环(和条件判断的组合)的方式生成列表更加简洁,效率更高。列表解析式可以嵌套。
扁平序列的元素是对象本身而不是对象的引用。扁平序列的类型包括字符串型(str 型)、数组型(array.array 型)、字节序列型(bytes 型)和字节数组型(bytearray 型)等。
字节序列字面量用 b’…’ 的形式表示。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值