Python: 字符串进阶

第06课我们学了字符串的基础操作,这一课来深入挖掘。字符串的不可变性到底意味着什么?除了基础方法还有哪些好用但不为人知的操作?中文字符在计算机里是怎么存的?学完这课,你对字符串的理解会上一个台阶。


1. 字符串的不可变性

第06课 FAQ 里提过——字符串一旦创建就不能修改。这里用具体例子来看"不可变"到底是什么意思。

PYTHON
text = "Hello"

# ❌ 不能这样修改——这行代码会报错
# text[0] = "h"    # TypeError: 'str' object does not support item assignment

那如果我想把首字母小写怎么办?答案是创建一个新字符串

PYTHON
text = "Hello"
new_text = "h" + text[1:]   # 创建新字符串
print(new_text)              # hello
print(text)                  # Hello——原字符串没变

(1) 不可变性的好处

PYTHON
# 安全的共享引用
a = "Python"
b = a       # b 和 a 指向同一个字符串

# 不管怎么操作 a,b 都不会受影响
a = a.upper()
print(a)    # PYTHON
print(b)    # Python——b 还是原来的值
💡 提示:不可变性带来的好处是线程安全哈希能力。字符串可以作为字典的键(后面会学),列表就不行——因为列表可变,变了以后字典就找不到它了。

(2) 不可变性的代价

每次"修改"字符串都会创建新对象,如果在一个循环里反复拼接大量字符串,性能会很差:

PYTHON
# ❌ 不推荐:循环中拼接大量字符串
result = ""
for i in range(10000):
    result += str(i) + ","
# 这样会创建 10000 个临时字符串对象

# ✅ 推荐:用列表收集 + join() 合并
parts = []
for i in range(10000):
    parts.append(str(i))
result = ",".join(parts)
⚠️ 注意:日常写代码中,拼接几十个字符串完全不用担心性能问题。你要警惕的是在循环里拼接成千上万条字符串。几百次以内的操作,用 += 完全没问题。

▶ 示例:模拟字符串方法的工作原理(难度⭐⭐)

PYTHON
# 自己实现一个"首字母大写"(模拟 capitalize 方法)
def my_capitalize(text):
    if not text:
        return text
    first = text[0].upper()
    rest = text[1:].lower()
    return first + rest

print(my_capitalize("hello WORLD"))      # Hello world
print(my_capitalize("python"))           # Python

# 自己实现一个"替换"(模拟 replace 方法)
def my_replace(text, old, new):
    parts = text.split(old)     # 用 old 分割,得到中间的部分
    return new.join(parts)      # 用 new 重新组合

print(my_replace("one, two, one", "one", "1"))    # 1, two, 1
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功


2. 字符串的查询方法

除了第06课学的 find()count(),还有几个好用的查询方法。

(1) startswith() 和 endswith()

PYTHON
# 检查字符串以什么开头/结尾

url = "https://www.example.com"

print(url.startswith("https"))    # True——是 HTTPS 协议
print(url.endswith(".com"))       # True——是 .com 域名
print(url.endswith(".org"))       # False

# 可以同时检查多个
print(url.endswith((".com", ".org", ".cn")))   # True——匹配了 .com

(2) 实际应用

PYTHON
# 文件扩展名检查
filename = "report.pdf"

if filename.endswith((".pdf", ".doc", ".docx")):
    print("文档文件")
elif filename.endswith((".jpg", ".png", ".gif")):
    print("图片文件")
elif filename.endswith((".py", ".js", ".html")):
    print("代码文件")
else:
    print("其他文件")

# URL 协议检查
link = "ftp://files.example.com"
if link.startswith(("http://", "https://")):
    print("网页链接")
elif link.startswith("ftp://"):
    print("FTP 链接")

运行结果:

TEXT 📖 仅展示
文档文件
FTP 链接

(3) removeprefix() 和 removesuffix()(Python 3.9+)

这两个是 Python 3.9 新增的方法,用来去掉前缀或后缀:

PYTHON
url = "https://www.example.com"

# 去掉协议头
domain = url.removeprefix("https://")
print(domain)               # www.example.com

# 去掉域名后缀
site_name = url.removesuffix(".com")
print(site_name)             # https://www.example

# 如果字符串不以指定内容开头/结尾,返回原字符串
print(url.removeprefix("ftp://"))   # https://www.example.com(没变)
💡 提示:在 Python 3.9 之前,去掉前缀需要写 url[len("https://"):] 或者 url.split("://", 1)[1]removeprefix()removesuffix() 让代码更直观。



3. split() 的高级用法

第06课学过基础的分割,这里学几个更强大的用法。

(1) 指定分割次数

PYTHON
# 限制分割次数——maxsplit 参数
text = "one,two,three,four,five"

print(text.split(",", 1))       # ['one', 'two,three,four,five']
print(text.split(",", 2))       # ['one', 'two', 'three,four,five']
print(text.split(",", 3))       # ['one', 'two', 'three', 'four,five']

(2) rsplit():从右往左分割

PYTHON
text = "one,two,three,four,five"

print(text.rsplit(",", 1))      # ['one,two,three,four', 'five']
print(text.rsplit(",", 2))      # ['one,two,three', 'four', 'five']

(3) splitlines():按行分割

PYTHON
multiline = """第一行
第二行
第三行"""

lines = multiline.splitlines()
print(lines)                    # ['第一行', '第二行', '第三行']

# 保留换行符
lines = multiline.splitlines(True)
print(lines)                    # ['第一行\n', '第二行\n', '第三行']

(4) partition() 和 rpartition()

partition() 不仅分割字符串,还保留分隔符的位置信息:

PYTHON
text = "user@example.com"

# 按 @ 分成三部分:(左边, 分隔符, 右边)
parts = text.partition("@")
print(parts)                    # ('user', '@', 'example.com')

# 如果没找到分隔符,返回 (原字符串, '', '')
print("hello".partition("@"))   # ('hello', '', '')

# rpartition 从右边开始找
path = "/home/user/documents/file.txt"
last_slash = path.rpartition("/")
print(last_slash)               # ('/home/user/documents', '/', 'file.txt')

partition()split() 更适合"提取"场景——它明确告诉你三部分各自是什么。

▶ 示例:解析 URL(难度⭐⭐)

PYTHON
# 用 partition 解析 URL
url = "https://www.example.com:8080/path/page.html?name=test&page=1"

# 拆协议
proto, _, rest = url.partition("://")
print(f"协议:{proto}")        # https

# 拆域名和路径
host_part, _, path_and_query = rest.partition("/")
print(f"主机:{host_part}")    # www.example.com:8080

# 拆端口
host, _, port = host_part.partition(":")
print(f"域名:{host}")         # www.example.com
print(f"端口:{port}")         # 8080

# 拆路径和查询参数
path, _, query = path_and_query.partition("?")
print(f"路径:/{path}")        # /path/page.html
print(f"查询:{query}")        # name=test&page=1
▶ 试一试

运行结果:

TEXT 📖 仅展示
协议:https
主机:www.example.com:8080
域名:www.example.com
端口:8080
路径:/path/page.html
查询:name=test&page=1


4. 字符串的对齐和填充

Python 提供了一组方法让字符串在指定宽度内左对齐、右对齐或居中对齐。

PYTHON
text = "Python"

print(text.ljust(10))           # 'Python    '(左对齐,右边补空格)
print(text.rjust(10))           # '    Python'(右对齐,左边补空格)
print(text.center(10))          # '  Python  '(居中,两边补空格)

# 可以指定填充字符
print(text.ljust(10, "-"))      # 'Python----'
print(text.rjust(10, "-"))      # '----Python'
print(text.center(10, "-"))     # '--Python--'

(1) zfill():用零填充

常用于数字编号:

PYTHON
print("42".zfill(5))            # 00042
print("-42".zfill(5))           # -0042——负号在前面
print("3.14".zfill(8))          # 00003.14

# 生成三位数编号
for i in range(1, 11):
    filename = f"photo_{str(i).zfill(3)}.jpg"
    print(filename, end="  ")

运行结果:

TEXT 📖 仅展示
photo_001.jpg  photo_002.jpg  photo_003.jpg  photo_004.jpg  photo_005.jpg
photo_006.jpg  photo_007.jpg  photo_008.jpg  photo_009.jpg  photo_010.jpg

▶ 示例:生成对齐报表(难度⭐⭐)

PYTHON
# 用 rjust 和 ljust 对齐表格
items = [
    ("苹果", 5.0, 3),
    ("香蕉", 3.5, 5),
    ("牛奶", 12.0, 2),
]

# 表头
header_name = "商品".ljust(8)
header_price = "单价".rjust(6)
header_qty = "数量".rjust(4)
header_total = "总价".rjust(6)
print(f"{header_name}{header_price}{header_qty}{header_total}")
print("-" * 28)

# 数据行
for name, price, qty in items:
    total = price * qty
    name_col = name.ljust(8)
    price_col = f"{price:.1f}".rjust(6)
    qty_col = str(qty).rjust(4)
    total_col = f"{total:.1f}".rjust(6)
    print(f"{name_col}{price_col}{qty_col}{total_col}")
▶ 试一试

运行结果:

TEXT 📖 仅展示
商品        单价  数量  总价
────────────────────────────
苹果        5.0    3  15.0
香蕉        3.5    5  17.5
牛奶       12.0    2  24.0


5. 字符编码:ord() 和 chr()

每个字符在计算机里都对应一个数字(码点)。ord() 获取字符的数字编码,chr() 反过来把数字转成字符。

PYTHON
# 查看字符的 Unicode 码点
print(ord("A"))          # 65
print(ord("中"))         # 20013
print(ord("\u2764"))          # 10084(❤ single codepoint)

# 根据码点获取字符
print(chr(65))           # A
print(chr(20013))        # 中
print(chr(10084))        # ❤

# 生成所有大写字母
for code in range(ord("A"), ord("Z") + 1):
    print(chr(code), end=" ")
# 输出:A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

(1) 什么是 Unicode

简单来说,Unicode 是一个全球统一的字符编码标准——它给世界上所有文字(中文、英文、阿拉伯文、日文……)的每个字符都分配了一个唯一的数字编号。Python 3 的字符串内部就是用 Unicode 存储的,所以它天生支持多语言。

PYTHON
# Python 3 的字符串天然支持 Unicode
text = "Hello,你好,مرحبا,こんにちは"

print(text)                     # 全部正常显示
print(len(text))                # 28——每个字符算一个长度

# 用 \u 转义符也可以表示字符
print("\u0048")                 # H
print("\u4e2d\u6587")          # 中文
⚠️ 注意:Python 2 中字符串分 str(字节)和 unicode(文字),处理中文经常出错。Python 3 统一了——所有字符串都是 Unicode,中英文一视同仁。这是 Python 3 做得最好的设计之一。

(2) 编码和解码

虽然 Python 内部用 Unicode,但文件存储和网络传输用的是字节(bytes)。所以需要编码(encode)和解码(decode):

PYTHON
text = "你好,世界"

# 编码:字符串 → 字节
utf8_bytes = text.encode("utf-8")
print(utf8_bytes)               # b'\xe4\xbd\xa0\xe5\xa5\xbd...'
print(len(utf8_bytes))          # 15(UTF-8 下每个中文字符占 3 字节)

gbk_bytes = text.encode("gbk")
print(len(gbk_bytes))           # 10(GBK 下每个中文字符占 2 字节)

# 解码:字节 → 字符串
decoded = utf8_bytes.decode("utf-8")
print(decoded)                  # 你好,世界

# 如果编码和解码方式不匹配,会报错
# gbk_bytes.decode("utf-8")    # UnicodeDecodeError!
💡 提示:日常开发中 99% 的场景用 UTF-8 就够了。UTF-8 是互联网的标准编码,兼容 ASCII,又能表示所有 Unicode 字符。如果你在读写文件时遇到乱码,十有八九是编码问题——打开文件时指定 encoding="utf-8" 基本能解决。



6. 字符串格式化补充

除了 f-string,Python 还有两种老的格式化方式。虽然 f-string 是现在的主流,但你可能会在旧代码中看到它们。

(1) format() 方法(Python 3.0+)

PYTHON
# 按位置
print("我叫{},今年{}岁。".format("Charlie", 18))
# 我叫Charlie,今年18岁。

# 按名称
print("我叫{name},今年{age}岁。".format(name="Diana", age=22))
# 我叫Diana,今年22岁。

# 格式化数字
print("圆周率:{:.3f}".format(3.14159))
# 圆周率:3.142

(2) % 格式化(Python 2 时代的老写法)

PYTHON
# 你可能会在旧代码中看到这种写法
print("我叫%s,今年%d岁。" % ("Alice", 25))
# 我叫Alice,今年25岁。

print("圆周率:%.2f" % 3.14159)
# 圆周率:3.14
💡 提示:f-string 是 Python 3.6 引入的,性能最好、可读性最强、写法最简洁。新代码请优先用 f-stringformat() 在需要"延迟格式化"(比如先定义模板后面再填值)时还有用。% 格式化看到认识就行,新代码不要用了。



7. 常见应用场景


❓ 常见问题

Q 为什么 Python 3 的字符串是 Unicode,而 Python 2 不是?
A Python 2 时代网络编码混乱(ASCII、Latin-1、GBK、Shift-JIS 同时存在),字符串处理是"字节 vs 文字"两套系统,非常容易出错。Python 3 做了一个彻底的改革:所有字符串都是 Unicode(文字),字节数据用 bytes 类型单独处理。这让字符串操作变得直观——len("中") 在任何系统上都是 1。代价是需要手动编码/解码才能读写文件或网络。
Q split()partition() 什么时候用哪个?
A 简单的分割用 split(),需要保留分隔符信息的用 partition()partition() 总是返回三个元素(左边、分隔符、右边),可以用元组解包直接赋值:left, sep, right = text.partition(":")。而 split() 返回列表,数量不确定。如果你确定分隔符只出现一次并且需要提取分隔符前后的内容,partition() 是最佳选择。
Q 对齐方法 ljust() 和 f-string 的 {value:10} 有什么区别?
A 效果是一样的——text.ljust(10) 等价于 f"{text:<10}"。f-string 的写法更灵活(可以同时控制对齐和数字格式),而方法调用更直观。个人偏好问题,选一种保持一致就好。f-string 的对齐符号:< 左对齐、> 右对齐、^ 居中对齐:f"{text:^10}"

📖 小节


📝 作业

  1. 基础题(难度⭐):给定文件名列表 files = ["report.pdf", "photo.jpg", "script.py", "notes.txt", "index.html"],用 endswith() 筛选出:

    • 所有图片文件(.jpg、.png、.gif)
    • 所有文档文件(.pdf、.doc、.docx、.txt)
    • 所有代码文件(.py、.js、.html、.css)
  2. 进阶题(难度⭐⭐):写一个"文件名批量格式化器"。给定一个列表 photos = ["IMG_1.jpg", "IMG_2.jpg", ..., "IMG_12.jpg"],将其重命名为 photo_001.jpgphoto_002.jpg、...、photo_012.jpg 格式。提示:用 partition() 分离文件名和扩展名,用 zfill() 补零。

  3. 挑战题(难度⭐⭐⭐):写一个"简易模板引擎"。给定一个模板字符串 template 和一个包含变量的字典 data,将模板中的 {变量名} 替换为对应的值。

    TEXT 📖 仅展示
    template = "您好,{name}!您的订单 {order_id} 已{status}。预计送达时间:{delivery_time}。"
    
    data = {
        "name": "Alice",
        "order_id": "20260623001",
        "status": "发货",
        "delivery_time": "3个工作日内"
    }
    
    # 你的代码:实现 replace_template(template, data) 函数
    # 输出:您好,Alice!您的订单 20260623001 已发货。预计送达时间:3个工作日内。
    

    额外要求:如果模板中的变量在 data 中不存在,保留 {变量名} 原样输出(不替换)。提示:遍历 data 字典的每一对键值,对 template 执行 replace()

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏