PY-03 字符串
03 · 字符串(String)
📅 预计 60 分钟 | ⭐ = 高频考点
✍️ 本讲配套练习:本地python grader.py 03,或网页练习
3.1 字符串基础
把字符串想象成一串糖葫芦:一颗一颗的字符,被一根签子串起来。签子上第一颗是 0 号,第二颗是 1 号……这个"第几号"就是索引(index)。Python 里造字符串有几种姿势,单引号 'abc'、双引号 "abc" 都行,三引号还能换行:
s1 = "Hello" # 双引号
s2 = '你好' # 单引号,中文没问题
s3 = """第一行
第二行""" # 三引号:可以跨多行
print(s3)
# 输出:
# 第一行
# 第二行
不可变(immutable) ⭐ 是字符串最重要的脾气:一旦创建,糖葫芦上的糖就不能抠下来换一颗。所有看起来是"改"的操作,其实都是造了一串新的给你:
s = "abc"
# s[0] = "x" # ❌ TypeError: 'str' object does not support item assignment
s2 = s.upper() # ✅ 返回新字符串 "ABC",s 本身还是 "abc"
print(s) # abc
print(s2) # ABC
为什么不可变反而好?因为不可变意味着安全:字符串在字典里做键、在多线程里共享,都不会被意外改坏,Python 内部还能复用内存,省下一大块空间。
⚠️ 常见错误
- 试图
s[0] = "x"原地修改 → 报TypeError。正确姿势是造新的:s = "x" + s[1:]。 - 引号嵌套出错:
'it's'会语法错误,要写成"it's"或用转义'it\'s'。
3.2 索引与切片 ⭐
索引:正着数 + 倒着数
正索引从 0 开始;负索引从 -1 开始(-1 是最后一个字符)。两种数法互为镜像,谁也绕不开:
s = "Python"
# 正索引: P(0) y(1) t(2) h(3) o(4) n(5)
# 负索引: P(-6) y(-5) t(-4) h(-3) o(-2) n(-1)
print(s[0]) # P 第一个字符
print(s[-1]) # n 最后一个字符 ⭐ 高频
print(s[-2]) # o 倒数第二个
切片:左闭右开
切片 s[start:end:step] 像切西瓜:从 start 下刀,切到 end 前停下——含 start,不含 end(左闭右开)。end 省略就是切到尾,start 省略就是从头上切。step 是步长,-1 就是倒着走。
s = "Python"
print(s[0:3]) # Pyt 索引 0、1、2,不含 3
print(s[2:]) # thon 从 2 到末尾
print(s[:3]) # Pyt 从头到 3(不含 3)
print(s[::2]) # Pto 步长 2:取 0、2、4
print(s[::-1]) # nohtyP 反转整串 ⭐ 常用
print(s[4:2]) # '' 左边比右边大?空串,不报错
⭐ 切片越界不报错:"abc"[1:10] → "bc",顶多截到边界。但单个索引越界会 IndexError:"abc"[5] 直接崩。
⚠️ 常见错误
- 把左闭右开记成"两头都含":
s[0:3]取 3 个字符(P、y、t),不是 4 个。口诀:含头不含尾。 - 混用正负索引切片:
s[-3:0]是空串,因为 -3 在 0 的左边,方向不对。要取倒数 3 个写s[-3:]。 - 忘了切片越界安全、索引越界危险,两个行为完全不一样。
3.3 大小写与字符判断
大小写家族
| 方法 | 作用 | 示例 |
|---|---|---|
.upper() |
全部转大写 | "AbC".upper() → "ABC" |
.lower() |
全部转小写 | "AbC".lower() → "abc" |
.swapcase() |
大小写互换 | "AbC".swapcase() → "aBc" |
.title() |
每个单词首字母大写 | "hello world".title() → "Hello World" |
.capitalize() |
整串首字母大写 | "hello world".capitalize() → "Hello world" |
print("hello world".title()) # Hello World 按单词拆开再逐个首字母大写
print("hello world".capitalize()) # Hello world 只把整串第一个字母大写
print("AbC".swapcase()) # aBc
字符判断家族
这类方法返回 True / False,是"筛选字符"的得力干将:
print("abc".isalpha()) # True 纯字母
print("abc123".isalpha()) # False 混了数字就不是纯字母
print("123".isdigit()) # True 纯数字
print("12.5".isdigit()) # False 小数点不是数字
print("abc123".isalnum()) # True 字母或数字都算
print(" ".isspace()) # True 空白(空格/换行/制表)
print(" \n\t".isspace()) # True 全是空白
print("AbC".isupper()) # False 有大写但不是全大写
print("ABC".isupper()) # True
⭐ 高频组合套路:if ch.isdigit(): 逐个筛数字,if ch.isalpha(): 筛字母,if ch.isspace(): 跳空白。
⚠️ 常见错误
"AbC".isupper()是 False——"全大写"不是"有大写"。- 空串
"".isdigit()是 False,"".isalpha()也是 False。空串"什么都是假的"。 - 中文的坑:
"你好".isalpha()返回 True。isalpha 判断的是"字母",Unicode 里汉字算字母。要严格判断英文用ch.isascii() and ch.isalpha()。
3.4 查找、替换与去除空白
查找:find / rfind / index / count
find(sub) 从左边找,返回第一次出现的位置,找不到返回 -1;rfind(sub) 从右边找,返回最后一次出现的位置。index(sub) 跟 find 一样找位置,但找不到抛 ValueError。count(sub) 数子串出现次数。
s = "abracadabra"
print(s.find("a")) # 0 第一次出现 a 的位置
print(s.rfind("a")) # 10 最后一次出现 a 的位置
print(s.find("z")) # -1 找不到返回 -1,不报错
print(s.index("a")) # 0 和 find 一样
# print(s.index("z")) # ❌ ValueError: substring not found
print(s.count("a")) # 5 a 出现了 5 次
什么时候用 find、什么时候用 index?——你想自己处理"找不到"就用 find(不会崩);你觉得"这里必须存在,不存在就是 bug",就用 index 早点把问题炸出来。
替换:replace
replace(old, new, [count]) 把 old 全部换成 new;第三个参数 count 限制最多换前几个。
print("hello world hello".replace("hello", "hi"))
# hi world hi 全部替换
print("aaaa".replace("a", "b", 2))
# bbaa 只换前 2 个
去除空白:strip / lstrip / rstrip
strip() 去掉两端空白;lstrip() 只去左边;rstrip() 只去右边。传一个字符串参数时,把两端属于这个字符串的字符全部剥掉:
s = " \t你好 \n"
print(repr(s.strip())) # '你好'
print(repr(s.lstrip())) # '你好 \n' 右边还留着
print(repr(s.rstrip())) # ' \t你好'
print("---hello---".strip("-")) # hello 两端所有 - 都被剥掉
print("aaahelloaaa".strip("a")) # hello
前缀后缀:startswith / endswith
判断字符串以什么开头 / 结尾,支持传元组一次判断多个:
print("app.py".endswith(".py")) # True
print("app.py".endswith((".py", ".txt"))) # True
print(".gitignore".startswith(".")) # True
⚠️ 常见错误
find找不到返回 -1,index找不到抛异常——选错会崩。replace不传第三参数替换全部,只想换第一个要replace(old, new, 1)。strip()只去两端不去中间:"a b ".strip()→"a b",中间空格动不了。strip()传参是按"字符集合"剥,不是按"子串"剥:"ababxabab".strip("ab")→"x",会把两端所有 a 和 b 全剥掉。
3.5 拆分与合并(split / join)⭐
split(sep) 按分隔符把字符串拆成列表。不传参数时按"任意空白"拆,连续多个空格/换行/制表都会被当作一个分隔符。join 是它的逆运算:把列表用分隔符串起来。「拆开 → 处理 → 拼回」是数据处理最常用的套路。
s = "a,b,c"
print(s.split(",")) # ['a', 'b', 'c'] 按逗号拆
print("2026-08-02".split("-")) # ['2026', '08', '02']
t = " python is great "
print(t.split()) # ['python', 'is', 'great'] 按空白拆,自动合并连续空白
print(t.split(" ")) # ['', '', 'python', '', '', 'is', '', 'great', ''] 手写空格拆会留下空串!
print("-".join(["a", "b", "c"])) # a-b-c 列表在前,分隔符在括号里
print("".join(["h", "i"])) # hi
为什么手写 split(" ") 会留空串?因为它严格按"一个空格"切,两个空格中间就切出一个空串。而 split()(不带参数)会智能地把连续空白当成整体。
⚠️ 常见错误
"a,b,c".split()不传参数 →['a,b,c']一整块。要按逗号拆必须写split(",")。- join 的方向写反:正确的是
",".join(list),不是list.join(",")。 split()和split(" ")结果可能天差地别(一个合并空白、一个留空串),别混用。
3.6 格式化输出(f-string 是主角)⭐
Python 有 3 种格式化:老式 %、.format()、f-string。f-string 最推荐:字符串前加 f,用 {} 直接塞变量和表达式,读起来最像人话。
name = "小明"
score = 88.5
print(f"{name} 考了 {score} 分") # 小明 考了 88.5 分
print(f"{score:.1f} 分") # 88.5 分 保留 1 位小数
高级格式(重点考点)
x = 1234567.891
print(f"{x:,.2f}") # 1,234,567.89 千分位 + 两位小数 ⭐
print(f"{x:.0f}") # 1234568 四舍五入到整数
price = 99
print(f"{price:05d}") # 00099 宽度 5,不足补 0
s = "hi"
print(f"[{s:<6}]") # [hi ] 左对齐,占 6 位
print(f"[{s:>6}]") # [ hi] 右对齐 ⭐
print(f"[{s:^6}]") # [ hi ] 居中
w = 10
print(f"[{s:>{w}}]") # [ hi] 宽度可以用变量 ⭐ 动态对齐
对齐、补零、千分位是格式化三大高频考点,> < ^ 三个符号和 , 千分位要记牢。
⚠️ 常见错误
- 忘写
f前缀:"{name}"会原样输出{name}这几个字,不替换。 :.2f是四舍五入不是截断:{2.345:.2f}一般得2.35(浮点误差偶尔让你看到 2.34,不必纠结)。- 对齐宽度是"总宽度"不是"补几个空格":
f"{'hi':>6}"是补 4 个空格(总长 6)。 - 中文对齐会"看着没对齐":中文字符显示更宽,
f"{'你好':<6}"视觉上比英文窄一截,别被误导。
3.7 字符编码:ord 与 chr
计算机只认识数字,所以每个字符都对应一个整数码点(code point)。ord(字符) 查码点,chr(数字) 反查字符。英文 26 个字母的码点是连续的,这是很多算法的突破口:
print(ord("A")) # 65
print(ord("a")) # 97
print(ord("中")) # 20013
print(chr(65)) # 'A'
print(chr(97)) # 'a'
print(chr(20013)) # '中'
字母序号换算:ord(ch) - ord("A") 得到 0~25 的位置。在这个位置基础上加 1 再 chr 回来,就是"下一个字母"。
经典应用:凯撒加密
把每个字母往后挪 shift 位,挪到末尾就绕回开头。绕回靠取模 % 26:
def caesar(s, shift):
out = []
for ch in s:
if ch.islower():
base = ord("a")
out.append(chr((ord(ch) - base + shift) % 26 + base))
elif ch.isupper():
base = ord("A")
out.append(chr((ord(ch) - base + shift) % 26 + base))
else:
out.append(ch) # 非字母原样保留
return "".join(out)
print(caesar("xyz", 3)) # abc x→a、y→b、z→c,绕回
print(caesar("Hello", 1)) # Ifmmp 大小写都保留
⚠️ 常见错误
- 取模前忘了减 base:
(ord(ch) + shift) % 26得到 0~25,直接当字符用会乱码。必须是(ord(ch) - base + shift) % 26 + base。 - 大写 base 是 65、小写 base 是 97,混着算会错位('A' 挪 1 会跑进小写区)。
- 忘了处理非字母字符,空格和标点也被"加密",结果对不上。
3.8 遍历与综合套路
字符串可以直接 for ch in s 逐字符遍历,不需要索引。四大高频套路:
① 计数:遍历 + 字符判断方法累加。
② 回文判断:s == s[::-1],正读反读一样就是回文。
③ 分词统计:s.split() 拆词,len(words) 数词数。
④ 提取连续片段:遍历时用一个"临时变量"累积,遇到不匹配的字符就"结算"一次。易错点:遍历结束后还要再结算一次,否则最后一个片段会被丢掉。
# ④ 提取所有连续数字子串
def extract_numbers(s):
result, cur = [], ""
for ch in s:
if ch.isdigit():
cur += ch # 数字就累积
else:
if cur: # 遇到非数字,结算当前片段
result.append(cur)
cur = ""
if cur: # ⭐ 别忘了收尾结算
result.append(cur)
return result
print(extract_numbers("ab12cd345")) # ['12', '345']
print(extract_numbers("123 456")) # ['123', '456']
# ② 回文(忽略大小写更严谨)
s = "AbA"
print(s.lower() == s.lower()[::-1]) # True
⚠️ 常见错误
- 提取连续片段时忘了收尾结算,最后一个数字/单词丢了——这是最经典的漏 bug。
- 回文判断大小写不一致:
"AbA"直接==反转会得 False,先lower()再比。 - 空串直接
for循环一次都不进,累加变量保持初始值——初始值要合理(数字 0、空字符串、空列表)。
💡 记忆口诀
- 切片:含头不含尾;步长为负倒着走。
- split 与 join:一对逆运算,拆开再拼回;join 是"列表在前、分隔符在括号里"。
- 字符串:改就是造新的(不可变),别想原地动它。
- 格式化:f 开头,花括号塞变量;
> < ^对齐、0补零、,千分位。 - 编码:ord 查码、chr 反查;字母移位记得
% 26绕圈。
📌 双语术语表(本讲)
| 中文 | English |
|---|---|
| 拼接 | concatenation |
| 切片 | slicing |
| 索引 | index |
| 不可变 | immutable |
| 转义字符 | escape character |
| 子串 | substring |
| 反转 | reverse |
| 大小写 | case(upper/lower/swapcase) |
| 查找 | find |
| 替换 | replace |
| 去除空白 | strip |
| 拆分 | split |
| 合并 | join |
| 格式化 | format / f-string |
| 编码 | encoding / code point |
| 回文 | palindrome |
⭐ 本讲考点清单
- 切片
s[start:end:step]左闭右开,负索引从尾;s[::-1]反转 split()/join()互为逆运算;split()按空白合并拆、split(" ")会留空串find(找不到 -1)vsindex(找不到抛异常);rfind从右找replace(old, new, count)全部 / 限次替换strip() / lstrip() / rstrip()去首尾空白,可传字符集合startswith / endswith支持元组多后缀判断- 大小写家族:
upper / lower / swapcase / title / capitalize - 字符判断:
isalpha / isdigit / isalnum / isspace / isupper / islower;中文isalpha为 True - f-string:
{x:,.2f}千分位、{s:<10} / {s:>10} / {s:^10}对齐、{x:05d}补零、宽度用变量 ord()/chr()码点互转;凯撒加密用% 26绕回,大小写 base 不同- 字符串不可变——"改"都返回新串
- 综合套路:回文、分词统计、提取连续数字(记得收尾结算)