跳到主要内容

字符串

提示

字符串和列表的处理通常离不开循环条件等结构。虽然本书为了集中讲解相关内容,首先介绍了字符串和列表的各种操作方法,但对于初学者来说,更好的学习方式可能是先大致了解一下字符串和列表是什么样的数据。等到学习了循环结构后,再回头来学习这两种数据的复杂操作方法,会更容易理解。

Python 中的字符串(string)是一系列字符的有序集合,可以包括字母、数字、标点符号和其他特殊字符。字符串是一个不可变的序列数据类型。

创建字符串

在 Python 中,较短的字符串,可以使用单引号 ' '、双引号 " " 来表示。跨越多行的长字符串,可以使用三引号 ''' '''""" """ 来表示。例如:

string1 = 'Hello, World!'
string2 = "Hello, World!"
string3 = '''Hello
World!''' # 这是一个多行字符串
string4 = """Another
multi-line
string."""

三引号包裹的大段文字也经常被用来作为程序的注释,这样就不需要在每行的注释前面都加上一个井号了,相当于 C、Java 等语言中的 /* */ 注释符号。

一个小技巧是,如果需要创建的字符串中包含双引号,那么就用单引号来包裹它;反之,如果需要创建的字符串中包含单引号,那么就用双引号来包裹它。例如:

"""
这就是一段注释,程序逻辑并不会用到这里的文字。
下面的语句可以打印出带双引号的文字:
"""
print('小明说: "这不是我干的!"')
print("I'm a student.")

如果创建的字符串中单双引号都需要使用,那么可以使用三引号。如果这些都要用到,那就只能使用转义字符了。

字符串操作

最常用的字符串操作包括拼接、截断等。

拼接

连接两个字符串可以使用 + 运算符:

greeting = "Hello, " + "World!" # 结果:"Hello, World!"

如果重复一段文字,可以使用 * 运算符:

repeated = "abc" * 3 # 结果:"abcabcabc"

索引

索引(index)操作用于从字符串中提取单个字符。其表示方法是:在需要被索引的字符串或变量后面加上方括号,方括号内包含一个整数,表示该字符在字符串中的位置(从 0 开始计数)。非负整数表示从左向右数,最左侧字符的索引是 0;负数则表示从右向左数,最右侧字符的索引是 -1。如果索引超出了字符串的范围,程序会报错。例如:

greeting = "Hello, World!"
print(greeting[0]) # 输出:"H"
print(greeting[-1]) # 输出:"!"
print(greeting[20]) # 程序报错

chinese = "我在学习 Python"
print(chinese[1]) # 输出:"在"

Python 3 比很多其他语言做得更好的一点是,它的字符串默认采用了 Unicode 编码。这意味着字符串中的每个中文字同样是一个字符,占用一个索引单位。这样就不用担心在处理不同语言时使用不一样的索引机制了,避免了像 C 语言中不小心索引到半个中文字符数据的情况。

因为字符串是一个不可变的序列数据类型。试图改变字符串中的字符,程序会报错,比如运行下面的程序:

greeting = "Hello, " + "World!"
greeting[0] = "W" # 程序报错

切片

索引只会取一个字符,我们也可以从字符串中选取一串子字符串,这样的操作被称为切片(slice)。切片使用方括号和冒号获取字符串的子串。方括号中的冒号两边可以指定两个数字,第一个数字是子串开始的位置,第二个数字是子串结束的位置。但是需要注意的是,这两个位置是“左闭右开”的(即包含起始位置,不包含结束位置),只会截取到结束位置前的一个字符。

切片使用的这两个数字可以省略:如果省略左侧数字,表示从最左端开始截取;如果省略右侧数字,则表示截取到最右端。

greeting = "Hello, World!"
print(greeting[1:5]) # 输出:"ello"
print(greeting[7:-1]) # 输出:"World"
print(greeting[7:]) # 输出:"World!"
print(greeting[:]) # 输出:"Hello, World!"

切片还可以接受第三个参数,表示步长(step),即每隔几个字符取一个。

greeting = "Hello, World!"
print(greeting[0:5:2]) # 输出:"Hlo" (在索引0-5范围内,每隔2个取一个)
print(greeting[::-1]) # 输出:"!dlroW ,olleH" (步长为-1,表示倒序截取,这是反转字符串最常用的方法)

字符串长度

使用函数 len() 可以获取字符串的长度,比如:

greeting = "Hello, World!"
length = len(greeting) # 结果:13

字符串的方法

Python 中的各种数据本身都是对象,因而都有一系列操作方法。例如,使用整数的 bit_length() 方法可以得到该整数的二进制有效位数等。在 Python 中可以使用内置的 dir() 函数列出一个对象所有的属性和方法:

print(dir(""))

对比函数和方法:

  • 函数(Function): 是一段独立的代码块,可以接收输入参数,执行特定操作,并返回一个值。
  • 方法(Method): 是附加在类或对象上的函数。在面向对象编程中,方法通常用于操作或与对象的内部数据进行交互。方法就是函数在特定语境下的另一种称谓。

整数的方法可能不那么常用,但是字符串的一些方法还是非常常用的,比如:

  • str.upper(): 将所有字符转换为大写。
  • str.lower(): 将所有字符转换为小写。
  • str.startswith(prefix): 检查字符串是否以特定前缀开始。
  • str.endswith(suffix): 检查字符串是否以特定后缀结束。
  • str.find(sub): 返回子字符串首次出现的索引,如果未找到则返回-1。
  • str.replace(old, new): 将所有出现的旧子字符串替换为新子字符串。
  • str.split(delimiter): 根据指定的分隔符分割字符串。
  • str.join(iterable): 使用字符串作为分隔符连接可迭代对象中的字符串。

在使用上,对象的方法与普通函数的主要区别在于:函数在操作数据时,是将数据作为参数传给函数,例如 len(greeting);而方法虽然也是函数,但在调用时需要先写数据对象(或变量),紧接着写一个点号 .,再写方法名,例如 greeting.upper()

下面是一些使用字符串方法的示例:

greeting = "Hello, World!"

# 使用 str.upper() 将所有字符转换为大写
upper_string = greeting.upper()
print(upper_string) # 输出: "HELLO, WORLD!"

# 使用 str.lower() 将所有字符转换为小写
lower_string = greeting.lower()
print(lower_string) # 输出: "hello, world!"

# 使用 str.startswith() 检查字符串是否以特定前缀开始
prefix = "Hello"
is_start_with_prefix = greeting.startswith(prefix)
print(is_start_with_prefix) # 输出: True

# 使用 str.endswith() 检查字符串是否以特定后缀结束
suffix = "World!"
is_end_with_suffix = greeting.endswith(suffix)
print(is_end_with_suffix) # 输出: True

# 使用 str.find() 返回子字符串首次出现的索引
sub_string = "World"
index = greeting.find(sub_string)
print(index) # 输出: 7

# 如果子字符串不存在,会返回-1
sub_string = "Java"
index = greeting.find(sub_string)
print(index) # 输出: -1

# 使用 str.replace() 将所有出现的旧子字符串替换为新子字符串
replaced_string = greeting.replace("World", "Python")
print(replaced_string) # 输出: "Hello, Python!"

# 使用 str.split() 根据指定的分隔符分割字符串
delimiter = ", " # 使用逗号加空格分割
split_strings = greeting.split(delimiter)
print(split_strings) # 输出: ['Hello', 'World!']

# 使用 str.join() 使用字符串作为分隔符连接可迭代对象中的字符串
words = ["Hello", "Python"]
delimiter = ", "
joined_string = delimiter.join(words)
print(joined_string) # 输出: "Hello, Python"

转义字符

一些特殊的字符是无法直接在键盘上输入的,可能也无法直接在屏幕上显示出来。对于这些特殊字符,Python 通过“转义”(escape)来表示它们。转义是使用反斜杠 \ 开始的字符序列,它代表一个特定的字符或字符序列。

以下是 Python 中常用的转义序列:

  • \\: 代表一个反斜杠字符 \
  • \': 代表一个单引号字符 '
  • \": 代表一个双引号字符 "
  • \n: 代表一个换行符。
  • \t: 代表一个制表�为了解决这个问题,1990 年开始,计算机业界开始研发一种新的编码标准,它可以覆盖全世界所有的字符,也就是说,任何一个字符都有自己独占的编码,在任何系统下都会保持这个编码,这样就不会出现换个系统就乱码的问题了。这就是 Unicode 编码,也称万国码、单一码。Unicode 规定了字符集,但该字符集存在多种不同的编码实现格式。Windows 采用了 UTF-16LE 格式的 Unicode 编码(UTF 全称为 Unicode Transformation Format),使用 16 位(双字节)数据表示一个字符。而当前最流行的 Unicode 编码格式是 UTF-8。这是一种可变长度的编码方式,根据字符 of 常用程度,使用 1 到 4 个字节来表示。目前绝大多数的 Unicode 文档采用的都是 UTF-8 编码,它也是 Python 字符的默认编码格式。

多数情况下,字符串和字节之间的转换采用默认编码格式即可,但也有可能,我们需要把字符串转换成其他编码格式,以确保它们在一些非 UTF-8 编码的设备上正确显示。下面用代码演示一下几种不同编码格式的区别。出: Hello

World! (输出过程中换行了)

print("Hello\tWorld!") # 输出: Hello World! print("\ 是个反斜杠") # 输出: \ 是个反斜杠


如果不想让反斜杠起到转义作用,可以使用原始字符串。在字符串前加上 r 或 R,使其成为原始字符串,这样就不会对字符串中的 `\` 进行转义。

例如:

```python
print(r"Hello\nWorld") # 输出: Hello\nWorld

这种情况在表示文件路径时特别有用,例如 r"C:\path\to\directory",这样就不需要每个反斜杠都进行转义了。

字符串格式化

字符串格式化是将特定的值插入到字符串的某些位置的过程。Python 提供了多种方式来格式化字符串。早期 Python 使用 % 运算符进行格式化,后来出现了更强大的 str.format() 方法。虽然这两者在现代 Python 代码中依然有效,且在某些特定场景(如模板字符串存储在外部文件中)下仍然必须使用 str.format(),但对于日常编码,目前 Python 最推荐且最高效的方法是使用 f-string。

f-string 使用前缀 f 来定义字符串,然后在字符串中的花括号 {} 内直接包含 Python 表达式。在显示字符串的时候,Python 会计算花括号 {} 内表达式的值,直接把这个值作为字符串的一部分显示出来。比如,把变量值嵌入字符串:

name = "老王"
age = 60
print(f"我是{name},我今年 {age} 了。")
# 输出: 我是老王,我今年 60 了。

f-strings 允许执行简单的表达式,甚至函数调用:

name = "老王"
age = 60
print(f"我是{name},我五年后是 {age + 5} 岁。")
# 输出: 我是老王,我五年后是 65 岁。

如果只是把单个其他类型的数据转换成字符串,也可以不用 f-string,而是使用 str() 函数,比如:

print(str(123)) # 输出: '123'
print(str(123.456)) # 输出: '123.456'
print(str([1, 2, 3])) # 输出: '[1, 2, 3]'
print(str((1, 2, 3))) # 输出: '(1, 2, 3)'
print(str(True)) # 输出: 'True'

小数的格式代码

如果要格式化小数,可以在大括号中添加格式说明符来控制小数的显示方式。格式说明符通常遵循 {变量名:格式代码} 的形式。

例如,如果想要格式化一个浮点数,并且希望它显示为小数点后两位,你可以这样做:

number = 123.456
formatted_number = f"{number:.2f}"
print(formatted_number) # 输出: 123.46

在这个例子中,.2f 是一个格式说明符,它表示将数字格式化为浮点数,并保留两位小数。.2 指定了小数点后的位数,而 f 表示浮点数格式。

除了控制小数点后的位数,f-string 还允许进行各种其他类型的格式化,例如填充、对齐、百分比格式等。例如:

  • 使用百分比格式:

    percentage = 0.1234
    formatted_percentage = f"{percentage:.2%}"
    print(formatted_percentage) # 输出: 12.34%
  • 指定宽度和对齐方式,小数表示:

    number = 123.456
    formatted_number = f"{number:10.2f}"
    print(formatted_number) # 输出: 123.46

    这里 10.2f 表示总宽度为 10 个字符,其中小数点后有两位,右对齐。

  • 指定宽度和对齐方式,科学记数法表示:

    number = 123.456
    formatted_number = f"{number:10.2e}" # 显式指定 e 表示科学计数法
    print(formatted_number) # 输出: 1.2e+02

    这里 10.2 表示总宽度为 10 个字符,.2 表示保留小数点后两位。

字节序列

表示方法

字节序列类型(bytes,有时候就简称为“字节”)与字符串看上去非常类似,它在代码中通过在字符串字面量前加上字母 b 前缀来表示。

s = "Hello, World!" # 这是字符串
print(s)

byte_sequence = b'Hello, World!' # 这是字节序列,而非字符串
print(byte_sequence) # 输出: b'Hello, World!'

# 有时会用十六进制格式表示字节序列
b = b'\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21'
print(b) # 输出: b'Hello, World!'

注意:虽然我们用十六进制 \x48 等方式定义了变量 b,但 Python 在打印字节序列时,如果该字节对应的是 ASCII 可打印字符(如字母、数字),它会自动显示为对应的字符(如 H),只有无法显示的字节才会保留十六进制形式。

字节序列与字符串的主要区别在于存储的内容和应用场景。

字符串是由字符组成的序列,通常用于存储文本信息。在 Python 3 中,字符串由 Unicode 字符组成,可以表示几乎所有语言中的字符。字符串主要用于读取或写入文本文件、处理表单输入的文本、在屏幕上显示文本等。

字节序列保存的数据除了文本之外,也可以是任何二进制数据。字节序列顾名思义,是字节的序列。字节是 8 位长的二进制数,可以表示 0 到 255 的数值。字节序列通常用于读取或写入二进制文件(如图片或视频文件)、网络套接字通信、数据加解密等。

字符串和字节之间的转换

有时我们需要将从网络或其他设备中读取的、以字节形式保存的文本数据转换为字符串,或者需要将字符串以二进制形式发送。这时,可以使用字符串的 encode() 方法和字节序列的 decode() 方法在字符串与字节之间进行转换。转换时必须指定文字编码。使用不同的编码,同样的文字转换成的二进制数据是不同的。Python 默认使用 UTF-8 来编码字符串。在此之前,我们先来简单了解一下字符编码的发展历史:

计算机是在美国被发明的,所以当时很自然的就只考虑了处理英文。最早出现且最著名的字符编码标准是 ASCII 标准(American Standard Code for Information Interchange,美国信息互换标准代码)。它定义了 128 个字符,包括英文字母大小写,数字,常用的标点和一些特殊符号。当时世界上大多数计算机都在使用 ASCII 方案来保存英文文本。该方案最大的问题是只支持英文字符,于是其他国家、组织和公司纷纷开始扩展这个标准,以支持其他字符,如中文、日文字符、制表符及数学符号等。在中国,最常用的标准,包括 GB2312、GBK、GB18030 等都是对 ASCII 的中文扩展。这些扩展出的标准有一个很麻烦的问题:同一个数值在不同的编码标准下被赋予了不同的含义。例如,某一数值在中文标准下可能是一个中文字符,在韩文标准下可能就是一个完全不相关的制表符。这就导致在中文环境下开发的软件,在韩文系统上运行时显示的完全是乱码。如果有人需要在一个系统中同时运行一个中文软件和一个韩文软件,就只能有一个软件可以正确显示文字。

为了解决这个问题,1990 年开始,计算机业界开始研发一种新的编码标准,它可以覆盖全世界所有的字符,也就是说,任何一个字符都有自己独占的编码,在任何系统下都会保持这个编码,这样就不会出现换个系统就乱码的问题了。这就是 unicode 编码,也叫万国码、单一码。unicode 规定了字符集,但是这套字符集也还存在多种不同的编码格式。Windows 采用了 UTF-16LE 格式的 unicode 编码(UTF全称为 Unicode Transformation Format),使用 16 位的(双字节)数据表示一个字符。但是当前最流行的 unicode 编码格式却是 UTF-8,这是一种变长的编码方式,根据字符的常用程度,使用不同长度的编码来表示这个字符,编码长度有可能是 1 到 6 个字节。目前大多数的 unicode 文档采用的都是 UTF-8 编码,它也是 Python 中字符的默认编码格式。

多数情况下,字符串和字节之间的转换采用默认编码格式即可,但也有可能,我们需要把字符串转换成其它编码格式,以确保它们在一些非 UTF-8 编码的设备上正确显示。下面用代码演示一下几种不同编码格式的区别。

中文的几种不同编码:

chinese_str = "中文"

# UTF-8 是 Python 默认的编码格式
print(chinese_str.encode('UTF-8')) # 输出: b'\xe4\xb8\xad\xe6\x96\x87'
# UTF-16LE 是 Windows 的编码格式
print(chinese_str.encode('UTF-16LE')) # 输出: b'-N\x87e'
# GB2312 是最常用的非 Unicode 中文编码格式
print(chinese_str.encode('GB2312')) # 输出: b'\xd6\xd0\xce\xc4'

可以看到,几种不同编码把同一个中文字符转换成了不同的二进制数据。

英文也可以使用这些编码格式:

english_str = "Hello"
print(english_str.encode('UTF-8')) # 输出: b'Hello'
print(english_str.encode('GB2312')) # 输出: b'Hello'
print(english_str.encode('ASCII')) # 输出: b'Hello'
print(english_str.encode('UTF-16LE')) # 输出: b'H\x00e\x00l\x00l\x00o\x00'

UTF-8 和 GB2312 对于英文字符的编码是兼容 ASCII 标准的,它们把英文字符转换成的二进制数据与 ASCII 的标准完全相同。但 Windows 采用的 UTF-16LE 编码并不兼容 ASCII 标准。

把二进制数据解码为字符串的时候,一定要采用同样格式的编码,否则转换出来很可能就不是正确的字符串了:

chinese_str = "中文"
print(chinese_str.encode('UTF-8').decode('UTF-8'))
print(chinese_str.encode('UTF-16LE').decode('UTF-16LE'))
print(chinese_str.encode('GB2312').decode('GB2312'))

# 上面的解码都可以正确得到字符串,但下面的解码过程会出错,因为使用了错误的编码格式
# print(chinese_str.encode('UTF-8').decode('GB2312'))

english_str = "Hello"
print(english_str.encode('UTF-8').decode('ASCII')) #输出: Hello
# 上一行代码虽然使用了不同的格式编解码,但两种格式对于英文字符是兼容的,因此依然可以得到正确的字符串

练习

字符串的很多操作方法需要结合条件、循环等语句才能完成。这里仅列出一些最基础的练习题目。等到我们学习了后续章节,会再进行更多的练习。

  1. 判断下面程序的运行结果:print(len("Hello\nWorld!"))
  2. 反转字符串:输入一个字符串并输出它的反转形式,比如输入 "hello",则输出 "olleh"。
  3. 是否是回文:编写一个程序检查输入的字符串是否为回文(正反读一样的字符串)。
  4. 首字母大写:编写一个程序,将字符串中的每个单词的首字母大写。
  5. 整数所有位上数字的乘积:输入一个表示整数的字符串,编写程序计算该数字符串中所有位上数字的乘积。
  6. 变位词:编写程序,输入两个字符串,判断它们是否为变位词(即由相同字符组成,字符顺序不同)。
  7. 最大相同字符长度:输入一个字符串,统计其中连续相同字符的最大长度。例如,字符串 aaabbbbaa 中,最大连续长度是 4,有连续的 4 个 b。