Python从入门到实战(五):序列数据容器
目录
一、什么是数据容器
在前几章中,我们处理的数据是孤立的,无论是数字还是单个布尔值,它们一次只能表达一个状态。但在实际开发中,我们需要管理成千上万的数据流——例如电商系统的商品列表、用户的敏感词文本等
为了高效组织这些成组的数据,Python 引入了数据容器。本篇博客将系统拆解 Python 中的容器序列,并揭示它们背后相通的设计
1. 为什么需要数据容器
试想一下,如果班级里有 30 个学生,我们需要在程序中记录所有人的名字。在没有数据容器的情况下,你可能需要声明 30 个独立的变量:
student1 = "张三"
student2 = "李四"
# ... 声明到 student30,代码将变得极其难以维护
这种做法不仅极其臃肿,而且当班级人数动态增加时,程序根本无法运行。数据容器是一种可以容纳多个元素的高效数据类型。它允许我们用一个变量名来管理成组的数据,并提供了批量遍历、检索和统计的标准化接口
2. Python 常见数据容器
根据底层数据结构与组织方式的不同,Python 的数据容器主要分为四大类:列表(List)、元组(Tuple)、字符串(String)、字典(Dictionary) 和 集合(Set)
而在这些容器中,列表、元组、字符串 拥有一个共同的底层身份:序列(Sequence)
在 Python 的世界里,序列意味着在内存中都是连续排列的,且全部原生支持下标索引、切片、拼接、成员运算等底层操作。这种统一的序列思维,也是后续深入学习 NumPy 矩阵运算和 Pandas 数据分析的基石
二、列表
列表是 Python 中使用频率最高、功能最强大的数据容器。其具有以下四个特征:
-
有序性:元素在内存中是连续有序排列的,每个元素都有固定的物理下标(支持双向索引)
-
可变性:可以在不改变列表自身内存地址的前提下,随时随地进行增、删、改
-
元素无限制:一个列表内可以同时塞入整型、浮点型、字符串、布尔值,甚至是另一个列表
-
允许重复:列表内允许存在完全相同的元素
1. 列表的声明与下标索引
列表使用方括号进行定义,内部元素之间使用英文逗号隔开
# 声明一个可以容纳混合类型的列表
my_list = ["Python", 2026, True, 3.14]
双向下标索引
由于列表是序列,它内部的元素是有序排列的。Python 为序列提供了天才般的双向索引机制:
-
正向索引:从左往右,从 0 开始递增
-
反向索引:从右往左,从 -1 开始递减(用于快速定位末尾元素)
names = ["张三", "李四", "王五"]
print(names[0]) # 输出:"张三"(第一个元素)
print(names[-1]) # 输出:"王五"(倒数第一个元素)
2. 列表的增删改查
列表是可变容器,这意味着我们可以在不改变其内存地址的前提下,动态修改其内部的元素
2.1 查
-
list.index(obj):查找某个元素在列表中第一次出现的下标,若不存在则报错
-
list.count(obj):统计某个元素在列表中出现的总次数
tools = ["Python", "Java", "Python"]
print(tools.index("Python")) # 输出:0
print(tools.count("Python")) # 输出:2

2.1 增
-
list.append(obj):在列表的末尾追加一个新元素(高频)
-
list.insert(index, obj):在指定的下标位置插入元素,后续元素自动后移
-
list.extend(iterable):将另一个可迭代对象中的所有元素合并到当前列表中
nums = [1, 2]
nums.append(3) # [1, 2, 3]
print(nums)
nums.insert(1, 99) # [1, 99, 2, 3]
print(nums)
nums.extend([4, 5]) # [1, 99, 2, 3, 4, 5]
print(nums)

2.3 删
-
del list[index]:根据下标直接删除指定位置的元素(Python 内置关键字)
-
list.pop([index]):移除并返回指定下标处的元素。如果不传参数,默认移除并返回最后一个元素
-
list.remove(obj):根据具体的值移除列表中第一次匹配到的元素(若值不存在会报错)
-
list.clear():清空整个列表中的所有元素
fruits = ["苹果", "香蕉", "橘子", "香蕉"]
del fruits[0] # 删除了"苹果"
popped_item = fruits.pop() # 移除了最后的"香蕉"并返回
print(popped_item)
fruits.remove("香蕉") # 移除了中间的"香蕉"
print(fruits) # 输出:["橘子"]

2.4 改
直接通过下标锁定位置,并使用赋值运算符 = 赋予新值
languages = ["C", "C++"]
languages[0] = "Python" # 修改为 ["Python", "C++"]
print(languages)

3. 常用内置函数
除了基础的增删改查,Python 还提供了一系列强大的内置函数和列表自身的方法,用于对数据进行统计、排序和重组。
统计函数
这三个全局函数不仅通用于列表,也是未来处理海量数据的利器:
-
len(list):返回列表中元素的总个数
-
max(list) / min(list):返回列表中的最大值或最小值(要求内部元素类型一致且可比较,如全为数值或全为字符串)
-
sum(list):计算列表中所有数值型元素的总和
grades = [78, 92, 85, 60, 100]
print(len(grades)) # 输出:5
print(max(grades)) # 输出:100
print(sum(grades)) # 输出:415

原位排序与临时排序
在对列表进行排序时,Python 提供了两种完全不同的机制:
-
方法 A:list.sort()(原位排序) 直接对当前列表内部的元素顺序进行调整,会彻底改变原列表,没有返回值
-
函数 B:sorted(list)(临时排序) 这是一个全局内置函数。它会复制一份原列表,排好序后返回一个全新的列表对象,原列表的顺序不发生改变
# 案例 A:原位排序
nums = [3, 1, 4, 2]
nums.sort()
print(nums) # 原列表已被彻底修改,输出:[1, 2, 3, 4]
# 案例 B:临时排序
scores = [30, 10, 40, 20]
new_scores = sorted(scores)
print(scores) # 原列表不变,输出:[30, 10, 40, 20]
print(new_scores) # 拿到了排好序的新列表,输出:[10, 20, 30, 40]
# 拓展:如果需要降序(从大到小),只需传入参数 reverse=True
# nums.sort(reverse=True) 或 sorted(scores, reverse=True)

-
反转列表 list.reverse():不进行大小比较,单纯将列表的物理顺序首尾彻底反转
4. 列表的循环遍历
遍历是指将列表中的每一个元素依次取出来执行相同的操作。在 Python 中,遍历列表有两种最标准的方法:
直接迭代元素
如果你只需要读取数据,不需要知道或者修改元素的下标位置,首选这种方式
languages = ["Python", "Java", "Go"]
for lang in languages:
print(f"推荐学习语言:{lang}")

利用 range() 结合下标控制
如果你在遍历的过程中需要通过下标去修改原列表中的值,应当使用这种方式
prices = [10, 20, 30]
# 通过 len() 获取长度,range() 生成 0, 1, 2 的下标序列
for i in range(len(prices)):
# 将列表中的每件商品价格打八折
prices[i] = prices[i] * 0.8
print(prices) # 输出:[8.0, 16.0, 24.0]

三、元组
如果在开发中,所有的容器都是可变的,那就意味着程序中的关键数据随时面临着被后续代码误修改、误删除的风险。为了在解决这个问题,Python 设计了元组,其具有以下特征
-
有序性:与列表一致,支持双向物理下标索引
-
不可变性:数据一旦写入便处于只读状态,具备天然的线程安全性
-
高效性:由于结构固定,元组在底层的内存开销比列表更小,数据检索和加载的速度也更快
1. 元组的定义与读取
元组使用小括号进行定义,内部元素之间使用英文逗号隔开。由于元组本质上也是序列,它也继承了列表的下标索引机制
# 定义一个存储服务器配置的元组
server_config = ("192.168.1.100", 8080, "admin")
# 使用物理下标读取元素(正向、反向均可)
print(server_config[0]) # 输出:"192.168.1.100"
print(server_config[-1]) # 输出:"admin"
注意:如果一个元组内部只包含一个元素,在定义时必须在这个元素的后面加上一个英文逗号!否则,Python 解释器会将小括号误识别为数学运算符中的括号,从而把它当成一个普通的字符串或数值
fake_tuple = ("Python") # 没有逗号,type(fake_tuple) 结果为 <class 'str'> real_tuple = ("Python",) # 加上逗号,type(real_tuple) 结果为 <class 'tuple'>
2. 为什么元组不可修改
元组最核心的特性是不可变。一旦元组被创建,任何尝试在其身上执行 append、insert、pop 或直接赋值修改的操作,结果都会引发 TypeError 导致程序崩溃
dimensions = (200, 50)
# dimensions[0] = 250 # 报错
相对不可变机制
需要特别注意的是,元组的不可变性指的是元组各个内存单元所指向的对象的引用(内存地址)不可改变
如果元组内部嵌套了一个可变对象(如列表),那么这个列表内部的元素是可以被改变的,因为这并没有改变元组本身指向该列表的物理地址
# 元组包含一个不可变的数值和一个可变的列表
nested_tuple = (10, ["张三", "李四"])
# 尝试改变列表内部的值
nested_tuple[1][0] = "王五"
print(nested_tuple) # 输出:(10, ['王五', '李四'])

3. 常用方法、内置函数与循环遍历
由于元组是只读的,它内部的方法比列表要精简得多,仅保留了两个用于查找与统计的安全方法:
-
tuple.index(obj):查找元素第一次出现的下标位置
-
tuple.count(obj):统计元素在元组中出现的总次数
内置函数
列表通用的内置统计函数,元组同样可以调用:
nums_tuple = (10, 20, 30, 20)
print(len(nums_tuple)) # 获取长度,输出:4
print(nums_tuple.count(20)) # 统计计数,输出:2
print(sum(nums_tuple)) # 求和,输出:80
元组的循环遍历
因为不需要在遍历时修改元素,元组通常直接采用最轻量、最具 Python 风格的遍历范式:
fruits = ("苹果", "香蕉", "橘子")
for fruit in fruits:
print(f"水果:{fruit}")
4. 解包操作
解包是 Python 序列特有的语法糖。它允许我们一次性将元组(或列表)中的所有元素,成批地赋值给多个独立的变量
4.1 变量赋值解包
一次性将元组中的所有元素,成批地赋值给多个独立的变量
# 声明一个包含 3 个元素的元组
user_info = ("Tom", 18, "Beijing")
# 执行解包:变量的数量必须与元组中元素的数量严格一致
name, age, city = user_info
print(name) # 输出:"Tom"
print(age) # 输出:18
print(city) # 输出:"Beijing"

快速交换两个变量的值
在其他语言(如 C, Java)中,交换两个变量需要引入一个第三方临时变量。但在 Python 中,利用元组的隐式打包与解包,一行代码即可优雅搞定:
a = 10
b = 20
a, b = b, a # 右边隐式打包成元组 (20, 10),左边解包赋值
print(a, b) # 输出:20 10
4.2 函数调用中的解包传参
当一个函数需要接收多个位置参数,而你手头的数据刚好被打包在一个元组中时,你不需要通过 tuple[0]、tuple[1] 这种死板的方式挨个传参
在调用函数时,只需在元组变量前加上一个星号,Python 就会自动把元组解包,将其内部的元素按照先后顺序,对应地传给给函数的形参
# 定义一个计算长方体体积的函数,需要 3 个确定的参数
def calc_volume(length, width, height):
return length * width * height
# 现成的数据被封装在一个配置元组中
box_dimensions = (10, 5, 2)
# 使用 * 号进行解包传参
# 它等价于:calc_volume(10, 5, 2)
volume = calc_volume(*box_dimensions)
print(f"计算得出的体积为:{volume}") # 输出:100

注意:使用
*解包传参时,元组内元素的个数必须与函数要求的实参个数完全一致。如果元组里有 4 个元素,而函数只要 3 个参数,程序会直接崩溃
四、字符串
在前面的章节中,我们已经频繁接触过字符串。现在,我们需要给字符串贴上它真正的技术标签:字符串是一个只读的字符序列。在 Python 中,文本处理本质上都是对这一字符序列的操作。其具有以下特性
-
有序性:内部字符呈线性连续排列,支持双向下标
-
不可变性:文本一旦生成,任何修改行为都会导致新字符串的诞生
-
类型单一性:与列表、元组不同,字符串容器的内部只能容纳单字符
1. 字符串的特点
既然字符串是序列,它同样支持正反向物理下标,且每个物理单元存储的就是单个字符
text = "Python"
# 利用序列的下标进行读取
print(text[0]) # 输出:"P"
print(text[-1]) # 输出:"n"
不可变性
字符串与元组一样,其底层的物理结构是固定不可变的。你无法直接通过下标去修改、替换或删除字符串里的某个字符
word = "python"
# word[0] = "P" # 错误!引发崩溃
2. 字符串常用方法
由于字符串在实际业务中承担了大量的文本清洗、日志解析和数据对齐工作,Python 为其内置了极为丰富的处理方法
所有字符串方法都不会改变原字符串,它们在处理完毕后,都会返回一个全新创建的字符串对象
2.1 文本切分与合并
-
str.split(sep):根据指定的分割符,将字符串切片并拆分成一个列表
-
str.join(iterable):以当前字符串作为连接符,将传入的容器(如列表)中的所有字符串串联成一个新字符串
# 数据清洗案例:解析日志数据
raw_data = "张三,18,北京"
user_list = raw_data.split(",")
print(user_list) # 输出列表:['张三', '18', '北京']
# 将列表重新用波浪号组装
new_data = "~".join(user_list)
print(new_data) # 输出字符串:"张三~18~北京"
2.2 文本修剪与替换
-
str.strip():移除字符串首尾两侧的所有空白字符(包括空格、换行符 \n、制表符 \t)
-
str.replace(old, new):将字符串中所有的 old 子串替换为 new 子串
dirty_str = " \n admin_root \t "
clean_str = dirty_str.strip()
print(clean_str) # 输出:"admin_root"
sentence = "I love Java"
print(sentence.replace("Java", "Python")) # 输出:"I love Python"
2.3 状态检索与前缀判定
-
str.find(sub) / str.index(sub):查找子串第一次出现的下标。区别在于:find 找不到时返回 -1,而 index 找不到时会直接报错崩溃
-
str.startswith(prefix) / str.endswith(suffix):判断字符串是否以特定的前缀或后缀开头/结尾,返回布尔值
filename = "report.xlsx"
# 快速判断文件类型
if filename.endswith(".xlsx"):
print("这是一个 Excel 表格文件")
2.4 字母大小写转换
-
str.lower():全部转换为小写(常用于忽略大小写的用户登录验证)
-
str.upper():全部转换为大写
3. 常用内置函数与遍历
通用内置函数
-
len(str):统计字符串中字符的总个数
-
max(str) / min(str):根据 ASCII 编码的大小,找出字符串中编码最大/最小的字符
code = "ABC"
print(len(code)) # 输出:3
遍历字符串
因为是序列,直接使用 for...in 循环即可依次读取每一个字符:
message = "SOS"
for char in message:
print(char, end="-") # 输出:S-O-S-

五、序列的通用操作
正如本章开头所强调的,List、Tuple 和 String 在 Python 中并不仅仅是三个孤立的数据类型,它们共同属于序列
这意味着,它们在底层的内存组织结构上有着高度的相似性。学会了列表的操作,其核心逻辑可以平移到元组和字符串上
1. 切片操作
切片是指从一个现有的序列中,截取出一部分连续的子序列。切片操作最优雅的地方在于:它完全不破坏原有的序列实体,而是复制并生成一个相同类型的全新子序列
切片语法
sequence[start:stop:step]
-
start:起始位置下标(包含该位置)。如果不写,默认从最开头(正向为 0,反向为末尾)开始截取
-
stop:结束位置下标(不包含该位置)。这遵循了数学中经典的左闭右开原则 [start, stop)。如果不写,默认一路截取到序列末尾
-
step:步长,控制截取时的间隔与推进方向。默认为 1。如果将其设为负数,则代表从右向左进行逆序截取
切片操作案例
完全相同的切片语法,在列表、元组和字符串中的运行规律完全一致:
# 1. 列表切片
nums_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(nums_list[2:5]) # 截取下标 2 到 4,输出:[2, 3, 4]
print(nums_list[:4]) # 缺省 start,从头截取,输出:[0, 1, 2, 3]
print(nums_list[1:8:2]) # 步长为 2 隔一个取一个,输出:[1, 3, 5, 7]
print(nums_list[::-1]) # 逆序输出:[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
# 2. 元组切片(返回的依然是只读元组)
nums_tuple = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
print(nums_tuple[2:5]) # 输出:(2, 3, 4)
print(nums_tuple[::-1]) # 输出:(9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
# 3. 字符串切片(返回的依然是字符串)
text_str = "0123456789"
print(text_str[2:5]) # 输出:"234"
print(text_str[::-1]) # 输出:"9876543210"

2. 共通操作符
除了切片,以下四个操作符在整个序列家族中具有完全统一的物理语义:
-
下标访问 [ ]:支持正向 0 ~ N-1 以及反向 -1 ~ -N 的双向寻址
-
序列拼接 +:将两个同类型的序列物理融合成一个全新的长序列
-
序列重复 *:将当前序列复制指定的次数,生成一个冗余的新序列
-
成员运算符 in / not in:判断某个元素(或子串)是否存在于当前序列中,返回布尔值(True/False)
# 序列拼接 (+)
print([1, 2] + [3, 4]) # 列表拼接,输出:[1, 2, 3, 4]
print("Hello " + "World") # 字符串拼接,输出:"Hello World"
# 序列重复 (*)
print("-" * 5) # 字符串重复,输出:"-----"
print((0,) * 3) # 元组重复,输出:(0, 0, 0)
# 成员检测 (in)
print(5 in [1, 3, 5, 7]) # 列表检测,输出:True
print("Py" in "Python") # 字符串子串检测,输出:True

3. 共通内置函数
Python 提供了四个全局内置函数,专门用来提取任何序列的宏观统计状态:
-
len(seq):返回当前序列的成员总个数
-
max(seq):返回当前序列中的最大值。
-
min(seq):返回当前序列中的最小值。
-
sum(seq):计算当前序列中所有元素总和(注意:这要求序列内部必须全为数值类型,若是字符串序列调用 sum() 会直接报错)
my_list = [10, 30, 20]
my_tuple = (10, 30, 20)
my_str = "abc" # 'c'的 ASCII 码最大,'a'的最小
print(len(my_list), max(my_tuple), min(my_str)) # 输出:3 30 a

4. List、Tuple、String 对比
我们通过下表对这三个序列容器进行对比:
| 类型 | 列表 | 元组 | 字符串 |
| 声明 | 方括号 [ ] | 小括号 ( ) | 单/双/三引号 " " |
| 可变性 | 可变 | 只读 | 只读 |
| 数据限制 | 可同时容纳任何类型 | 可同时容纳任何类型 | 只能容纳单字符 |
| 性能 | 较高(需预留额外的扩容空间) | 较低(结构紧凑,读取速度极快) | 极低(底层针对文本进行了深度优化) |
总结
本章围绕 Python 中的序列数据容器展开,系统介绍了列表、元组和字符串的基本概念、特点及常用操作,并进一步学习了序列之间共享的索引、切片、遍历、拼接等通用特性
虽然三种数据容器都属于序列类型,但它们在是否可修改、使用场景以及设计目的上各有侧重:列表适合存储和维护可变数据,元组适合保存不可变数据,而字符串则专门用于文本处理。理解它们之间的联系与区别,是后续学习 Python 数据结构的重要基础
下一篇我们将继续学习另外两种常用的数据容器——集合和字典。它们虽然不属于序列类型,却在去重、快速查找、键值映射等场景中发挥着不可替代的作用。届时,我们还会对 Python 的五种核心数据容器进行系统对比,并通过综合案例进一步掌握它们在实际开发中的应用

更多推荐






所有评论(0)