字符串基本操作
字符串是 Python 中使用最频繁的数据类型,几乎所有程序都在处理文本:日志、配置、用户输入、接口数据。本节将系统介绍字符串的创建、索引切片、常用方法、格式化与拼接,帮你建立处理文本的完整能力。
前置知识
阅读本节前,建议先了解:第一个 Python 程序 与 使用 Python 作为计算器。
基础概念
字符串(String,类型名 str)是不可变(Immutable)的 Unicode 字符序列。不可变意味着任何「修改」操作都会创建新字符串,原字符串保持不变。字符串支持索引(按位置取字符)与切片(取子串),前者从 0 开始,后者使用 [start:stop:step] 语法。
| 术语 | 英文 | 说明 |
|---|---|---|
| 不可变 | Immutable | 创建后内容不可修改 |
| 索引 | Index | 从 0 开始,负索引从末尾算起 |
| 切片 | Slicing | [start:stop:step] 取子序列 |
| Unicode | Unicode | 统一的字符编码标准 |
| f-string | Formatted String Literal | 以 f 前缀的内插字符串 |
| 方法 | Method | 绑定在对象上的函数,如 str.upper() |
语法与用法
创建字符串有多种方式,注意单引号、双引号与三引号的差异:
python
# 最简可运行示例:创建与输出字符串
s1 = '单引号字符串'
s2 = "双引号字符串"
s3 = """三引号可跨行
第二行内容"""
print(s1)
print(s2)
print(s3)
print(type(s1))text
单引号字符串
双引号字符串
三引号可跨行
第二行内容
<class 'str'>详细说明
字符串的创建与引号选择
| 写法 | 说明 | 适用场景 |
|---|---|---|
'abc' | 单引号 | 内容含双引号时 |
"abc" | 双引号 | 内容含单引号时(如 "it's") |
'''abc''' | 三单引号 | 多行文本、含双引号的内容 |
"""abc""" | 三双引号 | 多行文本、文档字符串 |
r"a\nb" | 原始字符串 | 正则、Windows 路径,反斜杠不转义 |
b"abc" | 字节串 | 二进制数据,类型是 bytes |
python
# 引号的选择:内容含引号时用另一种引号包围,避免转义
print("He said: 'Hello'")
print('She replied: "Hi"')
# 原始字符串:反斜杠不转义
print(r"C:\Users\name\docs") # 路径保持原样
print("C:\\Users\\name\\docs") # 等价写法,需转义
# 三引号保留换行与缩进
sql = """
SELECT id, name
FROM users
WHERE age > 18
"""
print(sql.strip())text
He said: 'Hello'
She replied: "Hi"
C:\Users\name\docs
C:\Users\name\docs
SELECT id, name
FROM users
WHERE age > 18字符串前缀速查
f(格式化)、r(原始)、b(字节)、u(Unicode,Python 3 中无实际作用)。前缀可组合,如 rf"..."、fr"..."。
索引与切片
字符串是序列,可用索引访问单个字符,用切片获取子串:
python
text = "Python"
# P y t h o n
# 索引: 0 1 2 3 4 5
# 负索引:-6 -5 -4 -3 -2 -1
print(text[0]) # 第 1 个字符
print(text[-1]) # 最后一个字符
print(text[2:5]) # 索引 2 到 4(不含 5)
print(text[:3]) # 开头到索引 2
print(text[3:]) # 索引 3 到末尾
print(text[::-1]) # 反转字符串
print(text[::2]) # 每隔一个取一个text
P
n
tho
Pyt
hon
nohtyP
Pto| 切片写法 | 含义 | 对 "Python" 的结果 |
|---|---|---|
s[2:5] | 索引 2 到 4 | "tho" |
s[:3] | 前 3 个字符 | "Pyt" |
s[3:] | 索引 3 之后全部 | "hon" |
s[-3:] | 最后 3 个字符 | "hon" |
s[::2] | 步长为 2 | "Pto" |
s[::-1] | 步长 -1,反转 | "nohtyP" |
s[100:200] | 越界不报错,返回空串 | "" |
python
# 切片越界不报错,但索引越界会报错
text = "Python"
print(repr(text[100:200])) # 切片越界安全
try:
print(text[100]) # 错误!索引越界
except IndexError as exc:
print(f"IndexError: {exc}")text
''
IndexError: string index out of range字符串的不可变性
「修改」字符串实际是创建了新对象,理解这点可以避免性能陷阱:
python
original = "hello"
# 所有方法都返回新字符串,原字符串不变
upper = original.upper()
print(f"原字符串:{original}")
print(f"新字符串:{upper}")
print(f"是同一个对象吗:{original is upper}")
# 错误!试图直接修改会报错
try:
original[0] = "H"
except TypeError as exc:
print(f"TypeError: {exc}")text
原字符串:hello
新字符串:HELLO
是同一个对象吗:False
TypeError: 'str' object does not support item assignment常用方法一览
字符串方法数量众多,按用途分类记忆更高效:
| 类别 | 方法 | 说明 |
|---|---|---|
| 大小写 | upper() / lower() | 转大写/小写 |
| 大小写 | title() / capitalize() | 首字母大写 |
| 大小写 | swapcase() | 大小写互换 |
| 去空白 | strip() / lstrip() / rstrip() | 去两端/左/右空白 |
| 查找 | find() / index() | 查找子串位置 |
| 查找 | count() | 统计出现次数 |
| 判断 | startswith() / endswith() | 前缀/后缀判断 |
| 判断 | isalpha() / isdigit() / isspace() | 字符类型判断 |
| 替换 | replace(old, new) | 替换子串 |
| 分割 | split(sep) / rsplit() | 按分隔符切分 |
| 连接 | join(iterable) | 用自身连接可迭代对象 |
| 对齐 | ljust() / rjust() / center() | 左右居中对齐 |
| 补零 | zfill(width) | 左侧补 0 |
python
text = " Hello, Python World! "
# 去除空白与大小写转换
print(f"[{text.strip()}]")
print(text.strip().lower())
print(text.strip().upper())
print(text.strip().title())
# 查找与统计
clean = text.strip()
print(clean.find("Python"))
print(clean.count("l"))
print(clean.startswith("Hello"))
print(clean.endswith("!"))
# 替换与分割
print(clean.replace("World", "Language"))
print(clean.split(", "))
print(clean.split()) # 无参数时按任意空白分割text
[Hello, Python World!]
hello, python world!
HELLO, PYTHON WORLD!
Hello, Python World!
7
3
True
True
Hello, Python Language!
['Hello', 'Python World!']
['Hello,', 'Python', 'World!']分割与连接
split() 与 join() 是文本处理中最重要的一对方法:
python
# split:字符串 -> 列表
csv_line = "alice,28,engineer,shanghai"
fields = csv_line.split(",")
print(fields)
print(f"字段数:{len(fields)}")
# 限制分割次数:maxsplit
path = "a/b/c/d/e"
print(path.split("/", maxsplit=2))
# rsplit:从右侧分割
print(path.rsplit("/", maxsplit=2))
# join:列表 -> 字符串(注意 join 是「分隔符」的方法)
words = ["Python", "is", "great"]
print(" ".join(words))
print("-".join(words))
print("".join(words))
# 连接数字需先转换为字符串
numbers = [1, 2, 3, 4]
print(",".join(map(str, numbers)))text
['alice', '28', 'engineer', 'shanghai']
字段数:4
['a', 'b', 'c/d/e']
['a/b/c', 'd', 'e']
Python is great
Python-is-great
Pythonisgreat
1,2,3,4join 常用于替代低效的 +=
在循环中用 += 拼接字符串会反复创建新对象,复杂度为 O(n²)。应收集到列表后用 "".join() 一次性拼接。
python
# 不推荐:循环中反复 +=(每次创建新字符串)
def bad_concat(items: list[str]) -> str:
result = ""
for item in items:
result += item + ","
return result
# 推荐:收集后 join(一次分配内存)
def good_concat(items: list[str]) -> str:
return ",".join(items)
words = [f"item{i}" for i in range(5)]
print(bad_concat(words))
print(good_concat(words))text
item0,item1,item2,item3,item4,
item0,item1,item2,item3,item4格式化输出
三种格式化方式中,f-string 是当前推荐方案:
| 方式 | 写法 | 版本 | 评价 |
|---|---|---|---|
% 格式化 | "%s: %d" % (name, age) | 古老 | 不推荐 |
str.format() | "{}: {}".format(name, age) | 2.6+ | 可用 |
| f-string | f"{name}: {age}" | 3.6+ | 推荐 |
| 模板字符串 | t"Hello {name}" | 3.14+ | 新特性,防注入 |
python
name = "小码农"
score = 92.5
rank = 3
# f-string 基础用法:直接内插变量
print(f"姓名:{name},得分:{score}")
# 格式说明符:对齐、精度、千分位
print(f"得分:{score:.1f} 分")
print(f"得分:{score:>8.2f}(右对齐)")
print(f"名次:第 {rank:02d} 名(补零)")
# 表达式与函数调用
print(f"下一位名次:{rank - 1}")
print(f"姓名长度:{len(name)}")
print(f"大写得:{name.upper()}")
# 调试用法:变量名 = 值(Python 3.8+)
print(f"{score=}, {rank=}")
# 字典与格式化
user = {"name": "alice", "age": 28}
print(f"用户:{user['name']},年龄:{user['age']}")text
姓名:小码农,得分:92.5
得分:92.5 分
得分: 92.50(右对齐)
名次:第 03 名(补零)
下一位名次:2
姓名长度:3
大写得:小码农
score=92.5, rank=3
用户:alice,年龄:28实战示例
示例一:文本清洗与分词
处理从网页或日志中抓取的脏数据,是字符串操作最典型的应用:
python
import re
def clean_text(raw: str) -> str:
"""清洗文本:去多余空白、统一标点、去除控制字符"""
# 1. 去除首尾空白
text = raw.strip()
# 2. 把连续空白替换为单个空格
text = re.sub(r"\s+", " ", text)
# 3. 去除不可见控制字符
text = re.sub(r"[\x00-\x1f\x7f]", "", text)
# 4. 全角空格转半角
text = text.replace("\u3000", " ")
return text
def tokenize(text: str) -> list[str]:
"""简单分词:按非字母数字中文边界切分"""
# 统一转为小写后切分
tokens = re.findall(r"[a-zA-Z]+|[\u4e00-\u9fff]", text.lower())
return tokens
dirty = " Python 是 一门\t强大的\n语言! Hello WORLD "
cleaned = clean_text(dirty)
print(f"清洗后:{cleaned}")
tokens = tokenize(cleaned)
print(f"分词结果:{tokens}")
print(f"词数:{len(tokens)}")text
清洗后:Python 是 一门 强大的 语言! Hello WORLD
分词结果:['python', '是', '一', '门', '强', '大', '的', '语', '言', 'hello', 'world']
词数:11示例二:结构化数据解析
解析配置文件或日志行,体现 split、strip 与字典的配合:
python
def parse_config(text: str) -> dict[str, str]:
"""解析 key=value 格式的配置文本,忽略注释与空行"""
config: dict[str, str] = {}
for line_no, raw_line in enumerate(text.splitlines(), start=1):
line = raw_line.strip()
# 跳过空行与注释行
if not line or line.startswith("#"):
continue
if "=" not in line:
print(f"第 {line_no} 行格式错误,已跳过:{line}")
continue
key, _, value = line.partition("=")
config[key.strip()] = value.strip()
return config
config_text = """
# 数据库配置
host = 127.0.0.1
port = 3306
user = admin
password = secret123
# 缓存配置
cache_ttl=3600
invalid_line_without_equals
"""
config = parse_config(config_text)
for key, value in config.items():
print(f"{key:<12} = {value}")text
第 9 行格式错误,已跳过:invalid_line_without_equals
host = 127.0.0.1
port = 3306
user = admin
password = secret123
cache_ttl = 3600示例三:字符串校验与脱敏
在数据入库或展示前做校验与脱敏,是生产环境的常见需求:
python
def validate_email(email: str) -> bool:
"""极简邮箱校验:含 @、有域名、无空格"""
if " " in email or email.count("@") != 1:
return False
local, _, domain = email.partition("@")
return bool(local) and "." in domain and not domain.startswith(".") and not domain.endswith(".")
def mask_phone(phone: str) -> str:
"""手机号脱敏:保留前 3 位与后 4 位"""
digits = "".join(ch for ch in phone if ch.isdigit())
if len(digits) != 11:
return "号码格式错误"
return f"{digits[:3]}****{digits[-4:]}"
def mask_name(name: str) -> str:
"""姓名脱敏:仅保留姓氏"""
return name[0] + "*" * (len(name) - 1) if name else ""
emails = ["alice@example.com", "bad-email", "bob@site", "c@x.com.cn"]
for email in emails:
mark = "有效" if validate_email(email) else "无效"
print(f"{email:<22} {mark}")
print("-" * 30)
print(mask_phone("13812345678"))
print(mask_phone("138-1234-5678"))
print(mask_name("张三"))
print(mask_name("欧阳娜娜"))text
alice@example.com 有效
bad-email 无效
bob@site 无效
c@x.com.cn 有效
------------------------------
138****5678
138****5678
张*
欧***注意事项
- 字符串不可变:任何「修改」都返回新对象,循环中拼接应改用列表 +
join。 - 索引越界报错,切片不报错:
s[100]抛IndexError,s[100:200]返回""。 find()与index()差异:找不到时find返回-1,index抛ValueError。strip()不是去掉所有空格:只去两端,中间的空格保留;去中间用replace或正则。join()是分隔符的方法:",".join(list),不是list.join(",")。join()只接受字符串元素:含数字需先map(str, ...)转换。- 中文的索引按「码点」计数:
len("你好")是 2,但某些 emoji 由多个码点组成,len()可能大于视觉字数。 \行连接符:字符串内换行应使用三引号或\n,行尾\是「续行」而非换行。
最佳实践
- 统一使用 f-string:可读性最好,且比
%与format()更快。 - 处理路径用
pathlib:不要用字符串拼接路径,避免分隔符与转义问题。 - 正则用于复杂模式匹配:简单替换用
str.replace,复杂模式才引入re。 - 用
casefold()做大小写无关比较:它比lower()对 Unicode 更正确。 - 用
startswith/endswith支持元组:s.startswith(("http://", "https://"))一次判断多个前缀。 - 对外输出做脱敏:手机号、身份证、邮箱等在日志中应脱敏。
- 注意编码显式声明:读写文件时始终指定
encoding="utf-8"。
python
# 推荐:多前缀判断与 Unicode 安全比较
urls = ["http://a.com", "https://b.com", "ftp://c.com", "//d.com"]
for url in urls:
is_web = url.startswith(("http://", "https://"))
print(f"{url:<18} 是 Web 协议:{is_web}")
# casefold 适合做大小写无关比较(比 lower 更严格)
a, b = "Straße", "STRASSE"
print(a.lower() == b.lower()) # False
print(a.casefold() == b.casefold()) # Truetext
http://a.com 是 Web 协议:True
https://b.com 是 Web 协议:True
ftp://c.com 是 Web 协议:False
//d.com 是 Web 协议:False
False
True下一节
继续学习:列表快速上手