5. 使用字符串处理文本
在编程中,到处都离不开文本。从向用户显示消息,到处理数据文件,再到构建 Web 应用,处理文本是你作为程序员将要发展的最基础技能之一。在 Python 中,我们使用字符串 (string) 来处理文本——字符序列,可以表示单词、句子或任何类型的文本数据。
你已经在前几章中用过 print() 和 input() 时短暂接触过字符串。现在我们将深入探索字符串,学习如何创建它们、操作它们,以及使用 Python 强大的内置字符串功能来解决真实的文本处理问题。
在本章中,你将学习如何创建带特殊字符的字符串,把字符串连接在一起,从字符串中提取特定部分,改变大小写和格式,在字符串中搜索文本,并理解为什么在修改方面字符串与数字的行为不同。学完本章后,你就能为在 Python 中处理文本打下扎实基础。
5.1) 字符串字面量和转义序列
5.1.1) 创建字符串字面量
字符串字面量 (string literal) 是直接写在代码中的字符串值。你已经见过使用单引号 (') 或双引号 (") 创建的字符串:
# string_basics.py
greeting = 'Hello, World!'
message = "Python is great!"
print(greeting) # Output: Hello, World!
print(message) # Output: Python is great!在 Python 中,单引号和双引号的作用完全相同——你可以任意选择。不过,当字符串本身包含引号字符时,有两种选择就很方便:
# quotes_in_strings.py
# 当字符串包含单引号时使用双引号
sentence = "It's a beautiful day!"
print(sentence) # Output: It's a beautiful day!
# 当字符串包含双引号时使用单引号
quote = 'She said, "Hello!"'
print(quote) # Output: She said, "Hello!"如果你需要在字符串中包含与外层相同类型的引号,可以使用反斜杠 (\) 对它进行转义 (escape):
# escaping_quotes.py
# 在单引号字符串中转义单引号
sentence = 'It\'s a beautiful day!'
print(sentence) # Output: It's a beautiful day!
# 在双引号字符串中转义双引号
quote = "She said, \"Hello!\""
print(quote) # Output: She said, "Hello!"反斜杠告诉 Python,后面的引号是字符串内容的一部分,而不是字符串的结束标记。
5.1.2) 使用三引号的多行字符串
对于跨多行的字符串,Python 提供了三引号 (triple quotes)——可以是三个单引号 (''') 或三个双引号 ("""):
# multiline_strings.py
poem = """Roses are red,
Violets are blue,
Python is awesome,
And so are you!"""
print(poem)
# Output:
# Roses are red,
# Violets are blue,
# Python is awesome,
# And so are you!三引号字符串会完整保留你输入的所有换行和空格。它们对较长的文本块、文档字符串(我们将在第 19 章看到)或需要同时包含单引号和双引号而无需转义时尤其有用:
# triple_quotes_convenience.py
dialogue = '''The teacher said, "Don't forget: it's important to practice!"'''
print(dialogue) # Output: The teacher said, "Don't forget: it's important to practice!"5.1.3) 常见转义序列
除了转义引号以外,反斜杠还能引入转义序列 (escape sequence)——一些特殊的两个字符组合,用来表示那些难以或无法直接输入的字符:
# escape_sequences.py
# 换行:移动到下一行
print("First line\nSecond line")
# Output:
# First line
# Second line
# 制表符:插入水平间隔
print("Name:\tJohn\nAge:\t25")
# Output:
# Name: John
# Age: 25
# 反斜杠:用于包含一个字面的反斜杠
path = "C:\\Users\\Documents"
print(path) # Output: C:\Users\Documents下面是最常用的转义序列:
| Escape Sequence | 含义 | 示例输出 |
|---|---|---|
\n | 换行(换行符) | 两行 |
\t | 制表符(水平间隔) | 缩进文本 |
\\ | 反斜杠 | \ 字符 |
\' | 单引号 | ' 字符 |
\" | 双引号 | " 字符 |
在处理文件路径(尤其是使用反斜杠的 Windows)、格式化输出或任何需要特殊格式的文本时,理解转义序列非常重要。
5.1.4) 用于字面反斜杠的原始字符串
有时你希望反斜杠被当作字面字符处理,而不是触发转义序列。这在处理文件路径或正则表达式(我们会在第 39 章简单接触)时很常见。Python 通过在字符串前添加前缀 r 提供原始字符串 (raw string):
# raw_strings.py
# 普通字符串:反斜杠会触发转义序列
regular = "C:\new\test"
print(regular) # Output: C:
# ew est
# (\n 变成换行, \t 变成制表符)
# 原始字符串:反斜杠按字面含义处理
raw = r"C:\new\test"
print(raw) # Output: C:\new\test在原始字符串中,\n 就是反斜杠和 n 这两个字符本身,而不是换行符。原始字符串对 Windows 文件路径尤其有用:
# windows_paths.py
# 不使用原始字符串时,你需要对每个反斜杠转义
path1 = "C:\\Users\\John\\Documents\\file.txt"
# 使用原始字符串时,反斜杠可以自然书写
path2 = r"C:\Users\John\Documents\file.txt"
print(path1) # Output: C:\Users\John\Documents\file.txt
print(path2) # Output: C:\Users\John\Documents\file.txt两种方式产生的结果相同,但在包含大量反斜杠时,原始字符串更易读。
5.2) 拼接与字符串重复
5.2.1) 使用 + 拼接字符串
你可以使用 + 运算符把字符串连接在一起,这叫做拼接 (concatenation):
# string_concatenation.py
first_name = "John"
last_name = "Smith"
# 使用 + 拼接字符串
full_name = first_name + " " + last_name
print(full_name) # Output: John Smith
# 构建更长的字符串
greeting = "Hello, " + full_name + "!"
print(greeting) # Output: Hello, John Smith!拼接会通过把字符串首尾相接来创建一个新字符串。注意 Python 不会自动添加空格——你需要显式包含它们:
# concatenation_spacing.py
word1 = "Hello"
word2 = "World"
# 没有空格
no_space = word1 + word2
print(no_space) # Output: HelloWorld
# 有空格
with_space = word1 + " " + word2
print(with_space) # Output: Hello World你可以在一个表达式中拼接任意多个字符串:
# multiple_concatenation.py
address = "123" + " " + "Main" + " " + "Street"
print(address) # Output: 123 Main Street重要限制:你只能把字符串和字符串拼接。尝试把字符串和数字拼接会导致错误:
# concatenation_error.py
age = 25
# 这会导致错误:
# message = "I am " + age + " years old" # TypeError!
# 你必须先把数字转换为字符串
message = "I am " + str(age) + " years old"
print(message) # Output: I am 25 years old我们将在 5.6 节中更详细地讨论字符串与数字的转换。
5.2.2) 使用 * 重复字符串
Python 提供了一种便捷方式,使用 * 运算符把字符串重复多次:
# string_repetition.py
separator = "-" * 20
print(separator) # Output: --------------------
# 创建模式
pattern = "abc" * 3
print(pattern) # Output: abcabcabc
# 对输出进行简单的格式化
print("=" * 30)
print("Important Message")
print("=" * 30)
# Output:
# ==============================
# Important Message
# ==============================重复运算符可以和任意正整数一起使用:
# repetition_examples.py
# 重复 0 次得到空字符串
nothing = "Hello" * 0
print(nothing) # Output: (empty string)
print(len(nothing)) # Output: 0
# 重复 1 次得到原字符串
once = "Hello" * 1
print(once) # Output: Hello
# 更多次的重复
many = "Go! " * 5
print(many) # Output: Go! Go! Go! Go! Go!字符串重复对创建视觉分隔线、填充(padding)或生成测试数据非常有用:
# practical_repetition.py
# 创建一个简单的文本框
width = 40
border = "=" * width
title = "Welcome"
padding = " " * ((width - len(title)) // 2)
print(border)
print(padding + title)
print(border)
# Output:
# ========================================
# Welcome
# ========================================5.2.3) 结合使用拼接和重复
你可以在同一个表达式中组合使用这两个运算符,遵循 Python 的运算符优先级规则(和数字一样,乘法优先于加法):
# combined_operations.py
# 先进行重复,再进行拼接
result = "=" * 10 + " Title " + "=" * 10
print(result) # Output: ========== Title ==========
# 使用括号控制运算顺序
repeated_phrase = ("Hello " + "World ") * 3
print(repeated_phrase) # Output: Hello World Hello World Hello World这些操作构成了字符串操作的基础,让你可以从简单片段构建复杂字符串。
5.3) 字符串的索引和切片
在 Python 中,字符串是序列 (sequence) 的一种,由多个字符按顺序组成,这意味着每个字符都有特定位置。你可以使用索引和切片访问单个字符或提取字符串的一部分。
5.3.1) 理解字符串索引
字符串中的每个字符都有一个数值位置,称为索引 (index)。Python 使用从 0 开始的索引 (zero-based indexing),也就是说第一个字符的索引为 0,第二个为 1,依此类推:
# string_indexing.py
text = "Python"
# 使用索引访问单个字符
print(text[0]) # Output: P (first character)
print(text[1]) # Output: y (second character)
print(text[5]) # Output: n (sixth character)下面是索引与字符对应关系的示意图:
String: P y t h o n
Index: 0 1 2 3 4 5Python 还支持负索引 (negative index),它从字符串末尾开始计数。索引 -1 表示最后一个字符,-2 表示倒数第二个,依此类推:
# negative_indexing.py
text = "Python"
print(text[-1]) # Output: n (last character)
print(text[-2]) # Output: o (second-to-last)
print(text[-6]) # Output: P (first character)当你想访问接近字符串末尾的字符,但又不清楚确切长度时,负索引尤其有用:
String: P y t h o n
Positive: 0 1 2 3 4 5
Negative: -6 -5 -4 -3 -2 -1重要:尝试访问不存在的索引会导致 IndexError:
# index_error.py
text = "Python"
# 这没有问题
print(text[5]) # Output: n
# 这会导致错误,因为索引 6 不存在
# print(text[6]) # IndexError: string index out of range5.3.2) 使用切片提取子字符串
索引得到的是单个字符,而切片 (slicing) 能让你提取字符串的一部分(称为子字符串 (substring))。基本语法是:
string[start:stop]它会提取从索引 start 开始到索引 stop 之前(但不包括 stop)的字符:
# basic_slicing.py
text = "Python Programming"
# 提取从索引 0 到(但不包括)6 的字符
print(text[0:6]) # Output: Python
# 提取从索引 7 到 18 的字符
print(text[7:18]) # Output: Programming
# 提取中间一段
print(text[7:11]) # Output: Prog要记住的关键是,stop 索引在结果中不包含。可以把索引想象成位于字符之间的刻度:
P y t h o n
0 1 2 3 4 5 6因此 text[0:6] 的意思是“从位置 0 开始,在位置 6 之前停止”,得到位置 0、1、2、3、4、5 处的字符。
5.3.3) 省略起始或结束索引
你可以省略 start 索引来从开头开始切片,或省略 stop 索引来一直切到末尾:
# omitting_indices.py
text = "Python Programming"
# 从开头到索引 6
print(text[:6]) # Output: Python
# 从索引 7 到末尾
print(text[7:]) # Output: Programming
# 整个字符串(从头到尾)
print(text[:]) # Output: Python Programming这些简写在 Python 代码中非常常见,因为它们既清晰表达意图,又避免硬编码长度。
5.3.4) 在切片中使用负索引
负索引也可以用于切片,允许你从末尾开始计数:
# negative_slice_indices.py
text = "Python Programming"
# 最后 11 个字符
print(text[-11:]) # Output: Programming
# 除最后 11 个字符外的所有内容
print(text[:-11]) # Output: Python
# 最后 7 个字符
print(text[-7:]) # Output: ramming
# 从索引 7 到倒数第三个字符
print(text[7:-3]) # Output: Programm (在 'ing' 之前停止)当你想从末尾排除一定数量的字符时,负索引尤其有用:
# removing_suffix.py
filename = "document.txt"
# 获取除最后 4 个字符(.txt)以外的所有内容
name_without_extension = filename[:-4]
print(name_without_extension) # Output: document5.3.5) 带步长的切片
切片还可以包含第三个值,称为步长 (step),决定每次跳过多少字符:
string[start:stop:step]# slicing_with_step.py
text = "Python Programming"
# 整个字符串中每隔一个取一个字符
print(text[::2]) # Output: Pto rgamn
# 从索引 0 到 6,每隔一个取一个字符
print(text[0:6:2]) # Output: Pto
# 每隔两个字符(步长为 3)
print(text[::3]) # Output: Ph oai一个特别有用的小技巧是使用步长 -1 来反转字符串:
# reversing_strings.py
text = "Python"
# 反转整个字符串
reversed_text = text[::-1]
print(reversed_text) # Output: nohtyP
# 实用示例:检查回文
word = "radar"
if word == word[::-1]:
print(f"{word} is a palindrome!") # Output: radar is a palindrome!5.3.6) 切片不会引发错误
与索引不同,切片非常宽容。如果你指定的索引超出范围,Python 只会自动调整它们以适配字符串:
# safe_slicing.py
text = "Python"
# 这些都不会报错
print(text[0:100]) # Output: Python (在末尾停止)
print(text[10:20]) # Output: (empty string - start is beyond end)
print(text[-100:3]) # Output: Pyt (起始索引被调整为 0)这种行为使得即使在不清楚确切字符串长度时,使用切片也很安全。
5.3.7) 实用切片示例
下面是一些你会经常用到的常见模式:
# practical_slicing.py
text = "Hello, World!"
# 前 5 个字符
print(text[:5]) # Output: Hello
# 最后 6 个字符
print(text[-6:]) # Output: World!
# 除第一个和最后一个字符外的所有内容
print(text[1:-1]) # Output: ello, World
# 每隔一个字符取一个
print(text[::2]) # Output: Hlo ol!
# 反转字符串
print(text[::-1]) # Output: !dlroW ,olleH掌握索引和切片是 Python 文本处理的基础。在你的编程旅程中,你会反复用到这些技巧。
5.4) 大小写与空白的常用字符串方法
Python 字符串自带许多内置的方法 (method)——这些函数附着在字符串对象上,对它们进行操作。本节我们将探索用来改变大小写和管理空白的多种方法,它们是清洗和格式化文本的必备工具。
5.4.1) 理解字符串方法
方法使用点号调用:string.method_name()。方法是属于特定对象类型的函数。对于字符串,Python 提供了几十个有用的方法:
# method_basics.py
text = "hello"
# 在字符串上调用方法
result = text.upper()
print(result) # Output: HELLO
# 原始字符串未被改变(我们会在 5.8 节讨论原因)
print(text) # Output: hello方法可以链式调用,因为每个方法都会返回一个新的字符串:
# method_chaining.py
text = " hello world "
# 链式调用多个方法
result = text.strip().upper().replace("WORLD", "PYTHON")
print(result) # Output: HELLO PYTHON5.4.2) 大小写转换方法
Python 提供了数个用来改变字符串大小写的方法:
# case_methods.py
text = "Python Programming"
# 转为全大写
print(text.upper()) # Output: PYTHON PROGRAMMING
# 转为全小写
print(text.lower()) # Output: python programming
# 首字母大写,其余字母小写
print(text.capitalize()) # Output: Python programming
# 标题格式:每个单词首字母大写
print(text.title()) # Output: Python Programming这些方法对规范化用户输入尤其有用:
# case_normalization.py
# 模拟用户输入
user_input = "YES"
# 不区分大小写的比较
if user_input.lower() == "yes":
print("User confirmed!") # Output: User confirmed!
# 使用 upper() 的另一种写法
command = "start"
if command.upper() == "START":
print("Starting process...") # Output: Starting process...title() 方法会把每个单词的首字母大写,这对格式化姓名和标题很有用:
# title_case.py
name = "john smith"
print(name.title()) # Output: John Smith
book = "the great gatsby"
print(book.title()) # Output: The Great Gatsby不过要注意,title() 在处理撇号和特殊情况时是有限制的:
# title_limitations.py
text = "it's a beautiful day"
print(text.title()) # Output: It'S A Beautiful Day (注意大写的 S)
# 若要更复杂的标题化规则,可能需要自定义逻辑capitalize() 方法只会把整个字符串的第一个字符大写:
# capitalize_examples.py
sentence = "python is great"
print(sentence.capitalize()) # Output: Python is great
# 注意:只有第一个字母被大写
multi_word = "hello world"
print(multi_word.capitalize()) # Output: Hello world (不是 Hello World)5.4.3) 大小写检查方法
Python 还提供了用于检查字符串大小写的方法:
# case_checking.py
text1 = "HELLO"
text2 = "hello"
text3 = "Hello World"
# 检查是否全部为大写
print(text1.isupper()) # Output: True
print(text2.isupper()) # Output: False
# 检查是否全部为小写
print(text1.islower()) # Output: False
print(text2.islower()) # Output: True
# 检查是否为标题格式
print(text3.istitle()) # Output: True
print(text2.istitle()) # Output: False这些检查方法返回 True 或 False(我们在第 3 章学习了布尔值),非常适合用于条件判断:
# case_checking_conditions.py
password = "SECRET123"
if password.isupper():
print("Password is all uppercase") # Output: Password is all uppercase5.4.4) 空白移除方法
空白 (whitespace) 包括空格、制表符 (\t) 和换行符 (\n)。Python 提供了从字符串两端移除空白的方法:
# whitespace_removal.py
text = " Hello, World! "
# 移除两端的空白
print(text.strip()) # Output: Hello, World!
# 移除左侧(开头)的空白
print(text.lstrip()) # Output: Hello, World!
# 移除右侧(结尾)的空白
print(text.rstrip()) # Output: Hello, World!strip() 方法在清理用户输入时极其有用:
# cleaning_input.py
# 模拟带多余空格的用户输入
user_name = " John Smith "
# 清理输入
clean_name = user_name.strip()
print(f"Welcome, {clean_name}!") # Output: Welcome, John Smith!这些方法也会移除制表符和换行符:
# strip_all_whitespace.py
text = "\n\t Hello \t\n"
print(repr(text)) # Output: '\n\t Hello \t\n'
cleaned = text.strip()
print(repr(cleaned)) # Output: 'Hello'注意,strip()、lstrip() 和 rstrip() 只会移除两端的空白,不会移除中间的空格:
# strip_edges_only.py
text = " Hello World "
print(text.strip()) # Output: Hello World (中间的空格仍然保留)5.4.5) 移除特定字符
这些 strip 方法还可以从字符串两端移除特定字符(不仅仅是空白):
# 移除多种不同的字符
text = "...Hello!!!"
cleaned = text.strip(".!")
print(cleaned) # Output: Hello当你把一个字符串传给 strip() 时,它会从两端移除参数字符串中“任意组合”的字符:
# strip_character_set.py
text = "xxxyyyHelloyyyxxx"
# 从两端移除任意数量的 x 或 y
result = text.strip("xy")
print(result) # Output: Hello5.4.6) 结合大小写和空白方法的实用示例
下面是一些这些方法非常有用的真实场景:
# practical_text_cleaning.py
# 清理并规范化用户输入
user_email = " JohnSmith@EXAMPLE.com "
clean_email = user_email.strip().lower()
print(clean_email) # Output: johnsmith@example.com
# 正确格式化姓名
raw_name = " john smith "
formatted_name = raw_name.strip().title()
print(formatted_name) # Output: John Smith
# 处理命令(不区分大小写)
command = " START "
if command.strip().upper() == "START":
print("Command recognized!") # Output: Command recognized!这些方法构成了文本清洗和规范化的基础,在处理用户输入、读取文件或准备数据分析时,你会持续使用它们。
5.5) 在字符串中搜索与替换
在字符串中查找和修改文本是编程中的常见任务。Python 提供了强大的方法来搜索子字符串并替换文本。
5.5.1) 使用 find() 和 index() 查找子字符串
find() 方法会搜索子字符串,并返回它第一次出现的位置索引:
# find_method.py
text = "Python is great. Python is powerful."
# 查找第一次出现的 "Python"
position = text.find("Python")
print(position) # Output: 0 (在开头找到)
# 查找 "great"
position = text.find("great")
print(position) # Output: 10
# 查找不存在的内容
position = text.find("Java")
print(position) # Output: -1 (没找到)当子字符串未找到时,find() 会返回 -1,因此它可以安全使用,不会导致错误:
# safe_searching.py
text = "Hello, World!"
# 检查子字符串是否存在
if text.find("World") != -1:
print("Found 'World'!") # Output: Found 'World'!
if text.find("Python") == -1:
print("'Python' not found") # Output: 'Python' not found你还可以从指定位置之后开始搜索子字符串:
# find_with_start.py
text = "Python is great. Python is powerful."
# 第一次出现的位置
first = text.find("Python")
print(first) # Output: 0
# 在第一次出现之后查找下一次出现
second = text.find("Python", first + 1)
print(second) # Output: 17index() 方法和 find() 类似,但在未找到子字符串时会引发错误:
# index_method.py
text = "Hello, World!"
# 这没问题
position = text.index("World")
print(position) # Output: 7
# 这会导致 ValueError:
# position = text.index("Python") # ValueError: substring not found使用时机:
- 当你想检查某个内容是否存在时,使用
find()(找不到时返回 -1) - 当你确信子字符串一定存在时,使用
index()(找不到时抛出错误)
# choosing_find_vs_index.py
text = "Python Programming"
# 使用 find() 进行安全检查
if text.find("Java") != -1:
print("Found Java")
else:
print("Java not found") # Output: Java not found
# 在你确信一定存在时使用 index()
position = text.index("Python") # 我们知道 Python 一定在这里
print(f"Found at position {position}") # Output: Found at position 05.5.2) 使用 rfind() 和 rindex() 从末尾查找
rfind() 和 rindex() 方法会从字符串右侧(末尾)开始搜索:
# rfind_method.py
text = "Python is great. Python is powerful."
# 查找最后一次出现的 "Python"
last_position = text.rfind("Python")
print(last_position) # Output: 17
# 与 find() 比较,find() 返回第一次出现的位置
first_position = text.find("Python")
print(first_position) # Output: 0当你只需要最后一次出现的位置时,这会很有用:
# last_occurrence.py
filename = "document.backup.txt"
# 查找最后一个点(以获取文件扩展名)
last_dot = filename.rfind(".")
if last_dot != -1:
extension = filename[last_dot:]
print(extension) # Output: .txt5.5.3) 使用 count() 统计出现次数
count() 方法告诉你子字符串出现了多少次:
# count_method.py
text = "Python is great. Python is powerful. Python is fun."
# 统计 "Python" 出现了多少次
count = text.count("Python")
print(count) # Output: 3
# 统计某个字符
letter_count = text.count("o")
print(f"Letter 'o' appears {letter_count} times") # Output: Letter 'o' appears 4 times你还可以在指定范围内统计:
# count_in_range.py
text = "abcabcabc"
# 在整个字符串中统计 "abc"
total = text.count("abc")
print(total) # Output: 3
# 只在前 6 个字符中统计 "abc"
partial = text.count("abc", 0, 6)
print(partial) # Output: 25.5.4) 使用 replace() 替换文本
replace() 方法会创建一个新字符串,将所有匹配子字符串的部分替换为另一段文本:
# replace_method.py
text = "I love Java. Java is great."
# 将所有 "Java" 替换为 "Python"
new_text = text.replace("Java", "Python")
print(new_text) # Output: I love Python. Python is great.
# 原始字符串保持不变
print(text) # Output: I love Java. Java is great.你可以通过第三个参数限制替换次数:
# limited_replace.py
text = "one one one one"
# 只替换前 2 次
result = text.replace("one", "two", 2)
print(result) # Output: two two one onereplace() 方法是区分大小写的:
# case_sensitive_replace.py
text = "Python is great. python is powerful."
# 这只会替换大写 P 的 "Python"
result = text.replace("Python", "Java")
print(result) # Output: Java is great. python is powerful.要进行不区分大小写的替换,你需要手动处理:
# case_insensitive_approach.py
text = "Python is great. python is powerful."
# 转为小写后替换,但会丢失原有大小写信息
result = text.lower().replace("python", "java")
print(result) # Output: java is great. java is powerful.5.5.5) 搜索与替换的实用示例
下面是这些方法在实际场景中的表现:
# practical_search_replace.py
# 清洗数据:移除不需要的字符
phone = "123-456-7890"
clean_phone = phone.replace("-", "")
print(clean_phone) # Output: 1234567890
# 屏蔽敏感词
message = "This is a bad word and another bad word."
censored = message.replace("bad", "***")
print(censored) # Output: This is a *** word and another *** word.
# 提取文件扩展名
filename = "document.txt"
dot_position = filename.rfind(".")
if dot_position != -1:
extension = filename[dot_position + 1:]
print(f"File type: {extension}") # Output: File type: txt
# 统计单词出现次数(简单方式)
text = "Python is fun. I love Python. Python rocks!"
word = "Python"
occurrences = text.count(word)
print(f"'{word}' appears {occurrences} times") # Output: 'Python' appears 3 times这些搜索与替换方法是 Python 程序中文本处理、数据清洗和字符串操作的基础工具。
5.6) 在字符串和数字之间转换
在编程中,最常见的任务之一就是在文本和数字表示之间转换。使用 input() 读取用户输入时,你得到的是字符串——即使用户输入的是数字。同样,当你想在文本中显示数字时,需要先把它们转换为字符串。
5.6.1) 把字符串转换为数字
我们在第 3 章已经见过 int() 和 float() 函数,现在更深入地看看它们:
# string_to_number.py
# 将字符串转换为整数
age_text = "25"
age = int(age_text)
print(age) # Output: 25
print(type(age)) # Output: <class 'int'>
# 将字符串转换为浮点数
price_text = "19.99"
price = float(price_text)
print(price) # Output: 19.99
print(type(price)) # Output: <class 'float'>在处理用户输入时,这些转换非常关键:
# user_input_conversion.py
# 模拟用户输入(在真实代码中,你会使用 input())
user_age = "30"
user_height = "5.9"
# 转换为数字,以便进行数学运算
age = int(user_age)
height = float(user_height)
# 现在我们可以做计算了
print(f"In 10 years, you'll be {age + 10}") # Output: In 10 years, you'll be 40
print(f"Your height in meters: {height * 0.3048:.2f}") # Output: Your height in meters: 1.80重要:字符串必须表示一个有效的数字,否则会报错:
# conversion_errors.py
# 这些是没问题的
print(int("123")) # Output: 123
print(float("3.14")) # Output: 3.14
# 这些会导致 ValueError:
# print(int("hello")) # ValueError: invalid literal for int()
# print(int("12.5")) # ValueError: invalid literal for int() with base 10
# print(float("12.5.3")) # ValueError: could not convert string to float我们会在第 28 章学习如何优雅地处理这些错误。现在先知道一点:如果字符串不表示有效数字,转换就会失败。
5.6.2) 处理数字字符串中的空白
Python 的转换函数会自动处理前后空白:
# whitespace_handling.py
# 即使有空格,这些也是没问题的
print(int(" 42 ")) # Output: 42
print(float(" 3.14 ")) # Output: 3.14
# 为更稳妥,可以结合 strip() 和转换
user_input = " 100 "
number = int(user_input.strip())
print(number) # Output: 100这在处理经常包含多余空格的用户输入时非常有用。
5.6.3) 把数字转换为字符串
str() 函数可以把任何值转换为它的字符串表示:
# number_to_string.py
age = 25
height = 5.9
# 把数字转换为字符串
age_text = str(age)
height_text = str(height)
print(type(age_text)) # Output: <class 'str'>
print(type(height_text)) # Output: <class 'str'>
# 现在我们可以把它们与其它字符串拼接
message = "I am " + str(age) + " years old"
print(message) # Output: I am 25 years old每当你想把数字和字符串拼接在一起时,这是必要步骤:
# concatenation_with_numbers.py
score = 95
total = 100
# 要进行拼接,必须先把数字转换为字符串
result = "Score: " + str(score) + "/" + str(total)
print(result) # Output: Score: 95/100
# 另一种方式:使用 f-string(在第 6 章详细介绍)
result = f"Score: {score}/{total}"
print(result) # Output: Score: 95/1005.6.4) 在整数和浮点数之间转换
你也可以在整数类型和浮点类型之间转换:
# int_float_conversion.py
# 浮点数转整数(截断小数部分)
price = 19.99
price_int = int(price)
print(price_int) # Output: 19 (小数部分被移除,不会四舍五入)
# 整数转浮点数
age = 25
age_float = float(age)
print(age_float) # Output: 25.0重要:把浮点数转换为整数时会截断(直接去掉)小数部分——不会进行四舍五入:
# truncation_not_rounding.py
print(int(3.9)) # Output: 3 (不是 4!)
print(int(3.1)) # Output: 3
print(int(-3.9)) # Output: -3 (向 0 截断)
# 如果要四舍五入,先用 round() 函数(在第 4 章介绍)
print(int(round(3.9))) # Output: 45.6.5) 实用转换示例
下面是一些类型转换非常关键的实际场景:
# practical_conversions.py
# 读取并处理用户输入
# (模拟 input() - 在真实代码中,你会用 input())
user_input = "42"
# 转换为数字以进行计算
number = int(user_input)
doubled = number * 2
print(f"Double of {number} is {doubled}") # Output: Double of 42 is 84
# 构建格式化输出
name = "John"
age = 30
height = 5.9
# 方法 1:把数字转换为字符串
info = name + " is " + str(age) + " years old and " + str(height) + " feet tall"
print(info) # Output: John is 30 years old and 5.9 feet tall
# 方法 2:使用 f-string(更易读 - 第 6 章讲解)
info = f"{name} is {age} years old and {height} feet tall"
print(info) # Output: John is 30 years old and 5.9 feet tall
# 处理来自文件的数据(预览)
data_line = "100,200,300" # 模拟 CSV 文件中的一行
numbers = data_line.split(",") # 拆分为字符串列表
total = int(numbers[0]) + int(numbers[1]) + int(numbers[2])
print(f"Total: {total}") # Output: Total: 6005.6.6) 常见转换陷阱
要注意以下常见错误:
# conversion_pitfalls.py
# 陷阱 1:试图转换非数字字符串
# text = "hello"
# number = int(text) # ValueError!
# 陷阱 2:在进行算术运算前忘记转换
age_text = "25"
# next_year = age_text + 1 # TypeError: can only concatenate str to str
# 正确做法:
age = int(age_text)
next_year = age + 1
print(next_year) # Output: 26
# 陷阱 3:使用 int() 时丢失精度
price = 19.99
price_int = int(price) # 变成 19,不是 20!
print(price_int) # Output: 19
# 陷阱 4:试图转换包含逗号或货币符号的字符串
# price_text = "$1,234.56"
# price = float(price_text) # ValueError!
# 需要先清洗字符串:
price_text = "$1,234.56"
clean_price = price_text.replace("$", "").replace(",", "")
price = float(clean_price)
print(price) # Output: 1234.56理解类型转换对构建与用户交互和处理真实世界数据的程序至关重要。在你的 Python 编程旅程中,你会一直频繁地使用这些转换。
5.7) 使用 in 和 not in 检查子字符串
Python 提供了简单、可读性很强的方式,使用 in 和 not in 运算符来检查一个字符串是否包含另一个字符串。这在验证、过滤和程序决策中非常好用。
5.7.1) 使用 in 检查子字符串
如果一个字符串出现在另一个字符串中,in 运算符会返回 True,否则返回 False:
# in_operator.py
text = "Python is a powerful programming language"
# 检查子字符串是否存在
print("Python" in text) # Output: True
print("powerful" in text) # Output: True
print("Java" in text) # Output: False这比使用 find() 或 index() 要更易读:
# in_vs_find.py
text = "Hello, World!"
# 使用 in(清晰易读)
if "World" in text:
print("Found World!") # Output: Found World!
# 使用 find(可读性较差)
if text.find("World") != -1:
print("Found World!") # Output: Found World!in 运算符是区分大小写的:
# case_sensitivity.py
text = "Python Programming"
print("python" in text) # Output: False (小写 p)
print("Python" in text) # Output: True (大写 P)
# 若要不区分大小写的检查,可以先转为小写
print("python" in text.lower()) # Output: True5.7.2) 使用 not in 检查不存在
not in 运算符用来检查子字符串是否不存在:
# not_in_operator.py
text = "Python is great"
print("Java" not in text) # Output: True (Java 不在其中)
print("Python" not in text) # Output: False (Python 在其中)它在验证时尤其有用:
# validation_examples.py
# 检查用户名中是否包含非法字符
username = "john_smith"
if " " not in username:
print("Username is valid (no spaces)") # Output: Username is valid (no spaces)5.7.3) 其它字符串检查方法
Python 还提供了若干用于检查字符串属性的有用方法:
# string_checking_methods.py
text = "Python"
# 检查字符串是否以某个子字符串开头
print(text.startswith("Py")) # Output: True
print(text.startswith("Ja")) # Output: False
# 检查字符串是否以某个子字符串结尾
print(text.endswith("on")) # Output: True
print(text.endswith("ing")) # Output: False
# 这些方法比使用 in 更精确
filename = "report.txt"
print(filename.endswith(".txt")) # Output: True
print(".txt" in filename) # Output: True (但精确性较差)
# startswith/endswith 可以检查多个选项
filename = "document.pdf"
print(filename.endswith((".pdf", ".doc", ".txt"))) # Output: True这些检查方法是进行输入验证、数据过滤和条件逻辑的必备工具。相比手动字符串搜索,它们能让你的代码更易读、更易维护。
5.8) 字符串是不可变的:实际含义是什么
Python 字符串最重要的特性之一是它们是不可变 (immutable) 的——一旦创建,就不能再被改变。这一开始看起来像是个限制,但理解不可变性对于编写正确的 Python 代码并避免微妙的 bug 至关重要。
5.8.1) 不可变性的含义
当我们说字符串是不可变的,意思是你不能修改现有字符串中的字符。任何看起来会“改变”字符串的操作,实际上都是创建了一个新字符串:
# immutability_basics.py
text = "Hello"
# 这看起来像是在改变字符串,其实不是
text = text + " World"
print(text) # Output: Hello World
# 实际发生的是:
# 1. Python 创建了一个新字符串 "Hello World"
# 2. 变量 'text' 现在引用这个新字符串
# 3. 原来的 "Hello" 字符串仍然存在(直到被垃圾回收)你不能修改字符串中的单个字符:
# cannot_modify_characters.py
text = "Hello"
# 这会导致错误:
# text[0] = "J" # TypeError: 'str' object does not support item assignment
# 你必须创建一个新字符串
text = "J" + text[1:]
print(text) # Output: Jello这和列表的行为根本不同(我们将在第 13 章学习列表)。列表是可变的——你可以改变它们的元素:
# lists_are_mutable.py
# 列表预览(第 13 章详细讲解)
numbers = [1, 2, 3]
numbers[0] = 10 # 对列表来说这是可以的
print(numbers) # Output: [10, 2, 3]
# 但字符串不允许这样做:
text = "Hello"
# text[0] = "J" # 对字符串会报 TypeError!5.8.2) 为什么字符串方法会返回新字符串
所有看起来会修改字符串的字符串方法,实际上都是返回一个新字符串,原字符串保持不变:
# methods_return_new_strings.py
original = "hello world"
# 这些方法返回新的字符串
uppercase = original.upper()
capitalized = original.capitalize()
replaced = original.replace("world", "Python")
# 原始字符串没有改变
print(original) # Output: hello world
print(uppercase) # Output: HELLO WORLD
print(capitalized) # Output: Hello world
print(replaced) # Output: hello Python因此,如果你想保留更改结果,需要把返回值赋给某个变量(或同一个变量):
# keeping_changes.py
text = " hello "
# 错误写法:结果被丢弃
text.strip()
print(text) # Output: hello (仍然有空格!)
# 正确写法:把结果赋值回来
text = text.strip()
print(text) # Output: hello (空格被移除)这是初学者常见的错误:
# common_mistake.py
message = "python programming"
# 错误:调用方法但不使用返回值
message.upper()
message.replace("python", "Python")
print(message) # Output: python programming (没变!)
# 正确:把结果赋值回来
message = message.upper()
message = message.replace("PYTHON", "Python")
print(message) # Output: Python PROGRAMMING5.8.3) 不可变性的影响
理解不可变性有助于你写出更好的代码:
1. 字符串可以安全共享:
# safe_sharing.py
original = "Hello"
copy = original # 两个变量指向同一个字符串
# 由于字符串是不可变的,这很安全
copy = copy + " World"
print(original) # Output: Hello (未改变)
print(copy) # Output: Hello World (新字符串)2. 字符串操作会创建新对象:
# new_objects.py
text = "Python"
# 每个操作都会创建新的字符串对象
result1 = text.upper()
result2 = text.lower()
result3 = text.replace("P", "J")
# 都是不同的对象
print(id(text)) # 某个内存地址
print(id(result1)) # 不同的内存地址
print(id(result2)) # 不同的内存地址
print(id(result3)) # 不同的内存地址3. 在循环中构建字符串可能效率不高:
# inefficient_string_building.py
# 这种写法会创建很多临时字符串对象
result = ""
for i in range(5):
result = result + str(i) # 每次都会创建新字符串
print(result) # Output: 01234
# 更高效的方式(用于大量拼接):
# 使用列表并 join(我们会在第 6 章学习)
parts = []
for i in range(5):
parts.append(str(i))
result = "".join(parts)
print(result) # Output: 012345.8.4) 不可变性与函数参数
当你把字符串传给函数时,不必担心它会被意外修改:
# safe_function_arguments.py
def process_text(text):
# 任何操作都会创建新字符串
text = text.upper()
text = text.replace("A", "X")
return text
original = "banana"
result = process_text(original)
print(original) # Output: banana (未改变)
print(result) # Output: BXNXNX (修改后的版本)这和可变类型(比如列表,我们会在第 13 章学习)不同,后者在函数内部的修改会影响到原始对象。
5.8.5) 形象理解不可变性
下面是你“修改”字符串时发生情况的形象表示:
理解字符串是不可变的有助于你:
- 避免忘记接收方法返回值导致的错误
- 理解为什么字符串操作会创建新对象
- 在构建大型字符串时写出更高效的代码
- 在程序的不同部分之间安全地共享字符串
这种不可变性是字符串与列表等可变类型之间的重要区别,我们将在本书第 IV 部分详细探索列表等内容。
本章小结:
在本章中,你学习了在 Python 中使用字符串处理文本的基础知识。你现在已经知道如何:
- 使用引号和转义序列创建字符串字面量
- 使用拼接和重复组合字符串
- 使用索引和切片访问单个字符和提取子字符串
- 使用大小写转换与空白处理方法来变换字符串
- 在字符串中搜索文本并进行替换
- 在字符串与数字之间转换,以便进行输入处理和输出格式化
- 使用
in和not in运算符检查子字符串 - 认识字符串是不可变的,以及这对你的代码意味着什么
这些字符串操作技能构成了 Python 文本处理的基础。在构建用户界面、处理数据文件、验证输入和格式化输出时,你会一直使用这些技术。
在下一章中,我们将在这些基础之上继续深入,探索更高级的字符串处理技巧,包括拆分与合并字符串、使用 f-string 和 format() 方法进行强大的格式化,以及理解文本编码,以便处理国际化字符。