← 返回题目列表

Python 字符串为什么是不可变对象?常见字符串方法要注意什么?

高频 简单 第 3 / 21 题 更新于 2026/07/25
Pythonstr字符串不可变对象

简化版

Python 的 str 是不可变对象,创建后内容不能原地修改,任何看似修改字符串的操作都会产生新字符串。常见字符串方法如 replacestriplower 也不会改变原字符串,而是返回处理后的新字符串。

详细版

字符串不可变意味着:

s = "hello"
# s[0] = "H"  # TypeError

t = s.replace("h", "H")
print(s)  # hello
print(t)  # Hello

常见方法的特点:

  • strip():去掉两端空白或指定字符,返回新字符串。
  • replace(old, new):替换内容,返回新字符串。
  • split(sep):按分隔符拆分,返回列表。
  • join(iterable):把多个字符串连接成一个字符串。
  • lower() / upper():大小写转换,返回新字符串。
  • startswith() / endswith():判断前缀和后缀。
  • find():找不到返回 -1
  • index():找不到抛 ValueError

性能上,大量循环拼接字符串时不要反复 s += part,更推荐:

parts = ["a", "b", "c"]
result = "".join(parts)

因为字符串不可变,频繁拼接可能创建很多临时对象。

完整版教学

一、不可变到底是什么意思

不可变不是变量不能重新赋值,而是对象本身不能原地改。

s = "abc"
s = "xyz"

这段代码合法,但不是把 "abc" 这个对象改成 "xyz",而是让变量 s 重新绑定到另一个字符串对象。

真正的原地修改是不允许的:

s = "abc"
# s[0] = "A"  # TypeError: 'str' object does not support item assignment

所以字符串方法大多不会修改原对象,而是返回新对象。

可以画成这样:

s ───> "abc"
s = "xyz"
s ───> "xyz"     原来的 "abc" 没被改,只是 s 换了绑定

这和 list 很不一样。nums[0] = 9 能改列表对象内部槽位;s[0] = "A" 不允许,因为字符串没有暴露原地修改字符的能力。面试回答要把“变量重新绑定”和“对象原地修改”分开讲。

二、为什么字符串设计成不可变

字符串不可变有几个好处:

  1. 可以安全作为字典 key 和集合元素,因为哈希值稳定。
  2. 多个地方共享同一个字符串对象时,不会被某处偷偷改掉。
  3. 解释器可以做缓存、复用等优化。
  4. 字符串语义更接近“值”,使用起来更安全。

例如:

cache = {"python": 1}

如果字符串可以原地修改,"python" 被改成别的内容后,字典内部哈希结构就会被破坏。

不可变还让字符串在多处共享时更安全。比如函数接收到一个字符串,不必担心另一个引用会把这个字符串的第 0 个字符偷偷改掉。解释器也可以对某些字符串做驻留或缓存优化;这些是实现优化,不应作为业务判断身份的依据,但不可变性是优化成立的重要前提。

三、replacestriplower 都返回新字符串

看一个典型坑:

s = "  Python  "
s.strip()
print(s)  # "  Python  "

strip() 的结果没有被接住,原字符串不会变。正确写法:

s = s.strip()
print(s)  # "Python"

同理:

name = "Tom"
name.lower()
print(name)  # Tom

name = name.lower()
print(name)  # tom

这些方法的共同点是“不改原字符串,返回处理后的新字符串”。strip(chars) 还有一个常见误解:参数不是要去掉的完整前后缀,而是“字符集合”。如果要去固定前缀,Python 3.9+ 有 removeprefix;固定后缀有 removesuffix

print("www.example.com".strip("w.com"))  # example
print("www.example.com".removeprefix("www."))  # example.com

易错点:strip("abc") 去的是两端任意 a/b/c 字符,不是去掉字符串 "abc" 这个整体。

四、splitjoin 是一对常见组合

split 把字符串拆成列表:

line = "java,python,go"
items = line.split(",")
print(items)  # ['java', 'python', 'go']

join 把字符串序列连接起来:

items = ["java", "python", "go"]
line = ",".join(items)
print(line)  # java,python,go

注意 join 的调用者是分隔符,不是列表:

",".join(items)

这个写法刚开始有点反直觉,但它能清楚表达“用逗号连接这些字符串”。

还要注意,join 要求参与连接的元素都是 str,不会自动把数字转成字符串。需要连接数字列表时,要先转换:

nums = [1, 2, 3]
print(",".join(str(x) for x in nums))  # 1,2,3

split() 不带参数时,会按任意空白分割,并自动合并连续空白;split(" ") 则只按单个空格分割,连续空格会产生空字符串。这是日志解析和命令行参数处理里常见细节。

五、查找、判断和大小写方法的边界

findindex 都能查子串,但找不到时行为不同:

s = "hello"
print(s.find("x"))   # -1
# s.index("x")       # ValueError

startswithendswith 适合判断前后缀,比手写切片更直观。lowerupper 做简单大小写转换,涉及大小写无关比较时,可以了解 casefold(),它比 lower() 更适合某些 Unicode 文本的大小写折叠。不过中文面试里别把它扩太远,知道普通英文场景 lower() 常用即可。

方法找不到/结果特点常见用途
find找不到返回 -1容忍不存在
index找不到抛 ValueError不存在是异常
startswith返回 bool前缀判断
replace返回新字符串内容替换
strip返回新字符串去两端字符

六、字符串拼接的性能要怎么理解

少量拼接直接用 + 或 f-string 没问题:

message = f"hello {name}"

但大量循环拼接要小心:

s = ""
for part in parts:
    s += part

因为字符串不可变,每次拼接都可能创建新字符串。更常见的写法是:

result = "".join(parts)

它先收集片段,再一次性连接,语义清楚,也更适合大量字符串构造。

数字例子:循环拼接 10000 个片段时,如果每次都创建一个更长的新字符串,可能产生大量临时对象;join 可以先知道所有片段,再统一分配结果。CPython 对某些局部字符串拼接有优化,但不要把优化当成写法依赖;写生成大量文本时,list append + join 仍是清晰稳妥的模式。

七、常见误区与追问

  • 误区:s.strip() 会修改原字符串。 字符串不可变,方法返回新字符串,必须接住返回值。
  • 误区:strip("abc") 删除完整子串。 它删除两端任意属于字符集合的字符,固定前后缀用 removeprefixremovesuffix
  • 误区:大量循环 += 拼接一定没问题。 少量可以,大量构造更推荐收集片段后 join
  • 追问:字符串为什么能做 dict key? 字符串不可变,哈希值稳定,适合放进 dict/set。
  • 追问:findindex 区别是什么? 找不到时 find 返回 -1indexValueError
  • 追问:split()split(" ") 有什么不同? 前者按任意空白并合并连续空白,后者按明确空格分隔,可能产生空字符串。

八、加强记忆

把字符串记成“不可变文本值”:不能按下标原地改,replacestriplower 等方法都返回新字符串;查找要分清 findindex,拆分连接常用 split/join,大量拼接优先 join。回答时把不可变带来的哈希稳定、共享安全和方法返回新对象串起来,内容就不会散。