本章重点介绍有关Python的基础知识,这是每一个Python用户所要走过的必经之路,因为任何一段Python代码中都会包含一些基础知识。对于读者来说,只有基础夯实牢了,在之后的代码编程中才会轻松自如。如果你是从零开始的Python用户,希望能够认真学完本章的Python入门基础知识,相信本章内容对你将有很大的帮助;如果你是Python的中级或高级用户,通过本章内容的阅读,也许多少会有一点查缺补漏的功效,当然读者也可以直接跳过本章内容,进入下一章节的学习。
通过本章内容的学习,读者将会掌握如下的Python常用基础知识以及一个简单的爬虫案例:
本节所介绍的Python数据结构,并非等同于数据库中的数据结构,而是指列表、元组和字典,它们都属于存储数据的容器。如何构建和灵活使用这三种数据结构将是本节的主要内容。
3.1.1 列表
关于列表,需要对其说明如下三点:
“列表是一种序列”指的是可以通过索引(或下标)的方式实现列表元素的获取,Python中的索引都是用英文状态下的方括号表示,而且,对于位置索引来说,都是从0开始。接下来通过具体的例子来解释四种常见的索引方式。
1.正向单索引
正向单索引指的是只获取列表中的某一个元素,并且是从左到右的方向数元素所在的位置,可以用[n]表示,例如:
list1 = ['张三','男',33,'江苏','硕士','已婚',['身高178','体重72']]
# 取出第一个元素
print(list1[0])
# 取出第四个元素
print(list1[3])
# 取出最后一个元素
print(list1[6])
# 取出“体重72”这个值
print(list1[6][1])
结果
张三
江苏
['身高178', '体重72']
体重72
如上结果显示,变量list1是一个含有7个元素的列表,包含字符串(注意,字符串必须用引号引起来)、数值和列表。由于位置索引是从0开始,所以索引号与实际的位置正好差1,最后使用print函数将取回的元素打印出来。列表中最后一个元素正好又是一个列表(一般称为嵌套列表),所以要取出嵌套列表中的元素就需要两层索引实现。
2.负向单索引
负向单索引是指在正向单索引的基础上添加一个负号“-”,所表达的含义是从右向左的方向获取元素,可以用[-n]表示,例如:
# 取出最后一个元素
print(list1[-1])
# 取出“身高178”这个值
print(list1[-1][0])
# 取出倒数第三个元素
print(list1[-3])
结果
['身高178', '体重72']
身高178
硕士
如果列表元素特别多,而需要获取的数据恰好又是最后几个,那么负向单索引就显得尤为方便和简单,否则从头开始数下去,就显得非常麻烦。注意,最后一个列表元素可以用[-1]表示,千万不要写成[-0],这是初学者容易犯错的地方。
3.切片索引
切片索引指的是按照固定的步长,连续取出多个元素,可以用[start : end : step]表示。start指索取元素的起始位置;end指索取元素的终止位置(注意,end位置的元素是取不到的!);step指索取元素的步长,默认为1,表示逐个取出一连串的列表元素;切片,你可以把它理解成高中所学的值域范围,属于左闭右开的效果。例如:
# 取出头三个元素
print(list2[:3])
# 取出最后三个元素
print(list2[-3:])
# 取出所有元素
print(list2[::])
# 取出奇数位置的元素
print(list2[::2])
结果:
['江苏', '安徽', '浙江']
['山西', '湖南', '湖北']
['江苏', '安徽', '浙江', '上海', '山东', '山西', '湖南', '湖北']
['江苏', '浙江', '山东', '湖南']
如上结果显示,如果需要从头开始返回元素,可以将切片中的start设置为冒号(:);如果需要返回至结尾的元素,可以将切片中的end设置为冒号;当然,start和end都设置为冒号的话,返回的是整个列表元素(等同于复制的功效),再通过step控制步长,实现有规律地跳格取数。
“列表是可变类型的数据结构”指的是可以通过列表特有的“方法”,实现列表元素的增加、删除和修改,一旦通过这些方法完成列表的改操作,列表本身就发生变化了。注意,这里说的是特有的“方法”,而不是函数,对于初学者来说,不易分清Python中的“方法”和函数。
为了让读者容易理解两者的区别,这里举个形象的例子加以说明。“方法”可以理解为“婴幼儿专用商品”,写成Python的语法就是object.method,这里的object就是婴幼儿,method就是专用商品,例如儿童玩具、奶嘴、尿不湿等商品就是给婴幼儿使用的,这些商品是限定用户的,就像“方法”是限定特有对象一样;函数可以理解为普通商品,写成Python的语法就是function(object),这里的object就是普通大众,function就是大众商品,例如雨伞、自行车、米饭等商品是不限定任何人群的,就像函数可以接受任何一类参数对象一样(如所有可迭代对象)。上面是从狭义的角度简单理解两者的区别,如果从广义的角度来看“方法”和函数,它们都属于对象的处理函数。
5.列表元素的增加
如果需要往列表中增加元素,可使用Python提供的三种方法,即append、extend和insert。下面通过例子来解释三者的区别:
list3 = [1,10,100,1000,10000]
# 在列表末尾添加数字2
list3.append(2)
print(list3)
结果:
[1, 10, 100, 1000, 10000, 2]
append是列表所特有的方法,其他常见对象是没有这个方法的,该方法是往列表的尾部增加元素,而且每次只能增加一个元素。如果需要一次增加多个元素,该方法无法实现,只能使用列表的extend方法。
# 在列表末尾添加20,200,2000,20000四个值
list3.extend([20,200,2000,20000])
print(list3)
结果:
[1, 10, 100, 1000, 10000, 2, 20, 200, 2000, 20000]
使用extend方法往列表尾部增加多个元素时,一定要将多个元素捆绑为列表传递给该方法,即使只有一个元素,也需要以列表的形式传递。
# 在数字10后面增加11这个数字
list3.insert(2,11)
print(list3)
# 在10000后面插入['a','b','c']
list3.insert(6,['a','b','c'])
print(list3)
结果:
[1, 10, 11, 11, 100, 1000, 10000, ['a', 'b', 'c'], 2, 20, 200, 2000, 20000]
[1, 10, 11, 11, 100, 1000, ['a', 'b', 'c'], 10000, ['a', 'b', 'c'], 2, 20, 200, 2000, 20000]
insert方法可以在列表的指定位置插入新值,该方法需要传递两个参数:一个是索引(或下标)参数,如上面的2,是指在列表元素的第三个位置插入;另一个参数是具体插入的值,既可以是一个常量,也可以是一个列表,如果是列表,就是以嵌套列表的形式插入。
6.列表元素的删除
能往列表中增加元素,就能从列表中删除元素。关于列表元素的删除有三种方法,分别是pop、remove和clear,下面举例说明:
# 删除list3中20000这个元素
list3.pop()
print(list3)
# 删除list3中11这个元素
list3.pop(2)
print(list3)
结果:
[1, 10, 11, 11, 100, 1000, ['a', 'b', 'c'], 10000, ['a', 'b', 'c'], 2, 20, 200, 2000]
[1, 10, 11, 100, 1000, ['a', 'b', 'c'], 10000, ['a', 'b', 'c'], 2, 20, 200, 2000]
如上结果所示,通过pop方法,可以完成列表元素两种风格的删除,一种是默认删除列表的末尾元素,另一种是删除指定位置的列表元素,而且都只能删除一个元素。
# 删除list3中的['a', 'b', 'c']
list3.remove(['a', 'b', 'c'])
print(list3)
结果:
[1, 10, 11, 100, 1000, 10000, ['a', 'b', 'c'], 2, 20, 200, 2000]
remove方法提供了删除指定值的功能,该功能非常棒,但是它只能删除首次出现的指定值。如果你的列表元素特别多,通过pop方法删除指定位置的元素就显得非常笨拙,因为你需要数出删除值的具体位置,而使用remove方法就很方便。
# 删除list3中所有元素
list3.clear()
print(list3)
结果:
[]
clear从字面理解就是清空的意思,确实,该方法就是将列表中的所有元素全部删除。如上结果所示,通过clear方法返回的是一个空列表。
7.列表元素的修改
如果列表元素值存在错误该如何修改呢?不幸的是对于列表来说,没有具体的方法可言,但可以使用“取而改之”的思想实现元素的修改。下面通过具体的例子来加以说明:
list4 = ['洗衣机','冰响','电视机','电脑','空调']
# 将“冰响”修改为“冰箱”
print(list4[1])
list4[1] = '冰箱'
print(list4)
结果:
冰响
['洗衣机', '冰箱', '电视机', '电脑', '空调']
“取而改之”是指先通过错误元素的获取(通过索引的方法),再使用正确的值重新替换即可。正如上面的结果所示,就是用新值替换旧值,完成列表元素的修改。
当然,除了上面介绍的列表元素增加和删除所涉及的“方法”外,还有其他“方法”,如排序、计数、查询位置、逆转,接下来仍然通过具体的例子来说明它们的用法:
list5 = [7,3,9,11,4,6,10,3,7,4,4,3,6,3]
# 计算列表中元素3的个数
print(list5.count(3))
# 找出元素6所在的位置
print(list5.index(6))
# 列表元素的颠倒
list5.reverse()
print(list5)
# 列表元素的降序
list5.sort(reverse=True)
print(list5)
结果:
4
5
[3, 6, 3, 4, 4, 7, 3, 10, 6, 4, 11, 9, 3, 7]
[11, 10, 9, 7, 7, 6, 6, 4, 4, 4, 3, 3, 3, 3]
count方法是用来对列表中的某个元素进行计数,每次只能往count方法中传递一个值;index方法则返回指定值在列表中的位置,遗憾的是只返回首次出现该值的位置;reverse方法是将列表元素全部翻转,最后一个元素重新排到第一个位置,倒数第二个元素排到第二个位置,以此类推;sort方法可以实现列表元素的排序,默认是升序,可以将reverse参数设置为True,进而调整为降序。需要注意的是,sort方法只能对同质数据进行排序,即列表元素统一都是数值型或字符型,不可以混合多种数据类型或数据结构。
3.1.2 元组
元组与列表类似,关于元组同样需要做如下三点说明:
由于元组只是存储数据的不可变容器,因此其只有两种可用的“方法”,分别是count和index。它们的功能与列表中的count和index方法完全一样,这里就简单举例,不再详细赘述:
t = ('a','d','z','a','d','c','a')
# 计数
print(t.count('a'))
# 元素位置
print(t.index('c'))
结果:
3
5
3.1.3 字典
字典是非常常用的一种数据结构,它与json格式的数据非常相似,核心就是以键值对的形式存储数据,关于Python中的字典做如下四点说明:
首先介绍字典的键索引如何实现元素值的获取,举例如下:
dict1 = {'姓名':'张三','年龄':33,'性别':'男','子女':{'儿子':'张四','女儿':'张美'},'兴趣':['踢球','游泳','唱歌']}
# 打印字典
print(dict1)
# 取出年龄
print(dict1['年龄'])
# 取出子女中的儿子姓名
print(dict1['子女']['儿子'])
# 取出兴趣中的游泳
print(dict1['兴趣'][1])
对于字典来说,它不再是序列,通过第一条输出结果可知,构造时的字典元素与输出时的字典元素顺序已经发生了变化,要想获取元素值,只能在索引里面写入具体的键;在字典dict1中,键“子女”对应的值是另一个字典,属于dict1的嵌套字典,所以需要通过双层键索引获取张三儿子的姓名;键“兴趣”对应的值是列表,所以“游泳”这个值只能通过先锁定字典的键再锁定列表元素的位置才能获得。
接下来介绍字典的可变性。关于可变性,仍然是对字典元素进行增加、删除和修改的操作,这些操作都可以通过字典的“方法”实现,下面将依次介绍字典的各个操作。
1.字典元素的增加
针对字典元素的增加,可以使用如下三种方式实现,分别是setdefault方法、update方法和键索引方法:
# 往字典dict1中增加户籍信息
dict1.setdefault('户籍','合肥')
print(dict1)
# 增加学历信息
dict1.update({'学历':'硕士'})
print(dict1)
# 增加身高信息
dict1['身高'] = 178
print(dict1)
结果:
{'姓名': '张三', '年龄': 33, '性别': '男', '子女': {'儿子': '张四', '女儿': '张美'}, '兴趣': ['踢球', '游泳', '唱歌'], '户籍': '合肥'}
{'姓名': '张三', '年龄': 33, '性别': '男', '子女': {'儿子': '张四', '女儿': '张美'}, '兴趣': ['踢球', '游泳', '唱歌'], '户籍': '合肥', '学历': '硕士'}
{'姓名': '张三', '年龄': 33, '性别': '男', '子女': {'儿子': '张四', '女儿': '张美'}, '兴趣': ['踢球', '游泳', '唱歌'], '户籍': '合肥', '学历': '硕士', '身高': 178}
如上结果所示,setdefault方法接受两个参数,第一个参数为字典的键,第二个参数是键对应的值;update从字面理解是对字典的更新,关于update方法完成字典元素的修改可参见后面的内容,除此,它还可以增加元素,与setdefault不同的是该方法接受的是一个字典对象;第三种方法是通过键索引实现的,如果原字典中没有指定的键,就往字典中增加元素,否则,起到修改字典元素的功能。
2.字典元素的删除
关于字典元素的删除可以使用pop、popitem和clear三种“方法”实现,具体操作如下:
# 删除字典中的户籍信息
dict1.pop('户籍')
print(dict1)
# 删除字典中女儿的姓名
dict1['子女'].pop('女儿')
print(dict1)
# 删除字典中的任意一个元素
dict1.popitem()
print(dict1)
# 清空字典元素
dict1.clear()
print(dict1)
结果:
{'姓名': '张三', '年龄': 33, '性别': '男', '子女': {'儿子': '张四', '女儿': '张美'}, '兴趣': ['踢球', '游泳', '唱歌'], '学历': '硕士', '身高': 178}
{'姓名': '张三', '年龄': 33, '性别': '男', '子女': {'儿子': '张四'}, '兴趣': ['踢球', '游泳', '唱歌'], '学历': '硕士', '身高': 178}
{'姓名': '张三', '年龄': 33, '性别': '男', '子女': {'儿子': '张四'}, '兴趣': ['踢球', '游泳', '唱歌'], '学历': '硕士'}
{}
如上结果显示,pop方法在列表中同样起到删除元素的作用,如果不传递任何值给pop方法,则表示删除列表末尾的一个元素,否则就是删除指定下标的一个元素,但是在字典中pop方法必须指定需要删除的键,否则就会引起语法错误;如果需要删除嵌套字典中的某个键,就必须先通过键索引取出对应的字典,然后使用pop方法完成嵌套字典元素的删除;popitem方法不需要传递任何值,它的功能就是任意删除字典中的某个元素;clear方法则可以干净利落地清空字典中的所有元素。
3.字典元素的修改
最后来看一下字典元素的修改,关于修改部分,可以使用如下两种方法:
# 将学历改为本科
dict1.update({'学历':'本科'})
print(dict1)
# 将年龄改为35
dict1['年龄'] = 35
print(dict1)
# 将兴趣中的唱歌改为跳舞
dict1['兴趣'][2] = '跳舞'
print(dict1)
结果:
{'姓名': '张三', '年龄': 33, '性别': '男', '子女': {'儿子': '张四'}, '兴趣': ['踢球', '游泳', '唱歌'], '学历': '本科', '身高': 178}
{'姓名': '张三', '年龄': 35, '性别': '男', '子女': {'儿子': '张四'}, '兴趣': ['踢球', '游泳', '唱歌'], '学历': '本科', '身高': 178}
{'姓名': '张三', '年龄': 35, '性别': '男', '子女': {'儿子': '张四'}, '兴趣': ['踢球', '游泳', '跳舞'], '学历': '本科', '身高': 178}
正如“字典元素的增加”部分所提到的,也可以使用update方法和键索引方法完成字典元素的修改,具体如上面的例子所示。需要注意的是,如果字典中的值是另一个字典或列表,需要先通过键索引实现字典元素的查询,然后在查询的基础上应用对应的修改方法即可(如update方法或“取而改之”的方法)。
字典还有一些其他“方法”,这里列出几个比较重要的方法并通过例子来解释:
dict2 = {'电影':['三傻大闹宝莱坞','大话西游之大圣娶亲','疯狂动物城'],
'导演':['拉吉库马尔·希拉尼','刘镇伟','拜伦·霍华德 '],
'评分':[9.1,9.2,9.2]}
# 取出键'评分'所对应的值
print(dict2.get('评分'))
# 取出字典中的所有键
print(dict2.keys())
# 取出字典中的所有值
print(dict2.values())
# 取出字典中的所有键值对
print(dict2.items())
结果:
[9.1, 9.2, 9.2]
dict_keys(['电影', '导演', '评分'])
dict_values([['三傻大闹宝莱坞', '大话西游之大圣娶亲', '疯狂动物城'], ['拉吉库马尔·希拉尼', '刘镇伟', '拜伦·霍华德 '], [9.1, 9.2, 9.2]])
dict_items([('电影', ['三傻大闹宝莱坞', '大话西游之大圣娶亲', '疯狂动物城']), ('导演', ['拉吉库马尔·希拉尼', '刘镇伟', '拜伦·霍华德 ']), ('评分', [9.1, 9.2, 9.2])])
get方法的功能与键索引已知,可以从字典中取出键对应的值。所不同的是,如果某个键在字典中不存在,应用键索引的方法会产生“键错误”的信息;而get方法则不会报错,也就不会影响其他脚本的正常执行。keys、values和items方法分别取出字典中的所有键、值和键值对。
Python中的控制流语句和其他编程软件控制流相似,主要包含if分支、for循环和while循环,而且控制流的使用非常频繁。例如,分不同情况执行不同的内容就可以使用if分支完成;对每一个对象进行相同的操作可以使用for循环实现;当无法确定循环的对象是什么时,还可以使用while循环完成重复性的操作。下面就详细介绍if分支、for循环和while循环的具体使用说明。
3.2.1 if分支
if分支是用来判别某个条件是否满足时所对应的执行内容,常见的分支类型有二分支类型和多分支类型。二分支是指条件只有两种情况,例如年龄是否大于18周岁,收入是否超过15000元等。多分支是指条件个数超过两种,例如将考试成绩分成合格、良好和优秀三种等级,将年龄分为少年、青年、中年和老年4个段。可以将if分支形象地表示成图3-1。
如图3-1所示,菱形代表条件,矩形代表不同条件下执行的语句块。左图展示的就是二分支的情况,右图为三分支的判断风格。在Python中二分支和三分支的语法可以写成表3-1的形式。
针对上面的语法,通过简单的例子(见表3-2)来加以说明,希望读者能够比较好地理解if分支的语法和注意事项。
# 返回绝对值
x = -3
if x >= 0:
print(x)
else:
print(-1*x)
结果:
3
# 返回成绩对应的等级
score = 68
if score < 60:
print('不及格')
elif score < 70:
print('合格')
elif score < 80:
print('良好')
else:
print('优秀')
结果:
合格
3.2.2 for循环
循环的目的一般都是为了解决重复性的工作,如果你需要对数据集中的每一行做相同的处理,但不使用循环的话,就会导致代码量剧增,而且都是无意义的重复代码。如果使用循环的语法解决类似上面的问题,也许只要10行左右的代码即可,既保证代码的简洁性,又保证问题得到解决。为了使读者形象地理解for循环的操作流程,可将其表示为如图3-2所示的效果。
如图3-2所示,对于for循环来说,就是把可迭代对象中的元素(如列表中的每一个元素)通过漏斗的小口依次倒入之后的执行语句中。在图3-2中,漏斗代表可迭代对象,小球代表可迭代对象中的元素,黑框是对每一个元素的具体计算过程,菱形是需要对每一个元素做条件判断,圆柱体则存放了计算后的结果。
对于左图来说,直接将漏斗中的每一个元素进行某种计算,最终把计算结果存储起来;右图相对复杂一些,多了一步计算前的判断,这个就需要if分支和for循环搭配完成,然后将各分支的结果进行存储。接下来,分别对如上两种for循环用法加以案例说明。
# 将列表中的每个元素作平方加1处理
list6 = [1,5,2,8,10,13,17,4,6]
result = []
for i in list6:
y = i ** 2 + 1
result.append(y)
print(result)
结果:
[2, 26, 5, 65, 101, 170, 290, 17, 37]
如上展示的就是对列表list6中每个元素做平方加1的结果,在for循环之前先构造了空列表result,用于最终计算结果的存储;Python中的指数运算可以使用两个星号表示,如3的5次方可以写成3**5;最后通过列表的append方法将每个元素的计算结果依次存入result变量中。下面再看一个有判断条件的for循环用法。
# 计算1到100之间的偶数和
s1_100 = 0
for i in range(1,101):
if i % 2 == 0:
s1_100 = s1_100 + i
else:
pass
print('1到100之间的偶数和为%s'%s1_100)
结果:
# 计算1到100之间的偶数和
s1_100 = 0
for i in range(1,101):
if i % 2 == 0:
s1_100 = s1_100 + i
else:
pass
print('1到100之间的偶数和为%s'%s1_100)
结果:
1到100之间的偶数和为2550
如上结果所示,通过for循环可以非常方便地计算出所有1到100之间的偶数和。对于上面Python语句有如下5点说明:
如果是对可迭代对象中的每一个元素做相同处理的话,正如上面的例子中对列表list6的每个元素做平方加1的计算,不仅可以使用for循环,还可以通过更简单的列表表达式完成。对于列表表达式,可以写成如下语法:
[expression for i in iterable if condition]
在上面的列表表达式中,expression就是对每一个元素的具体操作表达式;iterable是某个可迭代对象,如列表、元组或字符串等;if condition是对每一个元素做分支判断,如果条件符合,则expression操作对应的元素。为了更好地说明列表表达式,下面举一个示例:
# 对列表中的偶数作三次方减10的处理
list7 = [3,1,18,13,22,17,23,14,19,28,16]
result = [i ** 3 - 10 for i in list7 if i % 2 == 0]
print(result)
结果:
[5822, 10638, 2734, 21942, 4086]
如上结果所示,在原列表list7中通过余数判断获得5个偶数,分别是18、22、14、28和16,再对这些数做三次方减10的操作就得到了最终的输出结果,而且结果还是列表型的数据结构。Python中除了有列表表达式,还有元组表达式和字典表达式,它们的语法跟列表表达式类似,由于它们在实际工作中的使用并不是很频繁,所以就不对它们做详细说明了。
如果读者在学习或工作中需要解决的问题既可以用for循环实现也可以通过列表表达式完成,建议优先选择列表表达式的方法,因为其语法简洁,而且在计算的效率上也比多行的for循环高得多。关于for循环的内容就讲解这么多,最后介绍控制流中的while循环。
3.2.3 while循环
while循环与for循环有一些相似之处,有时for循环的操作和while循环的操作是可以互换的,但while循环更适合无具体迭代对象的重复性操作。这句话理解起来可能比较吃力,下面通过一个比较形象的例子来说明两者的差异。
当你登录某手机银行APP账号时,一旦输入错误,就会告知用户还剩几次输入机会,很明显,其背后的循环就是限定用户只能在N次范围内完成正确的输入,否则当天就无法再进行用户名和密码的输入,对于for循环来说,就有了具体的迭代对象,如从1到N;当你在登录某邮箱账号时,输入错误的用户名或密码,只会告知“您的用户名或密码错误”,并不会限定还有几次剩余的输入机会,所以对于这种重复性的输入操作,对方服务器不确定用户将会输入多少次才会正确,对于while循环来说,就相当于一个无限次的循环,除非用户输入正确。
首先来了解一下while循环在Python中的语法表达:
while condition:
if condition1:
expression1
elif condition2:
expression2
else:
expression3
当while关键词后面的条件满足时,将会重复执行第二行开始的所有语句块。一般情况下,while循环都会与if分支搭配使用,就像for循环与if分支搭配一样,如上面的while循环语法中就内嵌了三分支的if判断,读者可以根据具体的情况调整分支的个数。针对上文提到的两种账号登录模式,进一步通过实例(见表3-3)代码来比较for循环和while循环的操作差异。
# 使用for循环登录某手机银行APP
for i in range(1,6):
user = input('请输入用户名:')
password = int(input('请输入密码:'))
if (user == 'test') & (password == 123):
print('登录成功!')
break
else:
if i < 5:
print('错误!您今日还剩%d次输入机会。' %(5-i))
else:
print('请24小时后再尝试登录!')
# 使用while循环登录某邮箱账号
while True:
user = input('请输入用户名:')
password = int(input('请输入密码:'))
if (user == 'test') & (password == 123):
print('登录成功!')
break
else:
print('您输入的用户名或密码错误!')
对如上呈现的代码做几点解释:
3.3.1 字符串的常用方法
在平时的工作中,也会碰见字符串型数据的处理,例如如何截取字符串中的某一段内容、如何将字符串按照某个指定的分隔符将其切割开、如何对字符串中的某些值进行替换等。本节内容重点讲述有关字符串的几种常见处理“方法”,首先介绍一下Python中的字符串有哪些构造方法:
# 单引号构造字符串
string1 = '"commentTime":"2018-01-26 08:59:30","content":"包装良心!馅料新鲜!还会回购"'
# 双引号构造字符串
string2 = "ymd:'2017-01-01',bWendu:'5℃',yWendu:'-3℃',tianqi:'霾~晴',fengxiang:'南风',aqiInfo:'严重污染'"
# 三引号构造字符串
string3 = ''''nickName':"美美",'content':"环境不错,服务态度超好,就是有点小贵",'createTimestring':"2017-09-30"'''
string4 = '''据了解,持续降雪造成安徽部分地区农房倒损、种植养殖业大棚损毁,
其中合肥、马鞍山、铜陵3市倒塌农房8间、紧急转移安置8人。'''
print(string1)
print(string2)
print(string3)
print(string4)
结果:
"commentTime":"2018-01-26 08:59:30","content":"包装良心!馅料新鲜!还会回购"
ymd:'2017-01-01',bWendu:'5℃',yWendu:'-3℃',tianqi:'霾~晴',fengxiang:'南风',aqiInfo:'严重污染'
'nickName':"美美",'content':"环境不错,服务态度超好,就是有点小贵",'createTimestring':"2017-09-30"
据了解,持续降雪造成安徽部分地区农房倒损、种植养殖业大棚损毁,
其中合肥、马鞍山、铜陵3市倒塌农房8间、紧急转移安置8人。
构造字符串可以使用三种形式的引号,如果字符串的内容不包含任何引号,那么单引号、双引号和三引号都可以使用;如果字符串的内容仅包含双引号,类似变量string1的形式,那么只能使用单引号或三引号构造字符串;如果字符串的内容仅包含单引号,类似变量string2的形式,那么只能使用双引号或三引号完成字符串的创建;如果字符串的内容既包含单引号,又包含双引号,类似变量string3所示,那只能使用三引号构建字符串。所以,三引号是适用情况最多的字符串构造方法,而且三引号允许长字符串的换行,这是其他两种引号无法实现的,如变量string4所示。
接下来将字符串的常用“方法”汇总到表3-4中,以便读者学习和查阅。
为了使读者很好地理解表3-4中的字符串“方法”,下面通过一些小例子作为字符串常用“方法”的解释:
# 获取身份证号码中的出生日期
print('123456198901017890'[6:14])
# 将手机号中的中间四位替换为四颗星
tel = '13612345678'
print(tel.replace(tel[3:7],'****'))
# 将邮箱按@符分隔开
print('[email protected]'.split('@'))
# 将Python的每个字母用减号连接
print('-'.join('Python'))
# 删除" 今天星期日 "的首尾空白
print(" 今天星期日 ".strip())
# 删除" 今天星期日 "的左边空白
print(" 今天星期日 ".lstrip())
# 删除" 今天星期日 "的右边空白
print(" 今天星期日 ".rstrip())
# 计算子串“中国”在字符串中的个数
string5 = '中国方案引领世界前行,展现了中国应势而为、勇于担当的大国引领作用!'
print(string5.count('中国'))
# 查询"Python"单词所在的位置
string6 = '我是一名Python用户,Python给我的工作带来了很多便捷。'
print(string6.index('Python'))
print(string6.find('Python'))
# 字符串是否以“2018年”开头
string7 = '2017年匆匆走过,迎来崭新的2018年'
print(string7.startswith('2018年'))
# 字符串是否以“2018年”年结尾
print(string7.endswith('2018年'))
结果:
19890101
136****5678
['12345', 'qq.com']
P-y-t-h-o-n
今天星期日
今天星期日
今天星期日
2
4
4
False
True
需要说明的是,字符串的index和find方法都是只能返回首次发现子串的位置,如果子串在原字符串中没有找到,对于index方法来说,则返回报错信息,对于find方法,则返回值-1。所以,推荐使用find方法寻找子串的位置,因为即使找不到子串也不会因为错误而影响其他程序的正常执行。
有时,光靠字符串的这些“方法”无法实现字符串的其他处理功能,例如,怎样在字符串中找到有规律的目标值、怎样替换那些不是固定值的目标内容、怎样按照多个分隔符将字符串进行切割等。关于这方面问题的解决,需要用到字符串的正则表达式,接下来我们就进入正则表达式的学习。
3.3.2 正则表达式
正则表达式就是从字符串中发现规律,并通过“抽象”的符号表达出来。打个比方,对于2,5,10,17,26,37这样的数字序列,如何计算第7个值,肯定要先找该序列的规律,然后用n2+1这个表达式来描述其规律,进而得到第7个值为50。对于需要匹配的字符串来说,同样把发现规律作为第一步,本节主要使用正则表达式完成字符串的查询匹配、替换匹配和分割匹配。在进入字符串的匹配之前,先来了解一下都有哪些常用的正则符号,见表3-5。
表3-5 常用的正则符号:
如果读者能够比较熟练地掌握表3-5中的内容,相信在字符串处理过程中将会游刃有余。如前文所说,本节将基于正则表达式完成字符串的查询、替换和分割操作,这些操作都需要导入re模块,并使用如下几个函数。
1.匹配查询函数
findall(pattern, string, flags=0)
findall函数可以对指定的字符串进行遍历匹配,获取字符串中所有匹配的子串,并返回一个列表结果。该函数的参数含义如下:
2.匹配替换函数
sub(pattern, repl, string, count=0, flags=0)
sub函数的功能是替换,类似于字符串的replace方法,该函数根据正则表达式把满足匹配的内容替换为repl。该函数的参数含义如下:
3.匹配分割函数
split(pattern, string, maxsplit=0, flags=0)
split函数是将字符串按照指定的正则表达式分隔开,类似于字符串的split方法。该函数的具体参数含义如下:
如果上面的函数和参数含义都已经掌握了,还需要进一步通过案例加强理解,接下来举例说明上面的三个函数:
# 导入第三方包
import re
# 取出出字符中所有的天气状态
string8 = "{ymd:'2018-01-01',tianqi:'晴',aqiInfo:'轻度污染'},{ymd:'2018-01-02',tianqi:'阴~小雨',aqiInfo:'优'},{ymd:'2018-01-03',tianqi:'小雨~中雨',aqiInfo:'优'},{ymd:'2018-01-04',tianqi:'中雨~小雨',aqiInfo:'优'}"
print(re.findall("tianqi:'(.*?)'", string8))
# 取出所有含O字母的单词
string9 = 'Together, we discovered that a free market only thrives when there are rules to ensure competition and fair play, Our celebration of initiative and enterprise'
print(re.findall('\w*o\w*',string9, flags = re.I))
# 将标点符号、数字和字母删除
string10 = '据悉,这次发运的4台蒸汽冷凝罐属于国际热核聚变实验堆(ITER)项目的核二级压力设备,先后完成了压力试验、真空试验、氦气检漏试验、千斤顶试验、吊耳载荷试验、叠装试验等验收试验。'
print(re.sub('[,。、a-zA-Z0-9()]','',string10))
# 将每一部分的内容分割开
string11 = '2室2厅 | 101.62平 | 低区/7层 | 朝南 \n 上海未来 - 浦东 - 金杨 - 2005年建'
split = re.split('[-\|\n]', string11)
print(split)
split_strip = [i.strip() for i in split]
print(split_strip)
结果:
['晴', '阴~小雨', '小雨~中雨', '中雨~小雨']
['Together', 'discovered', 'only', 'to', 'competition', 'Our', 'celebration', 'of']
据悉这次发运的台蒸汽冷凝罐属于国际热核聚变实验堆项目的核二级压力设备先后完成了压力试验真空试验氦气检漏试验千斤顶试验吊耳载荷试验叠装试验等验收试验
['2室2厅 ', ' 101.62平 ', ' 低区/7层 ', ' 朝南 ', ' 上海未来 ', ' 浦东 ', ' 金杨 ', ' 2005年建']
['2室2厅', '101.62平', '低区/7层', '朝南', '上海未来', '浦东', '金杨', '2005年建']
如上结果所示,在第一个例子中通过正则表达式"tianqi:’(.*?)’“实现目标数据的获取,如果不使用括号的话,就会产生类似"tianqi:‘晴’”, "tianqi:‘阴~小雨’"这样的值,所以,加上括号就是为了分组,且仅返回组中的内容;第二个例子并没有将正则表达式写入圆括号,如果写上圆括号也是返回一样的结果,所以findall就是用来返回满足匹配条件的列表值,如果有括号,就仅返回括号内的匹配值;第三个例子使用替换的方法,将所有的标点符号换为空字符,进而实现删除的效果;第四个例子是对字符串的分割,如果直接按照正则’[,。、a-zA-Z0-9()]'分割的话,返回的结果中包含空字符,如’2室2厅’后面就有一个空字符。为了删除列表中每个元素的首尾空字符,使用了列表表达式,并且结合字符串的strip方法完成空字符的压缩。
3.4.1 自定义函数语法
虽然Python的标准库中自带了很多“方法”或函数,并且第三方模块也提供了更多的现成“方法”与函数,但有时还是不能满足学习或工作中的需求,这时就需要自定义函数了。另外,为了避免重复代码的编写,也可以将常用的代码块封装为函数,在需要时调用函数即可,这样也会使代码简洁易读。
在Python中有一种自定义函数叫匿名函数,可以用lambda关键字定义。通过lambda构造的函数可以没有名称,最大特点是“一气呵成”,即在自定义匿名函数时,所有代码只能在一行内完成,语法如下:
lambda parameters : function_expression
如上语法中,lambda为匿名函数的关键起始词;parameters是函数可能涉及的形参,如果有多个参数,需要用英文状态的逗号隔开;function_expression为具体的函数体。需要再次强调的是,如果需要构造的函数不是很复杂,可以使用lambda匿名函数一气呵成地表达完,否则就只能使用def关键字构造有名称的自定义函数了。下面举一个实例来描述lambda匿名函数的使用:
# 统计列表中每个元素的频次
list6 = ['A','A','B','A','A','B','C','B','C','B','B','D','C']
# 构建空字典,用于频次统计数据的存储
dict3 = {}
# 循环计算
for i in set(list6):
dict3[i] = list6.count(i)
print(dict3)
# 取出字典中的键值对
key_value = list(dict3.items())
print(key_value)
# 列表排序
key_value.sort()
print(key_value)
# 按频次高低排序
key_value.sort(key = lambda x : x[1], reverse=True)
print(key_value)
结果:
{'C': 3, 'A': 4, 'B': 5, 'D': 1}
[('C', 3), ('A', 4), ('B', 5), ('D', 1)]
[('A', 4), ('B', 5), ('C', 3), ('D', 1)]
[('B', 5), ('A', 4), ('C', 3), ('D', 1)]
本案例的目的是统计列表中的元素频次,并根据频次从高到低排序。首先在统计元素频次时使用了for循环,其中set函数是构造集合对象,可以实现列表元素的去重;然后直接对存储键值对的列表直接排序,发现默认是按照字母排序,见第三行输出,并不是以实际的频次排序;最后通过构建匿名函数,对列表元素(每一个键值对元组)的第二个元素降序排序,进而实现输出结果中的最后一行效果。
虽然匿名函数用起来很灵活,会在很多代码中遇到,但是它的最大特点也是它的短板,即无法通过lambda函数构造一个多行而复杂的函数。为了弥补其缺陷,Python提供了另一个关键字def构造复杂的自定义函数,其语法如下:
def function_name(parameters):
function_expression
return(result)
如上语法中,def是define单词的缩写,表示自定义;function_name为自定义的函数名称;parameters为自定义函数的形参,需要放在圆括号内;第一行的结束必须要加上英文状态的冒号,这是很多初学者容易忽略的细节;function_expression是具体的函数体(注意,第二行开始需要缩进),根据自定义的需求,可以很简单也可以很复杂;return用于返回函数的计算结果,如果有多个值需要返回,可以全部写在return的括号内,并以逗号隔开。首先,编写一段猜数字游戏的自定义函数,用于说明自定义函数的语法:
# 猜数字
def game(min,max):
import random
number = random.randint(min,max) # 随机生成一个需要猜的数字
while True:
guess = float(input('请在%d到%d之间猜一个数字: ' %(min, max)))
if guess < number:
min = guess
print('不好意思,你猜的的数偏小了!请在%d到%d之间猜一个数!' %(min,max))
elif guess > number:
max = guess
print('不好意思,你猜的的数偏大了!请在%d到%d之间猜一个数!' %(min,max))
else:
print('恭喜你猜对了!')
print('游戏结束!')
break
# 调用函数
game(10,20)
如上的猜数字游戏代码,大家可能见过,这里在《Python简明教程》的基础上做了一定的修改,进而可以更加“智能”地告知参与游戏的用户可以在什么范围内猜数。代码中用到的知识点都是前面介绍过的基础内容,这里就不对代码做详细解释了。
3.4.2 自定义函数的几种参数
通过构造自定义函数,可以避免冗余代码的出现。关于Python中的自定义函数,还有四类重要的参数需要跟读者一一解释,即必选参数、默认参数、可变参数和关键字参数。
1.必选参数
必选参数,顾名思义就是当你在调用一个自定义函数时必须给函数中的必选参数赋值,否则程序将会报错,并提醒用户“缺少一些必选的位置参数”。就以上面的猜数字函数为例,如果不给该函数的max参数传递一个值,结果就是这样的:
如上所示,返回“类型错误”的提示,再具体查看最后一行的反馈信息,结论为“game函数缺少一个必要的位置参数max”表明game函数需要给max参数传值。
2.默认参数
默认参数是指在构造自定义函数的时候就已经给某些参数赋予了各自的初值,当调用函数时,这样的参数可以不用传值。例如计算1到n的p次方和:
# 计算1到n的平方和
def square_sum(n, p = 2):
result = sum([i ** p for i in range(1,n+1)])
return(n,p,result)
print('1到%d的%d次方和为%d!' %square_sum(200))
print('1到%d的%d次方和为%d!' %square_sum(200,3))
结果:
1到200的2次方和为2686700!
1到200的3次方和为404010000!
如上构造的自定义函数中,n为必选参数,p为默认参数。根据结果显示,在第一次调用函数时,并没有给p参数传递任何值,函数正常运行,而且默认计算平方和;在第二次调用函数时,给p传递了新值3,此时p参数由原来的2换成了3,进而可以计算立方和。
3.可变参数
上面讲解的必选参数和默认参数都是在已知这个自定义函数需要多少个形参的情况下构建的,如果不确定该给自定义函数传入多少个参数值时,该如何自定义函数呢?这么说可能有点抽象,接下来通过对比的例子来说明。
例如,小明的弟弟小亮刚读一年级,老师布置了一些关于两个数的求和运算,针对这个问题,我们可以构建如下的自定义函数:
# 两个数的求和
def add(a,b):
s = sum([a,b])
return(a,b,s)
print('%d加%d的和为%d!' %add(10,13))
结果:
10加13的和为23!
如果只是两个数求和的问题可以很简单地利用自定义函数add解决,但如果不是两个数之和,而是三个数或四个数或五个数之和,也就是说不确定接下来会计算几个数的和,这时再使用上面的add函数似乎就不合理了。好在Python给自定义函数提供了可变参数,目的就是解决这类问题。举例如下:
# 任意个数的数据求和
def adds(*args):
print(args)
s = sum(args)
return(s)
print('和为%d!' %adds(10,13,7,8,2))
print('和为%d!' %adds(7,10,23,44,65,12,17))
结果:
(10, 13, 7, 8, 2)
和为40!
(7, 10, 23, 44, 65, 12, 17)
和为178!
如上自定义函数中,参数args前面加了一个星号*,这样的参数就称为可变参数,该参数是可以接纳任意多个实参的。之所以能够接纳任意多个实参,是因为该类型的参数将这些输入的实参进行了捆绑,并且组装到元组中,正如输出结果中的第一行和第三行,就是自定义函数中print(args)语句的效果。
4.关键字参数
虽然一个可变参数可以接受多个实参,但是这些实参都被捆绑为元组了,而且无法将具体的实参指定给具体的形参,那有没有一种参数既可以接受多个实参,又可以把多个实参指定给各自的实参名呢?答案是关键字参数,而且这种参数会把带参数名的参数值组装到一个字典中,键就是具体的实参名,值就是传入的参数值。为了帮助读者理解关键字参数的含义,下面举一个例子来解释关键字参数。
例如某电商平台,在用户注册时,用户的手机号及出生日期为必填项,其他信息为选填项。对于选填项来说,电商平台并不知道用户会不会填,以及可能填多少个信息,而且这些信息都是有对应含义的。为了搜集信息,可以创建一个含关键字参数的自定义函数:
# 关键字参数
def info_collection(tel, birthday, **kwargs):
user_info = {} # 构造空字典,用于存储用户信息
user_info['tel'] = tel
user_info['birthday'] = birthday
user_info.update(kwargs)
# 用户信息返回
return(user_info)
# 调用函数
info_collection(13612345678,'1990-01-01',nickname='月亮',gender = '女',edu = '硕士',income = 15000,add = '上海市浦东新区',interest = ['游泳','唱歌','看电影'])
结果:
{'tel': 13612345678,
'birthday': '1990-01-01',
'nickname': '月亮',
'gender': '女',
'edu': '硕士',
'income': 15000,
'add': '上海市浦东新区',
'interest': ['游泳', '唱歌', '看电影']}
如上结果所示,在自定义函数info_collection中,tel和birthday都是必选参数,kwargs为关键字参数。当调用函数时,tel和birthday两个参数必须要传入对应的值,而其他的参数都是用户任意填写的,并且关键字参数会把这些任意填写的信息组装为字典,如输出中的第一行信息;为了把必选参数的值和关键字参数的值都汇总起来,在自定义函数时初设了空字典user_info,并通过字典元素增加的方法完成用户信息的搜集,如输出的第二个结果。
虽然前面的基础知识点都通过一些小例子加以解释和说明,但毕竟都是零散的。为了能够将前文的基础知识点串起来,下面给出一个简单的爬虫案例,希望读者在学习该案例的同时,更进一步地认识到基础知识的重要性。
该案例主要是为了获取某城市的历史天气数据,字段包含日期、最低气温、最高气温、风向、风力、天气状况、空气质量指标值、空气质量等级和空气质量说明,所有数据一共包含2544天的记录。下面就详细写出整个爬虫的代码:
# 导入第三方包
import requests
import time
import random
import pandas as pd
import re
# 生成请求头
headers = {
'Accept':'*/*',
'Accept-Encoding':'gzip, deflate',
'Accept-Language':'zh-CN,zh;q=0.9',
'Connection':'keep-alive',
'Cookie':'widget_dz_id=54511; widget_dz_cityValues=,; timeerror=1; defaultCityID=54511; defaultCityName=%u5317%u4EAC; Hm_lvt_a3f2879f6b3620a363bec646b7a8bcdd=1516245199; Hm_lpvt_a3f2879f6b3620a363bec646b7a8bcdd=1516245199; addFavorite=clicked',
'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3236.0 Safari/537.36'
}
# 生成所有需要抓取的链接
urls = []
for year in range(2011,2018):
for month in range(1,13):
if year <= 2016:
urls.append('http://tianqi.2345.com/t/wea_history/js/58362_%s%s.js' %(year,month))
else:
if month<10:
urls.append('http://tianqi.2345.com/t/wea_history/js/%s0%s/58362_%s0%s.js' %(year,month,year,month))
else:
urls.append('http://tianqi.2345.com/t/wea_history/js/%s%s/58362_%s%s.js' %(year,month,year,month))
urls
# 循环并通过正则匹配获取相关数据
info = []
for url in urls:
seconds = random.randint(3,6)
response = requests.get(url, headers = headers).text
ymd = re.findall("ymd:'(.*?)',",response)
high = re.findall("bWendu:'(.*?)℃',",response)
low = re.findall("yWendu:'(.*?)℃',",response)
tianqi = re.findall("tianqi:'(.*?)',",response)
fengxiang = re.findall("fengxiang:'(.*?)',",response)
fengli = re.findall(",fengli:'(.*?)'",response)
aqi = re.findall("aqi:'(.*?)',",response)
aqiInfo = re.findall("aqiInfo:'(.*?)',",response)
aqiLevel = re.findall(",aqiLevel:'(.*?)'",response)
# 由于2011~2015没有空气质量相关的数据,故需要分开处理
if len(aqi) == 0:
aqi = None
aqiInfo = None
aqiLevel = None
info.append(pd.DataFrame({'ymd':ymd,'high':high,'low':low,'tianqi':tianqi,'fengxiang':fengxiang,'fengli':fengli,'aqi':aqi,'aqiInfo':aqiInfo,'aqiLevel':aqiLevel}))
else:
info.append(pd.DataFrame({'ymd':ymd,'high':high,'low':low,'tianqi':tianqi,'fengxiang':fengxiang,'fengli':fengli,'aqi':aqi,'aqiInfo':aqiInfo,'aqiLevel':aqiLevel}))
time.sleep(seconds)
# 生成数据表
weather = pd.concat(info)
# 数据导出
weather.to_csv('weather.csv',index = False)
代码说明:如上所示的爬虫代码中,绝大多数都添加了相应的注释性语言,另外再解释两点,一个是爬虫中添加字典类型的请求头headers,这样做的目的是为了将Python伪装成一个真实的浏览器,进而促使被访问的网站(或者称服务器)将Python当作一个正常的访问用户;另一个是在爬虫的循环中随机停顿几秒,这样做的目的是为了减轻被访问网站的流量压力,否则单机在一秒内访问对方十几次甚至上百次,会消耗对方很多资源。之所以在代码中添加这两方面的内容,都是为了防止被访问的网站对爬虫代码实施反爬举措,如访问需要输入验证码、重新登录甚至是封闭IP。
最终运行完上面的Python爬虫代码,就可以获得如下结构的数据表:
本章主要向读者介绍了有关Python的基础知识,包含三种基本的数据结构及对应的常用方法、三类控制流语法、字符串的常用处理方法、正则表达式的灵活使用、如何编写自定义函数以及如何基于这些知识点完成一个小的爬虫案例。通过本章内容的学习,希望读者能够牢牢掌握基础,为后续章节的学习做好充分的准备。
最后,回顾一下本章中学到的Python“方法”和函数,以便读者查询和记忆: