3-字符串、列表
约 12341 字大约 41 分钟
2026-10-30
在前面的章节中,已经学习了Python编程的基础知识,包括变量、数据类型、运算符、选择结构和循环结构,能够编写一些简单的程序来解决实际问题了。但是细心的话会发现一个问题:到目前为止,一个变量每次只能保存一个数据。比如,用一个变量保存一个学生的姓名没问题,可是要保存全班50个学生的姓名呢?难道要定义50个变量name1、name2、name3……一直写到name50吗?再比如,从键盘输入一个学生的三门课成绩,用三个变量还能应付,如果要处理一个班、一个年级的成绩呢?
显然,靠"一个变量存一个值"的老办法是不行了。实际的数据往往有两种典型形态:一种是一段文本,比如学生的姓名、家庭住址、一句话的留言;另一种是一组同类数据,比如一个班所有人的成绩、购物车里的所有商品。Python为这两种形态分别准备了专门的"容器":字符串用来存储和加工一段文本,列表用来管理和加工一组数据。
字符串和列表是Python中使用频率最高的两种数据类型,也是后续学习的基础。第四章的元组和字典、第五章的函数、第六章的模块、第九章的数据库操作,处处都要用到本章的知识。本章将先学习字符串的表示、索引、切片、常用方法和格式化,再学习列表的创建、增删改查、排序统计和遍历,最终能够独立开发一个班级成绩管理小程序。
【学习目标】
知识目标
理解字符串的概念,掌握字符串的创建、索引与切片方法。
掌握字符串的拼接、重复、长度计算、成员判断等基本操作。
熟悉字符串的常用方法,包括大小写转换、字符判断、查找统计、替换、分割与连接等。
掌握三种字符串格式化方式:%格式化、format()方法和f-string。
理解列表的概念和特点,掌握列表的创建与访问方法。
掌握列表元素的增加、删除、修改、查找、排序和统计操作。
掌握列表的两种遍历方式,了解嵌套列表的使用。
掌握字符串与列表相互转换的方法。
能力目标
能够运用字符串方法完成文本的清洗、查找、替换等常见处理任务。
能够使用格式化输出规范地展示数据。
能够使用列表管理批量数据,熟练完成增删改查操作。
能够对一组数据进行统计计算,如求总和、平均值、最高值、最低值。
能够综合运用字符串和列表,开发简单的信息管理小程序。
素养目标
培养规范编码的习惯,养成给代码写注释、见名知意的良好风格。
培养数据思维,学会从"数据如何组织"的角度分析和解决问题。
培养严谨细致、精益求精的工匠精神,体会"基础不牢、地动山摇"的道理。
1.字符串
1.1 什么是字符串
在日常生活中,经常要和各种文本信息打交道:学生的姓名、手机号码、家庭住址、一句问候语、一段商品介绍等等。在Python中,这些文本信息统一用字符串(String)来表示。可以把字符串想象成一串珍珠项链:每个字符就是一颗珍珠,珍珠按照顺序一颗一颗地串起来,就构成了一个完整的字符串。
创建字符串非常简单,就是把字符放在一对引号中间。Python提供了三种引号供选择:单引号(')、双引号(")和三引号('''或""")。先来看最基本的情况:
代码 3-1:用不同方式创建字符串
# 用单引号创建字符串
name = '张三'
# 用双引号创建字符串
school = "山西经贸职业学院"
# 单引号和双引号完全等价,选哪种都可以
greeting = "Hello"
greeting2 = 'Hello'
print(greeting == greeting2) # 输出:True
# 引号可以嵌套:字符串内容本身包含引号时,外层换一种引号即可
sentence = "小李说:'今天天气真好'"
print(sentence) # 输出:小李说:'今天天气真好'既然单引号和双引号效果一样,为什么还要提供两种呢?主要是为了处理引号嵌套的问题。就像上面代码的最后一段,想表示"某人说了某句话",话里本身带有单引号,这时候外层用双引号,内层就可以放心地使用单引号,反之亦然。
如果字符串的内容很长,需要跨越多行,就要用到三引号了。三引号允许字符串在代码中直接换行,换行符会自动保存在字符串里:
代码 3-2:使用三引号创建多行字符串
# 三引号字符串可以跨越多行
poem = """
静夜思
床前明月光,
疑是地上霜。
"""
print(poem)运行这段代码,会依次输出三行内容,包括最前面和最后面的换行。三引号字符串还有一个重要用途——作为程序的说明文档,这在第六章编写自定义模块时会大量用到。
还有一类特殊字符需要特别介绍。有些字符没办法直接写在字符串里,比如"换行"这个动作本身。要在字符串中表示换行、制表符这类特殊字符,就需要用到转义字符。转义字符以反斜杠(\)开头,告诉Python"后面的字符有特殊含义":
代码 3-3:转义字符的使用
# \n 表示换行
print("姓名:张三\n年龄:19")
# 输出:
# 姓名:张三
# 年龄:19
# \t 表示制表符,相当于按一次Tab键,常用来对齐输出
print("学号\t姓名\t成绩")
print("2024001\t张三\t92")
# \\ 表示一个真正的反斜杠
print("文件路径:D:\\works\\demo.py")
# \' 和 \" 分别表示单引号和双引号本身
print('我想说:\'你好\'')常用的转义字符见表3-1。
表 3-1 常用转义字符
| 转义字符 | 含义 | 说明 |
|---|---|---|
| \n | 换行 | 最常用,输出时移动到下一行 |
| \t | 制表符 | 相当于按一次Tab键,常用于对齐 |
| \ | 反斜杠 | 输出一个\字符 |
| ' | 单引号 | 在单引号字符串中输出' |
| " | 双引号 | 在双引号字符串中输出" |
| \r | 回车 | 光标移动到行首 |
最后还要记住字符串的一个重要特点:字符串是不可变的。也就是说,一个字符串一旦创建,它里面的字符就不能单独修改了。这一点现在只需要有个印象,等到学习列表的时候,会把两者放在一起对比,到时候就能深刻体会"不可变"和"可变"的区别了。
1.2 字符串的索引与切片
把字符串想象成一串珍珠,每颗珍珠都有自己的位置编号,这个编号就叫做索引(Index)。通过索引,可以精确地取出字符串中的某一个字符。这就好比去电影院看电影,凭座位号就能准确找到自己的位置。
Python中的索引从0开始计数,而不是从1开始。字符串的第一个字符索引是0,第二个是1,第三个是2,以此类推。这一点初学者最容易搞错,一定要牢牢记住。Python还很贴心地提供了负数索引:-1表示最后一个字符,-2表示倒数第二个字符,以此类推:
代码 3-4:字符串的索引
word = "Python"
# 正向索引,从0开始
print(word[0]) # 输出:P(第1个字符)
print(word[1]) # 输出:y(第2个字符)
print(word[5]) # 输出:n(第6个字符)
# 负向索引,从-1开始
print(word[-1]) # 输出:n(最后一个字符)
print(word[-2]) # 输出:o(倒数第2个字符)
# 索引不能超出范围
# print(word[6]) # 报错!IndexError: string index out of range索引一次只能取一个字符,如果想要取出一段连续的字符(比如一个字符串的前三个字符),就要用到切片(Slice)。切片的语法是:字符串[start:stop:step],其中start是起始索引,stop是结束索引(注意:取不到stop位置的字符,是"包头不包尾"),step是步长(默认为1,可以省略)。切片就像用刀切香肠,从哪个位置下刀、切到哪里停下、每隔几厘米切一段,都可以自己指定:
代码 3-5:字符串的切片
word = "Python"
# 基本切片:[start:stop],包头不包尾
print(word[0:3]) # 输出:Pyt(索引0、1、2的字符)
print(word[1:4]) # 输出:yth
# 省略start:默认从头开始
print(word[:3]) # 输出:Pyt
# 省略stop:默认取到末尾
print(word[3:]) # 输出:hon
# 全部省略:复制整个字符串
print(word[:]) # 输出:Python
# 带步长的切片:[start:stop:step]
print(word[0:6:2]) # 输出:Pto(每隔2个取1个)
# 步长为-1,实现字符串反转,这是一个经典技巧
print(word[::-1]) # 输出:nohtyP切片有几个非常人性化的设计:start省略时默认从头开始,stop省略时默认取到末尾,步长省略时默认为1。而且切片操作是"宽容"的——即使索引超出了范围也不会报错,最多取到字符串的边界为止。这和直接用索引取单个字符不一样,索引越界会直接报IndexError错误。
索引和切片在实际开发中用途非常广泛。比如手机号脱敏(只显示前3位和后4位)、提取身份证号中的出生日期、取文件名的扩展名等等,都离不开它们:
代码 3-6:索引与切片的实际应用
# 手机号脱敏:显示前3位和后4位,中间用*号
phone = "13812345678"
print(phone[:3] + "****" + phone[-4:]) # 输出:138****5678
# 从身份证号中提取出生日期
id_card = "140101200401011234"
birthday = id_card[6:14]
print("出生日期:", birthday) # 输出:出生日期: 200401011.3 字符串的基本操作
掌握了索引和切片,再来学习字符串的日常操作。字符串有一组非常直观的运算符,可以用"+"拼接、用"*"重复,还可以用len()计算长度、用in判断包含关系:
代码 3-7:字符串的拼接与重复
# 加号:拼接两个字符串
first_name = "张"
last_name = "三"
full_name = first_name + last_name
print(full_name) # 输出:张三
# 加号只能连接字符串,不能混搭数字
# age = 19
# print("年龄:" + age) # 报错!TypeError
# 乘号:重复字符串
line = "-" * 30
print(line) # 输出:------------------------------
msg = "重要的事情说三遍!" + "重要的事情说三遍!" + "重要的事情说三遍!"
msg2 = "重要的事情说三遍!" * 3
print(msg == msg2) # 输出:True
# len():计算字符串的长度(字符个数)
name = "Python程序设计"
print(len(name)) # 输出:10(7个英文+3个汉字,都按1个字符算)这里有一个特别容易踩的坑:加号左右两边必须是同种类型。字符串加数字会直接报TypeError错误。想把数字拼进字符串,可以先用str()把数字转成字符串,更优雅的方式是后面1.5节要学的格式化输出。
in运算符用来判断一个字符串是否包含在另一个字符串中,返回True或False。配合第二章学过的for循环,还可以逐个字符地遍历字符串:
代码 3-8:成员判断与字符串遍历
# in / not in:判断是否包含
sentence = "Python是一门优秀的编程语言"
print("Python" in sentence) # 输出:True
print("Java" in sentence) # 输出:False
print("Java" not in sentence) # 输出:True
# for循环遍历字符串:依次取出每个字符
for ch in "Hello":
print(ch)
# 输出:
# H
# e
# l
# l
# o
# 综合示例:统计字符串中元音字母的个数
word = "beautiful"
count = 0
for ch in word:
if ch in "aeiou":
count = count + 1
print(f"元音字母个数:{count}") # 输出:元音字母个数:51.4 字符串的常用方法
到目前为止,用过的像len()、print()、input()这样的功能都叫做函数。Python还为字符串这类数据"内置"了一大批专属功能,用"字符串.功能名()"的形式调用,这种专属功能叫做方法(Method)。可以把它理解为:函数是放在公共工具箱里的通用工具,而方法是长在数据身上、专门为这类数据定制的工具。字符串的常用方法非常多,下面分类学习。
upper()、lower()、title()、capitalize()、swapcase()——大小写转换
代码 3-9:大小写转换方法
s = "hello python"
# upper():全部转大写
print(s.upper()) # 输出:HELLO PYTHON
# lower():全部转小写
print("HELLO PYTHON".lower()) # 输出:hello python
# title():每个单词的首字母转大写
print(s.title()) # 输出:Hello Python
# capitalize():只有第一个字母大写,其余小写
print(s.capitalize()) # 输出:Hello python
# swapcase():大小写互换,原来的大写变小写、小写变大写
print("PyThOn".swapcase()) # 输出:pYtHoN
# 注意:这些方法都不会修改原字符串,而是返回一个新字符串
print(s) # 输出:hello python(原字符串没变)要特别留意最后一点:字符串是不可变的,所有的方法都不会改动原字符串,而是返回处理后的新字符串。所以想保留处理结果,必须用一个变量接住它,比如 s = s.upper()。
isdigit()、isalpha()、isalnum()、isspace()、islower()、isupper()——字符类型判断
这类方法用来"体检",判断字符串的内容是不是某种特定的类型,返回True或False。它们最典型的用途是校验用户的输入:
代码 3-10:字符类型判断方法
# isdigit():是否全部是数字
print("13812345678".isdigit()) # 输出:True
print("138-1234".isdigit()) # 输出:False
# isalpha():是否全部是字母
print("Python".isalpha()) # 输出:True
print("Python3".isalpha()) # 输出:False(数字3不是字母)
# isalnum():是否全部是字母或数字
print("Python3".isalnum()) # 输出:True
print("Python 3".isalnum()) # 输出:False(空格既不是字母也不是数字)
# isspace():是否全部是空白字符(空格、制表符、换行符)
print(" ".isspace()) # 输出:True
# islower() / isupper():是否全部是小写 / 大写字母
print("python".islower()) # 输出:True
print("PYTHON".isupper()) # 输出:True
print("Python".islower()) # 输出:False(含大写字母)
# 实际应用:校验用户输入的年龄是否为纯数字
age = input("请输入年龄:")
if age.isdigit():
print("输入合法")
else:
print("年龄必须是数字,请重新输入")
# 实际应用:密码不能全是小写字母(要求包含大写字母)
pwd = input("请设置密码:")
if pwd.islower():
print("密码中缺少大写字母,安全性不足")strip()、lstrip()、rstrip()——去除空白
用户在输入信息时经常手滑多敲几个空格,比如输入" 张三 "。strip()方法可以去掉字符串首尾的空白字符,还字符串一个"干净"的模样:
代码 3-11:去除空白
s = " Python 程序设计 "
# strip():同时去除两端空白
print(s.strip()) # 输出:Python 程序设计
# lstrip():只去除左端(left)空白
print(s.lstrip()) # 输出:Python 程序设计 (右边原样保留)
# rstrip():只去除右端(right)空白
print(s.rstrip()) # 输出: Python 程序设计
# 处理用户输入的标准动作:先strip再使用
name = input("请输入姓名:").strip()注意strip()只处理两端,字符串中间的空格不受影响。处理用户输入时养成"先strip再使用"的习惯,可以避免很多莫名其妙的比对失败。
find()、rfind()、count()——查找与统计
代码 3-12:查找与统计方法
s = "Python is easy, Python is powerful"
# find():查找子串第一次出现的位置,返回索引;找不到返回-1
print(s.find("Python")) # 输出:0
print(s.find("is")) # 输出:7
print(s.find("Java")) # 输出:-1(没找到)
# rfind():从右侧开始查找,返回最后一次出现的位置
print(s.rfind("is")) # 输出:23
# count():统计子串出现的次数
print(s.count("Python")) # 输出:2
print(s.count("is")) # 输出:2
# find()还可以指定查找范围:find(子串, 起始位置)
print(s.find("is", 10)) # 输出:23(从索引10开始找)find()找不到时返回-1而不报错,这个设计很贴心,通常配合if判断使用:if s.find("Java") != -1: 就表示"找到了"。字符串还有一个index()方法,功能和find()几乎一样,唯一的区别是找不到时会直接报错,初学阶段建议优先使用find()。
replace()——替换
代码 3-13:替换方法
s = "Python是世界上最难的语言"
# replace(旧子串, 新子串):把所有匹配的子串都替换掉
print(s.replace("最难", "最优雅")) # 输出:Python是世界上最优雅的语言
# 第三个参数可以限制替换次数
s2 = "苹果-香蕉-橙子-葡萄"
print(s2.replace("-", "、", 2)) # 输出:苹果、香蕉、橙子-葡萄
# 实际应用:敏感词替换
comment = "这个产品真是太差了,客服态度也差"
clean = comment.replace("差", "好")
print(clean) # 输出:这个产品真是太好了,客服态度也好
# 实际应用:把中文分隔符统一替换成英文
score_text = "85,92,78"
print(score_text.replace(",", ",")) # 输出:85,92,78replace()同样返回新字符串,原字符串不变。它和find()、count()一样,都是文本清洗的主力工具。
startswith()、endswith()——开头与结尾判断
代码 3-14:开头与结尾判断
filename = "成绩单.xlsx"
# endswith():判断是否以指定后缀结尾,常用于判断文件类型
print(filename.endswith(".xlsx")) # 输出:True
print(filename.endswith(".docx")) # 输出:False
# startswith():判断是否以指定前缀开头
phone = "13812345678"
print(phone.startswith("138")) # 输出:True
# 实际应用:只处理Excel文件
files = ["成绩单.xlsx", "说明.txt", "汇总.xlsx", "照片.jpg"]
for f in files:
if f.endswith(".xlsx"):
print("处理文件:", f)
# 输出:
# 处理文件: 成绩单.xlsx
# 处理文件: 汇总.xlsx这两个方法返回True或False,特别适合放在if条件里做"过滤"。判断文件类型、判断手机号段、判断网址协议等场景都很常用。
center()、ljust()、rjust()——对齐排版
在打印报表、菜单的时候,往往希望文字能够整齐排列:标题居中显示、姓名靠左对齐、金额靠右对齐。center()、ljust()、rjust()这三个方法就是字符串的"对齐按钮":center(总宽度)居中、ljust(总宽度)靠左、rjust(总宽度)靠右,宽度不足的部分用字符填充(默认是空格,也可以指定填充字符):
代码 3-15:对齐排版方法
# center(宽度, 填充字符):居中对齐
title = "成绩单"
print(title.center(11, "="))
# 输出:====成绩单====
# ljust(宽度):左对齐,右侧补空格
name = "张三"
print("姓名:" + name.ljust(6) + "性别:男")
# 输出:姓名:张三 性别:男("张三"占2格,右侧补4个空格)
# rjust(宽度):右对齐,左侧补空格,是数字对齐的利器
prices = [9.5, 12.8, 105.0]
for p in prices:
print(str(p).rjust(10))
# 输出:
# 9.5
# 12.8
# 105.0
# 填充字符可以自己指定
print("Python".center(20, "*"))
# 输出:*******Python*******这三个方法返回的都是新字符串,常和print配合,让控制台输出瞬间变得像一张规整的表格。等学到第九章打印数据库查询结果时,会再次用到它们。
maketrans()、translate()——字符映射转换
replace()一次只能替换一种内容,如果要对一批字符按"对照表"同时转换(比如把所有元音字母各换成对应的数字),就要用到maketrans()和translate()这对搭档:maketrans(原字符集, 新字符集)负责制作"对照表",translate(对照表)负责按表逐个字符转换:
代码 3-16:maketrans()与translate()
# 第一步:maketrans()制作对照表——原字符和新字符一一对应
table = str.maketrans("aeiou", "12345")
# 第二步:translate()按对照表批量转换
text = "hello python"
print(text.translate(table))
# 输出:h2ll4 pyth4n(e→2,o→4,不在表中的字符原样保留)
# 经典应用:凯撒密码——每个小写字母向后移3位
letters = "abcdefghijklmnopqrstuvwxyz"
shifted = "defghijklmnopqrstuvwxyzabc"
encrypt = str.maketrans(letters, shifted)
secret = "hello python".translate(encrypt)
print(f"加密后:{secret}")
# 输出:加密后:khoor sbwkrq
# 解密就是把对照表反过来再用一次
decrypt = str.maketrans(shifted, letters)
print(f"解密后:{secret.translate(decrypt)}")
# 输出:解密后:hello python
# maketrans()的第三种用法:第三个参数指定要直接删除的字符
vowel_del = str.maketrans("", "", "aeiou")
print("beautiful".translate(vowel_del))
# 输出:btfl(所有元音字母被删除)split()、join()——分割与连接
split()把一个字符串按指定的分隔符拆开,得到一个列表;join()反过来,把列表中的元素用指定的连接符拼成一个字符串。它们是一对"镜像"操作,也是字符串和列表之间最重要的桥梁(2.8节还会深入讲解):
代码 3-17:分割与连接
# split():按分隔符拆分字符串,返回列表
s = "张三,李四,王五,赵六"
names = s.split(",")
print(names) # 输出:['张三', '李四', '王五', '赵六']
# 不给参数时,默认按任意空白(空格、换行、制表符)拆分
text = "Python is powerful"
print(text.split()) # 输出:['Python', 'is', 'powerful']
# join():把列表拼成字符串,"连接符".join(列表)
names = ["张三", "李四", "王五"]
print("、".join(names)) # 输出:张三、李四、王五
print("->".join(names)) # 输出:张三->李四->王五eval()函数——把字符串当作代码执行
最后介绍一个特殊的工具。严格来说,eval()并不是字符串的方法,而是Python的内置函数,但它处理的正是一串"看起来像代码"的文本:eval()会把字符串当作Python表达式来执行,并返回计算结果:
代码 3-18:eval()函数
# 把"算式字符串"直接算出来
result = eval("3 + 5 * 2")
print(result) # 输出:13(字符串里的算式被真正计算了)
# 把"像列表的字符串"直接变成真正的列表
text = "[85, 92, 78]"
scores = eval(text)
print(scores) # 输出:[85, 92, 78]
print(scores[0] + scores[1]) # 输出:177(是真正的数字,能直接运算)
# 对比:split()拆出来的只是字符串,还得逐个转换成数字
parts = "85, 92, 78".split(", ")
print(parts) # 输出:['85', ' 92', ' 78'](元素还是字符串)看起来很方便,但必须敲响警钟:eval()是把字符串当代码执行,这相当于把程序的"方向盘"交了出去。如果字符串的内容来自不可信的来源(比如其他用户的输入、网络上传来的数据),对方可以在里面塞进任何代码,后果不堪设想。所以实际开发中有一条铁律:eval()只用于完全可信的内容。处理用户输入的数据时,请老老实实使用split()加int()的方式,就像本章例题中做的那样。
字符串常用方法汇总见表3-2。
表 3-2 字符串常用方法一览
| 方法 | 功能 | 返回结果 |
|---|---|---|
| upper() / lower() | 全部转大写 / 小写 | 新字符串 |
| title() / capitalize() / swapcase() | 单词首字母大写 / 首字母大写 / 大小写互换 | 新字符串 |
| isdigit() / isalpha() / isalnum() / isspace() | 判断数字 / 字母 / 字母或数字 / 空白 | True 或 False |
| islower() / isupper() | 判断全部小写 / 全部大写 | True 或 False |
| strip() / lstrip() / rstrip() | 去除两端 / 左端 / 右端空白 | 新字符串 |
| find() / rfind() | 正向 / 反向查找子串位置 | 索引值,找不到返回-1 |
| count() | 统计子串出现次数 | 整数 |
| replace(旧, 新) | 替换子串 | 新字符串 |
| startswith() / endswith() | 判断开头 / 结尾 | True 或 False |
| center() / ljust() / rjust() | 居中 / 左对齐 / 右对齐排版 | 新字符串 |
| maketrans() / translate() | 制作 / 执行字符对照表 | 对照表 / 新字符串 |
| split() | 按分隔符拆分 | 列表 |
| join() | 用连接符拼接 | 新字符串 |
1.5 字符串格式化
先来看一个熟悉的场景:计算出了平均分86.66666666666667,想输出"平均分是86.67分",该怎么办?用加号拼接?先得str()转换,还得想办法四舍五入保留两位小数,写起来非常别扭。字符串格式化就是用来解决这类问题的:把数据按照想要的样式"填"进文本模板里。Python支持三种格式化方式,从老到新依次是%格式化、format()方法和f-string,下面逐一学习。
%格式化——最早的写法
%格式化从Python诞生之初就存在,语法是在字符串中用占位符"挖坑",再在后面用%把数据"填坑":
代码 3-19:%格式化
name = "张三"
age = 19
score = 86.66666666666667
# %s填字符串,%d填整数,%f填小数
print("姓名:%s,年龄:%d" % (name, age))
# 输出:姓名:张三,年龄:19
# %.2f 表示保留2位小数
print("平均分:%.2f" % score)
# 输出:平均分:86.67
# %5d 表示宽度为5,不足左边补空格(右对齐)
print("学号:%5d" % 1) # 输出:学号: 1
# %05d 表示宽度为5,不足左边补0
print("学号:%05d" % 1) # 输出:学号:00001
# 字符串本身要输出百分号时,要写成 %%
print("正确率:%.1f%%" % 92.5) # 输出:正确率:92.5%常用占位符见表3-3。
表 3-3 常用%格式化占位符
| 占位符 | 含义 | 示例 | 结果 |
|---|---|---|---|
| %s | 字符串 | "%s" % "Python" | Python |
| %d | 整数 | "%d" % 19 | 19 |
| %f | 小数(默认6位) | "%.2f" % 3.14159 | 3.14 |
| %% | 百分号本身 | "%d%%" % 90 | 90% |
format()方法——通用写法
format()方法用大括号{}作为占位符,数据按顺序填入,或者按编号、按名字填入,比%格式化更灵活:
代码 3-20:format()方法
name = "张三"
age = 19
# 按顺序填入
print("姓名:{},年龄:{}".format(name, age))
# 输出:姓名:张三,年龄:19
# 按编号填入,同一个值可以重复用
print("{0}说:我叫{0},今年{1}岁".format(name, age))
# 输出:张三说:我叫张三,今年19岁
# 按名字填入,可读性最好
print("姓名:{n},年龄:{a}".format(n=name, a=age))
# 格式控制:冒号后面写格式说明
print("平均分:{:.2f}".format(86.66666666666667)) # 输出:平均分:86.67f-string——推荐写法
从Python 3.6开始,推出了f-string(格式化字符串字面量):在字符串前面加一个字母f,大括号里可以直接写变量甚至表达式,简洁得像在纸上填空。这是目前官方推荐的写法,也是实际开发中最常用的:
代码 3-21:f-string格式化
name = "张三"
age = 19
scores = [85, 92, 78]
# 大括号里直接放变量
print(f"姓名:{name},年龄:{age}")
# 输出:姓名:张三,年龄:19
# 大括号里可以写表达式
print(f"三门课总分:{scores[0] + scores[1] + scores[2]}")
# 输出:三门课总分:255
# 保留2位小数,语法和format()一致
average = (85 + 92 + 78) / 3
print(f"平均分:{average:.2f}")
# 输出:平均分:85.00
# 千位分隔符,金额展示常用
print(f"年收入:{12345678:,}元")
# 输出:年收入:12,345,678元
# 输出百分号:f-string里不需要写成%%
print(f"正确率:{92.5}%")三种格式化方式怎么选?一句话:新代码优先用f-string,它最简洁、最直观;读别人的老代码时要认识%格式化和format();三种方式的格式说明符(如.2f、,)是通用的,学一套就都能用。
【例题3-1】敏感词过滤程序
编写一个敏感词过滤程序:程序中预设一个敏感词列表,用户输入一段评论后,把评论中出现的敏感词全部替换为等长的*号,并统计替换了多少个词,最后用格式化输出结果。
代码 3-22:敏感词过滤程序
# 预设敏感词列表
bad_words = ["暴力", "垃圾", "诈骗"]
# 获取用户输入,并做清洗
comment = input("请输入你的评论:").strip()
total = 0
# 逐个处理敏感词
for word in bad_words:
if word in comment:
# 统计出现的次数
times = comment.count(word)
total = total + times
# 替换为等长的*号
comment = comment.replace(word, "*" * len(word))
# 格式化输出结果
print("-" * 30)
print(f"过滤后的评论:{comment}")
print(f"共过滤敏感词 {total} 个")程序运行效果如下(输入不同,结果不同):
请输入你的评论:这个平台全是垃圾广告,还有诈骗信息
------------------------------
过滤后的评论:这个平台全是**广告,还有**信息
共过滤敏感词 2 个这个例题综合运用了本章的多处知识点:strip()清洗输入、in判断包含、count()统计次数、replace()替换、len()计算长度、"*" * len()按长度生成星号、for遍历列表、f-string格式化输出。可以看到,把零散的方法组合起来,就能解决一个完整的应用问题。这个程序还可以继续扩展,比如把敏感词列表保存到文件中(第八章)、存入数据库(第九章),实现敏感词的动态维护。
2.列表
2.1 什么是列表
学会了字符串,处理一段文本没问题了。但是开篇提出的问题还没解决:怎么保存全班50个学生的姓名?总不能定义50个变量。这就轮到列表(List)登场了。
列表就像一张购物清单:清单上可以写很多东西,每件东西按书写顺序排列,随时可以划掉一件(删除)、添加一件(增加)、修改一件(替换)。在Python中,列表用一对方括号[]表示,元素之间用逗号分隔:
代码 3-23:从多个变量到列表
# 老办法:定义50个变量?想想都可怕
# name1 = "张三"
# name2 = "李四"
# ...
# 新办法:一个列表搞定
names = ["张三", "李四", "王五", "赵六", "钱七"]
print(names) # 输出:['张三', '李四', '王五', '赵六', '钱七']
print(len(names)) # 输出:5
# 列表可以存放任意类型的数据,甚至混合存放
student = ["张三", 19, True, 173.5]
print(student) # 输出:['张三', 19, True, 173.5]
# 列表里面还可以再放列表(嵌套列表,见2.7节)
matrix = [[1, 2, 3], [4, 5, 6]]列表有三个重要特点。第一,有序:元素按放入的先后顺序排列,每个元素都有确定的索引。第二,可变:可以随时增加、删除、修改元素,这是列表和字符串最本质的区别。第三,混合:一个列表里可以放不同类型的数据。
现在正式对比一下字符串和列表——它们都是"序列"(按顺序排列的一串数据),都支持索引、切片、len()、in、for遍历,但一个不可变、一个可变:
代码 3-24:可变与不可变的对比
# 字符串不可变:想修改其中一个字符?不行!
s = "hello"
# s[0] = "H" # 报错!TypeError: 'str' object does not support item assignment
# 只能生成一个新字符串
s = "H" + s[1:]
print(s) # 输出:Hello
# 列表可变:想改哪个元素,直接改
letters = ["h", "e", "l", "l", "o"]
letters[0] = "H"
print(letters) # 输出:['H', 'e', 'l', 'l', 'o']一句话总结:字符串像刻在石头上的字,改不了,要改只能重刻一块(生成新字符串);列表像白板上的清单,随手增删改。
2.2 列表的创建与访问
创建列表最常用的方式就是上面见过的"字面创建"——直接用方括号把元素列出来。此外还有两种方式也很常见:
代码 3-25:创建列表的常用方式
# 方式一:字面创建
scores = [85, 92, 78, 96, 60]
# 方式二:list()函数,把其他数据转换成列表
# 字符串转列表:每个字符变成一个元素
chars = list("Python")
print(chars) # 输出:['P', 'y', 't', 'h', 'o', 'n']
# 方式三:list()配合range(),快速生成一批连续数字
nums = list(range(1, 11))
print(nums) # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 创建空列表,之后再往里添加数据(很常用)
data = []
print(data) # 输出:[]range(1, 11)生成1到10的整数序列(包头不包尾,和切片的规则一致),配合list()转换,是批量生成数字列表的标准套路,后面写循环时用得非常多。
列表的访问和字符串完全一样:用索引取单个元素,用切片取一段元素:
代码 3-26:列表的索引与切片
scores = [85, 92, 78, 96, 60]
# 索引访问,同样从0开始,同样支持负数索引
print(scores[0]) # 输出:85
print(scores[-1]) # 输出:60
# 切片,规则和字符串完全一致
print(scores[1:4]) # 输出:[92, 78, 96]
print(scores[:3]) # 输出:[85, 92, 78]
print(scores[::-1]) # 输出:[60, 96, 78, 92, 85](反转列表)
# len()、in、for遍历也全部适用
print(len(scores)) # 输出:5
print(92 in scores) # 输出:True
for s in scores:
print(s)正因为字符串和列表同属"序列",1.2节、1.3节学的索引、切片、len()、in、for遍历可以原封不动地用在列表上,一通百通。
2.3 列表元素的增加
往列表里添加元素有三种方式,分别对应三种不同的场景:append()追加到末尾、insert()插入到指定位置、extend()合并另一个列表。
append()方法——末尾追加一个元素
代码 3-27:append()方法
names = ["张三", "李四"]
# append():把新元素添加到列表末尾
names.append("王五")
print(names) # 输出:['张三', '李四', '王五']
# 最典型用途:循环中收集数据
scores = []
for i in range(3):
s = int(input(f"请输入第{i + 1}门课的成绩:"))
scores.append(s)
print(scores) # 输出类似:[85, 92, 78]append()是使用频率最高的列表方法。要特别注意的是,append()每次只添加一个元素,如果添加的是一个列表,整个列表会被当成一个元素放进去,形成嵌套列表。
insert()方法——指定位置插入
代码 3-28:insert()方法
names = ["张三", "王五", "赵六"]
# insert(索引, 元素):把元素插入到指定索引的位置
# "李四"应该排在"王五"前面,插到索引1的位置
names.insert(1, "李四")
print(names) # 输出:['张三', '李四', '王五', '赵六']
# 原来索引1上的"王五"及后面的元素自动往后挪insert()适合"插队"场景:数据必须按顺序维护,新来的元素要插到正确的位置。它比append()慢一点点,不过初学阶段不必在意性能,选对语义最重要。
extend()方法——合并列表
代码 3-29:extend()方法与加号的对比
class1 = ["张三", "李四"]
class2 = ["王五", "赵六"]
# extend():把另一个列表的元素逐个添加进来
class1.extend(class2)
print(class1) # 输出:['张三', '李四', '王五', '赵六']
# 用加号也能合并,但注意区别:
a = [1, 2]
b = [3, 4]
c = a + b # 加号生成一个新列表,a和b都不变
print(c) # 输出:[1, 2, 3, 4]
print(a) # 输出:[1, 2]
a.extend(b) # extend直接修改a本身,没有返回值
print(a) # 输出:[1, 2, 3, 4]
# 对比:append放列表会变成嵌套
x = [1, 2]
x.append([3, 4])
print(x) # 输出:[1, 2, [3, 4]](列表成了单个元素!)三者如何选择?记住一句话:加一个用append,插指定位置用insert,并一批用extend(或加号)。
2.4 列表元素的删除
有增就有删。删除列表元素有四种方式:remove()按值删、pop()按位置删、del语句按位置删、clear()全部清空。
remove()方法——按值删除
代码 3-30:remove()方法
names = ["张三", "李四", "王五", "李四"]
# remove(值):删除第一个匹配的元素
names.remove("李四")
print(names) # 输出:['张三', '王五', '李四'](只删了第一个"李四")
# 要删除的值不存在,会直接报错
# names.remove("赵六") # 报错!ValueError: list.remove(x): x not in list
# 稳妥的做法:先判断再删除
if "王五" in names:
names.remove("王五")
print(names) # 输出:['张三', '李四']remove()只删除第一个匹配项,如果列表中有多个相同的值,需要配合循环来处理。删除前用in判断一下是否存在,是避免程序崩溃的好习惯。
pop()方法——按索引删除并返回
代码 3-31:pop()方法
scores = [85, 92, 78, 96]
# pop(索引):删除指定位置的元素,并返回它
removed = scores.pop(1)
print(removed) # 输出:92
print(scores) # 输出:[85, 78, 96]
# pop()不带参数:删除并返回最后一个元素(用得非常多)
last = scores.pop()
print(last) # 输出:96
print(scores) # 输出:[85, 78]pop()和remove()最大的区别是"有去有回":删掉元素的同时还能拿到它的值,适用于"取出并处理"的场景,比如模拟排队叫号——叫到谁,谁就出队。
del语句与clear()方法
代码 3-32:del语句与clear()方法
scores = [85, 92, 78, 96, 60]
# del语句:按索引(或切片)直接删除,不需要接收返回值
del scores[0]
print(scores) # 输出:[92, 78, 96, 60]
# del还可以配合切片一次删一片
del scores[1:3]
print(scores) # 输出:[92, 60]
# clear():清空整个列表,变成空列表
scores.clear()
print(scores) # 输出:[]四种删除方式总结:知道值用remove(先in判断),要拿回删掉的值用pop,按位置硬删用del,全部清空用clear。
2.5 列表的修改与查找
列表是可变的,修改元素非常直接:通过索引定位,直接赋新值即可。配合切片,还能一次性替换一段元素:
代码 3-33:修改列表元素
scores = [85, 92, 78, 96, 60]
# 索引赋值:修改单个元素
scores[4] = 88
print(scores) # 输出:[85, 92, 78, 96, 88]
# 修改负索引位置的元素
scores[-1] = 90
print(scores) # 输出:[85, 92, 78, 96, 90]
# 切片赋值:批量替换一段元素(数量可以不一致)
scores[0:2] = [100, 100, 100]
print(scores) # 输出:[100, 100, 100, 78, 96, 90](2个换成了3个)查找元素有两种常用手段:in判断"有没有",index()回答"在哪里":
代码 3-34:查找列表元素
names = ["张三", "李四", "王五", "赵六"]
# in / not in:判断元素是否存在
print("王五" in names) # 输出:True
print("钱七" not in names) # 输出:True
# index(值):返回元素第一次出现的索引
position = names.index("赵六")
print(position) # 输出:3
# 元素不存在时会报错,稳妥做法同样是先in再index
if "钱七" in names:
print(names.index("钱七"))
else:
print("钱七不在名单中")细心的读者可能发现:字符串的find()找不到返回-1,列表的index()找不到却直接报错。这是Python设计上确实存在的小"不一致",所以无论是字符串还是列表,"先判断再操作"都是最稳妥的套路。
2.6 列表的排序与统计
sort()方法与sorted()函数——排序
代码 3-35:列表排序
scores = [85, 92, 78, 96, 60]
# sort():就地排序,直接修改原列表,默认从小到大
scores.sort()
print(scores) # 输出:[60, 78, 85, 92, 96]
# reverse=True:从大到小(降序)
scores.sort(reverse=True)
print(scores) # 输出:[96, 92, 85, 78, 60]
# sorted():函数版排序,返回新列表,原列表不动
old = [85, 92, 78, 96, 60]
new = sorted(old)
print(old) # 输出:[85, 92, 78, 96, 60](原顺序不变)
print(new) # 输出:[60, 78, 85, 92, 96](新列表是排好序的)注意代码最后两行的输出:sort()是"就地"修改列表本身,没有返回值;sorted()则返回一个全新的列表。想要"保留原顺序、另存一份排序结果"时用sorted(),直接改原列表时用sort()。这个区别和字符串方法"永不修改原字符串"形成了有趣的对照——正因为列表可变,它才敢就地修改。
reverse()方法——反转
代码 3-36:列表反转
nums = [1, 2, 3, 4, 5]
# reverse():把列表元素原地首尾颠倒
nums.reverse()
print(nums) # 输出:[5, 4, 3, 2, 1]
# 注意:反转不是排序!只是顺序颠倒
# 切片[::-1]也能得到反转副本,且不改原列表
old = [1, 2, 3]
new = old[::-1]
print(new) # 输出:[3, 2, 1]
print(old) # 输出:[1, 2, 3]max()、min()、sum()——统计
代码 3-37:列表统计
scores = [85, 92, 78, 96, 60]
# max():最大值;min():最小值
print(max(scores)) # 输出:96
print(min(scores)) # 输出:60
# sum():求总和(只能用于数字列表)
print(sum(scores)) # 输出:411
# 组合使用:一行算出平均分
average = sum(scores) / len(scores)
print(f"平均分:{average:.2f}") # 输出:平均分:82.20max()、min()、sum()配合len(),统计一组数据的最大值、最小值、总和、平均值信手拈来——这在没有学列表之前,需要写一大段循环才能实现。
列表常用方法汇总见表3-4。
表 3-4 列表常用方法一览
| 方法/函数 | 功能 | 是否修改原列表 |
|---|---|---|
| append(x) | 末尾添加一个元素 | 是 |
| insert(i, x) | 在索引i处插入元素 | 是 |
| extend(列表) | 合并另一个列表的元素 | 是 |
| remove(x) | 删除第一个值为x的元素 | 是 |
| pop(i) | 删除并返回索引i的元素 | 是 |
| del 列表[i] | 删除索引i的元素(语句) | 是 |
| clear() | 清空列表 | 是 |
| index(x) | 返回x首次出现的索引 | 否 |
| count(x) | 统计x出现的次数 | 否 |
| sort() | 就地排序 | 是 |
| reverse() | 就地反转 | 是 |
| sorted(列表) | 返回排序后的新列表(函数) | 否 |
| max / min / sum / len | 求最大、最小、总和、个数(函数) | 否 |
2.7 列表的遍历
处理批量数据,遍历是核心动作。列表遍历有两种标准写法,各有用武之地。
方式一:for直接遍历元素
代码 3-38:for直接遍历
scores = [85, 92, 78, 96, 60]
# 依次取出每个元素,最简洁、最常用
for s in scores:
print(s)
# 应用:统计不及格的人数
count = 0
for s in scores:
if s < 60:
count = count + 1
print(f"不及格人数:{count}") # 输出:不及格人数:1方式二:for配合range()按索引遍历
代码 3-39:按索引遍历
names = ["张三", "李四", "王五"]
scores = [85, 92, 78]
# 需要索引时(比如按格式输出序号),用range生成索引
for i in range(len(names)):
print(f"第{i + 1}名同学:{names[i]},成绩{scores[i]}")
# 输出:
# 第1名同学:张三,成绩85
# 第2名同学:李四,成绩92
# 第3名同学:王五,成绩78两种方式的选择标准很简单:只需要元素本身,用方式一;需要知道"第几个",或者要同时访问两个平行列表(如names和scores),用方式二。
嵌套列表——用列表模拟表格
一个列表可以存放多名学生每人一门的成绩,那每个学生多门课的成绩怎么存?答案是嵌套列表——列表里面套列表,就像Excel表格:外层列表是"行",内层列表是每行的"格子":
代码 3-40:嵌套列表及其遍历
# 每个内层列表是一名学生的三门课成绩
report = [
["张三", 85, 92, 78],
["李四", 90, 88, 95],
["王五", 72, 65, 80]
]
# 访问:先定位行,再定位列
print(report[0][0]) # 输出:张三(第1行第1列)
print(report[1][2]) # 输出:88(第2行第3列)
# 遍历嵌套列表:外层循环取行,内层循环处理每个学生
for row in report:
name = row[0]
total = row[1] + row[2] + row[3]
average = total / 3
print(f"{name}:总分{total},平均分{average:.2f}")
# 输出:
# 张三:总分255,平均分85.00
# 李四:总分273,平均分91.00
# 王五:总分217,平均分72.33嵌套列表是"表格数据"的初步解决方案。到了第四章会学习字典,用"键值对"来管理这样的数据会更直观;第九章学习数据库后,一张真正的数据表就能解决所有问题。技术总是在一步步演进,但"数据组织"的思想是一脉相承的。
2.8 字符串与列表的相互转换
字符串和列表之间的转换,是数据处理中非常高频的操作。1.4节已经初步认识了split()和join(),这里结合实际场景深入使用。
split()——把字符串拆成列表
代码 3-41:split()的典型应用
# 场景一:一次输入多个数据
# 用户在一行里输入三个成绩,用逗号分隔
text = input("请输入三门课成绩(用逗号分隔):")
# 假设输入:85,92,78
# 先拆成字符串列表
score_strs = text.split(",")
print(score_strs) # 输出:['85', '92', '78'](还是字符串!)
# 再逐个转换成整数
scores = []
for s in score_strs:
scores.append(int(s))
print(scores) # 输出:[85, 92, 78](变成数字了)
# 场景二:处理不规范输入——中文逗号、空格混着来
text2 = "85,92, 78"
text2 = text2.replace(",", ",") # 统一成英文逗号
parts = text2.split(",") # 拆分
print(parts) # 输出:['85', '92', ' 78']
# 场景三:split()不带参数,自动处理多余空白
text3 = "85 92 78"
print(text3.split()) # 输出:['85', '92', '78'](连续空格自动处理)特别提醒:split()拆出来的每个元素都是字符串,哪怕内容看起来是数字。要参与计算,必须再用int()或float()逐个转换,这是初学者最容易忽略的一步。
join()——把列表拼成字符串
代码 3-42:join()的典型应用
# 场景一:把成绩列表拼成一行输出
scores = [85, 92, 78]
# 注意:join要求所有元素都是字符串,数字要先转换
score_strs = []
for s in scores:
score_strs.append(str(s))
print("、".join(score_strs)) # 输出:85、92、78
# 场景二:生成菜单文案
dishes = ["宫保鸡丁", "鱼香肉丝", "麻婆豆腐"]
menu = "今日菜单:" + "、".join(dishes)
print(menu) # 输出:今日菜单:宫保鸡丁、鱼香肉丝、麻婆豆腐
# 场景三:批量生成文件名
parts = ["2026", "10", "成绩单"]
print("-".join(parts) + ".xlsx") # 输出:2026-10-成绩单.xlsxjoin()同样只认字符串元素。split()和join()一对搭档,一个负责"拆"、一个负责"合",把"一整行文本"和"一组数据"自由转换,是批量数据录入和展示的关键技术。
【例题3-2】班级成绩管理小程序
综合运用本章知识,编写一个班级成绩管理小程序:支持一次录入多名学生的成绩,能显示成绩单、添加成绩、删除成绩、统计平均分和最高分,并用格式化方式规范输出。
代码 3-43:班级成绩管理小程序
# ---------- 1. 初始化与成绩录入 ----------
names = []
scores = []
print("=" * 40)
print(" 班级成绩管理小程序")
print("=" * 40)
# 一次录入多名学生:张三,李四,王五
name_text = input("请输入学生姓名(用逗号分隔):").strip()
for n in name_text.replace(",", ",").split(","):
names.append(n.strip())
# 一次录入多门成绩:85,92,78,90,66
score_text = input("请输入成绩(用逗号分隔,与姓名一一对应):").strip()
for s in score_text.replace(",", ",").split(","):
scores.append(int(s))
# ---------- 2. 显示成绩单 ----------
print("-" * 40)
print("序号\t姓名\t成绩")
for i in range(len(names)):
print(f"{i + 1}\t{names[i]}\t{scores[i]}")
# ---------- 3. 添加一名转学生 ----------
names.append("赵六")
scores.append(88)
print(f"\n提示:转学生赵六已加入,当前共{len(names)}人")
# ---------- 4. 删除一名学生 ----------
leave = "王五"
if leave in names:
pos = names.index(leave)
names.pop(pos)
scores.pop(pos)
print(f"提示:{leave}已转出,当前共{len(names)}人")
# ---------- 5. 成绩统计与排序 ----------
print("-" * 40)
print(f"平均分:{sum(scores) / len(scores):.2f}")
print(f"最高分:{max(scores)}")
print(f"最低分:{min(scores)}")
# 降序排列,颁发前两名"学习之星"
top = sorted(scores, reverse=True)
print(f"学习之星成绩:{top[0]}分、{top[1]}分")
# ---------- 6. 格式化输出最终成绩单 ----------
print("-" * 40)
print("最终成绩单(按成绩降序):")
rank = 1
for s in sorted(scores, reverse=True):
pos = scores.index(s)
print(f"第{rank}名:{names[pos]} - {s}分")
rank = rank + 1程序运行效果如下:
========================================
班级成绩管理小程序
========================================
请输入学生姓名(用逗号分隔):张三,李四,王五
请输入成绩(用逗号分隔,与姓名一一对应):85,92,78
----------------------------------------
序号 姓名 成绩
1 张三 85
2 李四 92
3 王五 78
提示:转学生赵六已加入,当前共4人
提示:王五已转出,当前共3人
----------------------------------------
平均分:88.33
最高分:92
最低分:85
学习之星成绩:92分、88分
----------------------------------------
最终成绩单(按成绩降序):
第1名:李四 - 92分
第2名:赵六 - 88分
第3名:张三 - 85分这个例题把本章知识串成了一条完整的"流水线":用strip()、replace()、split()清洗和解析用户输入(字符串处理),用append()、pop()、index()、in完成名单的动态增删查(列表管理),用sum()、max()、min()、sorted()完成统计排名(数据分析),最后用\t、f-string和join的思路完成规范输出(格式化展示)。这套"输入解析→数据管理→统计分析→格式化输出"的流程,正是绝大多数信息管理类程序的基本骨架。
这个程序目前还有明显的局限:数据只能运行时录入,程序一关数据就没了;功能写死在代码里,不能交互选择。这些问题将在后续章节逐一解决:第五章学习函数后,可以把它拆分成结构清晰的多个功能模块;第八章学习文件操作后,成绩可以永久保存;第九章学习数据库后,它就能进化成一个真正的学生成绩管理系统。编程之路,正是这样一砖一瓦垒起来的。
