跳到主要内容

过早优化是万恶之源。

Donald Knuth计算机程序设计艺术作者

📝 字符串

字符串是编程中最常用的数据类型之一——几乎所有程序都需要处理文本。Python 的字符串功能相当全面:切片、拼接、查找、格式化,一应俱全。本节涵盖字符串的创建方式、常用方法、切片索引,以及 f-string 格式化。

📌 本节要点

  • 字符串的三种创建方式与转义字符
  • 理解字符串的不可变性及其性能影响
  • 熟练使用索引、切片和常用字符串方法
  • 掌握 f-string 格式化,包括 Python 3.12+ 的嵌套引号特性

字符串创建

Python 支持三种引号方式创建字符串:单引号 '...'、双引号 "..."、三引号 '''...'''"""..."""。选择哪种取决于字符串内容:

Python
# 单引号
s1 = 'hello'

# 双引号
s2 = "world"

# 两者完全等价,选择哪种主要看字符串内是否包含对方
s3 = "It's a cat" # 字符串内含单引号,用双引号包裹
s4 = 'He said "hi"' # 字符串内含双引号,用单引号包裹

# 三引号:可跨多行
s5 = """多行字符串
第二行
第三行"""

s6 = '''也是多行
字符串'''

print(s1, s2, s3, s4) # 输出: hello world It's a cat He said "hi"
print(s5) # 输出: 多行字符串\n第二行\n第三行
核心概念:单双引号无本质区别

与某些语言不同,Python 中单引号和双引号字符串完全等价,没有任何性能差异。选择哪种纯粹是为了避免转义:当字符串包含单引号时用双引号包裹,反之亦然,这样能让代码更清晰。

转义字符

在字符串中表示特殊字符(换行、制表符等)需要用到转义字符:

转义序列含义示例
\n换行"line1\nline2"
\t制表符"a\tb"
\\反斜杠本身"C:\\Users"
\'单引号'It\'s ok'
\"双引号"He said \"hi\"
\r回车"hello\rworld"
\uXXXXUnicode"\u4e2d" → 中
Python
print("第一行\n第二行") # 输出: 第一行\n第二行(换行)
print("a\tb\tc") # 输出: a b c(制表对齐)
print("C:\\Users\\Tom") # 输出: C:\Users\Tom(Windows 路径)
print("It\'s a test") # 输出: It's a test(转义单引号)

# Unicode 转义
print("\u4e2d\u6587") # 输出: 中文
print("\U0001F600") # 输出: 😀 (笑脸 emoji)

原始字符串

如果不想让 Python 处理转义字符——比如处理 Windows 路径或正则表达式——用 r 前缀创建原始字符串:

Python
# 普通字符串:\n 被解释为换行
print("C:\new\test") # 输出会换行和制表,路径"损坏"

# 原始字符串:反斜杠原样保留
print(r"C:\new\test") # C:\new\test

# 正则表达式中的典型用法
import re
pattern = r"\d+\.\d+" # 匹配数字
print(re.findall(pattern, "价格 12.5 元,数量 3")) # ['12.5', '3']
原始字符串的最后一个字符不能是反斜杠

原始字符串中反斜杠仍是"转义字符"的角色,所以 r"abc\" 会报错(它把末尾的 \" 当作转义的双引号)。如果需要以反斜杠结尾,可以用 r"abc" "\\" 拼接,或使用普通字符串。

f-string(格式化字符串)

f-string 是 Python 3.6+ 引入的字符串格式化方式,也是现代 Python 的首选。用 f 前缀和 {} 占位符,格式化变得非常简单:

Python
name = "Alice"
age = 25

# 基本用法
s = f"我叫 {name},今年 {age} 岁"
print(s) # 我叫 Alice,今年 25 岁

# 表达式
print(f"明年我 {age + 1} 岁") # 明年我 26 岁
print(f"名字长度: {len(name)}") # 名字长度: 5
print(f"{'Python'.upper()}") # PYTHON

# 格式化数字
pi = 3.14159
print(f"{pi:.2f}") # 3.14 (保留两位小数)
print(f"{pi:>10.2f}") # 3.14 (右对齐,宽度 10)
print(f"{pi:<10.2f}") # 3.14 (左对齐)
print(f"{pi:^10.2f}") # 3.14 (居中)

# 千位分隔符
big = 1234567
print(f"{big:,}") # 1,234,567
print(f"{big:_}") # 1_234_567 (下划线分隔)

# 百分比
ratio = 0.875
print(f"{ratio:.2%}") # 87.50%

# 科学计数法
print(f"{123456789:.2e}") # 1.23e+08

Python 3.12+ 嵌套引号

Python 3.12 之前,f-string 中的表达式不能使用与外层相同的引号。Python 3.12(PEP 701)解除了这个限制:

Python
# Python 3.12+:可在 f-string 中使用相同引号
names = ["Alice", "Bob", "Charlie"]

print(f"列表: {', '.join(names)}")

# Python 3.12+:可在外层双引号内继续用双引号
new = f"列表: {", ".join(names)}"
print(new)

# 嵌套字典访问
user = {"name": "Alice", "city": "Beijing"}
print(f"用户: {user["name"]}, 城市: {user["city"]}")

# 嵌套多行 f-string
print(f"""姓名: {user["name"]}
城市: {user["city"]}""")
Python 3.12+ f-string 增强

Python 3.12 (PEP 701) 重写了 f-string 解析器,支持:

  1. 相同引号嵌套:f-string 内可使用与外层相同的引号
  2. 多行表达式:f-string 内可包含换行、注释
  3. 反斜杠:f-string 内可使用 \ 转义

Python 3.12+ 的多行表达式与注释

Python
# Python 3.12+:f-string 表达式内可换行、加注释
data = [1, 2, 3, 4, 5]
result = f"统计: {
# 计算平均值
sum(data) / len(data)
}"
print(result) # 统计: 3.0

字符串是不可变的

Python 中的字符串是不可变对象:一旦创建,其内容不能被修改。任何"修改"字符串的操作都会返回一个新字符串

Python
s = "hello"

# 尝试修改字符(会报错)
# s[0] = "H" # TypeError: 'str' object does not support item assignment

# 正确做法:创建新字符串
s = "H" + s[1:]
print(s) # Hello
性能提示:避免在循环中拼接字符串

由于字符串不可变,s = s + "x" 每次都会创建新对象,在循环中拼接大量字符串性能很差。推荐使用 "".join(list)

Python
# 不推荐
parts = []
for i in range(100):
parts.append(str(i))
result = ""
for p in parts:
result += p # 每次 + 都会复制整个字符串

# 推荐
result = "".join(parts) # 一次性高效拼接

索引与切片

字符串支持索引访问(从 0 开始)和切片操作 [start:stop:step]

索引

Python
s = "Python"

# 正向索引(从 0 开始)
print(s[0]) # P
print(s[1]) # y
print(s[5]) # n

# 负向索引(从 -1 开始,表示倒数)
print(s[-1]) # n (最后一个)
print(s[-2]) # o (倒数第二个)
print(s[-6]) # P (倒数第六个,即第一个)

切片

切片语法 s[start:stop:step],遵循"左闭右开"原则(包含 start,不包含 stop):

Python
s = "Python Programming"

# 基本切片
print(s[0:6]) # Python
print(s[7:18]) # Programming
print(s[:6]) # Python (省略 start,从头开始)
print(s[7:]) # Programming (省略 stop,到末尾)
print(s[:]) # Python Programming (整体复制)

# 负数索引切片
print(s[-11:]) # Programming
print(s[:-12]) # Python

# 步长 step
print(s[::2]) # PtoPormig (每隔一个字符取一个)
print(s[::-1]) # gnimmargorP nohtyP (反转字符串)
print(s[1:10:2]) # yho r (步长为 2)

# 负步长(从右向左)
print(s[10:1:-1]) # morP noht
print(s[::-2]) # gimroP nhy
切片不会越界

Python 切片非常"宽容"——即使索引超出范围也不会报错,会自动截断到有效范围:

Python
s = "hello"
print(s[0:100]) # hello (超出部分自动忽略)
print(s[10:20]) # 空字符串
print(s[-100:3]) # hel

s[:] vs s 的区别

对于不可变类型(如字符串),两者行为几乎一致:

Python
s = "abcdefghijklmnopqrstuvwxyz"

print(s[:]) # abcdefghijklmnopqrstuvwxyz
print(s) # abcdefghijklmnopqrstuvwxyz

print(s[:] is s) # True(CPython 优化,返回同一对象)
print(s[:] == s) # True

对于可变类型(如列表),区别才明显:

Python
lst = [1, 2, 3]

print(lst[:] is lst) # False!创建了新列表(浅拷贝)
print(lst[:] == lst) # True,内容相同

copy = lst[:]
copy.append(4)
print(lst) # [1, 2, 3] —— 原列表不受影响
print(copy) # [1, 2, 3, 4]
不可变 vs 可变

字符串是不可变的,s[:]s 通常返回同一对象;列表是可变的,lst[:] 创建浅拷贝,修改拷贝不影响原列表。

字符串长度与成员判断

Python
s = "Hello, Python"

# 长度
print(len(s)) # 13

# 成员判断 in / not in
print("Python" in s) # True
print("Java" in s) # False
print("Java" not in s) # True

# 中文也支持
zh = "你好,世界"
print("你好" in zh) # True
print(len(zh)) # 5 (5 个字符)

字符串方法

Python 字符串方法非常丰富,下面分类介绍常用的方法。

大小写转换

Python
s = "Hello World"

print(s.upper()) # HELLO WORLD
print(s.lower()) # hello world
print(s.title()) # Hello World (每个单词首字母大写)
print(s.capitalize()) # Hello world (仅首字母大写)
print(s.swapcase()) # hELLO wORLD (大小写互换)
print(s.casefold()) # hello world (更激进的小写转换,适合 Unicode)
casefold 与 lower 的区别

casefold()lower() 更激进,用于无大小写比较(如德语 ß → ss)。在需要做国际化字符串比较时,应使用 casefold() 而非 lower()

Python
"straße".lower() == "strasse".lower() # False
"straße".casefold() == "strasse".casefold() # True

查找与计数

Python
s = "Hello, Python, Hello World"

# find: 查找子串位置,找不到返回 -1
print(s.find("Python")) # 7
print(s.find("Java")) # -1
print(s.find("Hello")) # 0
print(s.rfind("Hello")) # 15 (从右查找)

# index: 与 find 类似,但找不到会抛出 ValueError
print(s.index("Python")) # 7
# print(s.index("Java")) # ValueError

# 计数
print(s.count("Hello")) # 2
print(s.count("l")) # 5

# 起始/结束判断
print(s.startswith("Hello")) # True
print(s.endswith("World")) # True
print(s.endswith(("World", "Java"))) # True (支持元组)

分割与拼接

Python
# split: 分割字符串为列表
csv = "Alice,25,Beijing"
parts = csv.split(",")
print(parts) # ['Alice', '25', 'Beijing']

# splitlines: 按行分割
text = "第一行\n第二行\n第三行"
print(text.splitlines()) # ['第一行', '第二行', '第三行']

# 限制分割次数
s = "a-b-c-d-e"
print(s.split("-", 2)) # ['a', 'b', 'c-d-e'] (只分割前 2 次)

# rsplit: 从右开始分割
print(s.rsplit("-", 2)) # ['a-b-c', 'd', 'e']

# join: 用某个字符串拼接列表
words = ["Hello", "Python", "World"]
print(" ".join(words)) # Hello Python World
print("-".join(words)) # Hello-Python-World
print("".join(words)) # HelloPythonWorld

# 数字列表转字符串(先转 str)
nums = [1, 2, 3]
print(",".join(str(n) for n in nums)) # 1,2,3

split() vs split(" ") 的关键区别

Python
s = "Hello, world! I am a student. "

# 按单个空格分割(会产生空字符串)
print(s.split(" "))
print(len(s.split(" ")))

# 按任意空白分割,自动忽略首尾和连续空格
print(s.split())
print(len(s.split()))

运行结果:

输出
# 按单个空格分割的输出
['Hello,', '', '', 'world!', '', 'I', 'am', 'a', 'student.', '', '']
11 —— 错误!

# 按任意空白分割的输出
['Hello,', 'world!', 'I', 'am', 'a', 'student.']
6 —— 正确!
统计单词数用 split()(不带参数)

split() 不带参数时会按任意空白字符(空格、制表符、换行符等)分割,并自动忽略首尾空白和连续空白,是统计单词数的正确方式。

替换与去除空白

Python
# replace: 替换子串
s = "Hello, World"
print(s.replace("World", "Python")) # Hello, Python
print(s.replace("l", "L")) # HeLLo, WorLd
print(s.replace("l", "L", 1)) # HeLlo, World (只替换 1 次)

# strip: 去除两端空白
s = " hello "
print(s.strip()) # hello
print(s.lstrip()) # hello (只去左端)
print(s.rstrip()) # hello (只去右端)

# strip 可指定要去除的字符
s = "##Hello##"
print(s.strip("#")) # Hello
print(s.lstrip("#")) # Hello##

# 去除前缀/后缀(Python 3.9+)
filename = "test.txt"
print(filename.removesuffix(".txt")) # test
url = "https://example.com"
print(url.removeprefix("https://")) # example.com
removeprefix / removesuffix (Python 3.9+)

这两个方法与 strip() 有本质区别:strip("#") 会去除所有 # 字符,而 removeprefix("#") 只在字符串以 # 开头时去除一次。这在处理文件扩展名、URL 前缀时更安全。

判断类方法

Python
# 这些方法返回布尔值,常用于输入验证
print("123".isdigit()) # True (全是数字)
print("abc".isalpha()) # True (全是字母)
print("abc123".isalnum()) # True (字母或数字)
print(" ".isspace()) # True (全是空白)
print("Hello".isupper()) # False
print("HELLO".isupper()) # True
print("hello".islower()) # True
print("Hello World".istitle()) # True (每个单词首字母大写)
print("abc".isidentifier()) # True (合法的标识符)

其他格式化方式

format() 方法和 % 格式化仍然存在于 Python 中,新代码应统一使用 f-string:

Python
name = "Alice"
age = 25

# format() 方法(遗留代码中可能遇到)
print("我叫 {},今年 {} 岁".format(name, age))

# % 格式化(C 风格,仅在旧代码中出现)
print("我叫 %s,今年 %d 岁" % (name, age))

字符编码

Python 3 的字符串是 Unicode 字符串,可使用 encode() / decode() 与字节串(bytes)互转:

Python
s = "中文"

# 编码为 bytes(默认 UTF-8)
b = s.encode("utf-8")
print(b) # b'\xe4\xb8\xad\xe6\x96\x87'
print(type(b)) # <class 'bytes'>

# 解码回字符串
print(b.decode("utf-8")) # 中文

# 其他编码
print(s.encode("gbk")) # b'\xd6\xd0\xce\xc4'
print(s.encode("gbk").decode("gbk")) # 中文

# 长度差异:字符数 vs 字节数
print(len(s)) # 2 (2 个字符)
print(len(s.encode())) # 6 (UTF-8 编码 6 字节)
print(len(s.encode("gbk"))) # 4 (GBK 编码 4 字节)
编码与解码必须用同一套规则

encode()decode() 可以使用相同的编码,否则会出现乱码或 UnicodeDecodeError。处理文件、网络数据时,明确指定编码(推荐 UTF-8)是好习惯。

🎯 动手练习

  1. 字符串反转:编写一个函数,接收字符串并返回其反转版本(不使用 [::-1]
  2. 回文判断:编写函数判断一个字符串是否是回文(忽略大小写和空格),如 "A man a plan a canal Panama"
  3. 单词统计:统计一段英文文本中每个单词的出现频率,输出前 5 个高频单词
  4. 字符串模板:使用 f-string 生成格式化的用户报告,包含姓名、年龄、城市等信息,右对齐显示
字符串操作练习

📚 延伸阅读

📋速查表
s.upper()转大写
"abc".upper() → "ABC"
s.lower()转小写
"ABC".lower() → "abc"
s.strip()去两端空白
" hi ".strip() → "hi"
s.split()分割字符串
"a,b".split(",") → ["a","b"]
",".join(lst)拼接列表
",".join(["a","b"]) → "a,b"
s.find(x)查找位置
"abc".find("b") → 1
s.replace(a,b)替换
"abc".replace("b","x") → "axc"
s.startswith(x)起始判断
"abc".startswith("a") → True
f"{x:.2f}"格式化数字
f"{3.14:.2f}" → "3.14"
s.encode()编码为 bytes
"中".encode() → b'\xe4\xb8\xad'

常见误区

误区 1:用 str 做变量名

Python
# ❌ 错误:覆盖了内置 str 类型
str = "hello"
print(str(123)) # TypeError: 'str' object is not callable

# ✅ 正确:使用其他变量名
text = "hello"
s = "hello"
sentence = "hello"

误区 2:用 split(" ") 统计单词数

Python
# ❌ 错误:无法处理多个空格
text = "Hello world"
print(len(text.split(" "))) # 3(包含空字符串)

# ✅ 正确:用 split() 不带参数
print(len(text.split())) # 2

误区 3:混淆字符串和代码

Python
# ❌ 错误:字符串不会自动变成代码
m = "1,2,3"
a = ["1,2,3"] # ['1,2,3'] 不是 [1, 2, 3]

# ✅ 正确:显式转换
a = [int(x) for x in m.split(",")] # [1, 2, 3]

实战:文本处理工具函数

Python
import re


def count_words(text: str) -> int:
"""统计文本中的单词数量(去除标点符号)。"""
words = re.findall(r"\w+", text)
return len(words)


def truncate_text(text: str, max_length: int, suffix: str = "...") -> str:
"""截断文本到指定长度,保留完整单词。"""
if len(text) <= max_length:
return text

truncated = text[:max_length].rsplit(" ", 1)[0]
return truncated + suffix


def format_number(num: float, decimals: int = 2, use_comma: bool = True) -> str:
"""格式化数字,支持千位分隔符和小数位数。"""
if use_comma:
return f"{num:,.{decimals}f}"
return f"{num:.{decimals}f}"


# 测试
text = "Python is a popular programming language known for its readability."
print(f"单词数: {count_words(text)}")
print(f"截断文本: {truncate_text(text, 30)}")
print(f"格式化数字: {format_number(1234567.89)}")

运行结果:

输出
单词数: 12
截断文本: Python is a popular programming...
格式化数字: 1,234,567.89

✅ 本节总结

  • 字符串是不可变的 Unicode 字符序列,任何"修改"操作都会返回新字符串
  • 单引号和双引号完全等价,选择取决于字符串内容
  • f-string 是现代 Python 字符串格式化的首选(Python 3.6+)
  • Python 3.12+ 支持 f-string 嵌套引号和多行表达式
  • 切片操作 [start:stop:step] 遵循"左闭右开"原则,不会越界
  • 大量字符串拼接应使用 "".join() 而非 + 循环
  • split() 不带参数时自动忽略连续空白,是统计单词的正确方式
  • 字符串方法丰富:大小写转换、查找、分割、替换、判断等
  • 处理文件和网络数据时,明确指定编码(推荐 UTF-8)