第 3 周:字符串、列表与元组
字符串、列表和元组都属于“序列”:它们有顺序、可以按位置取元素,也可以用循环逐个访问。区别在于保存什么、能否修改,以及常用方法。
建议用时:8-10 小时。
本周目标#
学完本周,你应该能够:
- 正确使用正索引和负索引。
- 按“起点包含、终点不包含”的规则计算切片。
- 使用字符串常用方法完成清理、查找、拆分和拼接。
- 创建、修改、增加、删除和排序列表。
- 区分原地修改方法与返回新对象的方法。
- 理解列表赋值别名和真正复制之间的差异。
- 创建、访问和解包元组。
- 独立完成文本统计、购物清单和成绩排名。
3.1 序列的四个共同动作#
字符串、列表和元组都支持:
- 获取长度。
- 按下标访问元素。
- 切片。
- 使用
for遍历。
text = "Python"
numbers = [10, 20, 30]
point = (4, 5)
print(len(text)) # 6
print(text[0]) # P
print(numbers[1]) # 20
print(point[-1]) # 5
3.2 索引与越界#
索引表示元素距离开头或末尾的位置。
s = P y t h o n
正索引 0 1 2 3 4 5
负索引 -6 -5 -4 -3 -2 -1
s = "Python"
print(s[0]) # P
print(s[2]) # t
print(s[-1]) # n
print(s[-3]) # h
如果索引超出有效范围,会报 IndexError:
# print(s[6])
# print(s[-7])
列表和元组规则相同:
items = ["语文", "数学", "英语"]
print(items[1]) # 数学
print(items[-1]) # 英语
3.3 切片#
基本写法:
序列[start:stop:step]
- 从
start开始。 - 到
stop前停止,不包含stop。 step是每次前进的步长。- 省略
start时默认从一端开始。 - 省略
stop时默认到另一端结束。 - 省略
step时默认是 1。
s = "Python"
print(s[1:4]) # yth
print(s[:3]) # Pyt
print(s[3:]) # hon
print(s[-3:]) # hon
print(s[:]) # Python
print(s[::2]) # Pto
print(s[::-1]) # nohtyP
s[1:4] 包含索引 1、2、3,不包含 4。可以记成“左闭右开”。
切片不会因为终点越界而报错#
print(s[2:100]) # thon
print(s[-100:2]) # Py
如果起点和终点让序列没有可取的区间,结果是空字符串 ""、空列表 [] 或空元组 ()。
步长为负数#
print(s[4:1:-1]) # oht
print(s[5:0:-2]) # nh
负步长表示从右向左。手推时先确定起点,再每次加上步长,直到越过终点前停止。
3.4 字符串#
字符串可以使用单引号、双引号或三引号:
name1 = '小林'
name2 = "李雨"
message = """第一行
第二行"""
单双引号作用相同。字符串内部出现同一种引号时,可以用另一种引号,或使用反斜杠转义:
print("他说:\"你好\"")
print('I\'m here')
常见转义:
| 写法 | 含义 |
|---|---|
\n |
换行 |
\t |
制表符 |
\\ |
一个反斜杠 |
\" |
双引号 |
字符串不可修改#
s = "Python"
# s[0] = "J" # TypeError
需要修改时,创建一个新字符串:
s = "Python"
new_s = "J" + s[1:]
print(new_s) # Jython
拼接、重复和成员判断#
print("Py" + "thon") # Python
print("ab" * 3) # ababab
print("th" in "Python") # True
print("py" in "Python") # False,区分大小写
遍历字符串#
for char in "Python":
print(char)
如果需要同时得到索引和字符:
for index, char in enumerate("Python"):
print(index, char)
enumerate() 每轮产生“索引、元素”两个值。
3.5 常用字符串方法#
字符串方法不会修改原字符串,而是返回一个新字符串或查询结果。
| 方法 | 作用 | 示例 |
|---|---|---|
len(s) |
长度 | len("abc") 是 3 |
upper() |
全部转大写 | "Ab".upper() 是 "AB" |
lower() |
全部转小写 | "Ab".lower() 是 "ab" |
strip() |
去掉两端空白 | " a ".strip() 是 "a" |
lstrip() |
去掉左边空白 | " a ".lstrip() 是 "a " |
rstrip() |
去掉右边空白 | " a ".rstrip() 是 " a" |
find(sub) |
查找位置,找不到返回 -1 | "abc".find("b") 是 1 |
count(sub) |
子串出现次数 | "aba".count("a") 是 2 |
replace(old, new) |
替换并返回新字符串 | "a-b".replace("-", "/") |
startswith(prefix) |
是否以某内容开头 | "PY-01".startswith("PY-") |
endswith(suffix) |
是否以某内容结尾 | "a.py".endswith(".py") |
split(sep) |
按分隔符切成列表 | "a,b".split(",") |
join(items) |
用当前字符串连接多个元素 | "-".join(["a", "b"]) |
例子:
text = " Python is useful "
clean_text = text.strip()
print(clean_text) # Python is useful
print(clean_text.lower()) # python is useful
print(clean_text.replace(" ", "-")) # Python-is-useful
print(clean_text.find("is")) # 7
print(clean_text.find("Java")) # -1
print(clean_text.count("u")) # 2
split() 和 join()#
sentence = "语文,数学,英语"
subjects = sentence.split(",")
print(subjects) # ['语文', '数学', '英语']
joined = " / ".join(subjects)
print(joined) # 语文 / 数学 / 英语
不写参数时,split() 会按连续空白拆分,并自动去掉多余空白:
print(" 1 2 3 ".split()) # ['1', '2', '3']
join() 中的每个元素都必须是字符串,整数列表不能直接连接。
3.6 列表#
列表用方括号表示,元素可以修改:
scores = [88, 92, 75]
scores[0] = 90
print(scores) # [90, 92, 75]
列表可以保存不同类型的数据:
student = ["S001", "张晨", 88, True]
不过同一个列表中混放太多不同含义的值,不利于阅读。结构化数据更适合字典或对象。
增加元素#
items = ["语文"]
items.append("数学") # 在末尾增加一个元素
items.insert(1, "英语") # 在索引 1 前插入
items.extend(["物理", "化学"]) # 把另一个可迭代对象逐个加入
print(items)
# ['语文', '英语', '数学', '物理', '化学']
差异:
append(["物理", "化学"])会把整个列表作为一个嵌套元素加入。extend(["物理", "化学"])会分别加入“物理”和“化学”。
删除元素#
numbers = [10, 20, 30, 20]
numbers.remove(20) # 删除第一个值为 20 的元素
last = numbers.pop() # 删除并返回末尾元素
first = numbers.pop(0) # 删除并返回索引 0 的元素
del numbers[0]
numbers.clear()
remove(99) 在值不存在时会报 ValueError。pop(100) 在索引越界时会报 IndexError。
查找、计数和成员判断#
students = ["张晨", "李雨", "王浩"]
print("李雨" in students) # True
print(students.index("王浩")) # 2
print(students.count("张晨")) # 1
列表的 index() 找不到时抛出异常;字符串的 find() 找不到时返回 -1。两者不要混淆。
修改多个位置:切片赋值#
numbers = [1, 2, 3, 4]
numbers[1:3] = [20, 30, 40]
print(numbers) # [1, 20, 30, 40, 4]
切片赋值会替换指定区间。基础题中先掌握单元素赋值即可。
3.7 排序:sort() 与 sorted()#
原地排序 sort()#
scores = [88, 76, 92]
result = scores.sort()
print(scores) # [76, 88, 92]
print(result) # None
sort() 修改原列表,并返回 None。不要写 scores = scores.sort(),否则会把 scores 变成 None。
降序:
scores = [88, 76, 92]
scores.sort(reverse=True)
print(scores) # [92, 88, 76]
返回新列表 sorted()#
original = [3, 1, 2]
ascending = sorted(original)
descending = sorted(original, reverse=True)
print(original) # [3, 1, 2]
print(ascending) # [1, 2, 3]
print(descending) # [3, 2, 1]
第一阶段常见推演:
a = [3, 1, 2]
a.sort(reverse=True)
b = sorted(a)
a.append(b[0])
print(a) # [3, 2, 1, 1]
print(b) # [1, 2, 3]
先让 a 变成 [3, 2, 1],b 是新列表 [1, 2, 3],最后把 1 加回 a。
反转#
numbers = [1, 2, 3]
numbers.reverse()
print(numbers) # [3, 2, 1]
copy_reversed = list(reversed(numbers))
print(copy_reversed) # [1, 2, 3]
reverse() 原地修改;reversed() 返回一个反向迭代器,常配合 list() 转成新列表。
3.8 列表赋值、别名与复制#
下面的 b = a 不会创建新列表:
a = [1, 2]
b = a
b.append(3)
print(a) # [1, 2, 3]
print(b) # [1, 2, 3]
print(a is b) # True
a 和 b 指向同一个列表,所以通过任何一个名称修改,另一边都能看见。
创建独立浅复制:
a = [1, 2]
b = a.copy()
b.append(3)
print(a) # [1, 2]
print(b) # [1, 2, 3]
print(a is b) # False
切片 a[:] 和 list(a) 也能得到浅复制。若列表里还有嵌套列表,浅复制不会复制内层列表;本周先把它理解成“复制第一层”。
3.9 嵌套列表#
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
print(matrix[0][1]) # 2
print(matrix[2][-1]) # 9
for row in matrix:
for value in row:
print(value, end=" ")
print()
结果:
1 2 3
4 5 6
7 8 9
要先把 matrix[0] 取到第一行,再从这个列表中取索引 1。
3.10 元组#
元组用圆括号表示,创建后不能修改:
point = (4, 5)
print(point[0]) # 4
# point[0] = 10 # TypeError
只有一个元素时,必须写逗号:
not_tuple = (5)
real_tuple = (5,)
print(type(not_tuple).__name__) # int
print(type(real_tuple).__name__) # tuple
解包#
student = ("S001", "张晨", 88)
student_id, name, score = student
print(student_id) # S001
print(name) # 张晨
print(score) # 88
解包时左右数量要一致,否则会报 ValueError。
元组常用于表示“一起返回的一组结果”:
def findMinMax(values):
minimum = min(values)
maximum = max(values)
return minimum, maximum
result = findMinMax([5, 2, 8])
print(result) # (2, 8)
print(type(result).__name__) # tuple
不带括号的 return minimum, maximum 也会构成元组。
3.11 本周作品一:文本统计器#
需求#
输入一句英文,输出:
- 原句。
- 去掉两端空白后的句子。
- 总字符数(包含空格)。
- 单词个数。
- 大写形式。
- 单词列表。
sentence = input("请输入一句英文:")
clean_sentence = sentence.strip()
words = clean_sentence.split()
print("原句:", sentence)
print("清理后:", clean_sentence)
print("字符数:", len(sentence))
print("单词数:", len(words))
print("大写:", clean_sentence.upper())
print("单词:", words)
输入:
Python is fun and useful
输出中的关键结果:
字符数:28
单词数:5
单词:['Python', 'is', 'fun', 'and', 'useful']
注意:输入内容两端的空格也属于原字符串,所以“字符数”是否包含它们取决于统计规则。题目说“总字符数”时,应明确统计原串还是清理后的字符串。
3.12 本周作品二:购物清单#
items = []
while True:
command = input("add 名称 / remove 名称 / show / quit:")
parts = command.split(maxsplit=1)
action = parts[0]
if action == "quit":
break
if action == "show":
print("清单:", items)
elif action == "add" and len(parts) == 2:
items.append(parts[1])
print("已添加:", parts[1])
elif action == "remove" and len(parts) == 2:
name = parts[1]
if name in items:
items.remove(name)
print("已删除:", name)
else:
print("清单中没有", name)
else:
print("命令格式不正确")
print("最终清单:", items)
split(maxsplit=1) 最多拆成两部分,所以 add 牛奶 2 盒 中的名称能保留空格。
3.13 本周作品三:成绩排名#
scores = [88, 59, 92, 70, 100]
ascending = sorted(scores)
descending = sorted(scores, reverse=True)
print("原顺序:", scores)
print("升序:", ascending)
print("降序:", descending)
print("最高分:", descending[0])
print("最低分:", ascending[0])
print("前三名:", descending[:3])
如果需要保留姓名和分数的对应关系,两个平行列表容易错位。第 4 周会用字典或元组列表解决这个问题。
3.14 独立练习#
A. 快速判断题#
"Python"[1:4]是什么?"Python"[-2:]是什么?"Python"[::-1]是什么?" a b ".strip()的结果是什么?"a,b,c".split(",")的结果是什么?"-".join(["A", "B", "C"])的结果是什么?a = [1, 2]; b = a; b.append(3)后,a是什么?a = [1, 2]; b = a.copy(); b.append(3)后,a和b分别是什么?a.sort()的返回值是什么?s[0] = "J"能否修改字符串?
B. 编程题#
- 输入一个单词,输出它的第一个字符、最后一个字符和反转结果。
- 输入一个句子,统计其中字母
a出现多少次,不区分大小写。 - 输入用逗号分隔的姓名,输出去掉两端空格后的姓名列表。
- 创建空列表,让用户连续输入 5 个整数,然后按从小到大输出。
- 对列表
[3, 1, 2]先原地降序排序,再创建升序新列表,最后把新列表首元素追加到原列表,输出两个列表。 - 输入一个英文句子,找出最长的一个单词。若长度相同,取最先出现的单词。
- 编写函数
reverse_text(text),返回反转后的字符串,不修改原字符串。 - 编写函数
normalize_name(name),去掉姓名两端空格,并把英文姓名转换为每个单词首字母大写、其余小写。
3.15 练习答案与解释#
A. 快速判断题答案#
"yth"。"on"。"nohtyP"。"a b"。只去掉两端空白,中间空格保留。["a", "b", "c"]。"A-B-C"。[1, 2, 3]。a和b指向同一个列表。a是[1, 2],b是[1, 2, 3]。None。排序效果体现在原列表中。- 不能,字符串不可修改,会报
TypeError。
B. 编程题参考答案#
单词首尾和反转:
word = input() print(word[0]) print(word[-1]) print(word[::-1])统计字母
a,不区分大小写:sentence = input() count = sentence.lower().count("a") print(count)清理姓名列表:
text = input() names = text.split(",") clean_names = [] for name in names: clean_names.append(name.strip()) print(clean_names)也可以使用列表推导式
[name.strip() for name in names],但本周先掌握普通循环。输入并排序 5 个整数:
numbers = [] for _ in range(5): numbers.append(int(input())) numbers.sort() print(numbers)原地排序和新列表:
a = [3, 1, 2] a.sort(reverse=True) b = sorted(a) a.append(b[0]) print(a) # [3, 2, 1, 1] print(b) # [1, 2, 3]最长单词:
sentence = input() words = sentence.split() longest = words[0] for word in words: if len(word) > len(longest): longest = word print(longest)这里使用
>而不是>=,所以长度相同时保留先出现的单词。反转函数:
def reverse_text(text): return text[::-1] original = "Python" print(reverse_text(original)) # nohtyP print(original) # Python姓名标准化:
def normalize_name(name): return name.strip().title() print(normalize_name(" zhang chen ")) # Zhang Chentitle()会把每个单词的首字母转为大写。中文不受影响。
3.16 常见错误清单#
- 把终点索引算进切片。
- 修改字符串中的单个字符。
- 以为
split()会自动把数字转成int。 - 以为
join()可以直接连接整数列表。 - 把
append()和extend()混用。 remove()删除所有相同值,实际只删第一个。- 把
sort()的返回值赋给原变量,得到None。 - 用
b = a误以为复制了列表。 - 单元素元组漏写逗号。
- 混用字符串
find()的-1和列表index()的异常。
3.17 过关检查#
- 能画出字符串正负索引表。
- 能不运行代码写出 6 种切片结果。
- 能说清字符串、列表、元组的可变性差异。
- 能正确使用
split()、join()、strip()、replace()。 - 能演示
append、insert、extend、remove、pop的效果。 - 能吃透
sort()、sorted()、reverse()、reversed()的返回值差异。 - 能通过例子解释列表别名和复制。
- 能写出返回元组的函数并完成解包。
- 独立完成文本统计器和成绩排名。
- 第一阶段的列表与字符串推演题正确率达到 80%。