一、字符串基础特性与定义
1. 定义方式
| 单引号 | str1 = 'Hello World' | 常规场景,内容含双引号时优先 |
| 双引号 | str2 = "I'm a Python developer" | 内容含单引号时优先 |
| 三单引号 | str3 = '''多行字符串\\n保留格式''' | 多行文本、保留换行 / 制表符,非文档注释场景 |
| 三双引号 | str4 = """文档注释/多行文本""" | 多行文本、Python 文档注释(函数 / 类说明) |
| 空字符串 | str5 = '' | 初始化、判断边界,'' in 任意字符串恒 True |
2. 核心特性
|
不可变性 |
单个字符无法直接修改(如s[0] = 'h'报错),修改操作生成新字符串 |
|
有序序列 |
字符有明确索引,支持索引、切片操作 |
|
可哈希性 |
可作为字典键、集合元素(不可变导致哈希值稳定) |
|
线程安全性 |
不可变特性无需加锁,多线程操作无并发问题 |
|
驻留机制 |
符合[a-zA-Z0-9_]且长度≤20 的字符串自动缓存,sys.intern()可强制驻留 |
-
自动驻留:符合[a-zA-Z0-9_]、长度≤20 的字符串(节省内存)
-
强制驻留:sys.intern(字符串)(大量重复字符串时使用)
-
注意:is判断内存地址,==判断内容(永远用==判断相等)
3.示例代码
# -*- coding: utf-8 -*-
"""
Python 字符串基础特性与定义示例代码
涵盖:4种定义方式、不可变性、有序序列、可哈希性、线程安全、驻留机制
"""
import sys
import time
import threading
print("=" * 70)
print("1. 字符串的四种定义方式")
print("=" * 70)
# 1.1 单引号:常规场景,内容含双引号时优先
str1 = 'Hello "Python" World'
print(f"单引号 (含双引号): {str1}")
# 1.2 双引号:内容含单引号时优先
str2 = "I'm a Python developer"
print(f"双引号 (含单引号): {str2}")
# 1.3 三单引号:多行文本,保留换行/制表符 (非文档注释)
str3 = '''第一行
第二行 (保留缩进)
\\t第三行 (制表符)'''
print(f"三单引号 (多行):\\n{str3}")
# 1.4 三双引号:常用于文档注释 (Docstring),也可作多行文本
def demo_func():
"""
这是一个函数文档注释。
使用三双引号包裹。
"""
pass
print(f"函数文档注释: {demo_func.__doc__}")
# 1.5 空字符串:初始化与边界判断
str5 = ''
print(f"\\n空字符串长度: {len(str5)}")
print(f"'' in '任意字符串': {'' in '任意字符串'}") # 恒为 True (空串是任何串的子串)
print("\\n" + "=" * 70)
print("2. 核心特性演示")
print("=" * 70)
# 2.1 不可变性 (Immutability)
# 尝试修改单个字符会报错
s = "Hello"
print(f"\\n原字符串: {s}, ID: {id(s)}")
try:
# s[0] = 'h' # 取消注释会报 TypeError: 'str' object does not support item assignment
pass
except TypeError as e:
print(f"直接修改报错: {e}")
# 修改操作本质是生成新对象
s_new = "h" + s[1:]
print(f"修改后新字符串: {s_new}, ID: {id(s_new)}")
print(f"地址是否改变: {id(s) != id(s_new)}") # True
# 2.2 有序序列 (Ordered Sequence)
# 支持索引和切片
text = "Python Programming"
print(f"\\n原字符串: {text}")
print(f"索引 [0]: {text[0]}") # 'P'
print(f"索引 [-1]: {text[-1]}") # 'g'
print(f"切片 [0:6]: {text[0:6]}") # 'Python'
print(f"切片 [::-1] (反转): {text[::-1]}")
# 2.3 可哈希性 (Hashable)
# 可作为字典键或集合元素
print("\\n[可哈希性测试]")
num_dict = {
"name": "Alice",
100: "Score",
(1, 2): "Tuple Key" # 元组也是可哈希的
}
# 字符串作为键
user_info = {"username": "admin", "role": "root"}
print(f"字典 (字符串作键): {user_info}")
# 字符串放入集合 (自动去重)
char_set = {"a", "b", "a", "c"}
print(f"集合 (自动去重): {char_set}")
# 验证哈希值稳定性
s_hash = "constant"
h1 = hash(s_hash)
h2 = hash(s_hash)
print(f"哈希值稳定性: {h1 == h2}") # True
# 2.4 线程安全性 (Thread Safety)
# 由于不可变,多线程读取无需加锁
print("\\n[线程安全测试]")
shared_str = "Immutable Data"
results = []
def read_data(thread_id):
# 模拟多次读取
for _ in range(1000):
# 即使多线程同时访问,也不会出现数据竞争或脏读
results.append(f"Thread-{thread_id}: {shared_str}")
threads = []
for i in range(5):
t = threading.Thread(target=read_data, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"多线程读取完成,总记录数: {len(results)} (无并发错误)")
# 2.5 字符串驻留机制 (String Interning)
# Python 会自动缓存符合特定规则的字符串 (字母/数字/下划线, 较短)
print("\\n[驻留机制测试]")
# 情况 A: 符合驻留规则 (字母数字下划线,通常长度<=20)
a = "hello_world_123"
b = "hello_world_123"
print(f"符合规则 ('hello_world_123'): a is b -> {a is b}") # True (共用地址)
# 情况 B: 不符合规则 (包含特殊符号如空格、连字符等)
c = "hello world" # 包含空格
d = "hello world"
# 注意:在脚本文件中,解释器优化可能使 c is d 为 True,但在交互式环境或动态创建时为 False
# 为了演示差异,我们动态创建
c_dyn = "".join(["hello", " ", "world"])
d_dyn = "".join(["hello", " ", "world"])
print(f"含空格 (动态创建): c_dyn is d_dyn -> {c_dyn is d_dyn}") # 通常 False
# 情况 C: 强制驻留 (sys.intern)
e = "special-string!"
f = "special-string!"
print(f"含特殊符号 (未强制): e is f -> {e is f}") # 通常 False
e_interned = sys.intern(e)
f_interned = sys.intern(f)
print(f"含特殊符号 (强制 intern): e_interned is f_interned -> {e_interned is f_interned}") # True
print("\\n演示结束。")
二、字符串索引与切片
1. 索引规则
| 正向索引 | 0 | s[0] → 'P' | 从左到右,越界抛IndexError |
| 反向索引 | -1 | s[-1] → 'n' | 从右到左,s[-len(s)] = s[0] |
2. 切片语法与特性
基础语法
字符串[起始索引:结束索引:步长]
# 起始索引:包含(默认0);结束索引:不包含(默认len(字符串));步长:默认1(负数反向)
核心特性与示例
| 基础切片 | s[0:5] → 'Hello' | 取索引 0-4 的字符(左闭右开) |
| 省略起始索引 | s[:5] → 'Hello' | 从开头到结束索引 |
| 省略结束索引 | s[5:] → 'Python' | 从起始索引到末尾 |
| 反向切片(反转) | s[::-1] → 'nohtyPolleH' | 最优字符串反转方案 |
| 指定步长 | s[0:10:2] → 'HloPto' | 每隔 1 个字符取 1 个 |
| 惰性复制 | s_slice = s[:5] | 切片仅记录原字符串引用 + 索引,修改时才复制数据 |
3.示例代码
# -*- coding: utf-8 -*-
"""
Python 字符串索引与切片示例代码
涵盖:正向/反向索引、切片基础语法、步长控制、反转技巧、惰性复制原理
"""
print("=" * 70)
print("1. 字符串索引规则 (Indexing)")
print("=" * 70)
s = "Python"
print(f"原字符串: '{s}' (长度: {len(s)})")
# 1.1 正向索引 (0 开始,从左到右)
print("\\n[正向索引]")
print(f"s[0] : '{s[0]}' (第一个字符)")
print(f"s[2] : '{s[2]}' (第三个字符)")
print(f"s[5] : '{s[5]}' (最后一个字符)")
# 越界测试
try:
# print(s[6]) # 取消注释会报 IndexError
pass
except IndexError as e:
print(f"正向越界报错: {e}")
# 1.2 反向索引 (-1 开始,从右到左)
print("\\n[反向索引]")
print(f"s[-1] : '{s[-1]}' (最后一个字符,等价于 s[5])")
print(f"s[-2] : '{s[-2]}' (倒数第二个字符)")
print(f"s[-6] : '{s[-6]}' (第一个字符,等价于 s[0])")
# 反向越界测试
try:
# print(s[-7]) # 取消注释会报 IndexError
pass
except IndexError as e:
print(f"反向越界报错: {e}")
print("\\n" + "=" * 70)
print("2. 切片基础语法 (Slicing)")
print("=" * 70)
# 语法: string[start:end:step]
# 规则: [start, end) 左闭右开,包含 start 不包含 end
text = "HelloPython"
print(f"原字符串: '{text}' (长度: {len(text)})")
# 索引对应:
# H e l l o P y t h o n
# 0 1 2 3 4 5 6 7 8 9 10
# -11… -1
# 2.1 基础切片 [start:end]
print("\\n[基础切片]")
sub1 = text[0:5]
print(f"text[0:5] : '{sub1}' (取索引 0 到 4,'Hello')")
# 2.2 省略起始索引 [:end] -> 默认从 0 开始
print("\\n[省略起始索引]")
sub2 = text[:5]
print(f"text[:5] : '{sub2}' (从开头到索引 4)")
# 2.3 省略结束索引 [start:] -> 默认到末尾
print("\\n[省略结束索引]")
sub3 = text[5:]
print(f"text[5:] : '{sub3}' (从索引 5 到末尾,'Python')")
# 2.4 省略起止 [:] -> 浅拷贝整个字符串
sub4 = text[:]
print(f"text[:] : '{sub4}' (完整拷贝,但地址可能因驻留机制相同)")
print(f"地址比较: {id(text) == id(sub4)}")
print("\\n" + "=" * 70)
print("3. 步长与高级切片 (Step & Advanced)")
print("=" * 70)
# 3.1 指定步长 [start:end:step]
print("\\n[指定步长]")
# 每隔 1 个字符取 1 个 (即取索引 0, 2, 4, 6, 8…)
sub_step = text[0:10:2]
print(f"text[0:10:2]: '{sub_step}' ('HloPto')")
# 3.2 反向切片 (字符串反转神器)
print("\\n[反向切片/反转]")
reversed_s = text[::-1]
print(f"text[::-1] : '{reversed_s}' ('nohtyPolleH')")
print("说明: step 为 -1 时,默认 start 为末尾,end 为开头")
# 3.3 负数步长配合索引
# 取倒数第 5 个到倒数第 2 个,并反转
# 原串: H(0)…o(-5) P(-6)…n(-1)
# 目标范围: oPyt (索引 4 到 7) -> 反转后: tyPo
sub_neg = text[-2:-6:-1]
# 解析: start=-2('o'), end=-6('P', 不包含), step=-1
# 路径: -2('o') -> -3('t') -> -4('y') -> -5('P') -> 停止 (因为 -6 不包含)
print(f"text[-2:-6:-1]: '{sub_neg}' ('otyP' ? 稍等,让我们仔细推导)")
# 修正推导:
# text = "HelloPython"
# 索引: 01234567890
# 负号: -11-10-9-8-7-6-5-4-3-2-1
# 字符: H e l l o P y t h o n
# text[-2] 是 'o' (倒数第 2 个)
# text[-6] 是 'P' (倒数第 6 个)
# text[-2:-6:-1] -> 从 'o' 开始,向左走,直到 'P' (不含)
# 序列: 'o' (idx -2), 'h' (idx -3), 't' (idx -4), 'y' (idx -5). 下一个是 'P' (idx -6, 停止)
# 结果应该是 'ohty'
print(f"实际输出: '{sub_neg}' (推导验证: o->h->t->y)")
print("\\n" + "=" * 70)
print("4. 惰性复制与内存优化 (Lazy Copy)")
print("=" * 70)
# Python 的切片操作通常是“浅拷贝”对于不可变对象
# 对于字符串,切片对象在内部可能只是记录原字符串的引用 + 偏移量 + 长度
# 只有当需要修改(生成新串)或特定实现细节下才会真正复制数据
large_str = "A" * 1000000 # 100 万个字符
print(f"原字符串长度: {len(large_str)}, 占用内存: {len(large_str)} bytes (近似)")
# 执行切片
slice_str = large_str[100:200]
# 验证内容
print(f"切片后长度: {len(slice_str)}")
print(f"切片内容前 10 位: '{slice_str[:10]}'")
# 内存说明:
# 在 CPython 实现中,字符串切片通常会创建一个新的字符串对象并复制数据。
# 但概念上,它被视为一种高效的序列视图操作。
# 真正的“惰性/零拷贝”在 Python 字符串中不如在 numpy 或 memoryview 中明显,
# 但对于不可变对象,解释器会尽可能优化内存管理(如共享底层缓冲区在某些版本/场景中)。
# 重点:切片不会修改原字符串,而是返回新字符串。
import sys
original_size = sys.getsizeof(large_str)
slice_size = sys.getsizeof(slice_str)
print(f"\\n原对象内存: {original_size} bytes")
print(f"切片对象内存: {slice_size} bytes (仅包含切片部分的实际数据)")
print("结论: 切片生成了独立的新对象,只存储截取部分的数据,未保留对大字符串的引用防止内存泄漏。")
print("\\n演示结束。")
三、字符串基本操作
1. 拼接与重复
| 拼接 | + | 'Hello' + 'Python' | 小规模可用,大规模频繁创建新对象(低效) |
| 重复 | * | 'Hello' * 3 | 生成重复字符串,'a'*0返回空字符串 |
| 高效拼接 | ''.join() | ''.join(['a','b','c']) | 时间复杂度 O (n),循环拼接首选 |
| 超大拼接 | io.StringIO | 见下方示例 | 避免频繁创建对象,超大量字符串拼接最优 |
# 超大字符串拼接示例
import io
buffer = io.StringIO()
for i in range(100000):
buffer.write(str(i))
result = buffer.getvalue() # 最终拼接结果
2. 成员判断与长度计算
| 成员存在 | in | 'Python' in 'Python is fun' |
| 成员不存在 | not in | 'Java' not in 'Python is fun' |
| 长度计算 | len() | len('中文') → 2(Unicode 字符数) |
3.示例代码
# -*- coding: utf-8 -*-
"""
Python 字符串基本操作示例代码
涵盖:拼接与重复(+、*、join、StringIO)、成员判断、长度计算
"""
import io
import time
print("=" * 70)
print("1. 字符串拼接与重复 (Concatenation & Repetition)")
print("=" * 70)
# 1.1 使用 + 号拼接 (小规模场景)
# 适用:少量字符串拼接,代码可读性高
s1 = "Hello"
s2 = "Python"
result_plus = s1 + " " + s2
print(f"[+] 拼接: '{result_plus}'")
# 性能警告:在循环中频繁使用 + 会创建大量临时对象,效率极低
# 每次 + 操作都会分配新内存并复制旧数据,时间复杂度接近 O(n^2)
# 1.2 使用 * 号重复
# 适用:生成重复图案、占位符
repeat_str = "Ha" * 3
print(f"[*] 重复: '{repeat_str}'") # HaHaHa
zero_repeat = "Test" * 0
print(f"[*] 零次重复: '{zero_repeat}' (空字符串)") # ''
# 1.3 使用 ''.join() (推荐:循环拼接的标准做法)
# 适用:列表/元组转字符串,循环内拼接
# 时间复杂度 O(n),只分配一次内存
char_list = ['P', 'y', 't', 'h', 'o', 'n']
result_join = "".join(char_list)
print(f"[join] 列表转字符串: '{result_join}'")
# 模拟循环拼接对比
words = ["Line"] * 1000
# ❌ 低效写法 (注释掉以免运行慢,仅示意)
# bad_result = ""
# for w in words:
# bad_result += w + "\\n"
# ✅ 高效写法
good_result = "\\n".join(words)
print(f"[join] 高效拼接 1000 行: 总长度 {len(good_result)} 字符")
# 1.4 使用 io.StringIO (超大规模拼接的最优解)
# 适用:海量数据流、日志缓冲、避免内存碎片
# 原理:内部维护一个可变缓冲区,类似文件写入,最后一次性导出
print("\\n[StringIO] 超大规模拼接测试…")
start_time = time.time()
buffer = io.StringIO()
count = 100000
for i in range(count):
buffer.write(f"ID:{i},Data:{i*2};") # 模拟写入结构化数据
huge_result = buffer.getvalue()
end_time = time.time()
print(f"拼接数量: {count} 条")
print(f"结果总长度: {len(huge_result)} 字符")
print(f"耗时: {(end_time – start_time)*1000:.2f} ms")
print("说明: StringIO 避免了每次拼接都创建新字符串对象,内存效率最高。")
print("\\n" + "=" * 70)
print("2. 成员判断 (Membership Testing)")
print("=" * 70)
text = "Python is fun and powerful"
# 2.1 in (存在性判断)
is_present = "Python" in text
print(f"'Python' in text: {is_present}") # True
is_sub = "fun" in text
print(f"'fun' in text: {is_sub}") # True
# 2.2 not in (不存在性判断)
is_absent = "Java" not in text
print(f"'Java' not in text: {is_absent}") # True
# 区分大小写
case_check = "python" in text
print(f"'python' (小写) in text: {case_check}") # False (区分大小写)
print("\\n" + "=" * 70)
print("3. 长度计算 (Length Calculation)")
print("=" * 70)
# 3.1 英文字符串
eng_str = "Hello"
print(f"len('{eng_str}'): {len(eng_str)}") # 5
# 3.2 中文字符串 (重点:Unicode 字符数)
# Python 3 中 len() 统计的是 Unicode 码点数量,而非字节数
zh_str = "中文"
print(f"len('{zh_str}'): {len(zh_str)}") # 2 (不是 4 或 6)
# 3.3 混合字符串
mixed_str = "Hi 你好 🚀"
print(f"len('{mixed_str}'): {len(mixed_str)}")
# 解析: H(1) + i(1) + 空格 (1) + 你 (1) + 好 (1) + 空格 (1) + 🚀(1) = 7
# 注意:Emoji 表情在 UTF-8 编码下可能占 4 个字节,但 len() 仍计为 1 个字符
# 对比:字节长度 (encode 后)
byte_len = zh_str.encode('utf-8')
print(f"'{zh_str}' 的 UTF-8 字节长度: {len(byte_len)}") # 通常中文为 3 字节/字,2*3=6
print("\\n演示结束。")
四、字符串转义与原始字符串
1. 常用转义字符
| \\n | 换行 | print('hello\\nworld') |
| \\t | 制表符 | print('hello\\tworld') |
| \\' | 单引号 | print('\\'hello\\'') |
| \\" | 双引号 | print("\\"hello\\"") |
| \\\\ | 反斜杠 | print('C:\\\\Python') |
| \\r | 回车 | print('hello\\rworld') → world |
| \\u200b | 零宽空格 | 肉眼不可见,易导致匹配异常 |
2. 原始字符串
-
语法:前缀r/R,关闭转义功能
-
场景:文件路径、正则表达式编写
-
示例:r'C:\\Python\\test'(\\t不被解析为制表符)
3.示例代码
# -*- coding: utf-8 -*-
"""
Python 字符串转义与原始字符串示例代码
涵盖:常用转义字符、特殊控制符、原始字符串 (Raw String) 的应用场景
"""
print("=" * 70)
print("1. 常用转义字符 (Escape Characters)")
print("=" * 70)
# 1.1 换行 (\\n) 与 制表符 (\\t)
text_basic = "Line1\\nLine2\\t(Tabbed)"
print("[\\\\n & \\\\t] 输出效果:")
print(text_basic)
# 实际存储: 'Line1\\nLine2\\t(Tabbed)'
# 1.2 引号转义 (\\' 与 \\")
# 场景:在单引号字符串中包含单引号,或双引号中包含双引号
quote_single = 'It\\'s a sunny day'
quote_double = "He said \\"Hello\\""
print(f"\\n[\\\\'] 单引号转义: {quote_single}")
print(f"[\\\\\\"] 双引号转义: {quote_double}")
# 1.3 反斜杠本身 (\\\\)
# 场景:Windows 文件路径 (传统写法)
path_legacy = "C:\\\\Users\\\\Admin\\\\Documents"
print(f"\\n[\\\\\\\\] 反斜杠转义: {path_legacy}")
# 注意:打印出来是 C:\\Users\\Admin\\Documents,但内存中存的是两个字符 '\\'
# 1.4 回车符 (\\r) – 覆盖效果
# \\r 将光标移回行首,后续字符会覆盖前面的字符
# 'hello\\rworld' -> 先打印 hello, 光标回开头, 打印 world (覆盖 hell) -> 显示 world
cr_text = "hello\\rworld"
print(f"\\n[\\\\r] 回车覆盖测试: '{cr_text}'")
# 终端显示应为: world
# 解释: h e l l o -> 光标回退 -> w o r l d (覆盖了 h e l l, o 被 w 覆盖? 不,w 覆盖 h, o 覆盖 e, r 覆盖 l, l 覆盖 l, d 覆盖 o)
# 详细推导:
# 初始: h e l l o
# \\r: 光标回到 h 前面
# 写入 w: w e l l o
# 写入 o: w o l l o
# 写入 r: w o r l o
# 写入 l: w o r l l
# 写入 d: w o r l d
# 最终显示: world
# 1.5 零宽空格 (\\u200b) – 隐形坑
# 肉眼不可见,但会影响字符串长度和匹配
zero_width = "Hello\\u200bWorld"
print(f"\\n[\\\\u200b] 零宽空格测试:")
print(f"显示内容: '{zero_width}'") # 看起来像 HelloWorld
print(f"实际长度: {len(zero_width)}") # 11 (Hello=5 + World=5 + 零宽=1)
print(f"包含零宽空格: {'\\u200b' in zero_width}") # True
print("\\n" + "=" * 70)
print("2. 原始字符串 (Raw Strings)")
print("=" * 70)
# 语法: 前缀 r 或 R
# 作用: 关闭转义机制,反斜杠被视为普通字符
# 2.1 路径处理 (最典型场景)
path_raw = r"C:\\Users\\NewFolder\\test.txt"
print(f"[r''] Windows 路径: {path_raw}")
# 对比普通字符串: "C:\\Users\\NewFolder\\test.txt"
# 其中的 \\n 会被解析为换行,\\t 被解析为制表符,导致路径错误
# 验证 \\t 是否被转义
raw_tab = r"Tab:\\tHere"
norm_tab = "Tab:\\tHere"
print(f"\\n原始字符串 (\\\\t 不转义): '{raw_tab}'") # 输出: Tab:\\tHere
print(f"普通字符串 (\\\\t 转义): '{norm_tab}'") # 输出: Tab: Here (带制表符)
# 2.2 正则表达式 (Regex)
# 正则中大量使用 \\d, \\w, \\s, \\n 等,使用 raw string 可避免双重转义
import re
text = "My phone is 123-4567"
# ❌ 普通写法: 需要写四个反斜杠才能匹配一个数字 (\\d -> \\\\d -> "\\\\\\\\d")
# pattern_bad = "\\\\d+-\\\\d+"
# ✅ 推荐写法: r"\\d+-\\d+" (只需两个反斜杠,直观易读)
pattern_raw = r"\\d+-\\d+"
match = re.search(pattern_raw, text)
print(f"\\n[Regex] 匹配结果: {match.group() if match else 'No Match'}")
# 2.3 原始字符串的限制
# 注意:原始字符串不能以奇数个反斜杠结尾,因为会转义后面的引号
try:
# invalid_path = r"C:\\Path\\" # SyntaxError: EOL while scanning string literal
pass
except SyntaxError:
print("\\n[警告] 原始字符串不能以单个反斜杠结尾 (会转义结束引号)")
# 解决方法:切片掉最后一个字符,或者拼接一个普通空串
valid_path = r"C:\\Path" + "\\\\"
print(f"[修正] 正确结尾方式: {valid_path}")
print("\\n" + "=" * 70)
print("3. 综合对比总结")
print("=" * 70)
s_normal = "Line1\\nLine2"
s_raw = r"Line1\\nLine2"
print(f"普通字符串: {repr(s_normal)} -> 打印:\\n{s_normal}")
print(f"原始字符串: {repr(s_raw)} -> 打印:\\n{s_raw}")
print("\\n结论: 涉及路径、正则、包含大量反斜杠的文本时,优先使用 r'' 原始字符串。")
print("\\n演示结束。")
五、字符串格式化(3 种方式)
| % 格式化(旧式) | 'Hello %s, %d岁' % ('张三', 20) | 兼容旧代码,需匹配格式符类型 |
| str.format ()(新式) | 'Hello {}, {}岁'.format('张三', 20) | 支持位置 / 关键字参数,更灵活 |
| f-string(推荐) | f'Hello {name}, {age}岁' | Python3.6+,简洁高效,支持表达式 |
| f-string 增强(3.8+) | f'{x=}' → x=10 | 直接显示变量名 + 值,调试实用 |
| Template(安全) | Template('姓名:$name').substitute(name='张三') | 避免注入风险,适合动态替换用户输入 |
# -*- coding: utf-8 -*-
"""
Python 字符串格式化示例代码
涵盖:% 格式化、str.format()、f-string (含调试模式)、string.Template
"""
from string import Template
print("=" * 70)
print("1. % 格式化 (旧式,Legacy)")
print("=" * 70)
# 特点:类似 C 语言 printf,需严格匹配类型,兼容旧代码
name = "张三"
age = 20
height = 1.756
# %s: 字符串, %d: 整数, %f: 浮点数
msg_percent = "姓名:%s, 年龄:%d岁, 身高:%.2f米" % (name, age, height)
print(f"[%%] 基础用法: {msg_percent}")
# 字典映射方式 (当参数较多时较清晰)
data = {"name": "李四", "age": 25}
msg_dict = "姓名:%(name)s, 年龄:%(age)d岁" % data
print(f"[%%] 字典映射: {msg_dict}")
# 注意:类型不匹配会报错
# "%d" % "20" # TypeError
print("\\n" + "=" * 70)
print("2. str.format() (新式,灵活)")
print("=" * 70)
# 特点:支持位置参数、关键字参数、属性访问、复杂格式控制
# 2.1 位置参数
msg_pos = "Hello {}, {}岁".format("王五", 30)
print(f"[.format] 位置参数: {msg_pos}")
# 2.2 关键字参数 (推荐,可读性高)
msg_kw = "姓名:{name}, 年龄:{age}".format(name="赵六", age=28)
print(f"[.format] 关键字: {msg_kw}")
# 2.3 重复使用参数
msg_repeat = "{0} 喜欢 {1}, {0} 经常玩 {1}".format("小明", "Python")
print(f"[.format] 重复引用: {msg_repeat}")
# 2.4 格式控制 (对齐、精度、进制)
num = 123.45678
pi = 3.1415926
bin_num = 10
# :.2f -> 保留 2 位小数
# :<10 -> 左对齐,总宽 10
# :^10 -> 居中对齐
# :b -> 二进制
msg_fmt = "数字:{:.2f}, 左对齐:[{: <10}], 二进制:{:b}".format(num, num, bin_num)
print(f"[.format] 格式控制: {msg_fmt}")
# 2.5 访问对象属性/索引
class Person:
def __init__(self, name):
self.name = name
p = Person("孙七")
msg_obj = "对象属性:{p.name}".format(p=p)
lst = ["A", "B", "C"]
msg_idx = "列表索引:{l[0]}".format(l=lst)
print(f"[.format] 属性/索引: {msg_obj}, {msg_idx}")
print("\\n" + "=" * 70)
print("3. f-string (Python 3.6+, 强烈推荐)")
print("=" * 70)
# 特点:语法最简洁,性能最高,支持直接在花括号内写表达式
user = "周八"
score = 95.5
level = "A"
# 3.1 基础变量插入
msg_f = f"用户:{user}, 得分:{score}"
print(f"[f-str] 基础: {msg_f}")
# 3.2 支持表达式计算
msg_expr = f"明年分数预测:{score + 2}, 是否优秀:{score >= 90}"
print(f"[f-str] 表达式: {msg_expr}")
# 3.3 格式控制 (同 format 语法,但更紧凑)
msg_align = f"姓名:{user:<10}, 分数:{score:.1f}"
print(f"[f-str] 格式控制: '{msg_align}'")
# 3.4 调用函数
msg_func = f"大写名字:{user.upper()}"
print(f"[f-str] 函数调用: {msg_func}")
# 3.5 调试模式 (Python 3.8+)
# 语法:{variable=} 自动输出 "variable=value"
x = 100
y = 200
msg_debug = f"调试信息:{x=}, {y=}, {x+y=}"
print(f"[f-str] 调试模式 (3.8+): {msg_debug}")
# 输出示例:调试信息:x=100, y=200, x+y=300
print("\\n" + "=" * 70)
print("4. string.Template (安全替换)")
print("=" * 70)
# 特点:语法简单 ($var),不进行 Python 代码执行,适合用户输入模板,防注入
template_str = "欢迎 ${name} 加入 ${group} 群!"
tmpl = Template(template_str)
# 安全替换
safe_msg = tmpl.substitute(name="吴九", group="Python 学习")
print(f"[Template] 安全替换: {safe_msg}")
# 处理包含 $ 符号的文本 (转义 $$)
price_template = Template("价格:$$${price} 元") # $$ 转义为单个 $
price_msg = price_template.substitute(price="99")
print(f"[Template] 转义$: {price_msg}")
# 为什么安全?
# 如果用户输入 name = "__import__('os').system('rm -rf /')"
# Template 只会将其作为纯文本替换,不会执行代码
# 而 f-string 或 eval 可能会有风险(如果直接拼接用户代码)
dangerous_input = "${__import__('os').system('echo hacked')}"
# 即使放入 Template,它也只会被当作普通字符串处理,不会执行
tmpl_safe = Template("输入内容:$input")
# 注意:substitute 需要关键字参数名匹配,这里演示如果用字典
result_safe = tmpl_safe.safe_substitute(input=dangerous_input)
print(f"[Template] 防注入演示: {result_safe}")
# 输出:输入内容:${__import__('os').system('echo hacked')} (原样输出,未执行)
print("\\n" + "=" * 70)
print("5. 性能与场景总结")
print("=" * 70)
print("""
场景推荐:
1. 日常开发、日志记录、API 响应构建 -> 首选 f-string (最快、最易读)
2. 需要兼容 Python 3.5 及以下 -> 使用 str.format()
3. 维护古老代码库 -> 可能会遇到 % 格式化
4. 处理用户提供的模板字符串 (如邮件模板、配置文件) -> 使用 string.Template (安全)
""")
print("\\n演示结束。")
六、字符串内置方法(分类汇总)
1. 大小写处理
| upper() | 转大写 | 'hello'.upper() → 'HELLO' |
| lower() | 转小写 | 'HELLO'.lower() → 'hello' |
| capitalize() | 首字母大写,其余小写 | 'hello python' → 'Hello python' |
| title() | 每个单词首字母大写 | 'hello python' → 'Hello Python' |
| swapcase() | 大小写互换 | 'Hello' → 'hELLO' |
| casefold() | 增强版小写(支持多语言,如德语 ß) | 'Straße'.casefold() → 'strasse' |
2. 查找与替换
| find(sub) | 查找子串位置,找不到返回 – 1 | 'hello'.find('l') → 2 |
| rfind(sub) | 从右侧查找子串 | 'hello'.rfind('l') → 3 |
| index(sub) | 同 find,但找不到抛异常 | 'hello'.index('x') → 报错 |
| rindex(sub) | 从右侧查找,找不到抛异常 | – |
| replace(old, new, count) | 替换子串(count 指定次数) | 'hello'.replace('l', 'x', 1) → 'hexlo' |
| count(sub) | 统计子串出现次数 | 'hello ll'.count('l') → 3 |
3. 空白 / 字符去除
| strip([chars]) | 去除首尾空白 / 指定字符 | '###hello###'.strip('#') → 'hello' |
| lstrip([chars]) | 去除左侧空白 / 指定字符 | ' hello '.lstrip() → 'hello ' |
| rstrip([chars]) | 去除右侧空白 / 指定字符 | ' hello '.rstrip() → ' hello' |
| removeprefix(prefix) | 去除前缀(3.10+,语义化) | 'HelloPython'.removeprefix('Hello') → 'Python' |
| removesuffix(suffix) | 去除后缀(3.10+,语义化) | 'HelloPython'.removesuffix('Python') → 'Hello' |
4. 分割与拼接
| split(sep, maxsplit) | 按分隔符分割(maxsplit 指定次数) | 'a,b,c'.split(',') → ['a','b','c'] |
| rsplit(sep) | 从右侧分割 | 'a,b,c'.rsplit(',', 1) → ['a,b','c'] |
| splitlines([keepends]) | 按换行符分割,keepends 保留换行符 | 'a\\nb\\nc'.splitlines() → ['a','b','c'] |
| join(iterable) | 拼接可迭代对象为字符串(高效) | ','.join(['a','b','c']) → 'a,b,c' |
| partition(sep) | 分割为 (前,分隔符,后) 三元组 | 'a,b,c'.partition(',') → ('a', ',', 'b,c') |
| rpartition(sep) | 从右侧分割为三元组 | 'a,b,c'.rpartition(',') → ('a,b', ',', 'c') |
5. 判断类方法(返回布尔值)
| isalpha() | 是否全为字母 / 中文 | 'Python编程' → True |
| isdigit() | 是否全为数字(含①、² 等数字字符) | '123'→True,'①'→True |
| isdecimal() | 是否全为十进制数字(最严格) | '123'→True,'①'→False |
| isnumeric() | 是否全为数字字符(含汉字 / 罗马数字) | '壹'→True,'Ⅲ'→True |
| isalnum() | 是否全为字母 / 数字 | 'Python123'→True |
| isspace() | 是否全为空白字符(空格 / 制表符 / 换行) | ' \\t\\n'→True |
| islower()/isupper() | 是否全小写 / 全大写 | 需包含至少一个区分大小写字符 |
| istitle() | 是否为标题格式(每个单词首字母大写) | 'Hello Python'→True |
| startswith(prefix) | 是否以指定前缀开头 | 'Python'.startswith('Py')→True |
| endswith(suffix) | 是否以指定后缀结尾 | 'Python'.endswith('on')→True |
| isidentifier() | 是否为合法 Python 标识符(变量名) | 'python_123'→True,'123python'→False |
6. 格式化与对齐
| center(width, fillchar) | 居中对齐,填充指定字符 | 'hello'.center(10, '*') → '**hello***' |
| ljust(width, fillchar) | 左对齐 | 'hello'.ljust(10, '*') → 'hello*****' |
| rjust(width, fillchar) | 右对齐 | 'hello'.rjust(10, '*') → '*****hello' |
| zfill(width) | 补零(数字字符串,右对齐) | '123'.zfill(5) → '00123' |
| expandtabs(tabsize) | 替换 \\t 为指定空格数(默认 8) | 'a\\tb'.expandtabs(4) → 'a b' |
7. 编码与解码
| encode(encoding='utf-8', errors='strict') | 字符串→字节串 | '中文'.encode('utf-8') → b'\\xe4\\xb8\\xad\\xe6\\x96\\x87' |
| decode(encoding='utf-8', errors='strict') | 字节串→字符串(需匹配编码) | b'\\xe4\\xb8\\xad'.decode('utf-8') → '中' |
-
核心规则:Python3 字符串默认 Unicode 编码,编码 / 解码优先用utf-8
-
常见错误:UnicodeDecodeError(编码格式不匹配)
- 解决方案:
b = b'Python \\xe7\\xbc\\x96\\xe7\\xa8\\x8b\\x80'
b.decode('utf-8', errors='ignore') # 忽略错误字符
b.decode('utf-8', errors='replace') # 错误字符替换为� -
BOM 处理:utf-8-sig编码自动添加 / 去除字节顺序标记
8. 其他实用方法
| maketrans()+translate() | 批量字符映射替换 | 见下方示例 |
| max()/min() | 返回字符串中最大 / 最小字母 | max('Python') → 'y' |
| ascii() | 仅输出 ASCII 字符,非 ASCII 转义 | ascii('Python编程') → 'Python \\u7f16\\u7a0b' |
| ord()/chr() | 字符↔Unicode 编码互转 | ord('中')→20013,chr(20013)→'中' |
# maketrans+translate批量替换示例
trans_table = str.maketrans({'a':'1', 'b':'2', 'c':'3'}, 'd') # a→1,b→2,c→3,删除d
s = 'abcde123'
print(s.translate(trans_table)) # 输出:123e123
七、字符串与其他数据类型交互
1. 字符串↔数字
| 数字→字符串 | str()/f-string | str(123) → '123' |
| 字符串→数字 | int()/float() | int('123') → 123(需处理 ValueError) |
2. 字符串↔列表 / 元组
| 字符串→列表 | list() | list('hello') → ['h','e','l','l','o'] |
| 列表→字符串 | ''.join() | ''.join(['h','e','l','l','o']) → 'hello' |
| 字符串→元组 | tuple() | tuple('hello') → ('h','e','l','l','o') |
3. 字符串↔字节串(bytes)
| 字符串→字节串 | encode() | '中文'.encode('utf-8') |
| 字节串→字符串 | decode() | b'\\xe4\\xb8\\xad'.decode('utf-8') |
!!!核心常见面试题
1. 问题:Python 字符串为什么是不可变的?不可变性带来了哪些好处?
陷阱分析
新手常误以为 “不可变” 是 “不能操作”(比如拼接、切片),实则混淆 “对象本身修改” 和 “创建新对象”;或只说表面现象,答不出底层原理。
正确解答
-
底层本质:Python 字符串底层由PyUnicodeObject结构体实现,其核心是存储字符的固定长度数组(如wchar_t数组),数组的内存地址和长度在创建后不可修改。任何看似 “修改” 字符串的操作(如s.replace()、s + "a"),本质都是创建新的字符串对象,原对象不变。
-
核心好处:
-
哈希安全:不可变保证字符串的哈希值固定,可作为字典的 key、集合的元素;
-
线程安全:多线程操作时无需加锁,避免数据竞争;
-
内存优化:可通过 “字符串驻留机制” 缓存重复的小字符串,减少内存占用。
2. 问题:is 和 == 判断字符串有什么区别?举例说明坑点。
陷阱分析
新手常混用is和==,误以为 “内容相等的字符串,内存地址一定相等”,忽略字符串驻留机制的规则限制。
正确解答
-
==:判断内容是否相等,会逐字符比较字符串的值,是判断字符串相等的正确方式;
-
is:判断内存地址是否相等(是否为同一个对象),依赖 Python 的 “字符串驻留机制”,仅对符合规则的字符串生效(如:长度 < 20、仅含字母 / 数字 / 下划线的小字符串)。
-
s1 = "hello"
s2 = "hello"
print(s1 == s2) # True(内容相等)
print(s1 is s2) # True(驻留机制,同一对象)s3 = "hello world!" # 含空格,不满足驻留规则
s4 = "hello world!"
print(s3 == s4) # True
print(s3 is s4) # False(不同对象)s5 = str(123)
s6 = "123"
print(s5 == s6) # True
print(s5 is s6) # False(动态生成的字符串不驻留) -
结论:永远用==判断字符串内容相等,仅在确认需要比较内存地址时用is。
3. 问题:字符串切片 s[a:b:c] 的底层逻辑是什么?修改切片结果会影响原字符串吗?
陷阱分析
新手误以为切片会复制原字符串的所有数据,或认为修改切片结果能改变原字符串。
正确解答
-
底层逻辑:切片不直接复制原字符串的字符数据,而是创建一个新的字符串对象,该对象仅记录 “原字符串的引用 + 起始索引 + 结束索引 + 步长”。只有当对切片结果进行修改操作(如拼接、替换)时,才会触发 “写时复制(COW)” 机制,真正复制数据生成新对象。
-
核心结论:切片结果是新对象,但底层复用原字符串数据(只读);由于字符串不可变,修改切片结果(如new_s = s[:2] + "a")不会影响原字符串。示例:
s = "abcdef"
s_slice = s[1:4] # 'bcd',仅记录原字符串引用+索引,未复制数据
s_slice_new = s_slice + "x" # 触发写时复制,生成新字符串 'bcdx'
print(s) # 原字符串仍为 'abcdef',无变化
4. 问题:循环中拼接大量字符串,s += str(i) 和 ''.join(列表) 哪个效率高?为什么?
陷阱分析
新手习惯用+=拼接字符串,忽略其频繁创建新对象的性能损耗,尤其数据量大时差距极明显。
正确解答
-
s += str(i):效率极低。因为字符串不可变,每次+=都会创建一个新的字符串对象,复制原字符串 + 新字符的所有数据。循环 N 次的时间复杂度为O(N2)(每次复制的字符数递增)。
-
''.join(列表):效率最优。先将所有待拼接的字符串存入列表(列表是可变类型,追加元素无性能损耗),最后调用join方法一次性拼接。join会先计算所有字符串的总长度,分配一次内存,再批量复制数据,时间复杂度为O(N)。
-
对比示例:
import time
# 低效:+= 拼接
start = time.time()
s = ""
for i in range(100000):
s += str(i)
print(f"+= 耗时:{time.time() – start:.2f}秒") # 约0.08秒(数据量越大差距越明显)# 高效:join 拼接
start = time.time()
lst = []
for i in range(100000):
lst.append(str(i))
s = ''.join(lst)
print(f"join 耗时:{time.time() – start:.2f}秒") # 约0.01秒 -
结论:拼接大量字符串必用''.join(列表),禁用循环+=。
5. 问题:处理超大字符串(如 10GB 日志文件)时,如何避免内存溢出?
陷阱分析
新手习惯一次性读取整个文件到字符串(s = open("file.log").read()),导致内存占满崩溃。
正确解答
核心思路:分块读取 + 跨块兼容处理,不加载整个字符串到内存:
# 正确做法:分块读取,每次处理4KB数据
with open("large_file.log", "r", encoding="utf-8") as f:
buffer = "" # 缓存跨块的不完整内容(如一行的后半部分)
while True:
chunk = f.read(4096) # 每次读取4KB
if not chunk:
break # 读取完毕
# 拼接缓存+当前块,按行处理
content = buffer + chunk
lines = content.split("\\n")
# 最后一行可能不完整,存入缓存
buffer = lines.pop()
# 处理完整的行
for line in lines:
process_line(line) # 自定义处理逻辑
# 处理最后一行缓存
if buffer:
process_line(buffer)
-
核心考点:理解 “字符串分块处理” 的思想,避免一次性加载大字符串到内存。
6. 问题:为什么 'C:\\new.txt' 中的路径会出错?如何正确处理 Windows 路径?
陷阱分析
新手忽略反斜杠\\是转义符,\\n会被解析为换行符,导致路径错误。
正确解答
-
错误原因:Python 中\\是转义符,\\n→换行、\\t→制表符、\\r→回车等,直接写'C:\\new.txt'会被解析为'C:\\new.txt'(含换行符),路径无效。
-
正确用法(3 种):
# 方法1:原始字符串(推荐),前缀r禁用转义
path = r'C:\\new.txt'
# 方法2:双反斜杠转义
path = 'C:\\\\new.txt'
# 方法3:使用os.path或pathlib(跨平台最优)
import os
path = os.path.join("C:", "new.txt")
7. 问题:'年龄:' + 20 会报错,如何正确拼接字符串和数字?
陷阱分析
新手忽略 Python 无 “隐式类型转换”,字符串和数字直接拼接会触发TypeError。
正确解答
-
错误原因:Python 不支持不同类型的隐式拼接,str + int 会报错 TypeError: can only concatenate str (not "int") to str。
-
正确用法(4 种,推荐 f-string):
# 方法1:f-string(Python3.6+,简洁高效)
res = f'年龄:{20}'
# 方法2:str() 转换
res = '年龄:' + str(20)
# 方法3:format() 格式化
res = '年龄:{}'.format(20)
# 方法4:% 格式化(旧写法,不推荐)
res = '年龄:%d' % 20
8. 问题:正则匹配 <div>aaa</div><div>bbb</div> 时,r'<div>(.*)</div>' 为什么只匹配到最后一个</div>?如何修复?
陷阱分析
新手忽略正则默认是 “贪婪匹配”,会尽可能匹配更多内容。
正确解答
-
错误原因:.* 是贪婪匹配,会从第一个<div>开始,一直匹配到最后一个</div>,导致结果包含所有中间内容。
-
修复方法:使用.*? 非贪婪匹配,匹配到第一个</div>就停止:
import re
s = '<div>aaa</div><div>bbb</div>'
# 贪婪匹配(错误)
res1 = re.search(r'<div>(.*)</div>', s)
print(res1.group(1)) # aaa</div><div>bbb# 非贪婪匹配(正确)
res2 = re.search(r'<div>(.*?)</div>', s)
print(res2.group(1)) # aaa# 匹配所有div内容(findall)
res3 = re.findall(r'<div>(.*?)</div>', s)
print(res3) # ['aaa', 'bbb']
9. 问题:统计字符串中每个字符的出现次数,要求按次数降序排列。
陷阱分析
新手容易遗漏 “空字符串”“大小写敏感”“特殊字符” 等边界情况,或排序逻辑错误。
正确解答
def count_char(s):
# 处理空字符串
if not s:
return {}
# 1. 统计字符次数(字典)
char_count = {}
for char in s:
char_count[char] = char_count.get(char, 0) + 1
# 2. 按次数降序、字符升序排序
sorted_items = sorted(char_count.items(), key=lambda x: (-x[1], x[0]))
# 3. 转换为字典(Python3.7+字典有序)
return dict(sorted_items)
# 测试
print(count_char("abracadabra")) # {'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}
print(count_char("")) # {}
10. 问题:判断一个字符串是否是回文字符串(忽略大小写、空格、标点)。
陷阱分析
新手常只判断原字符串是否反转相等,忽略 “预处理(去空格 / 标点、转小写)” 的步骤。
正确解答
import re
def is_palindrome(s):
# 预处理:去除所有非字母数字字符,转小写
s_clean = re.sub(r'[^a-zA-Z0-9]', '', s).lower()
# 切片反转对比(最优方式)
return s_clean == s_clean[::-1]
# 测试
print(is_palindrome("A man, a plan, a canal: Panama")) # True
print(is_palindrome("race a car")) # False
print(is_palindrome("12321")) # True
11. 问题:去除字符串中所有空格(包括首尾、中间、制表符、换行符),有哪些方法?
正确解答
s = " hello \\t world \\n "
# 方法1:replace(仅去除普通空格)
res1 = s.replace(" ", "") # 'hello\\tworld\\n'
# 方法2:正则(去除所有空白字符:空格、制表符、换行符等,推荐)
import re
res2 = re.sub(r'\\s', '', s) # 'helloworld'
# 方法3:split + join(仅去除普通空格)
res3 = ''.join(s.split()) # 'helloworld'
print(res2) # 最终推荐:helloworld
12. 问题:字符串反转有哪些方法?哪个效率最高?
正确解答
-
方法 1:切片[::-1](最优,底层 C 实现,效率最高)
s = "abcdef"
res = s[::-1] # 'fedcba' -
方法 2:reversed() + join(效率次之,适合迭代场景)
res = ''.join(reversed(s)) # 'fedcba'
-
方法 3:列表reverse() + join(效率最低,需转换类型)
lst = list(s)
lst.reverse()
res = ''.join(lst) # 'fedcba' -
结论:优先用切片[::-1]反转字符串。
13. 问题:UnicodeDecodeError 是什么原因导致的?如何解决?
陷阱分析
新手常混淆 “Unicode” 和 “UTF-8”,或读取文件时不指定编码,导致解码失败。
正确解答
-
核心概念:
-
Unicode:字符集(给每个字符分配唯一的数字编号,如'中'→U+4E2D);
-
UTF-8:编码规则(将 Unicode 编号转换为字节序列,如'中'→b'\\xe4\\xb8\\xad')。
-
-
错误原因:解码时使用的编码格式,与字符串的实际编码格式不匹配(如用gbk解码 UTF-8 编码的字节)。
-
常见场景 & 解决方案:
# 场景1:读取文件时编码不匹配
# 错误:open默认用系统编码(如Windows的gbk),读取UTF-8文件会报错
# f = open("file.txt", "r") # UnicodeDecodeError
# 正确:指定编码
f = open("file.txt", "r", encoding="utf-8")# 场景2:字节解码时编码错误
b = b'\\xe4\\xb8\\xad' # UTF-8编码的'中'
# 错误:用gbk解码
# s = b.decode("gbk") # UnicodeDecodeError
# 正确:用UTF-8解码
s = b.decode("utf-8") # '中'# 场景3:容错处理(无法确定编码时)
s = b.decode("utf-8", errors="ignore") # 忽略错误字符
# 或
s = b.decode("utf-8", errors="replace") # 用�替换错误字符
14. 问题:encode() 和 decode() 的区别?为什么 Python3 中字符串默认是 Unicode?
正确解答
-
encode():字符串(Unicode)→ 字节串(bytes),如s.encode("utf-8"),用于 “存储 / 传输” 字符串;
-
decode():字节串(bytes)→ 字符串(Unicode),如b.decode("utf-8"),用于 “读取 / 解析” 字节数据;
-
Python3 设计逻辑:字符串(str)默认以 Unicode 存储,脱离具体编码,仅在 IO 操作(读写文件、网络传输)时才需要编码为 bytes,避免了 Python2 的编码混乱问题。
15. 问题:Python 的字符串驻留机制(String Interning)是什么?如何强制驻留?
正确解答
-
核心原理:Python 通过intern_table哈希表缓存符合规则的小字符串,避免重复创建相同内容的字符串对象,节省内存。
-
缓存规则(默认):
-
字符串长度≤20;
-
仅包含字母、数字、下划线(a-z, A-Z, 0-9, _);
-
非动态生成(如str(123)、s1 + s2生成的字符串不缓存)。
-
强制驻留:使用sys.intern()突破规则限制,强制将字符串加入驻留表:
import sys
s1 = "hello world!" # 含空格,不满足默认规则
s2 = "hello world!"
print(s1 is s2) # False# 强制驻留
s1_intern = sys.intern(s1)
s2_intern = sys.intern(s2)
print(s1_intern is s2_intern) # True




