我们存储一个整数或字符串,可以用一个变量来表示,比如我现在想存储一名学生成绩和课程的名称,可以这么写:
score = 88course = "Python程序设计"类型我们无需关心,Python解释器自会帮我们推导,但我们知道这分别对应了int和str类型,这属于Python中的基本类型。
变量少倒是无所谓,但是如果我们要记录一个班里有50个人的学习成绩,要写50个变量吗?
虽然可以这么干,但这显然是一件耗时的事情,如果将规模再扩大,有5000个,5万个,50万,甚至更多的时候,如果还这么写变量,那就不用写程序了,因为写变量就浪费了大量的时间。
但问题不止于此,比如无法做批量操作,无法应对一些复杂的业务需求。于是一些复合的数据类型便应运而生,在Python中有列表、元组、字典等这些容器类型,它们属于Python中内置的基础数据结构,也就是我们本章要聊得主要内容。
但在聊列表、元组这些数据结构之前,我想先聊一下str(字符串)类型,因为字符串几乎是所有编程语言中应用最为广泛的数据类型之一,在Python中也不例外,而且提供了非常丰富的字符串操作方法。
Python中的字符串有一个核心的特性:不可变性,这意味着一旦创建,字符串的内容就不能被修改,即便你做了一些操作,看似修改了,实则返回了一个新的字符串。接下来我们来看一下比较高频的一些操作。
索引就是字符在字符串中的位置,从0开始计数,比如我们现在定义一个字符串Python,那么它的第0个位置就是字母P,第1个位置就是字母y,后面以此类推。用代码可以这么写:
s = "Python"print(s[0]) # 输出:Pprint(s[3]) # 输出:hPython不仅有从左到右这种正向索引访问字符串,还支持反向索引,也就是从右到左开始计数,即倒数第一个字符索引为-1,倒数第二个字符索引为-2,以此类推。比如说我们访问Python这个字符串中的字n,从正向索引访问是5,那么利用负向索引就是-1,代码如下:
s = "Python"print(s[5]) # 输出:nprint(s[-1]) # 输出:n索引只能帮助我们访问字符串中的某个字符,如果我们想访问多个或者说访问指定索引范围内的字符,也就是说我们访问的是一个字符串中的子串,可以使用切片操作,切片的基本语法为:
[start:stop: step]start:起始索引stop:结束索引,不包含结束位置step:步长,也就是每次可以跳跃的距离,默认为1
比如我们现在访问字符串Python中索引2到5部分的内容,也就是读取thon这几个字符,可以这么写:
s = "Python"print(s[2:6]) # 输出:thon因为stop结束索引是不包含的,所以结束的索引写的是6才能访问到最后一个字符,像这种直接从某个位置访问到字符串的最后一个位置,其实结束位置可以省略不写,也就是这样:
s = "Python"print(s[2:]) # 输出:thon同样,如果从第一个字符开始,我们也可以省略start索引,比如访问Python字符串中的Pyth部分,那就可以这么写:
s = "Python"print(s[:4]) # 输出:Pyth如果start和stop索引都不写,那就是要访问整个字符串了:
s = "Python"print(s[:]) # 输出:Python在我们没有设置step步长的时候,默认就是1,也就是读取完一个再读取下一个。如果我们设置步长为2,也就是隔一个字符读取一次,索引就变成了0,2,4...这种,代码可以这么实现:
s = "Python"print(s[::2]) # 输出:Pto上面的代码是字符串从头到尾读取,步长为2,其中的start和stop索引位置可以根据实际情况进行设置。
我们在前面聊索引的时候提到索引是支持反向索引的,也就是索引可以为负数,在切片这个操作中,不仅索引可以为负数,步长也可以为负数。
索引为负数就是从右往左开始计数,步长为负也是类似的思路,它表示切片从右往左取字符,步长的绝对值表示跳过的字符。索引和步长都可以为正为负,混合使用很容易造成理解困难,尽量在合适的场景中使用,比如字符串反转,使用负步长就是个不错的场景:
s = "Python"print(s[::-1]) # 输出:nohtyP再比如我们获取一个文件名的后缀,使用负索引就很容易实现:
s = "Python从入门到实战.pdf"print(s[-4:]) # 输出:.pdf需要注意的是,切片的操作必须满足start->stop方向与步长的方向一致,如果不一致则输出空字符串,像下面这种就会输出空的字符串:
s = "Python"print(s[-1:-6]) # 控制台看到的是空白之所以在控制台看到的是空白,因为索引-1到-6是从右往左的方向,而默认的步长是1,也就是从左到右的方向,两者方向不一致,就输出空白字符,如果想让上面的代码有字符串输出,则需要把步长也用负数,也就是这样:
s = "Python"print(s[-1:-6:-1]) # 输出:nohty字符串之间可以用+运算符来连接:
s1 = "Hello"s2 = "Python"s = s1 + "," + s2print(s) # 输出:Hello,Python但数字和字符串之间是不能拼接的,会出现TypeError异常,比如:
x = 1s = "Python"print(x + s)Python是一种动态类型语言,同时也是一种强类型语言,所以Python中的值都是有固定类型的,且不允许隐式类型转换,也就是Python不会偷偷地为你转换数据类型,而1和Python显然是不同的类型,所以就会出现TypeError异常。
如果现在让你实现一个打印分割线,用-来实现,你会如何做?
一种方式是可以直接打印一定数量的横线,比如20个:
print("--------------------")这是一种简单直接的方式,还有一种方式是可以使用*运算符让字符串重复一定次数:
print("-" * 20)这种方式更简洁,灵活,可维护性也更好,也更推荐使用。
这里面有一个需要注意的细节,如果我将重复的次数写成0或负数会发生什么?
答案是会输出空字符串,你可以自己尝试一下。
获取字符串的长度是一个比较常用的操作,比如我们想知道用户密码的长度是否符合要求,可以用len()函数来实现:
password = "admin"print(len(password))判断某个字符或字符串是否在一个字符串中,可以使用in或not in。
s = "Hello,Python"print("P" in s) # 输出:Trueprint("Python" not in s) # 输出:FalsePython中提供了丰富的大小写转换方法,比如:
还有一些判断方法,返回的都是布尔值,比如:
下面我们来看一些例子:
s1 = "python"print(s1.upper()) # 输出:PYTHONs2 = "PYTHON"print(s2.lower()) # 输出:pythons3 = "hello, python"print(s3.capitalize()) # 输出:Hello, pythonprint(s3.title()) # 输出:Hello, Pythons4 = "Python"print(s4.swapcase()) # 输出:pYTHONprint(s2.isupper()) # 输出:Trueprint(s1.islower()) # 输出:Trues5 = "Hello, Python"print(s5.istitle()) # 输出:Trueprint(s5.isalpha()) # 输出:Falses6 = "123456789"print(s6.isdigit()) # 输出:True去除空白字符最常用的方法是strip(),它会去掉字符串开头和结尾的所有空白字符,它还有两个更为细化的方法,lstrip()只去除字符串开头的空白字符,rstrip()去除字符串结尾的空白字符。
我们来看一下例子:
s = " Hello Python "print(s.strip()) # 输出:Hello Pythonprint(s.lstrip()) # 输出:Hello Python (右边空格还在)print(s.rstrip()) # 输出: Hello Python(左边有空格)这里的空白字符不仅仅指的是空格,还包括制表符\t、换行符\n、回车符\r等,而且它还可以自定义要去除的字符,比如去除字符串首尾的#,示例如下:
s = "##Python##"print(s.strip("#")) # 输出:Python字符串查找可以使用find()和index()方法,它们两个功能相似,不同之处在于find()如果查找不到指定子串会返回-1,而index()则会抛出异常,如果能查找到则返回子串第一次出现的索引。
我们来看一个例子,比如在“Hello,Python”这个字符串中查找字符o,那么我们可以这么实现:
s = "Hello,Python"print(s.find("o")) # 输出:4print(s.index("o")) # 输出:4print(s.find("a")) # 输出:-1print(s.index("a")) # 会打印ValueError的异常信息它们还都支持指定搜索范围,第二和第三个参数分别是start(开始索引)和end(结束索引)。它们还有rfind()和rindex()方法,也就是支持从字符串右侧开始查找。你猜为什么没有lfind()和lindex()方法呢?给你2秒钟时间考虑.
因为默认就是从左侧开始查找的啊(哈哈...)。
如果我们想统计一个子串在字符串中出现的次数,可以使用count()方法,比如统计字符串“Hello,Python”中有多少个o,可以这么写:
s = "Hello,Python"print(s.count("o"))count()方法中也支持start和end索引位置的设置。
替换字符串可以使用replace(old, new, /, count=-1)方法:
我们来看一个例子:
s = "Hello,Python,Python,Python"print(s.replace("Python", "Go")) # 输出:Hello,Go,Go,Goprint(s.replace("Python", "Go", 2)) # 输出:Hello,Go,Go,Python除了replace()方法,Python中还有translate()、re.sub()(基于正则表达式)等方法来实现字符串替换,这里就不再详细介绍了。
字符串分割是指将一个字符串按照指定的分隔符切成多个子串,并返回包含这些子串的列表。split()方法是比较常用的字符串分割方法,适合处理csv、日志文件、配置文件等有固定格式分割的文本,我们来看一下例子:
s = "Hello,Python"print(s.split(",")) # 输出:['Hello', 'Python']上面的例子,我们用逗号分隔了字符串,得到的是包含子串的列表,之后我们可以进行其他的操作。如果你想将字符串只分割一部分,split()方法还有第二个参数,可以指定分割的次数,示例如下:
s = "Hello,Python,Java,Go"print(s.split(",", 2)) # 输出:['Hello', 'Python', 'Java,Go']split()方法是从左侧开始分割,Python中还提供了反向分割的方法,也就是从右侧开始分割,使用rsplit()方法,它和split()几乎一样,差异在指定分割次数时,rsplit()是从右侧开始分割指定的次数,我们来看一个例子:
path = "/user/local/data/test.txt"print(path.split("/", 1)[1]) # 输出:user/local/data/test.txtprint(path.rsplit("/", 1)[1]) # 输出:test.txt对于一个文件路径,如果都只分割一次,并取索引为1的值,它们得到的结果是不同的,从这个例子中我们可以看出同样分割一次,提取文件名,rsplit()方法还是有些优势的。
以上我们说到的主要是分割单行文本,split()方法虽然也可以处理分割多行文本,但由于不同的系统换行分隔符有些差异,处理起来可能比较麻烦或者要踩一些坑,在Python中其实提供了一个更好的行分割方法:splitlines(),它比较适合处理多行文本,而且跨平台友好,我们来看一下如何使用:
lines = "IP:192.168.1.100\nport:5000"print(lines.splitlines()) # 输出:['IP:192.168.1.100', 'port:5000']拼接和分割可以说是互为逆操作,分割将一个字符串拆成列表,而拼接是将列表/元组中元素(必须是字符串类型)拼成一个字符串,join()方法是Python中比较常用的一个拼接方法。我们来看一个例子:
words = ["Hello", "Python"]print(",".join(words)) # 输出:Hello,Python这里尤其需要注意的是列表/元组中的元素必须是字符串类型,否则会出现TypeErrory异常。我们在拼接和重复那部分聊到了+运算符可以将字符串连接起来,从功能上来说,join()方法能实现的,+运算符也可以实现,但两者在性能上会有差距,join()方法更高效。因为字符串是不可变对象,使用+运算符就意味着每次拼接都要创建一个新的字符串并复制旧的内容,而join()方法会先计算最终字符串的总长度,只分配一次内存。
判断字符串的前缀用startswith()方法,后缀用endswith()方法,返回的结果是True或False,我们来看一个例子:
s = "http://localhost:5000/example"print(s.startswith("http")) # 输出:Trues1 = "Python从入门到实战.pdf"print(s1.endswith(".pdf")) # 输出:Truestartswith()和endswith()方法都还有两个参数,第二和第三个参数分别是start和end,也就是起始索引和结束索引,在这个指定范围内判断前缀和后缀。前缀和后缀的判断不仅仅支持一个元素,还可以使用元组进行多个元素的判断,匹配到元组中的任何一个元素都会返回True,否则返回False。
我们来看一个例子:
s = "http://localhost:5000/example"print(s.startswith(("ftp", "https", "http"))) # 输出:Trues1 = "Python从入门到实战.pdf"print(s1.endswith((".txt", "doc", ".pdf"))) # 输出:True在Python中格式化字符串不止一种方式,但对于我们现在使用的3.14.x的版本来说,必须要了解的当首推f-string,简单来说,就是在字符串前加f或F,在{}内嵌入变量或表达式,我们在前面的文章中也有用到,现在我们来看一个例子:
name = "张三"age = 20print(f"姓名:{name},年龄:{age}") # 输出:姓名:张三,年龄:20在{}中放入变量是比较基础的用法,我们上面说到还可以放入表达式,比如:
x = 1y = 2print(f"{x} + {y} = {x + y}") # 输出:1 + 2 = 3表达式也不仅仅支持上面的这种算术表达式,还可以是函数或方法。比如:
name = "zhang san"print(f"His name is {name.title()}") # 输出:His name is Zhang Sanf-string还提供了一些格式化输出,在{}内使用:来分割表达式和格式说明符,语法是这样的:
{expression:format_spec}它可以控制浮点数精度、数字填充与对齐、千位分隔符、进制转换、百分比等,我们来看一些例子:
PI = 3.1415926print(f"*保留2位小数:{PI:.2f}") # 输出PI结果:3.14N = 20print(f"****默认输出:{N}")print(f"指定宽度输出:{N:5}")print(f"******右对齐:{N:>5}")print(f"******左对齐:{N:<5}")print(f"****居中对齐:{N:^5}")print('-' * 20)LARGE_NUM = 1000000print(f"千位分隔符:{LARGE_NUM:,}")print(f"千位分隔符:{LARGE_NUM:_}")print(f"进制转换:{N:b}")PERCENT = 0.86743print(f"百分比:{PERCENT:.1%}")上述代码的输出结果为:
*保留2位小数:3.14****默认输出:20指定宽度输出: 20******右对齐: 20******左对齐:20 ****居中对齐: 20 --------------------千位分隔符:1,000,000千位分隔符:1_000_000进制转换:10100百分比:86.7%我们用*作为占位符,这样可以更容易看出指定宽度和不同对齐输出结果的区别。根据上面的代码和输出的结果想必你也大致总结出了格式说明符的规则,我们来说一下上面的这些格式说明符:
.2意思是保留两位小数,f表示以浮点数形式展示,保留的小数位数可以根据实际情况设置。>表示右对齐,^表示居中,<表示左对齐。我们还可以用,和_来将一个大数每三位分割一次,这样便于阅读。d表示十进制,o表示八进制,x表示十六进制等。%来展示百分比输出,这里的.1也是指定的输出小数的位数,但不需要带f,因为用了%也就意味着输出浮点格式。在Python 3.8 f-string中还增加了一个调试说明符=,在表达式后使用=可以同时输出表达式和值,方便调试,我们来看一个例子:
x = 1y = 2print(f"{x + y = }") # 输出:x + y = 3f-string是将变量或表达式嵌入到{}中,但是如果我想输出{}呢?那就多套两层,使用两个{{}}进行转义,示例如下:
name = "张三"print(f"{{{name}}}") # 输出:{张三}上面的f-string的使用方法,我们介绍的都是单行输出,f-string也可以进行多行输出,可以使用三个单引号(''')或三个双引号("""),我们来看一下例子:
name = "张三"age = 20s = f""""name": {name},"age": {age}"""print(s)上述代码输出的结果为:
"name": 张三,"age": 20如果你仔细观察可能会发现输出的内容在前后都多了一个空行,这是由于开头和结尾的三个引号引起的,输入三个引号后我们回车继续输入内容,其实是隐藏了一个换行符,结尾也是一样。
Python中的字符串类型是我们能够看得懂的Unicode字符序列,也就是文本,编码就是将文本转换为字节,解码则是将字节还原为文本。编码使用的是str中的encode()方法,解码使用的是decode()方法。
字符串的编码与解码在文本读取、网络通信、数据存储、字符集转换场景中都很常见。我们来看一下如何使用:
s = "Python从入门到实战"e = s.encode('utf-8')print(e) # 输出:b'Python\xe4\xbb\x8e\xe5\x85\xa5\xe9\x97\xa8\xe5\x88\xb0\xe5\xae\x9e\xe6\x88\x98'print(e.decode('utf-8')) # 输出:Python从入门到实战这一章内容比较多,我拆分成了两部分,第一部分就先写到这,第二部分在下一篇文章中写。