05 - 字符串
05 - 字符串
字符串是编程里用得最多的数据类型之一,没有之一。这章内容比较多,但都很实用,建议多动手试试。
创建字符串
前面已经见过了,用引号包起来的就是字符串。单引号、双引号、三引号都行:
s1='你好's2="你好"s3="""这是一段 很长的文字 可以换行"""单引号和双引号完全等价。三引号("""或''')比较特殊,它可以跨越多行:
poem="""静夜思 床前明月光, 疑是地上霜。 举头望明月, 低头思故乡。"""print(poem)索引和切片
字符串里的每个字符都有一个位置编号(索引),从 0 开始数:
s="Python"# P y t h o n# 0 1 2 3 4 5用索引取单个字符:
print(s[0])# Pprint(s[3])# hprint(s[-1])# n(负数从后往前数,-1 是最后一个)print(s[-2])# o切片就是取一段,格式是[起始:结束],包含起始不包含结束(左闭右开):
s="Python"print(s[0:3])# Pyt(索引 0、1、2)print(s[2:5])# thoprint(s[:3])# Pyt(从头开始,0 可以省略)print(s[3:])# hon(到末尾,可以省略)print(s[:])# Python(全部复制一份)左闭右开这个设计一开始可能觉得别扭,但用多了会发现很方便:s[0:3]就是取 3 个字符,s[3:6]也是取 3 个字符,长度直接就是结束 - 起始。
步长
切片还可以加第三个参数——步长:
s="Python"print(s[::2])# Pto(每隔一个取一个)print(s[::-1])# nohtyP(反转字符串)s[::-1]反转字符串这个用法很经典,面试也经常问。原理是步长为 -1,从后往前取。
字符串是不可变的
这个很重要:字符串一旦创建就不能修改。
s="hello"# s[0] = "H" # 这行会报错!TypeError# 要"修改"只能创建新的s="H"+s[1:]# "Hello"你可能会想,那s = "Hello"不是修改了吗?不是。那是创建了一个新字符串"Hello",然后让s指向它。原来的"hello"还在内存里,只是没有变量引用它了(之后会被垃圾回收)。
常用方法
字符串的方法太多了,我挑最常用的说。
大小写
s="Hello World"print(s.upper())# HELLO WORLDprint(s.lower())# hello worldprint(s.title())# Hello World(每个单词首字母大写)print(s.capitalize())# Hello world(只有第一个字母大写)print(s.swapcase())# hELLO wORLD查找和替换
s="Hello World"print(s.find("World"))# 6(返回起始位置)print(s.find("Python"))# -1(找不到返回 -1)print(s.index("World"))# 6(跟 find 类似)# print(s.index("Python")) # 这个会报错!找不到会抛异常print(s.replace("World","Python"))# Hello Pythonprint(s.count("l"))# 3("l" 出现的次数)find和index的区别:找不到时find返回 -1,index直接报错。看你的需求选,如果不确定字符串里一定有就用find。
去除空白
s=" hello "print(s.strip())# "hello"(去两端空白)print(s.lstrip())# "hello "(去左边)print(s.rstrip())# " hello"(去右边)# 也可以指定去除的字符s2="---hello---"print(s2.strip("-"))# "hello"处理用户输入时经常用strip(),因为你永远不知道用户会不会多打几个空格。
分割和拼接
这两个方法特别实用:
# split:把字符串按某个分隔符切成列表s="苹果,香蕉,橘子"fruits=s.split(",")print(fruits)# ['苹果', '香蕉', '橘子']# 不传参数的话,按空白字符切s2="hello world python"print(s2.split())# ['hello', 'world', 'python']# join:反过来,把列表拼成字符串(跟 split 相反)words=["I","love","Python"]result=" ".join(words)print(result)# "I love Python"# 用不同的连接符print("-".join(words))# "I-love-Python"print("".join(words))# "IlovePython"注意join是字符串的方法,不是列表的方法。语法是连接符.join(列表),不是列表.join(连接符)。我当初学的时候老搞反。
判断类
s="hello"print(s.startswith("he"))# Trueprint(s.endswith("lo"))# Trueprint(s.isalpha())# True(全是字母)print(s.isdigit())# False(不是纯数字)print(s.isalnum())# True(全是字母或数字)print("12345".isdigit())# Trueprint("hello world".isalpha())# False(有空格)字符串格式化
这个必须好好学,因为你会用到非常非常多次。
f-string(推荐)
Python 3.6 加入的,目前最好用的格式化方式:
name="小明"age=25print(f"我叫{name},今年{age}岁")# 我叫小明,今年25岁大括号里可以放表达式:
price=9.9count=3print(f"总价:{price*count:.2f}元")# 总价:29.70元:2f是格式说明符,.2f表示保留两位小数。
更多格式化选项:
# 控制小数位数print(f"{3.14159:.2f}")# 3.14# 百分比print(f"{0.856:.1%}")# 85.6%# 千位分隔符print(f"{1000000:,}")# 1,000,000# 补零print(f"{5:03d}")# 005# 对齐print(f"{'hello':<10}")# "hello "(左对齐,总宽10)print(f"{'hello':>10}")# " hello"(右对齐)print(f"{'hello':^10}")# " hello "(居中)format() 方法
比 f-string 早,有些老代码里会看到:
name="小明"age=25print("我叫{},今年{}岁".format(name,age))print("我叫{name},今年{age}岁".format(name=name,age=age))跟 f-string 效果差不多,但写起来啰嗦一些。新代码直接用 f-string 就行。
% 格式化(老式)
Python 2 时代的写法,了解就好,别在新代码里用:
name="小明"print("我叫%s"%name)print("价格是%.2f"%9.9)转义字符
有些特殊字符不能直接写在字符串里,需要用反斜杠\转义:
print("他说:\"你好\"")# 他说:"你好"print("第一行\n第二行")# 换行print("制表符\t在这里")# Tabprint("反斜杠:\\")# 反斜杠:\如果觉得转义麻烦,可以在字符串前面加r(raw string),所有字符都按原样处理:
# 文件路径经常用 raw stringpath=r"C:\Users\xiaoming\Desktop"print(path)# C:\Users\xiaoming\Desktop# 正则表达式也常用importre pattern=r"\d+"字符串的编码
Python 3 的字符串是 Unicode 的,所以中文、日文、emoji 都没问题:
s="你好🌍🎉"print(len(s))# 4(每个字符算一个,包括 emoji)但如果你需要跟文件、网络打交道,就会遇到编码问题。最常见的:
- UTF-8:目前最通用的编码,中文字符占 3 个字节
- GBK:Windows 中文系统默认编码,中文占 2 个字节
# 字符串转字节b="你好".encode("utf-8")print(b)# b'\xe4\xbd\xa0\xe5\xa5\xbd'# 字节转字符串s=b.decode("utf-8")print(s)# 你好乱码问题 90% 都是编码不一致导致的。记住一个原则:读进来的时候解码(decode),写出去的时候编码(encode),编码格式保持一致。
format_map:配合字典做模板
format_map()跟format()类似,但它接收一个字典来填充:
template="我叫{name},今年{age}岁,来自{city}"info={"name":"小明","age":25,"city":"北京"}print(template.format_map(info))# 我叫小明,今年25岁,来自北京跟format()的区别:format()要一个个传参数,format_map()直接丢一个字典进去。当你数据本身就是字典的时候(比如从 JSON 读出来的),用起来更顺手:
importjson data=json.loads('{"name": "小明", "age": 25}')template="用户 {name},年龄 {age}"print(template.format_map(data))还有一个技巧——用defaultdict来处理字典里缺少的键:
fromcollectionsimportdefaultdictclassSafeDict(defaultdict):def__missing__(self,key):returnf"{{{key}}}"# 缺少的键原样保留template="我叫{name},喜欢{hobby}"info=SafeDict(str,name="小明")print(template.format_map(info))# 我叫小明,喜欢{hobby}这样缺少的字段不会报错,而是保留占位符。在做邮件模板、消息模板这类场景很实用。
本章小结
- 字符串用引号创建,三引号可以跨行
- 索引从 0 开始,负数索引从后往前
- 切片
[起始:结束:步长],左闭右开 s[::-1]是反转字符串的经典写法- 字符串不可变,"修改"其实是创建新字符串
- f-string 是目前最好用的格式化方式
split()切、join()拼,这两个方法要记牢format_map()配合字典做模板填充
面试题
Q1:如何反转一个字符串?
点击查看答案最 Pythonic 的方式是用切片:
s="hello"reversed_s=s[::-1]# "olleh"[::-1]表示从头到尾取,步长为 -1(即从后往前),效果就是反转。
也可以用"".join(reversed(s)),但没有切片写法简洁。
Q2:find()和index()有什么区别?
都是查找子串的位置,区别在于找不到时的行为:
find()返回-1index()抛出ValueError异常
选择建议:如果不确定子串一定存在,用find();如果确定存在且找不到属于异常情况,用index()。
Q3:为什么说 Python 的字符串是"不可变的"?这对编程有什么影响?
点击查看答案不可变意味着字符串创建后,不能修改其中某个字符。s[0] = "H"会报 TypeError。
影响:
- 每次"修改"字符串(如拼接)都会创建新对象,频繁拼接大字符串效率低(应该用
join()) - 字符串可以安全地作为字典的 key(因为不可变所以哈希值稳定)
- 多个变量可以安全地引用同一个字符串,不用担心被意外修改
Q4:split()和join()分别做什么?为什么join是字符串的方法而不是列表的方法?
split()把字符串按分隔符切成列表:"a,b,c".split(",")→['a', 'b', 'c']join()把列表用连接符拼成字符串:",".join(['a', 'b', 'c'])→"a,b,c"
join()是字符串方法因为:
- 它要求列表元素都是字符串,连接符本身也是字符串,放在字符串类型下更自然
- 它可以作用于任何可迭代对象(不只是列表),放在字符串下更通用
- 从设计角度,
join的结果是字符串,由结果类型来提供方法是合理的设计
上一章 | 下一章:列表与元组 →
