第2章
编写第一个网络爬虫
笔者是一个喜欢学习的人,自学了各方面的知识,总结发现:学习的动力来自于兴趣,兴趣则来自于动手做出成果的快乐。因此,笔者特意将动手的乐趣提前。在第2章,读者就可以体会到通过完成一个简单的Python网络爬虫而带来的乐趣。希望这份喜悦能让你继续学习本书的其他内容。
本章主要介绍如何安装Python和编辑器Jupyter、Python的一些基础语法以及编写一个最简单的Python网络爬虫。
2.1 搭建Python平台
Python是一种计算机程序语言,由于其简洁性、易学性和可扩展性,已成为最受欢迎的程序语言之一。在2016年最受欢迎的编程语言中,Python已经超过C++排名第3位。另外,由于Python拥有强大而丰富的库,因此可以用来处理各种工作。
在网络爬虫领域,由于Python简单易学,又有丰富的库可以很好地完成工作,因此很多人选择Python进行网络爬虫。
2.1.1 Python的安装
Python的安装主要有两种方式:一是直接下载Python安装包安装,二是使用Anaconda科学计算环境下载Python。
根据笔者的经验,这两种方式也对应着用Python来爬虫的两类人群:如果你希望成为Python开发人员或者爬虫工程师,笔者推荐你直接下载Python安装包,配合着Pycharm编辑器,这将提升你的开发效率;如果你希望成为数据分析师或者商业分析师,爬虫只是方便之后做数据分析,笔者推荐你使用Anaconda,配合着自带的Jupyter Notebook,这会提升你的分析效率。
由于网络爬虫需要较多的代码调试,因此我推荐初学者使用Anaconda。因为Anaconda除了包含了Python安装包,还提供了众多科学计算的第三方库,如Numpy、Scipy、Pandas和Matplotlib等,以及机器学习库,如Scikit-Learn等。而且它并不妨碍你之后使用Pycharm开发。
请读者选择一种下载,不要两种都用,不然会带来Python版本管理的混乱。
第一种方法:Anaconda的安装十分简单,只需两步即可完成。下面将介绍在Windows下安装Anaconda的步骤,在Mac下的安装方法与此类似。
步骤一:下载Anaconda。打开Anaconda官方网站下载页面https://www.anaconda. com/download/,下载最新版的Anaconda。如果在国内访问,推荐使用清华大学的镜像https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 。如图2-1所示。
步骤二:安装Anaconda。双击打开Anaconda安装文件,就像安装普通软件一样,直接单击Install安装即可。注意,在图2-2所示的对话框中勾选第一个和第二个复选框。按照提示操作后,安装即可。
第二种方法:使用Python安装包方法也非常简单。下面将介绍在Windows下安装的步骤,在Mac下的安装方法类似。
步骤一:下载Python。打开Python下载页面https://www.python.org/downloads/ ,下载最新版的Python,如图2-3所示。
步骤二:安装Python。双击打开Python安装文件,选择 Add Python 3.7 to PATH,之后单击InstallNow安装即可。
2.1.2 使用pip安装第三方库
pip是Python安装各种第三方库(package)的工具。
对于第三方库不太理解的读者,可以将库理解为供用户调用的代码组合。在安装某个库之后,可以直接调用其中的功能,使得我们不用自己写代码也能实现某个功能。这就像你为计算机杀毒时,会选择下载一个杀毒软件,而不是自己写一个杀毒软件,直接使用杀毒软件中的杀毒功能来杀毒就可以了。这个比方中的杀毒软件就像是第三方库,杀毒功能就是第三方库中可以实现的功能,你可以调用第三方库实现某个功能。
由于Anaconda或者Python安装包自带了pip,因此不用再安装pip。
在下面的例子中,我们将介绍如何用pip安装第三方库bs4,它可以使用其中的BeautifulSoup解析网页。
步骤一:打开cmd.exe,在Windows中为cmd,在Mac中为terminal。在Windows中,cmd是命令提示符,输入一些命令后,cmd.exe可以执行对系统的管理。单击“开始”按钮,在“搜索程序和文件”文本框中输入cmd后按回车键,系统会打开命令提示符窗口,如图2-5所示。在Mac中,可以直接在“应用程序”中打开terminal程序。
步骤二:安装bs4的Python库。在cmd中键入pip install bs4后按回车键,如果出现successfully installed,就表示安装成功,如图2-6所示。
除了bs4这个库,之后还会用到requests库、lxml库等其他第三方库,帮助我们更好地进行网络爬虫。正因为这些第三方库的存在,才使得Python在爬虫领域越来越方便、越来越活跃。
2.1.3 使用编辑器Jupyter 编程
如果你使用Anaconda安装的Python,那么可以使用Anaconda自带的Jupyter Notebook编程;如果你使用Python安装包下载的Python,下一节会介绍Pycharm的安装方法。为了方便大家学习和调试代码,本书推荐使用Anaconda自带的Jupyter Notebook。下面将介绍Jupyter Notebook的使用方法。
步骤一:通过cmd打开Jupyter。打开cmd,键入jupyter notebook后按回车键,浏览器启动Jupyter界面,地址默认为http://localhost:8888/tree ,如图2-7所示。
步骤二:创建Python文件。这时浏览器会开启一个页面,在页面中选择想创建文件的文件夹,单击右上角的New按钮,从下拉列表中选择Python 3作为希望启动的Notebook类型,如图2-8所示。
步骤三:在新创建的文件中编写Python程序。键入print('hello world!')后,可以按Shift + Enter快捷键执行刚刚的代码,结果如图2-9所示。
为什么本书使用Jupyter Notebook学习和编写Python脚本呢?
首先,Jupyter Notebook的交互式编程可以分段运行Python,对于网络爬虫这种分阶段(获取网页-解析网页-存储数据)运行的脚本来说,在写代码和测试阶段可以边看边写,可以加快调试代码的速度,非常适合debug(代码纠错)。
其次是展示,Jupyter Notebook能够把运行和输出的结果保存下来,下次打开这个Notebook时也可以看到之前运行的结果。除了可以编写代码外,Jupyter还可以添加各种元素,比如图片、视频、链接等,同时还支持Markdown。
在完成代码之后,还可以在Jupyter左上角点击File > Download as > Python,下载为.py文件,就可以放到其他编辑器里运行了。
如果你对Python的其他自定义功能有要求的话,推荐下载Jupyter的插件nbextensions。具体指引可以到笔者知乎或本书官网www.santostang.com 了解。
2.1.4 使用编辑器Pycharm编程
如果你使用Python安装包下载的Python,推荐选择Pycharm编辑器。
步骤一:下载Pycharm。打开Pycharm下载页面https://www.jetbrains.com/ pycharm/download ,下载Community版本,如图2-10所示。
步骤二:安装Pycharm。双击打开Pycharm安装文件,根据自己电脑选择32bit还是64bit,记得在Create Associations勾选 .py,安装即可,如图2-11所示。
步骤三:打开Pycharm。在开始页面,选择自己喜欢的主题,如图2-12所示。
步骤三:随后点击Create New Project创建一个新的项目,如图2-13所示。
步骤三: 选择好存储项目的位置,这里我给项目起的名称是“WebScraping”,你可以按照自己的需求存放项目地址,如图2-14所示。
步骤三:进入Pycharm页面后,会看到如下页面。这时,点击File > New > Python File,并填上python文件名,例如“test”。创建完test.py文件后,打开test.py,键入print('hello world!')。选中代码,右键选择 Run ‘test’,即可得到结果,如图2-15所示。
2.2 Python 使用入门
本节主要介绍Python的一些基础语法。如果你已经学会使用Python,可以跳过这一节,直接开始编写第一个Python网络爬虫。
2.2.1 基本命令
Python是一种非常简单的语言,最简单的就是print,使用print可以打印出一系列结果。例如,键入print("Hello World!"),打印的结果如下(同图2-9):
Hello World!
另外,Python要求严格的代码缩进,以Tab键或者4个空格进行缩进,代码要按照结构严格缩进,例如:
Hello World!
如果需要注释某行代码,那么可以在代码前面加上“#”,例如:
In [3]:# 在前面加上#,代表注释
print ("Hello World!")
Hello World!
2.2.2 数据类型
Python是面向对象(object oriented)的一种语言,并不需要在使用之前声明需要使用的变量和类别。下面将介绍Python的4种数据类型。
1. 字符串(string)
字符串是常见的数据类型,一般用来存储类似“句子”的数据,并放在单引号(')或双引号(")中。如果要连接字符串,那么可以简单地加起来。
Python Web Scraping by Santos
如果字符串包含单引号(')和双引号("),应该怎么办?可以在前面加上右斜杠(),例如以下案例:
I'm Santos. I love "python".
2. 数字(Number)
数字用来存储数值,包含两种常用的数字类型:整数(int)和浮点数(float),其中浮点数由整数和小数部分组成。两种类型之间可以相互转换,如果要将整数转换为浮点数,就在变量前加上float;如果要将浮点数转换为整数,就在变量前加上int,例如:
7
还有其他两种复杂的数据类型,即长整数和复数,由于不常用到,感兴趣的读者可以自己学习。
3. 列表(list)
如果需要把上述字符串和数字囊括起来,就可以使用列表。列表能够包含任意种类的数据类型和任意数量。创建列表非常容易,只要把不同的变量放入方括号中,并用逗号分隔即可,例如:
怎么访问列表中的值呢?可以在方括号中标明相应的位置索引进行访问,与一般认知不一样的是,索引从0开始,例如:
list1[0]: Python
list2[1:3]: [2, 3]
如何修改列表中的值呢?可以直接为列表中的相应位置赋予一个新值,例如:
['Python', 'new', 'Scrappy']
如果想要给列表添加值呢?可以用append()方法,例如:
['Python', 'new', 'Scrappy', 'by Santos']
4. 字典(Dictionaries)
字典是一种可变容器模型,正如其名,字典含有“字”(直译为键值,key)和值(value),使用字典就像是自己创建一个字典和查字典的过程。每个存储的值都对应着一个键值key,key必须唯一,但是值不需要唯一。值也可以取任何数据类型,例如:
Alex
{'Name': 'Alex', 'Age': 7, 'Class': 'First'}
如何遍历访问字典中的每一个值呢?这里需要用到字典和循环的结合,例如:
Name Alex
Age 7
Class First
如果想修改字典中的值或者加入新的键值呢?可以直接修改和加入,例如:
{'Name': 'Tom', 'Age': 7, 'Class': 'First', 'Gender':'M'}
2.2.3 条件语句和循环语句
条件语句可以使得当满足条件的时候才执行某部分代码。条件为布尔值,也就是只有True和False两个值。当if判断条件成立时才执行后面的语句;当条件不成立的时候,执行else后面的语句,例如:
You are studying python.
如果需要判断的有多种条件,就需要用到elif,例如:
You are studying java.
Python的条件语句注意不要少了冒号(:)。
循环语句能让我们执行一个代码片段多次,循环分为for循环和while循环。
for循环能在一个给定的顺序下重复执行,例如:
Beijing
Shanghai
Guangzhou
除了对列表进行直接循环,有时我们还会使用range()进行循环,首先用len(citylist)得到列表的长度为3,然后range(3)会输出列表[0,1,2],从而实现循环,得到和上面一样的结果。例如:
Beijing
Shanghai
Guangzhou
while循环能不断重复执行,只要能满足一定条件,例如:
0
1
2
2.2.4 函数
在代码很少的时候,我们按照逻辑写完就能够很好地运行。但是如果代码变得庞大复杂起来,就需要自己定义一些函数(Functions),把代码切分成一个个方块,使得代码易读,可以重复使用,并且容易调整顺序。
其实Python就自带了很多函数,例如下面的sum()和abs()函数,我们可以直接调用。
10
1
此外,我们也可以自己定义函数。一个函数包括输入参数和输出参数,Python的函数功能可以用y = x +1的数学函数来理解,在输入x=2的参数时,y输出3。但是在实际情况中,某些函数输入和输出参数可以不用指明。下面定义一个函数:
3
参数必须要正确地写入函数中,函数的参数也可以为多个,也可以是不同的数据类型,例如可以是两个参数,分别是字符串和列表型的。
apple banana orange
2.2.5 面向对象编程
在介绍面向对象编程之前先说明面向过程编程。面向过程编程的意思是根据业务逻辑从上到下写代码,这个容易被初学者接受,按照逻辑需要用到哪段代码写下来即可。
随着时间的推移,在编程的方式上又发展出了函数式编程,把某些功能封装到函数中,需要用时可以直接调用,不用重复撰写。这也是上面提到的函数式编程,函数式的编程方法好处是节省了大量时间。
接下来,又出现了面向对象编程。面向对象编程是把函数进行分类和封装后放入对象中,使得开发更快、更强。例如:
santos
18
看到这里,也许你有疑问,要实现上述代码的结果,使用函数式编程不是比面向对象编程更简单吗?例如,如果我们使用函数式编程,可以写成:
santos
18
此处确实是函数式编程更容易。使用函数式编程,我们只需要写清楚输入和输出变量并执行函数即可;而使用面向对象的编程方法,首先要创建封装对象,然后还要通过对象调用被封装的内容,岂不是很麻烦?
但是,在某些应用场景下,面向对象编程能够显示出更大的优势。
如何选择函数式编程和面向对象编程呢?可以这样进行选择,如果各个函数之间独立且无共用的数据,就选用函数式编程;如果各个函数之间有一定的关联性,那么选用面向对象编程比较好。
下面简单介绍面向对象的两大特性:封装和继承。
1. 封装
封装,顾名思义就是把内容封装好,再调用封装好的内容。封装分为两步:
第一步为封装内容。
第二步为调用被封装的内容。
(1)封装内容
下面为封装内容的示例。
self在这里只是一个形式参数,当执行obj1 = Person('santos', 18 )时,self等于obj1,此处将santos和18分别封装到obj1及self的name和age属性中。结果是obj1有name和age属性,其中name="santos",age=18。
(2)调用被封装的内容
调用被封装的内容时有两种方式:通过对象直接调用和通过self间接调用。
通过对象直接调用obj1对象的name和age属性,代码如下:
santos
18
通过self间接调用时,Python默认会将obj1传给self参数,即obj1.detail(obj1)。此时方法内部的self = obj1,即self.name='santos',self.age =18,代码如下:
santos
18
上述例子定义了一个Person的类。在这个类中,可以通过各种函数定义Person的各种行为和特性,要让代码显得更加清晰有效,就要在调用Person类各种行为的时候也可以随时提取。这比仅使用函数式编程更加方便。
面对对象的编程方法不会像平时按照执行流程去思考,在这个例子中,是把Person这个类型视为一个对象,它拥有name和age两个属性,在调用过程中,让自己把自己打印出来。
综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到对象中,然后通过对象直接或self间接获取被封装的内容。
2. 继承
继承是以普通的类为基础建立专门的类对象。面向对象编程的继承和现实中的继承类似,子继承了父的某些特性,例如:
猫可以:喵喵叫、吃、喝、拉、撒
狗可以:汪汪叫、吃、喝、拉、撒
如果我们要分别为猫和狗创建一个类,就需要为猫和狗实现他们所有的功能,代码如下,这里为伪代码,无法在python执行:
从上述代码不难看出,吃、喝、拉、撒是猫狗共同的特性,我们没有必要在代码中重复编写。如果用继承的思想,就可以写成:
动物:吃喝拉撒
猫:喵喵叫(猫继承动物的功能)
狗:汪汪叫(狗继承动物的功能)
小白家的小黑猫 吃
喵喵叫
胖子家的小瘦狗 吃
汪汪叫
对于继承来说,其实就是将多个类共有的方法提取到父类中,子类继承父类中的方法即可,不必一一实现每个方法。
2.2.6 错误处理
在编程过程中,我们不免会遇到写出来的程序运行错误,所以程序员经常戏称自己是在“写bug(错误)而非写程序”。这些错误一般来说会使得整个程序停止运行,但是在Python中,我们可以用try/except语句来捕获异常。
try/except使用try来检测语句块中的错误,如果有错误的话,except则会执行捕获异常信息并处理。以下是一个实例:
division by zero
上述代码首先执行try里面的语句,除以0产生运算错误后,会执行except里的语句,将错误打印出来。在网络爬虫中,它可以帮我们处理一些无法获取到数据报错的情况。
此外,如果我们并不想打印错误,就可以用pass空语句。
2.3 编写第一个简单的爬虫
当了解了Python的基础语法后,就算你是编程小白,也可以轻松爬取一些网站了。
为了方便大家练习Python网络爬虫,笔者专门搭建了一个博客网站用于爬虫的教学,本书教学部分的爬虫全部基于爬取笔者的个人博客网站(www.santostang.com )。一方面,由于这个网站的设计和框架不会更改,因此本书的网络爬虫代码可以一直使用;另一方面,由于这个网站由笔者拥有,因此避免了一些法律上的风险。
下面以爬取笔者的个人博客网站为例获取第一篇文章的标题名称,教大家学会一个简单的爬虫。
2.3.1 第一步:获取页面
上述代码就能获取博客首页的HTML代码,HTML是用来描述网页的一种语言,也就是说网页呈现的内容背后都是HTML代码。如果你对HTML不熟悉的话,可以先去w3school (http://www.w3school.com.cn/html/index.asp ) 学习一下,大概花上几个小时就可以了解HTML。
在上述代码中,首先import requests引入包requests,之后获取网页。
(1)首先定义link为目标网页地址。
(2)之后用headers来定义请求头的浏览器代理,进行伪装。
(3)r是requests的Response回复对象,我们从中可以获取想要的信息。r.text是获取的网页内容代码。
运行上述代码得到的结果如图2-16所示。
2.3.2 第二步:提取需要的数据
在获取整个页面的HTML代码后,我们需要从整个网页中提取第一篇文章的标题。
这里用到BeautifulSoup这个库对页面进行解析,BeautifulSoup将会在第4章进行详细讲解。首先需要导入这个库,然后把HTML代码转化为soup对象,接下来用soup.find("h1", class_="post-title").a.text.strip()得到第一篇文章的标题,并且打印出来。
soup.find("h1", class_="post-title").a.text.strip()的意思是,找到第一篇文章标题,定位到class是"post-title"的h1元素,提取a元素,提取a元素里面的字符串,strip()去除左右空格。
对初学者来说,使用BeautifulSoup从网页中提取需要的数据更加简单易用。
那么,我们怎么从那么长的代码中准确找到标题的位置呢?
这里就要隆重介绍Chrome浏览器的“检查(审查元素)”功能了。下面介绍找到需要元素的步骤。
步骤一:使用Chrome浏览器打开博客首页www.santostang.com 。右击网页页面,在弹出的快捷菜单中单击“检查”命令,如图2-17所示。
步骤二:出现如图2-18所示的审查元素页面。单击左上角的鼠标键按钮,然后在页面上单击想要的数据,下面的Elements会出现相应的code所在的地方,就定位到想要的元素了。
步骤三:在代码中找到标蓝色的地方,为学习笔记(2) – 同一页面多图表。我们可以用soup.find("h1", class_="post-title").a.text.strip()提取该博文的标题。
2.3.3 第三步:存储数据
存储到本地的txt文件非常简单,在第二步的基础上加上2行代码就可以把这个字符串保存在text中,并存储到本地。txt文件地址应该和你的Python文件放在同一个文件夹。
返回文件夹,打开title.txt文件,其中的内容如图2-19所示。
2.4 Python实践:基础巩固
学习完基础知识,做完第一个爬虫例子后,是不是觉得网络爬虫并没有想象中那么难呢?本书的目标就是希望你可以快速上手Python和爬虫,然后在后面的实战中学习。但是Python爬虫入门简单,一步步深入学习后,你会发现坑越来越多。只有认真阅读、反复练习,才能熟能生巧。
为了巩固大家学习Python网络爬虫的成果,第2章~第7章的结尾都提供了一个实践项目。这些实践的目的一是让读者从实践中检验自己学习了多少知识,二是进一步巩固在该章节中学习的知识。这些实践项目的完整代码都在书中,你也可以从本书配书资源的下载地址下载。除此之外,章末还提供了一个进阶问题供感兴趣的读者思考。
如果你是一个编程新手,在进一步学习Python编程之前需要记得以下3点:
(1)实践是最快的学习方式。如果你打算通过阅读本书而学会Python爬虫,就算读上100遍可能也不会达到很好的效果,最有效的方法就是:手输代码,反复练习。这也是为什么本书均通过项目案例来讲解Python网络爬虫的原因。
(2)搜索引擎是最好的老师。如果遇到不明白的问题,请学会使用百度或谷歌引擎搜索。就笔者自己的体验而言,谷歌的有效信息检索速度比百度快,较新的回答很有可能是英文的,但是如果你的英文阅读能力不行,就另当别论了。记得使用谷歌搜索时,找到Stack Overflow网站上的回答可以非常快地解决你的问题。
(3)请不要复制、粘贴代码。复制、粘贴代码除了可以让你在短时间内完成任务之外,没有任何好处。只有通过亲自输入代码,并不断重复、不断加快速度,才会提升你的编程能力和编程效率。否则给你一张白纸,你会什么代码都写不出。
本章实践的项目主要是帮助Python的初学者巩固之前学过的知识,如果你已经对Python有所了解,可以跳过以下部分。为了达到最好的效果,请先自行完成下面的题目。每一题后面都会提供答案,这些答案并不是唯一解,也不是让你不思考直接复制、粘贴运行的,而是用来对比思路,巩固Python基础内容的。
2.4.1 Python基础试题
试题1:请使用Python中的循环打印输出从1到100的所有奇数。
试题2:请将字符串“你好$$$我正在学Python@#@#现在需要&&&修改字符串”中的符号变成一个空格,需要输出的格式为:“你好 我正在学Python 现在需要 修改字符串”。
试题3:输出9×9乘法口诀表。
试题4:请写出一个函数,当输入函数变量月利润为I时,能返回应发放奖金的总数。例如,输出“利润为100 000元时,应发放奖金总数为10 000元”。
其中,企业发放的奖金根据利润提成。利润(I)低于或等于10万元时,奖金可提10%;利润高于10万元,低于20万元时,低于10万元的部分按10%提成,高于10万元的部分,可提成7.5%;利润在20万元到40万元之间时,高于20万元的部分可提成5%;利润在40万元到60万元之间时,高于40万元的部分可提成3%;利润在60万元到100万元之间时,高于60万元的部分可提成1.5%;利润高于100万元时,超过100万元的部分按1%提成。
试题5:用字典的值对字典进行排序,将{1: 2, 3: 4, 4:3, 2:1, 0:0}按照字典的值从大到小进行排序。
试题6:请问以下两段代码的输出分别是什么?
试题7:请问以下两段代码的输出分别是什么?
2.4.2 参考答案
试题1答案:
在上述代码中,range(1,101) 返回的是从1到100所有整数的列表list,然后使用循环判断这个数字除以2的余数是否为1,i % 2返回的是i除以2的余数。如果余数等于1,就输出该数字。
试题2答案:
在上述代码中,使用replace方法可以将字符串中的一些字符替换成想要的字符。例如,str1.replace('$$$', ' ')就是把str1中的'$$$'替换成空格。
其实还可以采用另一种更加简单的方法:
这里用到一个库re(正则表达式),使用其中的re.sub可以进行替换。正则表达式的功能将在第5章进行详细说明。
试题3答案:
运行上述代码,得到的结果如图2-20所示。
上述代码使用了两个循环的嵌套,在第一个循环中i为1,在第二个循环中j为1。当j完成循环后,i会加1,变成2,j又从1开始一个新的循环,从而得到输出的这个9×9乘法表。
试题4答案:
在上述代码中,计算应发奖金时,我们对不同的情况使用if和elif进行了不同的处理。
还可以使用一个比较简洁的方式:
试题5答案:
运行上述代码,输出的结果是:
[(0, 0), (2, 1), (1, 2), (4, 3), (3, 4)]
对字典进行排序是不可能的,只有把字典转换成另一种方式才能排序。字典本身是无序的,但是如列表元组等其他类型是有序的,所以需要用一个元组列表来表示排序的字典。
试题6答案:
第一段代码输出的结果是:1
第二段代码输出的结果是:[1]
从结果发现,在第一段代码中,a为数字int,函数改变不了函数以外a的值,输出结果仍然为1;而在第二段代码中,a为列表,函数将函数以外的a值改变了。
这是因为在Python中对象有两种,即可更改(mutable)与不可更改(immutable)对象。在Python中,strings字符串、tuples元组和numbers数字是不可更改对象,而list列表、dict字典等是可更改对象。
在第一段代码中,当一个引用传递给函数时,函数自动复制一份引用。函数里和函数外的引用是不一样的。
在第二段代码中,函数内的引用指向的是可变对象列表a,函数内的列表a和函数外的列表a是同一个。
试题7答案:
第一段代码输出的结果是:bbb aaa aaa
第二段代码输出的结果是:[1] [1] [1]
代码中的p1.name="bbb"表示实例调用了类变量,其实就是函数传参的问题。p1.name一开始指向类变量name="aaa",但是在实例的作用域里把类变量的引用改变了,就变成了一个实例变量,self.name不再引用Person的类变量name了,所以第一个答案是bbb。而后面的两个答案还是调用类变量name="aaa",所以还是aaa。
第二段的答案因为正如上面所言,列表和字典是可更改对象,因此修改一个指向的对象时会把类变量也改变了。
2.4.3 自我实践题
读者若有时间,可以从W3school的Python 100例中学习Python的各种应用基础知识,网址是:https://www.w3cschool.cn/python/python-100-examples.html 。