在正式编写代码前,先来了解一下Python的基础语法,包括变量、逻辑、集合、字符串、函数、文件操作、面向对象和类库。
计算机程序不仅可以处理各种数值,还可以处理文本、图形、音频、视频、网页等各种各样的数据,不同的数据,需要定义不同的数据类型。在Python中,能够直接处理的数据类型有以下几种。
Python可以处理任意大小的整数,当然包括负整数,在程序中的表示方法与数学上的写法相同,如120、-70、0等。
计算机由于使用二进制,因此,有时用十六进制表示整数比较方便,十六进制用0x前缀及数字0~9和字母a~f表示,如0xcd011、0xf44a66等都是十六进制数字。
【范例1.3-1】整数(源码路径:ch01/1.3/1.3-1.py)
范例文件1.3-1.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)num1=120,这里的等号是赋值符号,表示将右侧的整数值120赋值给左侧的变量num1。
(2)type()函数是获取变量指向的值的类型,这里是int型整数。
(3)0x开头的数字表示十六进制的整数,0b开头的数字表示二进制的整数,0o开头的数字表示八进制的整数。打印(print)这些数字时,自动转换为十进制,然后输出结果。
提示
在打印十六进制的数值时,自动转换为十进制。
浮点数也就是小数。整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的,而浮点数运算则可能因四舍五入产生误差。
【范例1.3-2】浮点数(源码路径:ch01/1.3/1.3-2.py)
范例文件1.3-2.py的具体实现代码如下。
【运行结果】
【范例分析】
这里的float表示浮点数类型。
字符串是以单引号'或双引号"引起来的任意文本,如'abc'、"abc"等。需要注意的是,''或""本身只是一种表示方式,不是字符串的一部分,因此,字符串'abc'只有a、b、c这3个字符。如果'本身也是一个字符,就可以用""引起来,例如,"I'm OK"包含的字符是I、'、m、空格、O、K这6个字符。
如果字符串内部既包含'又包含"怎么办?可以用转义字符\来标识。
如果字符串中有很多字符都需要转义,就需要加很多\,为了简化,Python还允许用r表示内部的字符串默认不转义。
【范例1.3-3】字符串(源码路径:ch01/1.3/1.3-3.py)
范例文件1.3-3.py的具体实现代码如下。
【运行结果】
【范例分析】
这里的str表示字符串类型,另外,\表示转义,\n表示转义换行符。
布尔值和布尔代数的表示完全一致,一个布尔值只有True和False两种值,要么是True,要么是False,在Python中,可以直接用True、False表示布尔值(注意大小写),也可以通过布尔运算计算出来。布尔值可以用and、or和not运算。and运算是与运算,只有所有都为True, and运算结果才是True。or运算是或运算,只要其中一个为True, or运算结果就是True。not运算是非运算,它是一个单目运算符,把True变成False, False变成True。
【范例1.3-4】布尔值(源码路径:ch01/1.3/1.3-4.py)
范例文件1.3-4.py的具体实现代码如下。
【运行结果】
【范例分析】
这里的bool表示布尔类型,只有True和False两个值,可以用来存储判断结果。
提示
如果有多个逻辑运算,那么建议使用小括号来明确标识运算顺序。
空值是Python中一个特殊的值,用None表示。None不能理解为0,因为0是有意义的,而None是一个特殊的空值。
【范例1.3-5】空值(源码路径:ch01/1.3/1.3-5.py)
范例文件1.3-5.py的具体实现代码如下。
【运行结果】
【范例分析】
这里的NoneType表示空值类型。
此外,Python还提供了列表、字典等多种数据类型,后面会陆续讲到。
变量的概念基本上和初中代数的方程变量是一致的,只是在计算机程序中,变量不仅可以是数字,还可以是任意数据类型。
变量在程序中用一个变量名表示,变量名必须是大小写英文、数字和_的组合,且不能用数字开头。
【范例1.3-6】变量(源码路径:ch01/1.3/1.3-6.py)
范例文件1.3-6.py的具体实现代码如下。
【运行结果】
【范例分析】
Python是一个弱类型语言,不需要事先定义变量的类型,就可以直接赋值。变量的类型是根据当时指向的值来决定的。
Python的逻辑控制语句包括判断语句和循环语句。
只有某些条件满足,才能做某件事情,而不满足时不允许做,这就是所谓的判断。
Python判断语句是通过一条或多条语句的执行结果(True或False)来决定执行的代码块。
(1)if语句。语法如下。
(2)if……else语句。语法如下。
(3)if嵌套语句。语法如下。
(4)elif语句。语法如下。
【范例1.3-7】判断语句(源码路径:ch01/1.3/1.3-7.py)
范例文件1.3-7.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)if后的条件如果为真,就运行if里的代码块,否则运行else里的代码块。
(2)input用来获取控制台的输入,返回字符串类型,所以需要转换成数字类型之后,才可以与数字进行判断。
提示
嵌套使用,注意左对齐,避免语法报错。
通常情况下,需要多次重复执行的代码,都可以用循环的方式来完成。
循环不是必须要使用的,但是为了提高代码的重复使用率,有经验的开发者都会采用循环。
Python中的循环语句有以下两种。
(1)while循环语句。语法如下。
【范例1.3-8】while循环语句(源码路径:ch01/1.3/1.3-8.py)
范例文件1.3-8.py的具体实现代码如下。
【运行结果】
【范例分析】
while循环条件为真,进入循环,运行循环的代码块。在循环的代码块中有一个if判断,根据条件打印i的值。
(2)for循环语句。语法如下。
【范例1.3-9】for循环语句(源码路径:ch01/1.3/1.3-9.py)
范例文件1.3-9.py的具体实现代码如下。
【运行结果】
【范例分析】
range(1,31)使用for循环,可以依次获取一个1~30之间的值,31是取不到的。
提示
内置range()函数,它会在一定范围内生成数列,可以迭代。
循环里还有两个关键字:continue和break。continue可以用来结束本次循环,进入下一次循环;break可以用来结束整个循环。
【范例1.3-10】continue和break(源码路径:ch01/1.3/1.3-10.py)
范例文件1.3-10.py的具体实现代码如下。
【运行结果】
【范例分析】
break是结束整个循环,continue是结束本次循环内容而进入下一次循环。
提示
break和continue只能作用于当前所在的这一层循环,不能作用于其他外部循环。
Python常用的集合容器有list、tuple、dict和set,下面对这些容器一一进行介绍。
list(列表)是可变的,这是它区别于字符串和元组的最重要的特点,一句话概括,即列表可以修改,而字符串和元组不能修改。
Python中list的方法如表1-1所示。
表1-1 list的方法
【范例1.3-11】list的使用(源码路径:ch01/1.3/1.3-11.py)
范例文件1.3-11.py的具体实现代码如下。
【运行结果】
【范例分析】
这里a=[66.25,333,333,1,1234.5]表示定义了一个列表a,存储了一些值,然后调用了列表的一些功能方法。
提示
类似insert、remove或sort等修改列表的方法没有返回值。
tuple(元组)是不可变的,可以理解为只读的列表,列表的查询方法元组都有。
【范例1.3-12】tuple的使用(源码路径:ch01/1.3/1.3-12.py)
范例文件1.3-12.py的具体实现代码如下。
【运行结果】
【范例分析】
小括号内存储的值是元组的元素,元组相当于只读的列表,所以列表的查询方法元组都可以使用,如count、index等。
提示
如果元组中只有一个值100,就需要这样定义:a=(100,)。
列表和元组是存储值的,而dict(字典)是存储键值对的。
Python中dict的方法如表1-2所示。
表1-2 dict的方法
续表
【范例1.3-13】dict的使用(源码路径:ch01/1.3/1.3-13.py)
范例文件1.3-13.py的具体实现代码如下。
【运行结果】
【范例分析】
定义了一个字典,存储了3组键值对,然后调用了字典的一些功能方法。
提示
字典的键是唯一的,也是无序的。
set(集合)与字典类似,也是无序的,但是只存储值,没有键,所以经常使用set完成去重的功能。
Python中set的方法如表1-3所示。
表1-3 set的方法
【范例1.3-14】set的使用(源码路径:ch01/1.3/1.3-14.py)
范例文件1.3-14.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)set与字典类似,使用的也是大括号,但不同的是,set只存储唯一的值,所以这里set1={1,2,3,2,2,3},最后打印出来set1的值为{1,2,3}。
(2)set还可以计算交集、并集和差集。
在最新的Python 3.x版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言。
创建字符串时,只需为变量分配一个值即可,例如:
Python不支持单字符类型,单字符在Python中也是作为一个字符串使用的。
Python访问子字符串,可以在方括号中写入下标来截取字符串,例如:
提示
Python中的下标也支持负数,如﹣1标识最后一个值。
可以截取字符串的一部分并与其他字段拼接,得到一个新的字符串,例如:
有时,需要在字符中使用特殊字符,Python用反斜杠(\)转义字符,如表1-4所示。
表1-4 转义字符
续表
字符串运算符如表1-5所示,表1-5中实例变量a值为字符串"Hello",b值为"Python"。
表1-5 字符串运算符
提示
Python 2.6开始,新增了一种格式化字符串的函数str.format(),它增强了字符串格式化的功能。例如,print('我叫{}今年{}岁!'.format('小明',10)),输出结果:我叫小明今年10岁!
字符串提供了丰富的功能函数,如表1-6所示。
表1-6 字符串内建函数
续表
续表
【范例1.3-15】字符串的使用(源码路径:ch01/1.3/1.3-15.py)
范例文件1.3-15.py的具体实现代码如下。
【运行结果】
【范例分析】
字符串是最常见的类型,提供了很多的功能方法,可以对照语法一一实现。
函数是组织好的,可重复使用的,用来实现单一或相关联功能的代码段。
函数能提高应用的模块性和代码的重复利用率。Python提供了许多内建函数,如print(),但是也可以自己创建函数,这被称为用户自定义函数。
可以定义一个有自己想要功能的函数,语法如下。
以下是简单的规则。
(1)函数代码块以def关键词开头,后接函数标识符名称和圆括号(),最后以冒号结束。
(2)任何传入参数和自变量必须放在圆括号中,圆括号之间可以用于定义参数。
(3)函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
(4)函数内容缩进。
(5)return[表达式]结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回None。
【范例1.3-16】定义和调用函数(源码路径:ch01/1.3/1.3-16.py)
范例文件1.3-16.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)定义函数area(),有两个参数width和height,调用时传入4和5,4赋值给width参数,5赋值给height参数,函数返回二者的乘积,结果是20。
(2)定义函数print_welcome(),有一个参数name,调用时传入"Runoob",结果打印出来。
提示
函数必须先定义,然后才可以调用。
以下是调用函数时可使用的参数类型。
(1)必需参数。
必需参数必须以正确的顺序传入函数。调用时的数量必须和声明时的一样。
(2)关键字参数。
关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。
使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为Python解释器能够用参数名匹配参数值。
(3)默认参数。
调用函数时,如果没有传递参数,则会使用默认参数。
(4)不定长参数。
如果需要一个函数能处理比当初声明时更多的参数,那么这些参数就称为不定长参数,与上述两种参数不同,其声明时不会命名。
【范例1.3-17】函数的参数(源码路径:ch01/1.3/1.3-17.py)
范例文件1.3-17.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)函数printme(a, b)在调用时,必须给a和b两个参数赋值。
(2)函数printme(a, b=100)在调用时,至少给a赋值,因为b有默认值,如果不给b赋值,b就使用默认值;如果给b赋值,b就不再使用默认值了。另外,默认值必须定义在非默认值后面。
(3)函数printme(*args)和函数printme(**kwargs)表示不定长的参数,可以是0个,也可以是多个。不同的是,**kwargs传入时必须用键值对形式,而*args只需要传入值即可。
Python使用lambda来创建匿名函数。
所谓匿名,意即不再使用def语句这样标准的形式定义一个函数。
(1)lambda只是一个表达式,函数体比def简单很多。
(2)lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑。
(3)lambda函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间中的参数。
(4)虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
语法如下:
【范例1.3-18】匿名函数(源码路径:ch01/1.3/1.3-18.py)
范例文件1.3-18.py的具体实现代码如下。
【运行结果】
【范例分析】
lambda arg1,arg2:arg1+arg2表示一个匿名函数,其中函数的参数是arg1和arg2,函数的返回值是arg1+arg2。
提示
为了简写,匿名函数一般在函数作为参数时使用。
定义在函数内部的变量拥有一个局部作用域,定义在函数外部的变量拥有全局作用域。
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。
在函数内部不能直接修改全局变量,除非使用global声明。
下面看两个例子。
【范例1.3-19】全局和局部变量(源码路径:ch01/1.3/1.3-19.py)
范例文件1.3-19.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)var1是定义在函数外部的,是全局变量,所以在函数内部和外部都能够访问。
(2)var2是定义在函数内部的,是局部变量,所以在函数内部可以访问而外部不能访问。
【范例1.3-20】全局和局部变量(源码路径:ch01/1.3/1.3-20.py)
范例文件1.3-20.py的具体实现代码如下。
【运行结果】
【范例分析】
num是定义在函数外部的,是全局变量。在函数内部如fun1中直接修改全局变量num,是不允许的,除非使用global关键字声明,这样才可以在函数内部修改全局变量。
读写文件是经常要使用的,在后面章节中,爬虫的数据有时也需要存储到文件中。Python使用内置的open()函数打开一个文件,并返回可以读写的文件对象。步骤如下。
(1)打开文件,或者新建一个文件。
(2)读/写数据。
(3)关闭文件。
语法如下。
open()函数一般只设置3个参数,说明如表1-7所示。
表1-7 open()函数的3个参数
mode参数的说明如表1-8所示。
表1-8 mode参数
open之后得到一个file对象,file对象常用的函数如表1-9所示。
表1-9 file对象常用的函数
续表
下面看3个例子。
【范例1.3-21】写文件(源码路径:ch01/1.3/1.3-21.py)
范例文件1.3-21.py的具体实现代码如下。
【运行结果】
运行后,打开file文件,查看结果如下。
【范例分析】
(1)这里的w表示写(write),如果文件不存在,则会创建一个文件;如果文件存在,则会覆盖这个文件重写。
(2)使用open()函数得到一个文件对象,使用write()方法可以写一个字符串,使用writelines()方法可以写一个集合的内容到文件中。使用完毕后,用close()方法释放资源。
w只表示写,下面了解一下如何同时具备读写两个功能。
【范例1.3-22】读写文件(源码路径:ch01/1.3/1.3-22.py)
范例文件1.3-22.py的具体实现代码如下。
【运行结果】
运行后,打开file文件,查看结果如下。
打印结果如下。
【范例分析】
(1)这里的r+表示可读可写,seek是移动读写的位置,seek(0)表示开头,seek(0,2)表示结尾。
(2)使用next每次获取一行数据。使用完毕后,用close()方法释放资源。
但是每次都这么写关闭文件实在太烦琐,所以,Python引入了with语句来自动调用close()方法。
【范例1.3-23】简写(源码路径:ch01/1.3/1.3-23.py)
范例文件1.3-23.py的具体实现代码如下。
【运行结果】
【范例分析】
with只适用于上下文管理器的调用,在width结束后,自动关闭对应的资源对象,如这里的file对象。
面向对象编程(Object Oriented Programming, OOP)是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发送的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间的传递。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
这里以一个例子来说明面向过程和面向对象在程序流程上的不同之处。
假设要处理学生的个人信息表,为了表示一个学生的个人信息,面向过程的程序可以用一个dict表示。
【范例1.3-24】处理学生信息可以通过函数实现(源码路径:ch01/1.3/1.3-24.py)
范例文件1.3-24.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)定义了两个字典对象stu1和stu2,定义了一个函数show()。
(2)在调用show时分别传入stu1和stu2,然后通过字典的键获取值,打印出来。
如果采用面向对象的程序设计思想,那么首先思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有name和age这两个属性。如果要打印一个学生的信息,那么首先必须创建出这个学生对应的对象,然后给对象发一个print_stu消息,让对象自己把自己的数据打印出来。
【范例1.3-25】处理学生信息可以通过OOP实现(源码路径:ch01/1.3/1.3-25.py)
范例文件1.3-25.py的具体实现代码如下。
【运行结构】
运行结果与通过函数实现是一样的。
【范例分析】
(1)这里创建了一个类Student,在魔法方法__init__()中定义并添加了两个实例属性name和age,在实例方法show()中,打印实例属性name和age。
(2)stu1=Student('jack',22),等号左侧表示创建对象,将jack赋值给属性name,将22赋值给属性age。然后stu1指向这个对象,同理,stu2也是如此。
(3)stu1. show()表示调用stu1对象的实例方法show(),打印name和age。
提示
形如__方法名__()这样的方法称为魔法方法,因为它们像魔法一样神奇,有一些特殊的功能。
面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class是一种抽象概念,如上面定义的Class是Student,是指学生这个概念,而Instance则是一个个具体的Student,例如,stu1和stu2是两个具体的Student。
所以,面向对象的设计思想是抽象出Class,根据Class创建Instance。
面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。
面向对象有3个特征:封装、继承和多态,下面会详细讲解。
面向对象最重要的概念就是类和实例对象,必须牢记类是抽象的模板,如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
(1)定义类。
仍以Student类为例,在Python中,定义类是通过class关键字实现的。定义类的语法如下。
class后面是类名,即Student,类名通常是大写字母开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念会在后面讲解,通常如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
(2)创建对象。
定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名()实现的。
【范例1.3-26】创建对象(源码路径:ch01/1.3/1.3-26.py)
范例文件1.3-26.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)每次实例化都会得到一个新的对象,地址是不同的。变量stu1指向的就是一个Student的实例,后面的0x7f2a0a369f98是内存地址,每个object的地址都不一样,而Student本身则是一个类。
(2)可以自由地给一个实例变量绑定属性,给实例stu1绑定一个name属性。
由于类可以起到模板的作用,因此,可以在创建实例时,把一些认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__()方法,在创建实例时,就把name、age等属性绑上去。
【范例1.3-27】创建对象并给属性赋值(源码路径:ch01/1.3/1.3-27.py)
范例文件1.3-27.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)Student('jack',88)会先调用__init__()方法,将参数传入,并给属性赋值。
(2)__init__()方法的第一个参数始终是self,表示创建的实例本身,因此,在__init__()方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。
面向对象编程的一个重要特点就是数据封装。在上面的Student类中,每个实例就拥有各自的name和age这些数据。可以通过函数来访问这些数据,例如,打印一个学生的信息,代码如下。
但是,既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数与Student类本身是关联起来的,称之为类的方法。
封装的另一个好处是可以给Student类增加新的方法,如get_grade()。
【范例1.3-28】封装(源码路径:ch01/1.3/1.3-28.py)
范例文件1.3-28.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)通过get_grade()方法获取的是分数的等级,并不是直接给用户返回分数。
(2)通过在实例上调用方法,直接操作了对象内部的数据,但无须知道方法内部的实现细节,这就是封装的优点。
在OOP程序设计中,当定义一个class时,可以从某个现有的class继承,新的class称为子类,而被继承的class称为基类、父类或超类。
下面看一个例子。例如,已经编写了一个名为Animal的类,它有两个子类,分别是Cat和Dog。
【范例1.3-29】继承(源码路径:ch01/1.3/1.3-29.py)
范例文件1.3-29.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)class Animal(object)表示Animal继承了object,其实所有的类都默认继承object。
(2)class Dog(Animal)表示Dog继承了Animal。同理,Cat也是。
(3)cat. run(),虽然cat对象所在的类Cat没有定义run()方法,但是它的父类Animal定义了run()方法,表示cat也拥有了run()方法,所以才可以调用。
(4)dog. run(),dog对象所在的类Dog定义了run()方法,但是它的父类Animal也定义了run()方法,表示dog中重写了父类的run()方法,结果是调用dog自己的run()方法。
(5)子类也可以调用父类的方法,如super().__init__(name),表示在子类Cat中调用了__init__()方法时,再调用父类Animal的__init__()方法,传递参数。
继承的最大好处是子类获得了父类的全部功能。由于Animal实现了run()方法和name属性,因此,Dog和Cat作为它的子类,什么也没做,就自动拥有了run()方法和name属性。
继承的第二个好处需要我们对代码做一点改进。Dog重写了run()方法,Cat重写了__init__()方法。这样功能又得到了扩展。
要理解什么是多态,首先要对数据类型再作一点说明。当定义一个class时,实际上就定义了一种数据类型。判断一个变量是否为某个类型可以用isinstance()函数判断。
【范例1.3-30】多态(源码路径:ch01/1.3/1.3-30.py)
范例文件1.3-30.py的具体实现代码如下。
【运行结果】
【范例分析】
看来c不仅是Dog,还是Animal。不过仔细想想,这是有道理的,因为Dog是从Animal继承下来的,当创建了一个Dog的实例c时,认为c的数据类型是Dog,但c同时也是Animal, Dog本来就是Animal的一种。所以,在继承关系中,如果一个实例的数据类型是某个子类,那么它的数据类型也可以被看作是父类。但是,反过来就不行了。
要理解多态的好处,还需要再编写一个函数,这个函数接受一个Animal类型的变量。
【范例1.3-31】多态的好处(源码路径:ch01/1.3/1.3-31.py)
范例文件1.3-31.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)新增一个Animal的子类,不必对run_twice()做任何修改。实际上,任何依赖Animal作为参数的函数或方法都可以不加修改地正常运行,原因就在于多态。
(2)多态的好处就是,当需要传入Dog、Cat、Tortoise……时,只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或子类,就会自动调用实际类型的run()方法,这就是多态的意思。
(3)对于一个变量,只需要知道它是Animal类型,无须确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:对扩展开放(允许新增Animal子类),对修改封闭(不需要修改依赖Animal类型的run_twice()等函数)。
Python之所以自称“内置电池(Batteries Included)”,就是因为内置了许多非常有用的模块,无须额外安装和配置,即可直接使用。
下面将介绍一些常用的内建模块。
os模块提供了非常丰富的方法,用来处理文件和目录。
【范例1.3-32】os模块(源码路径:ch01/1.3/1.3-32.py)
范例文件1.3-32.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)listdir()方法表示得到此目录下所有文件和文件夹,是一个列表类型。
(2)makedirs()方法可以创建多级目录。
(3)exists()方法判断路径是否存在。
datetime模块提供了各种对日期和时间的处理方法。
【范例1.3-33】datetime模块(源码路径:ch01/1.3/1.3-33.py)
范例文件1.3-33.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)datetime()方法传入指定的时间参数,得到一个新的日期对象。
(2)timestamp()方法获取的是时间戳。
(3)strftime()方法是日期转字符串类型,这里有一些特殊符号,%Y表示年,%m表示月,%d表示日,其他符号请参考开发文档。
(4)strptime()方法是字符串转日期类型,相当于strftime的逆操作。
hashlib模块提供了常见的摘要算法,如MD5、SHA1等,常用于密码加密等功能。
【范例1.3-34】hashlib模块(源码路径:ch01/1.3/1.3-34.py)
范例文件1.3-34.py的具体实现代码如下。
【运行结果】
【范例分析】
MD5是最常见的摘要算法,速度很快,生成结果是固定的128位,通常用一个32位的十六进制字符串表示。SHA1的结果是160位,通常用一个40位的十六进制字符串表示。比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法不仅越慢,而且摘要长度更长。
random模块用于生成随机数。
【范例1.3-35】random模块(源码路径:ch01/1.3/1.3-35.py)
范例文件1.3-35.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)random()方法返回0~1之间的随机数,1是取不到的。
(2)randint()方法返回一个指定范围内的随机数,如这里的10~20之间。
(3)choice()方法的参数是一个列表,表示随机获取列表中的一个值。
json模块使用Python语言来编码和解码JSON对象。
默认实现中,JSON和Python之间的数据转换对应关系如表1-10所示。
表1-10 JSON和Python之间的数据转换对应关系
续表
【范例1.3-36】json模块(源码路径:ch01/1.3/1.3-36.py)
范例文件1.3-36.py的具体实现代码如下。
【运行结果】
【范例分析】
(1)dumps()方法将字典转换为JSON格式的字符串。
(2)loads()方法将JSON格式的字符串转换为字典格式。
提示
json模块也可以与文件交互,使用dump()方法和load()方法。
csv模块使用Python语言来读写CSV文件。最重要的是writer()方法和reader()方法,返回的对象完成读写的功能。
【范例1.3-37】csv模块(源码路径:ch01/1.3/1.3-37.py)
范例文件1.3-37.py的具体实现代码如下。
【运行结果】
生成的文件内容如下。
【范例分析】
首先创建一个文件对象作为csv.writer的参数,得到writer对象,然后调用writerow()方法写一行数据,参数是一维列表,如果使用writerows()方法可以一次写多行数据,则参数是二维列表。