Skip to content

字符串基本操作 ​

字符串是 Python 中使用最频繁的数据类型,几乎所有程序都在处理文本:日志、配置、用户输入、接口数据。本节将系统介绍字符串的创建、索引切片、常用方法、格式化与拼接,帮你建立处理文本的完整能力。

前置知识

阅读本节前,建议先了解:第一个 Python 程序 与 使用 Python 作为计算器。

基础概念 ​

字符串(String,类型名 str)是不可变(Immutable)的 Unicode 字符序列。不可变意味着任何「修改」操作都会创建新字符串,原字符串保持不变。字符串支持索引(按位置取字符)与切片(取子串),前者从 0 开始,后者使用 [start:stop:step] 语法。

术语英文说明
不可变Immutable创建后内容不可修改
索引Index从 0 开始,负索引从末尾算起
切片Slicing[start:stop:step] 取子序列
UnicodeUnicode统一的字符编码标准
f-stringFormatted 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,4

join 常用于替代低效的 +=

在循环中用 += 拼接字符串会反复创建新对象,复杂度为 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-stringf"{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())  # True
text
http://a.com       是 Web 协议:True
https://b.com      是 Web 协议:True
ftp://c.com        是 Web 协议:False
//d.com            是 Web 协议:False
False
True

下一节

继续学习:列表快速上手

参考链接 ​