跳到主要内容

字符串

字符串(str)是 Python 中表示文本数据的核心类型。它是一个不可变的 Unicode 字符序列,支持丰富的操作:切片、拼接、查找、格式化等。本节将系统讲解字符串的创建、常用方法、切片索引,以及 Python 3.12+ 增强的 f-string 嵌套引号特性。

字符串创建

Python 支持三种引号方式创建字符串:单引号 '...'、双引号 "..."、三引号 '''...'''"""..."""

# 单引号
s1 = 'hello'

# 双引号
s2 = "world"

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

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

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

print(s1, s2, s3, s4)
print(s5)

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

转义字符

使用反斜杠 \ 可以表示特殊字符:

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

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

原始字符串

原始字符串(raw string)以 r 前缀开头,不处理转义字符,常用于正则表达式和 Windows 路径:

# 普通字符串:\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']

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

f-string(Python 3.12+ 支持嵌套引号)

f-string(格式化字符串字面量)是 Python 3.6+ 引入的最现代的字符串格式化方式,使用 f 前缀和 {} 占位符:

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 3.12+:可在 f-string 中使用相同引号
names = ["Alice", "Bob", "Charlie"]

# 旧版需要切换引号
old = f"列表: {', '.join(names)}"
print(old)

# 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"]}""")

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

字符串是不可变的

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

s = "hello"

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

# 正确做法:创建新字符串
s = "H" + s[1:]
print(s) # Hello

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

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

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

:::

索引与切片

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

索引

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):

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

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

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

:::

字符串长度与成员判断

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 字符串方法非常丰富,下面分类介绍常用的方法。

大小写转换

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)

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

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

:::

查找与计数

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 (支持元组)

分割与拼接

# 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

替换与去除空白

# 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

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

判断类方法

# 这些方法返回布尔值,常用于输入验证
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 (合法的标识符)

格式化方法

除 f-string 外,还有两种传统格式化方式:

name = "Alice"
age = 25

# 1. format() 方法
print("我叫 {},今年 {} 岁".format(name, age))
print("我叫 {0},{0} 今年 {1} 岁".format(name, age)) # 引用参数
print("我叫 {n},{a} 岁".format(n=name, a=age)) # 关键字参数
print("{:>10}".format("hi")) # 右对齐
print("{:0>5}".format(42)) # 00042 (补零)
print("{:.2f}".format(3.14159)) # 3.14

# 2. % 格式化(C 风格,已不推荐用于新代码)
print("我叫 %s,今年 %d 岁" % (name, age))
print("圆周率: %.2f" % 3.14159)

:::tip 三种格式化方式如何选择?

  1. f-string(Python 3.6+):现代首选,简洁高效,可读性最好
  2. format() 方法:兼容性最好,适合模板字符串场景
  3. % 格式化:仅在维护旧代码或与 C 代码交互时使用,新代码不推荐 :::

字符编码

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

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 字节)

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

实战:CSV 行解析器

下面用一个综合示例演示字符串方法的实际应用:

def parse_csv_line(line: str, delimiter: str = ",") -> list[str]:
"""简单 CSV 行解析器:支持引号包裹的字段
例: Alice,"Hello, World",25 -> ['Alice', 'Hello, World', '25']
"""
fields = []
current = []
in_quotes = False

for ch in line:
if ch == '"':
in_quotes = not in_quotes
elif ch == delimiter and not in_quotes:
fields.append("".join(current).strip())
current = []
else:
current.append(ch)

# 添加最后一个字段
fields.append("".join(current).strip())
return fields


# 测试
line = 'Alice, "Hello, World", 25, Beijing'
result = parse_csv_line(line)
print(result)
# ['Alice', 'Hello, World', '25', 'Beijing']

# 综合应用:处理数据
csv_data = """name,age,city
Alice,25,Beijing
Bob,30,Shanghai
"Charlie Brown",28,"Guang Zhou"
"""

print("=== 用户信息 ===")
for i, line in enumerate(csv_data.strip().splitlines()):
if i == 0:
continue # 跳过表头
fields = parse_csv_line(line)
name, age, city = fields
print(f"姓名: {name:>12} | 年龄: {age:>3} | 城市: {city}")

运行结果:

['Alice', 'Hello, World', '25', 'Beijing']
=== 用户信息 ===
姓名: Alice | 年龄: 25 | 城市: Beijing
姓名: Bob | 年龄: 30 | 城市: Shanghai
姓名: Charlie Brown | 年龄: 28 | 城市: Guang Zhou

小结

  • Python 字符串可用单引号、双引号、三引号创建,三引号支持多行。
  • 转义字符 \n\t\\ 等表示特殊字符;原始字符串 r"..." 不处理转义。
  • f-string 是现代首选的格式化方式,Python 3.12+ 支持嵌套相同引号、多行表达式与注释。
  • 字符串是不可变的,"修改"操作总是返回新字符串;循环拼接大量字符串请用 "".join()
  • 切片 s[start:stop:step] 灵活强大,s[::-1] 可反转字符串,切片不会因越界报错。
  • 常用方法:split/joinreplacestrip/lstrip/rstripfind/index/countstartswith/endswithupper/lower/title 等。
  • Python 3.9+ 的 removeprefix/removesuffix 比传统 strip() 更安全。
  • 字符串与字节串通过 encode() / decode() 互转,处理文件/网络时务必明确指定编码。