目录

一、什么是数据容器

1. 为什么需要数据容器

2. Python 常见数据容器

二、列表

1. 列表的声明与下标索引

2. 列表的增删改查

2.1 查

2.1 增

2.3 删

2.4 改

3. 常用内置函数

4. 列表的循环遍历

三、元组

1. 元组的定义与读取

2. 为什么元组不可修改

3. 常用方法、内置函数与循环遍历

4. 解包操作

4.1 变量赋值解包

4.2 函数调用中的解包传参

四、字符串

1. 字符串的特点

2. 字符串常用方法

2.1 文本切分与合并

2.2 文本修剪与替换

2.3 状态检索与前缀判定

2.4 字母大小写转换

3. 常用内置函数与遍历

五、序列的通用操作

1. 切片操作

2. 共通操作符

3. 共通内置函数

4. List、Tuple、String 对比

总结


一、什么是数据容器

在前几章中,我们处理的数据是孤立的,无论是数字还是单个布尔值,它们一次只能表达一个状态。但在实际开发中,我们需要管理成千上万的数据流——例如电商系统的商品列表、用户的敏感词文本等

为了高效组织这些成组的数据,Python 引入了数据容器。本篇博客将系统拆解 Python 中的容器序列,并揭示它们背后相通的设计


1. 为什么需要数据容器

试想一下,如果班级里有 30 个学生,我们需要在程序中记录所有人的名字。在没有数据容器的情况下,你可能需要声明 30 个独立的变量:

student1 = "张三"
student2 = "李四"
# ... 声明到 student30,代码将变得极其难以维护

这种做法不仅极其臃肿,而且当班级人数动态增加时,程序根本无法运行。数据容器是一种可以容纳多个元素的高效数据类型。它允许我们用一个变量名来管理成组的数据,并提供了批量遍历、检索和统计的标准化接口


2. Python 常见数据容器

根据底层数据结构与组织方式的不同,Python 的数据容器主要分为四大类:列表(List)元组(Tuple)字符串(String)字典(Dictionary)集合(Set)

而在这些容器中,列表、元组、字符串 拥有一个共同的底层身份:序列(Sequence)

在 Python 的世界里,序列意味着在内存中都是连续排列的,且全部原生支持下标索引、切片、拼接、成员运算等底层操作。这种统一的序列思维,也是后续深入学习 NumPy 矩阵运算和 Pandas 数据分析的基石

二、列表

列表是 Python 中使用频率最高、功能最强大的数据容器。其具有以下四个特征:

  1. 有序性:元素在内存中是连续有序排列的,每个元素都有固定的物理下标(支持双向索引)

  2. 可变性:可以在不改变列表自身内存地址的前提下,随时随地进行增、删、改

  3. 元素无限制:一个列表内可以同时塞入整型、浮点型、字符串、布尔值,甚至是另一个列表

  4. 允许重复:列表内允许存在完全相同的元素


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 的五种核心数据容器进行系统对比,并通过综合案例进一步掌握它们在实际开发中的应用

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐