Skip to content
This page has been auto-translated and may contain errors.View in English

字符串

在你写的几乎每个程序中都会出现文本。名字、消息、分数、标签。在 Python 中,任何一段文本被称为字符串:任何用引号包围的值。单引号或双引号,两者的效果完全一样。

字符串是 Python 的主要文本类型。从用户名到 URL 路径再到格式化输出,它无处不在。单引号和双引号的结果一样;选择哪个只是风格问题。

str 出现在每个系统边界:终端 I/O、文件内容、网络响应、序列化数据。它是 Python 的不可变的(创建后无法改变)Unicode 序列类型。两种引号风格产生相同的对象,所以选择哪一种纯粹是风格问题。

python
greeting = "Hello, world"
username = 'alice'

引号的选择只在你的文本包含引号时才重要。用相反的风格就不用转义它们:

社区约定使用双引号。实际的原因是避免转义:当内容包含该字符时,切换引号风格可以避免反斜杠。

约定是双引号,Black 和 Ruff 这样的格式化工具会自动强制使用。手工编写时唯一切换的理由就是避免反斜杠转义:当内容包含分隔符时。

python
note = "It's a great day"      # 撇号在内部,使用双引号
message = 'She said "hello"'   # 双引号在内部,使用单引号
escaped = "She said \"hello\""  # 或者用反斜杠转义

不可变性

字符串是不可变的:一旦你创建了它,你就无法改变它。可以把字符串看作在创建的那一刻就被永久固定了。任何看起来在修改字符串的操作实际上都是在产生一个全新的字符串。原始的字符串始终保持不变。

字符串是不可变的:没有方法可以在原处修改字符串。每个转换文本的操作都会返回一个新字符串,并保持原字符串不变。实际的后果是,一个方法调用如果你没有把结果赋值给什么东西,就对任何事物都没有影响。

str 对象是不可变的,这意味着它们的内容在创建后就被固定了,没有任何东西可以改写它们。这赋予了字符串你实际会用到的两个特性:它们是可哈希的(一个内容永不改变的值可以按其内容来存档,这就是为什么字符串可以作为字典的键或集合的成员),以及它们可以在名字之间安全传递,而无需任何人复制,因为没有人能改写一个被共享的字符串。

python
name = "alice"
name = name.upper()   # "ALICE" 是一个新字符串;"alice" 保持不变

直接的后果是:你无法改变某个位置的字符。如果你试图这样做,Python 会抛出一个错误。

python
name = "alice"
name[0] = "A"   # TypeError: 'str' object does not support item assignment

要得到一个修改过的字符串,要用切片或方法来构建一个新字符串。两者都会在下面介绍。

尝试字符赋值直接显示了这个限制:

python
name = "alice"
name[0] = "A"   # TypeError: 'str' object does not support item assignment

当你需要一个修改过的版本时,标准工具是用切片加连接来做位置编辑,用 replace() 来做替换。两者都产生一个新字符串,并保持原字符串不变。

对某个位置赋值(name[0] = "A")每次都会抛出 TypeError,没有例外。要做位置编辑,通过在你想改变的部分周围切片来构建一个新字符串:name[:1].upper() + name[1:] 将第一个字符大写。要做字符串中任何地方的替换,用 replace()

Juno不可变性 字符串一旦存在就永远不变。任何看起来在编辑它的操作都会把一个新字符串交给你,所以要赋值结果或者它就消失了。让我早期栽过的坑:name[0] = "A" 不行,它会抛出 TypeError
Juno不可变性 没有字符串方法可以在原处编辑,每个都返回一个新字符串,并保持原字符串不变。所以单独执行 name.upper() 没有意义,你得赋值它。还有 name[0] = "A" 会抛出 TypeError,没有原处字符交换。
Juno不可变性 不可变性是什么让字符串可以作为 dict 键,也是为什么它们可以被安全共享而无需复制。实际的后果是:赋值每个转换方法的结果,因为没有任何方法改动原字符串。要做位置编辑,在那个位置周围切片并重建,因为项赋值总是抛出 TypeError

索引和切片

字符串中的每个字符都有一个编号的位置,从零开始。你可以通过把那个位置号放在方括号里来读取单个字符。负数从末尾向后计数。

字符串是零索引的序列。负索引从末尾开始计数。切片在一个表达式中提取任何连续的范围,而且永远不会在范围外的值上抛出错误。

一个字符串的表现像一个你可以索引和切片的有序序列。读取单个位置(s[i])如果索引指向末尾之外会抛出 IndexError。切片(s[start:stop:step])表现不同:范围外的边界被无声地钳制到存在的东西,所以切片永远不能抛出 IndexError。那个区别是实际会用到的:索引访问是严格的,切片是宽容的。

python
word = "Python"
#       012345

print(word[0])    # "P"
print(word[2])    # "t"
print(word[5])    # "n"
print(word[-1])   # "n"  (最后一个字符)
print(word[-2])   # "o"  (倒数第二个)

-1 总是最后一个字符,-2 是倒数第二个,以此类推。当你想要字符串的末尾而不知道其确切长度时,它们很有用。

负索引的工作方式:-1len(s) - 1-2len(s) - 2。在你不想手工计算长度时,最适合用于末尾锚定的访问。一个超出范围的负索引仍然会抛出 IndexError,就像正索引一样。

负索引在边界检查前被转换为 len(s) + i,所以 -1 落在最后一个字符上,-2 在它前面的那个。超出范围无论正负都抛出 IndexError:只有切片原谅这一点,普通索引不会。

切片提取一个块。[start:stop] 包含 start 但不包含 stop

python
word = "Python"

print(word[0:2])   # "Py"     (位置 0 和 1)
print(word[2:])    # "thon"   (位置 2 到末尾)
print(word[:3])    # "Pyt"    (开始到位置 2)
print(word[:])     # "Python" (整个字符串的副本)
print(word[::2])   # "Pto"    (每第二个字符)
print(word[::-1])  # "nohtyP" (反向)

三个最常用的模式:word[:n] 取前 n 个字符,word[n:] 取从位置 n 开始的一切,word[-n:] 取最后 n 个字符。word[::-1] 反转一个字符串。第一次看起来很奇怪,但这是习语化的 Python,你会经常看到它。

和直接索引不同,切片永远不会抛出 IndexError。Python 无声地钳制超出范围的索引,所以 word[100:] 在一个短字符串上返回 "",而不是崩溃。步长参数控制跨度:word[::2] 取每隔一个字符,word[::-1] 反向遍历。

s[start:stop:step] 中,任何你省略的部分都默认为"你要去的方向的自然末尾",而不是固定的 0len()。对于正步长那意味着从开始到末尾;对于负步长它反转,所以 start 变成最后一个字符,stop 从前面跑掉。那就是为什么 s[::-1] 在不写任何边界的情况下反向遍历整个字符串,也是 s[::-2] 给你每第二个字符向后的原因。

Juno索引和切片 位置从零开始,所以 word[0] 是第一个字符,word[-1] 是最后一个。一个切片抓住一个范围:word[start:stop] 保持 start 并在 stop 前停止。word[::-1] 反转字符串,第一次看起来奇怪然后你就会一直用它。
Juno索引和切片 从零索引,或者用负数从末尾索引,其中 word[-1] 是最后一个字符。切片取一个包含 start 并排除 stop 的范围,还有第三个 step 设置跨度,所以 word[::-1] 反转。值得记住的问题是:索引过末尾会抛出 IndexError,但切片过末尾则返回那里存在的东西而不是失败。
Juno索引和切片 普通索引是严格的,超出范围抛出 IndexError,切片是宽容的,钳制到无论什么存在,所以 word[100:] 返回 "" 而不是抛出。省略切片的一部分它默认为步长方向的自然末尾,这正是 word[::-1] 在不写边界的情况下反向的原因。当你不想在空或短输入上崩溃时用切片。

必要的字符串方法

字符串附带一组内置方法:你可以直接在任何字符串值上调用的操作。你写字符串(或持有它的变量),然后一个点,然后方法名。每个方法返回一个新字符串。原始字符串从不改变。

字符串方法是附加到 str 类型的函数。因为字符串是不可变的,每个方法都返回一个新字符串,而不是修改原字符串。一个你没有赋值或传递到某处的方法调用没有持久效果。

每个转换方法都返回一个新的 str 并保持原字符串不变,这直接来自不可变性。它们也在码点上工作(字符本身,而不是用来存储它们的原始字节),所以方法在重音和非拉丁文本上表现正确,而你什么都不用做。要记住的权衡是:每个方法分配一个新字符串,所以在大文本上链接很多会导致很多分配。

大小写

python
text = "Hello, World"

text.lower()       # "hello, world"
text.upper()       # "HELLO, WORLD"
text.title()       # "Hello, World"  (每个单词首字母大写)
text.capitalize()  # "Hello, world"  (仅第一个单词)

lower()upper() 是你最常用的两个。lower() 特别有用于比较文本:"Alice""alice" 在双方都调用 .lower() 后变成一样。

lower() 是比较或存储前的标准归一化步骤。title() 用粗规则把每个单词的首字母大写,在缩写词上会失败:"it's" 变成 "It'S"。把它当作仅供显示的格式。

lower() 应用 Unicode 完整大小写转换。对于大小写不敏感的比较,casefold() 更正确:它应用额外的转换(例如德文 ß 变成 ss)而 lower() 会跳过。title() 在任何非字母数字字符后大写,这会误处理缩写词和连字符名字。要正确的标题大小写,手工实现逻辑。

空白

python
text = "  hello  "

text.strip()    # "hello"    (两侧)
text.lstrip()   # "hello  "  (仅左侧)
text.rstrip()   # "  hello"  (仅右侧)

strip() 从字符串的两端移除空格。几乎任何时候你处理用户输入或来自文件的文本,你都会用到它,因为多余的空格会导致静默失败:"alice" != "alice "

strip() 从两端移除所有前导和尾随空白:空格、制表符和换行符。方向性变体让你只清理一侧,在去掉尾随换行符而不触及缩进时很有用。所有三个都接受一个可选的字符参数来替换地去掉特定的字符。

不带参数,strip() 从两端移除每种空白,包括非 ASCII 空白,不仅是普通空格。带字符参数它从末尾移除那些字符的任何一个,这是个陷阱:参数是要去掉的字符集,不是要匹配的前缀。"xxhelloxx".strip("x") 返回 "hello",但 "https://".strip("https") 不移除前缀 "https",它从两端去掉每个 htps 并返回 "://"。要移除一个已知的前缀或后缀,用 removeprefix()removesuffix() 代替。

查找

python
text = "Hello, world"

text.find("world")         # 7
text.find("Python")        # -1  (未找到)
text.count("l")            # 3
text.startswith("Hello")   # True
text.endswith("world")     # True

find() 返回一段文本在你的字符串中开始的位置。如果不在那里,它返回 -1。当你只关心字符串是否以某些东西开始或结束时,用 startswith()endswith()

find() 返回第一个匹配的起始索引,或 -1-1 约定让你直接在切片或算术中使用结果,无需检查。startswith()endswith() 各自接受一个字符串元组,所以你可以在一个调用中测试若干个前缀或后缀。

find() 从左到右扫描并在没有匹配时返回 -1index() 做同样的搜索但抛出 ValueError 而不是返回 -1:当一个缺失的匹配意味着你的代码有 bug 并且你想在那里停止时,选择 index();当缺失是你会检查的正常输入时,选择 find()。对于一个纯粹的"它是否以此开始或结束"问题,startswith()endswith() 说明你的意思并在第一个不匹配处停止,所以相比用 find()in 检查,更优先选择它们。

替换

python
text = "Hello, world"

text.replace("world", "Python")   # "Hello, Python"
text.replace("l", "L")            # "HeLLo, worLd"  (所有出现)
text.replace("l", "L", 1)         # "HeLlo, world"  (仅第一个)

replace() 把一段文本的每个出现都换成另一段,并给你返回一个新字符串。原字符串没有改变。如果你只想替换第一个出现,传一个第三个参数。

replace() 默认替换所有非重叠的出现。计数参数限制有多少个被替换。因为它返回一个新字符串,调用可以被链接:text.replace("a", "A").replace("e", "E") 按顺序应用两个替换。

replace() 匹配一个字面子字符串,不是一个模式,所以它不做正则表达式,并把参数中的每个字符当作其本身。它从左到右替换非重叠匹配,这会让你吃惊:"aaa".replace("aa", "b") 给出 "ba",不是 "bb",因为第二个 aa 和第一个匹配重叠。计数参数限制有多少个被替换,因为每个调用返回一个新字符串你可以链接它们:text.replace(",", "").replace(" ", "_")

分割和连接

split() 在分隔符处切开一个字符串为多段,并把它们作为列表返回。你告诉它在什么处切:

split() 在分隔符处分割并把段作为列表返回。不带参数调用,它在任何空白运行处分割并从多个连续空格中丢弃空字符串:

split(sep) 从左到右扫描,在 sep 的每个非重叠出现处分割。不带参数它使用不同的算法:它在任何空白运行处分割,并从结果中去掉前导和尾随空白。rsplit(sep, n) 从右边分割,对于隔离点分路径或命名空间标识符的最后一段很有用。

python
csv_row = "Alice,28,Beijing"
parts = csv_row.split(",")     # ["Alice", "28", "Beijing"]

"  hello   world  ".split()   # ["hello", "world"]

split() 返回一个列表,一个有序的值序列。它们有自己的列表章节;现在把它们当作 split() 产生的段序列和 join() 消耗的东西。

join() 做反向操作:它把一个字符串列表合并成一个。在 .join() 前的字符串被放在每个项目之间:

python
words = ["Hello", "world"]

" ".join(words)    # "Hello world"
", ".join(words)   # "Hello, world"
"".join(words)     # "Helloworld"

要记住的模式是:separator.join(list_of_strings)。分隔符在左边,列表在右边。" ".join(words) 在每个单词之间放一个空格。"".join(words) 用什么都不用粘合它们。

当你从多个段组装一个单一字符串时,join() 是正确的工具。它执行一次分配,而不是在每步创建一个新字符串。对于两个或三个字符串,+ 非常好。一旦你有任何有意义大小的列表,用 join()

join() 遍历段一次,算出总大小,一次分配结果,并全部写入。用重复的 + 在循环中构建同样的字符串是昂贵的形式:因为字符串不能改变,每个 + 分配一个新字符串并把到目前为止的所有东西复制到其中,所以成本随段数的平方增长。在一小把字符串上没人注意,但在几千个上它把一个快操作变成慢的。阻止它的规则:把段收集在列表中并在末尾 join() 一次,永远不要在循环中 += 一个字符串。

Juno必要的字符串方法 这些每一个都返回一个新字符串,所以赋值结果或者它就消失了。你每天都会用的几个:.lower().upper() 处理大小写,.strip() 削除多余空格,.find() 定位文本(不在那里时它返回 -1),.replace() 交换文本,.split() 配合 sep.join() 把一个字符串拆开和重新组合。
Juno必要的字符串方法 这些所有的返回一个新字符串,没有改变原字符串,所以链接或赋值它们。弄清楚这些问题:.strip("x") 取一个字符集来削除,不是前缀,.split() 不带参数会合并空白运行。要组装很多段,构建一个列表并 sep.join() 它。
Juno必要的字符串方法 在真实代码中咬伤的两个陷阱:.strip(chars) 从末尾去掉一个字符集,不是前缀,所以当你意思是前缀时用 removeprefix(),以及在循环中用 += 构建一个字符串是慢的形式,所以收集到一个列表中并 join() 一次。对于比较,用 .casefold() 而不是 .lower() 来归一化,并把 .title() 当作仅显示的因为它搞坏缩写词。

f 字符串

f 字符串把值直接嵌入到文本中。把 f 放在开放引号前,然后把任何变量或表达式包围在花括号中。Python 在代码运行时填充它。你也可以在值后加一个冒号来控制它是怎样显示的。

f 字符串在运行时评估 {} 内的任何表达式,并把结果转换为字符串。冒号在花括号内引入一个格式说明符:一个紧凑的语法来控制小数位、对齐和数字格式。

f 字符串中的每个 {} 最后都调用该值自己的 __format__ 方法(一个 dunder,当 Python 在值被格式化时调用的双下划线方法),沿着冒号后的任何东西传递。所以任何你写的类都可以通过定义 __format__ 来决定它在 f 字符串内如何出现。转换标志 !r!s!a 首先运行 repr()str()ascii()!r 是要保留的,因为它显示一个值的方式就是你会在代码中输入它,引号和转义包括,在下面的实际例子中显示。

python
name = "alice"
score = 94.5

print(f"Hello, {name}!")           # "Hello, alice!"
print(f"Score: {score:.1f}%")      # "Score: 94.5%"
print(f"2 + 2 = {2 + 2}")          # "2 + 2 = 4"
print(f"Name: {name.upper()}")     # "Name: ALICE"

冒号后的格式说明符控制值是如何显示的:

说明符含义例子
.2f2 位小数f"{3.14159:.2f}""3.14"
.0%百分比,无小数f"{0.94:.0%}""94%"
,千位分隔符f"{1000000:,}""1,000,000"
>10在 10 个字符中右对齐f"{'hi':>10}"" hi"

你最常用的是 .2f:任何时候你显示一个小数并想要一个整洁的数字而不是一长串数字。表中的其他都在你需要时才有。你可以把任何变量、算术或方法调用放在 {}

.2f.0% 覆盖大部分显示格式。对齐说明符(><^)当和一个宽度结合时生产表格输出。通用模式是 {value:[align][width][.precision][type]}。一旦你识别出这些部分,任何说明符都可以读懂而不用记住所有组合。

同样的说明符对不同的类型可以意味着不同的东西,因为每个类型都格式化自己:f"{'hi':5}" 在右边填充文本,而 f"{42:5}" 在左边填充一个数字,同样的 :5,反对的结果。值得成为习惯的转换标志是 !r:它在格式化前运行 repr(),它用引号包围字符串并把看不见的字符(制表符、尾随空格、换行符)变成可见的转义序列。当输出看起来微妙地错时,把 {value} 换为 {value!r} 是看到实际在那里的最快方式。

Junof 字符串f 放在开放引号前,然后把任何变量、和或方法调用包围在 {} 中,Python 在该行运行时就把结果放入。花括号内的冒号控制外观::.2f 表示两位小数是你会依赖的那个。比用 + 胶合文本整洁得多。
Junof 字符串f"..." 在运行时评估 {} 中的任何表达式,并把结果直接放入,不需要 str()。在冒号后来格式说明符::.2f:,:>10 和朋友,全部遵循 {value:[align][width][.precision][type]}。一旦学会这些部分你就可以读懂任何说明符而不用记住它们。
Junof 字符串 每个 {} 调用该值自己的 __format__,所以冒号后的说明符意味着无论那个类型决定什么,那就是为什么 :5 以相反的方向填充文本和数字。要保留的标志是 !r:它显示一个值的方式就是你会输入它的,所以多余空白和其他看不见的垃圾一旦输出看起来不对就跳出来。

多行字符串

要写一个跨越多行的字符串,用三个引号标记:开始处三个 " 和结束处三个。Python 完全保留所有换行符和间距。

三引号字符串按字面意思保留所有空白和换行符。它们是长文本块的标准,比如电子邮件模板和 SQL 查询,以及文档字符串:放在函数或类体开始处的内联文档。

一个三引号字符串完全保留每个字符,包括每行开头的前导空格,那是陷阱:缩进字面量以匹配你的代码,那个缩进会落到文本内。当它是一个函数、类或模块中的第一条语句时,Python 把它保留作为那个对象的 docstringhelp() 为它显示的文档),所以约定是在那里放一个短摘要。要缩进一个三引号块来匹配其周围而不让缩进泄进值,在运行时用 textwrap.dedent() 剥掉它。三个 '''""" 工作相同;""" 是约定。

python
message = """
Dear alice,

Thank you for your order.

Best regards,
The Team
"""
Juno多行字符串 每端三个引号标记让一个字符串跨越几行,Python 完全保留每个换行符和空格就像你输入的。任何时候你有一块文本,像一条消息或模板,它不舒服地放在一行,用它。
Juno多行字符串 三引号按字面意思保留换行符和间距,所以它们适合电子邮件模板、SQL 和任何长块。它们也是文档字符串怎样工作的:一个函数或类顶部的三引号字符串变成它的内联文档。注意缩进,因为你添加来对其的空格最后在文本内。
Juno多行字符串 三引号内的一切都按字面意思保留,前导空白包括,这是陷阱:缩进字面量来匹配你的代码,那个缩进会在值内。把一个短的放在函数或类顶部它就变成 help() 显示的文档字符串。当你需要它在源中缩进但在值中干净,用 textwrap.dedent() 运行它。

转义序列

一些字符很难直接在字符串内输入。Python 使用转义序列:一个反斜杠跟着一个代表某个东西的字母。你会不断用到的两个是 \n 表示新行和 \t 表示制表符。

转义序列让你嵌入会否则破坏语法或不能直接输入的字符。你会用到的:\n(新行)、\t(制表符)、\\(字面反斜杠)、\"\'(在匹配分隔符字符串中的引号)。Windows 路径需要反斜杠,这和转义处理冲突。用 r 作前缀来禁用它。

除了日常的 \n\t,Python 支持 Unicode 转义对于你不能直接输入的字符:\uXXXX\UXXXXXXXX 按其数字命名一个字符,\N{name} 按字按命名它,像 \N{GREEK SMALL LETTER ALPHA}。在真实代码中用到的是 raw string,写作 r"...",它完全关闭转义处理并通过每个反斜杠不变。用它来处理 Windows 路径和正则表达式模式,其中反斜杠是为了那个消费者而不是为了 Python,以及忘记 r 生产悄悄匹配错误东西的模式。

序列字符
\n新行
\t制表符
\\字面反斜杠
\"双引号
\'单引号
python
print("Line one\nLine two")        # 两行输出
print("Name:\tAlice")              # Name:   Alice
path = r"C:\Users\Alice\Documents" # raw 字符串,无转义处理
Juno转义序列 一个字符串内的反斜杠意味着"以特殊方式读下一个字符":\n 开始新行,\t 插入制表符,\\ 是一个真实反斜杠。那两个,\n\t,是你实际会输入的。在引号前弹一个 r 反斜杠回到普通的,对 Windows 路径很便利。
Juno转义序列\n\t\\\" 覆盖日常转义。节省麻烦的:一个 raw 字符串,r"...",关闭转义处理所以每个反斜杠保持字面。对 Windows 路径和正则表达式模式用它,其中反斜杠是为了别的而不是 Python。
Juno转义序列 过了 \n\t 有像 \N{name} 这样的 Unicode 转义,但工人阶级是 raw 字符串 r"...",它通过每个反斜杠不变。到正则表达式模式和 Windows 路径用到,因为忘记它给一个悄悄匹配错东西的模式而不是一个告诉你的错误。

检查字符串内容

Python 有方法回答是/否问题关于一个字符串包含什么。它们返回 TrueFalse。最早有用的:isdigit() 让你在转换它之前检查一个字符串是否全是数字,所以你可以避免在非预期输入上崩溃。

is* 方法每个测试整个字符串的特定属性,只在每个字符都满足条件时返回 True。它们的主要用途是输入验证:在转换前检查来避免在非预期输入上崩溃。isdigit()int() 前是经典模式,一个替代到在文件和异常章节中覆盖的捕获 ValueError

这些检查问"这个的每个字符都是这种吗"跨所有 Unicode,不仅是普通 ASCII 字母和数字。那意味着 "2".isdigit() 对上标 2 也是 True 以及普通 "2",它可以让惊人的输入在验证检查前溜过。当你意思是严格的数字 09 时,合并 s.isascii() and s.isdigit()isnumeric() 更宽还,计数分数和其他有数值的字符,所以用最窄的检查匹配你实际接受,而不是第一个看起来对的。

python
"42".isdigit()       # True
"hello".isalpha()    # True
"hello42".isalnum()  # True
"   ".isspace()      # True
"Hello".islower()    # False
"HELLO".isupper()    # True
Juno检查字符串内容is* 方法回答是或否的问题,只在每个字符符合时返回 True。你会首先用到的:在 int() 前调用 isdigit() 来确保文本真的是一个数字,所以奇怪的输入不会崩溃你。
Juno检查字符串内容 每个 is* 方法是 True 只有当整个字符串通过,这让它们对在转换前验证输入方便。isdigit()int() 前是经典守卫。记住它是测试整个字符串,所以一个空字符串对全部返回 False
Juno检查字符串内容 这些检查跨所有 Unicode,不是普通 ASCII,所以 isdigit() 通过上标和其他数字字符你不意思接受的。当你意思是数字 09 时,配对它:s.isascii() and s.isdigit()。选择最窄的检查匹配你真实的输入,而不是第一个看起来接近的。

实践

削除空白、归一化大小写,然后拉出你需要的。这个序列处理几乎任何用户提供的文本:

python
raw_input = "  [email protected]  "
email = raw_input.strip().lower()   # "[email protected]"

at_pos = email.find("@")
username = email[:at_pos]
domain = email[at_pos + 1:]

print(f"User:   {username}")    # "alice"
print(f"Domain: {domain}")      # "example.com"

从段构建一个 URL 并立即验证和解析它:

python
BASE_URL = "https://api.example.com"
version = "v2"
resource = "users"
user_id = 42

url = f"{BASE_URL}/{version}/{resource}/{user_id}"
# "https://api.example.com/v2/users/42"

parts = url.split("://")              # ["https", "api.example.com/v2/users/42"]
protocol = parts[0]                   # "https"
secured = url.startswith("https")
domain = parts[1].split("/")[0]       # "api.example.com"

print(f"Protocol : {protocol}")
print(f"Secure   : {secured}")
print(f"Domain   : {domain}")

find()、切片和 f 字符串对齐解析一个结构化日志行:

python
log_entry = "[2024-01-15 09:42:11] ERROR: File not found: report.csv"

timestamp = log_entry[1:20]
rest = log_entry[22:]                # "ERROR: File not found: report.csv"
colon_pos = rest.find(":")
level = rest[:colon_pos]             # "ERROR"
message = rest[colon_pos + 2:]       # "File not found: report.csv"

print(f"[{timestamp}] {level:>8}: {message}")
# [2024-01-15 09:42:11]    ERROR: File not found: report.csv

find() 定位边界,切片提取段,>8 格式说明符右对齐严重性标签所以当级别名称在长度上不同时列保持一致。

方法参考

方法作用
.lower() / .upper()转换为全小写 / 全大写
.title() / .capitalize()每个单词首字母大写 / 仅第一个
.strip() / .lstrip() / .rstrip()移除周围空白
.find(sub)第一个匹配的索引,或 -1
.count(sub)sub 出现多少次
.startswith(s) / .endswith(s)前缀 / 后缀检查
.replace(old, new)替换出现
.split(sep)分割成列表
sep.join(iterable)把项连接成字符串
.isdigit() / .isalpha() / .isalnum()字符类型检查