字符串基础与常用方法

字符串是编程中出现频率最高的数据类型之一——处理用户输入、读写文件、拼接 URL、格式化日志、拆解 CSV,几乎每个 Python 脚本都少不了它。前面 变量与基本数据类型 已经打过照面,这一篇要把 Python 字符串从零到能干活讲完整:创建、索引、切片、常用方法(超过 30 个)、编码与字节、以及最容易踩的那几个坑。

一、字符串的创建

Python 字符串支持四种界定方式:

1
2
3
4
5
6
7
8
9
10
11
s1 = 'hello'          # 单引号
s2 = "hello" # 双引号,与单引号完全等价
s3 = '''多行
字符串''' # 三引号,可跨行
s4 = """多行
字符串"""

# 里面含有引号
q1 = "He said 'hi'"
q2 = 'It\'s ok' # 用反斜杠转义
q3 = "It's ok" # 或者混用引号

惯例:普通字符串用双引号,需要输出双引号的字符串用单引号,跨多行的用三引号。别纠结,一致就行。

转义字符

1
2
3
4
5
6
print("换行\n再来一行")
print("\t缩进") # 制表符
print("反斜杠 \\")
print("Unicode: 中文") # 中文
print("原生字符串 r'C:\Users\name'") # 会转义 \U
print(r"原生字符串 r'C:\Users\name'") # 不转义,Windows 路径必备

Windows 路径写字符串时务必加 r,或者用双反斜杠、或者用正斜杠:

1
2
3
path = r"C:\Users\name\file.txt"       # 推荐
path = "C:\\Users\\name\\file.txt" # 也行
path = "C:/Users/name/file.txt" # 也行

二、字符串是不可变的

Python 字符串不可变(immutable)——一旦创建,不能修改任何一个字符:

1
2
s = "hello"
s[0] = "H" # TypeError!

要”修改”字符串,你只能创造一个新字符串:

1
2
3
s = "H" + s[1:]        # "Hello"
s = s.replace("h", "H")
s = s.upper()

这个特性的好处:字符串可以做字典的 key、可以放进 set、可以在多线程里安全共享。

三、索引与切片

字符串本质上是”字符的序列”,支持索引和切片(与列表一致):

1
2
3
4
5
6
7
8
9
10
s = "Python"
print(s[0]) # P,从 0 开始
print(s[-1]) # n,负数索引从右往左
print(len(s)) # 6

print(s[0:2]) # Py,切片 [start:stop),不含 stop
print(s[2:]) # thon,省略 stop 到结尾
print(s[:3]) # Pyt,省略 start 从头开始
print(s[::-1]) # nohtyP,反转字符串
print(s[::2]) # Pto,步长为 2

切片永远返回新字符串,不会修改原字符串。切片深入讲解见 序列切片深入解析

四、遍历字符串

字符串可迭代,直接 for 循环即可:

1
2
3
4
5
6
7
8
for ch in "咖飞":
print(ch)
# 咖
# 飞

# 需要索引时:
for i, ch in enumerate("Python"):
print(i, ch)

五、常用方法(按功能分组)

1. 大小写

1
2
3
4
5
6
7
s = "Hello, Python"

print(s.upper()) # HELLO, PYTHON
print(s.lower()) # hello, python
print(s.title()) # Hello, Python 每个词首字母大写
print(s.capitalize()) # Hello, python 首字母大写,其余小写
print(s.swapcase()) # hELLO, pYTHON 大小写互换

2. 检索

1
2
3
4
5
6
7
8
9
s = "hello world"

print(s.find("o")) # 4 第一次出现的位置,找不到返回 -1
print(s.rfind("o")) # 7 从右往左找
print(s.index("o")) # 4 同 find,但找不到抛 ValueError
print(s.count("l")) # 3 出现次数

print(s.startswith("he")) # True
print(s.endswith("ld")) # True

3. 判断

1
2
3
4
5
6
7
print("abc123".isalnum())    # True,全是字母数字
print("abc".isalpha()) # True,全是字母
print("123".isdigit()) # True,全是数字
print(" ".isspace()) # True,全是空白
print("ABC".isupper()) # True
print("abc".islower()) # True
print("Hello World".istitle()) # True

4. 修剪

1
2
3
4
5
6
7
8
9
10
11
12
s = "   hello   "
print(repr(s.strip())) # 'hello' 去两端空白
print(repr(s.lstrip())) # 'hello '
print(repr(s.rstrip())) # ' hello'

# 也可以去除指定字符
print("__abc__".strip("_")) # abc
print("www.baidu.com".lstrip("w.")) # baidu.com

# Python 3.9+ 有更精确的 removeprefix / removesuffix
print("test_module.py".removesuffix(".py")) # test_module
print("Mr. Wang".removeprefix("Mr. ")) # Wang

注意 strip("abc") 是去除”任意 a/b/c 字符”,不是去除子串 "abc"。用 removeprefix/removesuffix 才是去除子串。

5. 替换

1
2
3
s = "hello world hello"
print(s.replace("hello", "hi")) # hi world hi
print(s.replace("hello", "hi", 1)) # hi world hello,最多替换 1 次

6. 拆分与拼接

1
2
3
4
5
6
7
8
9
10
# 拆分
"a,b,c".split(",") # ['a', 'b', 'c']
"a b c".split() # ['a', 'b', 'c'] 不带参数按连续空白拆
"a,b,c".rsplit(",", 1) # ['a,b', 'c'] 从右拆一次
"line1\nline2\nline3".splitlines() # ['line1', 'line2', 'line3']

# 拼接:str.join(iterable) 是标准做法
",".join(["a", "b", "c"]) # 'a,b,c'
"\n".join(["行1", "行2"]) # '行1\n行2'
"".join(["a", "b", "c"]) # 'abc'

为什么用 join 不用 + 循环里 += 拼接字符串每次都会创建新对象,是 O(n²);而 "".join(list) 是 O(n)。写循环拼接大量字符串时永远用 join

1
2
3
4
5
6
7
# ❌ 慢,代码丑
result = ""
for word in words:
result += word + " "

# ✅ 快,代码美
result = " ".join(words)

7. 填充与对齐

1
2
3
4
5
"5".zfill(4)                # '0005'  左侧补 0

"abc".ljust(10, "-") # 'abc-------'
"abc".rjust(10, "-") # '-------abc'
"abc".center(10, "-") # '---abc----'

在打印表格、生成对齐输出时很有用。

8. 编码相关

1
2
3
s = "咖飞"
b = s.encode("utf-8") # b'\xe5\x92\x96\xe9\xa3\x9e' bytes 对象
s2 = b.decode("utf-8") # '咖飞'
  • str 是 Unicode 字符串(人读的);
  • bytes 是字节序列(机器传的);
  • encode 把 str → bytes,decode 把 bytes → str;
  • 中文一般用 UTF-8,遇到旧文件可能用 GBK。

六、格式化字符串(简版)

字符串格式化有三种主流方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
name = "咖飞"
age = 3

# 1. f-string(Python 3.6+,最推荐)
print(f"我叫 {name}{age} 岁")
print(f"两位小数:{3.14159:.2f}") # 3.14
print(f"补 4 位:{7:04d}") # 0007

# 2. str.format
print("我叫 {},{} 岁".format(name, age))
print("我叫 {n},{a} 岁".format(n=name, a=age))

# 3. % 老式,兼容 C printf
print("我叫 %s,%d 岁" % (name, age))

新代码永远用 f-string。format 和 % 只在维护老代码时才会遇到。

字符串格式化的完整讲解见 字符串格式化进阶

七、字符串常量

Python 标准库 string 里有一些方便的常量:

1
2
3
4
5
6
7
import string

print(string.ascii_letters) # abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
print(string.ascii_lowercase) # abcdefghijklmnopqrstuvwxyz
print(string.digits) # 0123456789
print(string.punctuation) # !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
print(string.whitespace) # 空白字符(含 \n \t)

生成随机密码时特别好用:

1
2
3
4
5
6
7
8
import random
import string

def gen_password(length: int = 12) -> str:
chars = string.ascii_letters + string.digits + string.punctuation
return "".join(random.choices(chars, k=length))

print(gen_password())

八、字符串与列表的互转

1
2
3
4
5
6
7
8
s = "咖飞"
lst = list(s) # ['咖', '飞']
back = "".join(lst) # '咖飞'

# 单词切分
sentence = "I love Python"
words = sentence.split() # ['I', 'love', 'Python']
back = " ".join(words) # 'I love Python'

九、一个完整例子:日志脱敏

我们把这一篇的知识揉到一起,写一个”手机号脱敏”工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# mask.py
"""把日志里的手机号和邮箱替换成脱敏形式。"""

def mask_phone(phone: str) -> str:
"""138****1234 形式。"""
if len(phone) != 11 or not phone.isdigit():
return phone
return phone[:3] + "*" * 4 + phone[-4:]


def mask_email(email: str) -> str:
"""把 @ 前只保留首尾两个字符。"""
if "@" not in email:
return email
name, domain = email.split("@", 1)
if len(name) <= 2:
masked = name
else:
masked = name[0] + "*" * (len(name) - 2) + name[-1]
return f"{masked}@{domain}"


def mask_line(line: str) -> str:
"""对一行日志同时脱敏(简化版:这里只演示 str 方法,实际生产建议用 re)。"""
words = line.split()
return " ".join(
mask_email(w) if "@" in w else
mask_phone(w) if len(w) == 11 and w.isdigit() else
w
for w in words
)


if __name__ == "__main__":
logs = [
"user 13812345678 login ok",
"notify kafei@example.com 已发送",
"顺序无关 admin 18899990000 alice@a.cn",
]
for line in logs:
print(mask_line(line))

输出:

1
2
3
user 138****5678 login ok
notify k****i@example.com 已发送
顺序无关 admin 188****0000 a****e@a.cn

真实生产环境的日志脱敏建议用 正则表达式 re 模块,本例只是演示字符串方法组合的威力。

十、常见陷阱

  1. strip("abc") 不是去掉子串,而是去掉两端属于 a/b/c 的任意字符。去子串用 removeprefix/removesuffix
  2. in 判断子串包含,别写成 s.find(x) != -1if "py" in s: 更 Pythonic。
  3. 不要在循环里 += 拼字符串,用 list.append + "".join
  4. isdigit() 对负号和小数点返回 False"-3".isdigit() 是 False,判断”是否是数字”最好写 try/except int()。
  5. Windows 路径中的反斜杠"C:\new\test.py" 会把 \n 解析成换行。用 raw string r"..." 最省心。
  6. 多行 f-string 里的花括号:想输出真正的 {,要写 {{`;输出 `}` 写 `}}
  7. split() 无参数与有参数不同:无参数按任意连续空白切且忽略首尾,split(" ") 是严格按单空格切且保留空串。
1
2
"  a  b  ".split()       # ['a', 'b']
" a b ".split(" ") # ['', '', 'a', '', 'b', '', '']

十一、小结与延伸阅读

  • 字符串不可变,索引切片都返回新字符串;
  • 用 f-string 做格式化;
  • join 不用 += 做拼接;
  • 熟悉 strip / split / replace / find / startswith / endswith 这几个高频方法;
  • 中文场景注意 encode/decode,默认用 UTF-8;
  • Windows 路径记得 r"..."
  • 需要更强查找/替换能力时上正则。

延伸阅读:

下一篇 输入输出与格式化字符串 我们要让程序真正开口和用户对话。