掌握基本语法是熟练使用Python语言进行自动化办公的必要前提。本文主要介绍要用到的Python编程基础知识,主要涉及Python的数据类型、变量、程序流程控制、函数、类、模块和包等内容。很多人为了图快,在学习Python语言时一上来就学习数据分析、机器学习。其实这些技术的基石是Python语言中最简单的基础知识。所以要老老实实打牢基础,这样才能看得懂那些深奥而复杂的源代码,在以后学习Python高级技术的过程中才会游刃有余。
2.1 数据类型与变量
计算机处理的都是数据,不同类型的数据有不同的处理方式。Python3中有6种数据类型:Number(数字)、String(字符串)、List(列表)、Tuple(元组)、Dictionary(字典)、Set(集合)。
2.1.1 数字
Python中常见的数字类型包括整型(int)、浮点型(float)、布尔型(bool)。
整型(int),通俗来说就是整数,不带小数点,如1、2、3。
浮点型(float),通俗来说就是小数,浮点型由整数部分与小数部分组成,如3.14。
布尔型(bool),只有对(True)或错(False)两种状态。False等值于0,True等值于1,所以它也可以当作整数使用。
>>> True+1
2
尽管1.0和1相等,但是Python自动将1.0看作一个浮点数,将1看作整数。使用Python内置的type函数可以查看一个数据的具体类型。
>>> type(1),type(1.0),type(True)
(<class 'int'>, <class 'float'>, <class 'bool'>)
可以使用Python内置的int、float函数转换数据类型。例如,把浮点型转为整型,把整型转为浮点型。
>>> int(1.0), float(1)
(1, 1.0)
整型、浮点型数字可以做四则运算,在解释器里输入一个运算表达式,它将输出表达式的值。整型和浮点型数字在计算机内部存储的方式是不同的,整型数字运算永远是精确的,而浮点型数字运算则可能会有四舍五入的误差。
>>> 3 * 0.1
0.30000000000000004
>>> 5 / 2
2.5
在整数除法中,使用除法运算符/总是返回一个浮点型数字,如果只想得到整数结果,要丢弃可能的分数部分,可以使用整除运算符//。
>>> 5//2
2
整除不一定能得到整型数字,不同类型的数混合运算时会将整型数字转换为浮点型数字。
>>> 5.0//2
2.0
Python3中采用%表示取模运算,它返回除法的余数。
>>> 5%2
1
布尔型数字可以做and、or和not逻辑运算。and运算是与运算,只有所有表达式都为True,and运算结果才是True;or运算是或运算,只要其中有一个表达式为True,or运算结果就是True;not运算是非运算,它把True变成False,False变成True。
>>> 2 > 3 and 3 > 1
False
>>> 2 > 3 or 3 > 1
True
>>> not 3 > 1
False
2 > 3 and 3 > 1,既有关系运算又有逻辑运算。多个运算同时出现的时候,执行顺序是算术运算>关系运算>逻辑运算。2 > 3是关系运算,结果是False;3 > 1运算结果是True;False and True,运算结果就是False。
值得注意的是,我们这里说的都是十进制的数。除了十进制数之外,还有二进制数、八进制数、十六进制数等。十进制数是逢10进1,十六进制数是逢16进1。进制问题,读者可以自行查找资料阅读。
2.1.2 字符串
与数字对应的就是字符串,也就是一串字符,例如前面输出的“Hello,World!”,就是字符串类型(str)的数据。
>>> type('Hello,World!')
<class 'str'>
1.字符串表示形式
字符串的特点就是数据的外部由引号包围,可以是单引号'、双引号"、三引号'''。通常来说,字符串里面有单引号,就用双引号包围;字符串里面有双引号,就用单引号包围;既有单引号又有双引号就用三引号包围。
>>> print('''He said:"It's love!"''')
He said:"It's love!"
使用三引号还可以表示多行字符串。多行代码的录入方法是:在>>>后面录入第一行代码,敲击回车,出现“…”,在后面继续录入第二行代码。也可以把多行代码复制下来,在>>>处单击右键粘贴。
>>> print('''Beautiful is better than ugly.
... Explicit is better than implicit.
... Simple is better than complex.
... Complex is better than complicated.''')
输出结果如下。
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
一般来说,引号包围的字符都会原模原样地输出,但是也有例外,那就是转义字符\。当Python读到字符串中的\,它会将接下来的字符看作一个普通字符。
>>> print("He said:\"It's love!\"")
He said:"It's love!"
常见的转义字符用法见表2-1。
表2-1
转义字符 | 描述 | 转义字符 | 描述 |
\(不在行尾时) | 转义符 | \(在行尾时) | 续行符 |
\' | 单引号 | \\ | 反斜杠符号 |
\" | 双引号 | \r | Enter键 |
\t | 横向制表符 | \n | 换行 |
例如,使用换行转义字符\n可以输出换行符。
>>> print('Flat is better than nested.\nSparse is better than\
... dense.\nReadability counts.')
Flat is better than nested.
Sparse is better than dense.
Readability counts.
上面这行代码太长,在行末输入转义字符“\”后按Enter键另起一行,Python会认为它们属于同一行。
下面是多个转义字符混合使用的效果。
>>> print('名单:\n\t\'小王\'\n\t\'小李\'\n\t\'小张\'')
名单:
'小王'
'小李'
'小张'
有时候我们看到以r开头的字符串,说明后面的字符都是普通的字符。也就是说,反斜杠可以用来转义,使用r可以让反斜杠不发生转义。
>>> print(r'Flat is better than nested.\nSparse is better\
... than dense.\nReadability counts.')
Flat is better than nested.\nSparse is better\
than dense.\nReadability counts.
字符串前面有r,也就不会把\n当作换行符了。由于文件路径有反斜杠,所以我们常常在文件路径字符串前面加r。
有时候会在字符串里看到%s、%d,它们表示占位符。
>>> age=5
>>> print('小明已经%d岁了!' % age)
小明已经5岁了!
%d是占位符,它最终会被后面的age变量的值所替代。
使用占位符是为了格式化输出字符串,一个字符串可以使用多个占位符。不同数据类型的占位符不一样,如%s代表字符串,%d代表整型数字。
>>> name='小明';age=5;like='画画'
>>> print('%s已经%d岁了,他爱好%s。' % (name, age, like))
小明已经5岁了,他爱好画画。
2.字符串基本操作
不同的数据类型,做运算的方式是不一样的。两个字符串使用加法运算符+做加法运算,是将两个字符串连接起来,下面举例说明。
>>> print(1+2)
3
>>> print('1'+'2')
12
要注意的是,数字带上引号也可以变成字符串。
>>> print('1' * 10)
1111111111
字符串使用乘法运算符*做乘法运算的含义是复制,上面的代码的意思是将1复制10次。
字符串是一串字符,我们可以通过Python内置的len函数查看字符个数。
>>> len('Beautiful')
9
我们可以截取其中的某个字符。例如,获取Beautiful的第6个字符。
>>> 'Beautiful'[5]
'i'
注意,Python中的字符串有两种索引方式,从左往右以0开始,从右往左以−1开始。
下面截取Beautiful的前3个字符。
>>> 'Beautiful'[0:2]
'Bea'
下面截取Beautiful从第1个到倒数第3个的所有字符。
>>> 'Beautiful'[0:-2]
'Beautif'
可以使用count方法统计B出现的次数,什么是方法?后面学习了类,我们会详细解释。
>>> 'Beautiful'.count('B')
1
还可以使用replace方法替换字符串中的字符B。
>>> 'Beautiful'.replace('B','b')
'beautiful'
有些字符串非常长,例如一篇网页的源代码就是一个字符串。要从杂乱无章的代码中获取特定的内容,就需要用到正则表达式,后面会详细介绍。
2.1.3 变量
我们先跳开数据类型这个话题,说一说变量。
在前面的例子中,需要多次用到字符串Beautiful,每次都写,就会显得很麻烦。特别是比较长的字符串,往往记不住又容易写错。
计算机的内存可以帮我们记住这些数据,给字符串Beautiful贴一个标签string_1,下次直接使用标签名即可。
>>> string_1='Beautiful'
>>> string_1[5]
'i'
string_1='Beautiful'的含义是什么?首先,Python会读取等式右边的数据,识别出数据类型为字符串,在内存中找一个足够容纳Beautiful的可用空间,把Beautiful放进去,并且以string_1这个名称指向它,string_1就是变量。要注意这里的=,它是“赋值、设置值”的意思,也就是把右边的值赋给左边的变量,而不是“等于”的意思。在Python中,要判断是否相等,要使用两个等号==。
有了变量,就可以更好地处理数据。例如,我们可以将两个字符串连接。
>>> string_1=string_1+' Girl!'
>>> print(string_1)
Beautiful Girl!
上例中,string_1同时出现在等号的左边和右边,意思是先计算等号右边的结果,然后将结果赋给左边的名称。
string_1开始指向的是Beautiful,后来指向的是Beautiful Girl!,所以叫作变量,表明它所引用的数据可以变化。而Beautiful一经写下,它就不会变化,因此它是常量。给数据起的名称仅仅是数据标签,它不会改变数据。当然,也可以给同一个数据起多个名称。
>>> string_1='Hello,World!'
>>> string_2=string_1
>>> string_1='Hello,Python!'
>>> print(string_1)
Hello,Python!
>>> print(string_2)
Hello,World!
我们可以看到尽管string_1的值改变,string_2指向的还是原来的值Hello,World!。
Python中的数据命名要遵守一些规则,有一些单词是系统保留的关键字,不能使用。我们可以通过代码查看这些关键字。
>>> import keyword
>>> print(keyword.kwlist)
['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
数据的名称不能以数值或非字母的字符开头(如逗号、加减号、斜杠等),但是下划线是合法的。也就是说变量名可以由字母、下划线和数字组成。但是变量名也不能太随意,最好是名字有明显的含义,看到名字就知道变量所代表的数据。不要被以下划线_开头的变量吓倒,把它理解为普通字符就可以了。这种以下划线开头的变量通常供程序内部调用,在源代码里经常可以看到。
2.1.4 组合数据类型
我们接着说数据类型。计算机不仅要对单个变量表示的数据进行处理,更多情况下,计算机还需要对一组数据进行批量处理。如果我们为每个数据都设计一个变量,就会显得很烦琐。这时就要用到Python中的组合数据类型,包括列表、元组、字典和集合,它们可以一次性组织多条数据。
1.列表
列表是最常用的一种数据形式,它可以把大量的数据放在一起,通过序号来访问其中的成员,而不需要为每个成员起名字,这样就可以大幅减少变量名称的使用。列表是以方括号[]包围的数据,不同的成员间以半角逗号,分隔。列表可以包含任何数据类型,也可以包含另一个列表。
例如,如果编程要用到'a'、1、1.0、'b'4个数据,我们可以把它们放入一个列表,然后赋给list_1。
>>> list_1=['a',1,1.0,'b']
可以通过Python内置的type函数查看变量类型。
>>> type(list_1)
<class 'list'>
还可以通过Python内置的len函数查看列表元素个数。
>>> len(list_1)
4
访问列表中的元素要用[索引号]的形式,列表元素的索引号从0开始,第一个元素的索引号是0。
例如,访问列表的第3个元素。
>>> list_1[2]
1.0
如果索引号为负数,则表示倒着取,-1表示最后一个元素。
>>> list_1[-1],list_1[-2]
('b', 1.0)
也可以通过冒号:和索引号来提取多个元素,下面分别提取索引号从0到3(不含)、从1到最后的元素。
>>> list_1[:3],list_1[1:]
(['a', 1, 1.0], [1, 1.0, 'b'])
我们可以用一个变量list_1的多种索引方式标识一堆数据,以提升编程的效率和灵活性。
可以使用count方法统计列表中元素出现的次数。
>>> list_1.count('b')
1
可以使用pop方法从列表中删除元素。
>>> list_1.pop()
'b'
>>> list_1
['a', 1, 1.0]
可以使用append方法增加列表元素。
>>> list_1.append(100)
>>> list_1
['a', 1, 1.0, 100]
列表中的元素是有顺序的,我们可以使用reverse方法颠倒其排序。
>>> list_1.reverse()
>>> list_1
[100, 1.0, 1, 'a']
前面用方括号创建了列表,还可以使用Python的内置函数list来创建列表。
>>> list_1=list((100, 1.0, 1, 'a'))
要注意这里用的是两层圆括号,外层圆括号是函数的固定写法,它只有一个参数,即(100, 1.0, 1, 'a')。
2.元组
元组可以看成特殊的列表,它用圆括号()将数据括起来。
>>> tuple_1=(100, 1.0, 1, 'a')
>>> type(tuple_1)
<class 'tuple'>
也可以使用Python的内置函数tuple来创建元组。
>>> tuple_1=tuple((1, 1.0, 1, 'a'))
与列表不同的是,元组一旦建立就不能改变里面的数据,不能添加或删除数据项。元组的应用场景主要是存放重要数据(如函数的参数和返回值),保护数据安全。由于元组数据不改变,所以它的速度快于列表,因此能用元组尽量不用列表。
元组和列表之间可以互相转换。如果不希望数据被程序所修改,就应该将其转换为元组类型。
>>> tuple_1=tuple(list_1)
>>> tuple_1
(100, 1.0, 1, 'a')
我们也可以用len函数统计元组中的元素个数。
>>> len(tuple_1)
4
提取元组中的元素要用[索引号]的形式,元组元素的索引号从0开始,第一个元素的索引号是0。例如,我们访问元组第1个元素。
>>> tuple_1[0]
100
要将元组转为列表,可以使用Python的内置函数list,将元组作为参数代入,返回列表对象。
>>> list_1=list(tuple_1)
>>> list_1
[100, 1.0, 1, 'a']
>>> type(list_1)
<class 'list'>
3.字典
当数据之间存在对应关系的时候,就需要用到字典。字典中每个成员是以“键:值”对的形式存在的。字典中的键都是不重复的。
字典中的元素以花括号{}包围。字典中的成员是没有顺序的,通过键来访问成员,而不能像列表那样通过位置访问。在字典中查找数据非常快捷。
>>> dict_1={'春':'Spring','夏':'Summer','秋':'Autumn','冬':'Winter'}
>>> type(dict_1)
<class 'dict'>
>>> dict_1['夏']
'Summer'
可以更新字典中键的值。
>>> dict_1.update({'秋':'Fall'})
>>> dict_1['秋']
'Fall'
4.集合
集合与字典类似,但它仅包含键,而没有值。由于键都是不重复的,所以集合是不包含重复数据的数据集。利用这一特点,我们可以快速整理数据,并删除重复数据。要注意的是,Python中集合内的元素是无顺序的。
>>> list_1=['春','夏','秋','冬','春','夏','秋','冬']
>>> set_1=set(list_1)
>>> set_1
{'夏', '春', '秋', '冬'}
>>> type(set_1)
<class 'set'>
>>> list_1=list(set_1)
>>> type(list_1)
<class 'list'>
>>> list_1
['夏', '春', '秋', '冬']
本小节只是简要介绍了列表、元组、字典、集合,涉及圆括号()、方括号[]、花括号{}、冒号:,它们还有千变万化的应用方式。正是借助这些简要的符号,Python语言才变得轻巧、灵活。
2.2 程序流程控制
程序流程控制就是指“程序怎么执行”,或者说“程序执行的顺序”。我们写一个程序,里面有很多行代码,这时候就有一个问题:哪行先执行,哪行后执行,某行执行完了之后再执行哪行?这些问题的答案就是程序流程控制。
程序流程控制可分为3类:顺序执行,先执行第一行再执行第二行……依次从上往下执行;选择执行;有些代码可以跳过不执行,有选择地执行某些代码;循环执行,有些代码会反复执行。我们日常工作中遇到的复杂任务,都可以用这3种结构来设计程序。程序通常按照由上而下的顺序来执行各条语句,直到整个过程结束。使用选择结构和循环结构,可以改变程序执行的流程。
Python编程中对程序流程的控制主要通过if语句、for循环语句、while循环语句来实现。
2.2.1 if语句
if语句是使用最为普遍的条件选择语句,它的格式如下。
if表达式1:
语句1
elif表达式2:
语句2
...
else:
语句N
elif与else语句也可以省略。
如果“表达式1”为真,则Python运行“语句1”,反之则往下运行。如果没有条件为真,就运行else内的语句。
if语句执行过程如图2-1所示。
图2-1
将下面的代码复制到Spyder编辑器中,保存后按F5键运行。
score=input('请您输入分数:')
score=int(score)
if score>=90:
print('优秀')
elif score>=80:
print('良好')
elif score>=70:
print('中等')
elif score>=60:
print('较差')
else:
print('未及格')
运行后,提示输入分数,输入不同的分数就会获得不同的成绩评价。
要注意的是,if语句里面还可以继续嵌套if语句。
有时候,我们会在一条语句里面使用if…else语句。
>>> is_male=True
>>> state='男性' if is_male else '女性'
>>> state
'男性'
2.2.2 for语句
下面介绍循环语句。“循环”是程序设计里一个很重要的概念,其实就是命令计算机去重复做一件事情(或者一类事情)。Python里面有两种循环语句:for循环语句和while循环语句。
前面我们已经用过for语句构造循环来完成重复工作。for语句还可以遍历任何序列的项目,如一个列表或者一个字符串。“遍历”就是依次取值,例如遍历列表,就是指依次从列表中取值。每取值一次,就运行循环体内的语句一次。
例如,我们需要输出一个字符串中的每个字符。for循环语句的录入方法是:在>>>后录入第一行后回车,在…后面录入四个空格再录入print(a),然后回车,再回车,代码就运行了。
>>> str_1='abc'
>>> for a in str_1:
... print(a)
a
b
c
一般来说,能用for循环遍历的对象都可以称为可迭代(Iterable)对象。判断一个对象是否是可迭代对象,可以用isinstance函数,它可以判断一个对象是否是一个已知的类型。
>>> from collections.abc import Iterable
>>> isinstance('abc', Iterable)
True
遍历一个列表的每个元素。
>>> list_1=['a', 'b', 'c']
>>> for a in list_1:
... print(a)
...
a
b
c
遍历数字序列。
>>> for i in range(3):
... print(i)
...
0
1
2
使用for语句,可以快速生成一个列表。
>>> [x+2 for x in range(10)]
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
方括号[]及其包含的内容又叫列表推导式。将方括号[]改为圆括号(),就成了生成器(generator),将它赋给变量gene_1。
>>> gene_1=(x+2 for x in range(10))
等式右边得到一个生成器。
>>> type(gene_1)
<class 'generator'>
使用Python的内置函数next可以依次获取生成器的值。
>>> next(gene_1)
2
>>> next(gene_1)
3
...
>>> next(gene_1)
11
2.2.3 while语句
while语句是循环语句,也是条件判断语句。其语法如下。
while表达式1:
语句
看一个例子。
>>> m=3
>>> n=0
>>> while m>n:
... print(m)
... m=m-1
...
3
2
1
本例中,m初始值为3,如果条件m>n为True,那么就运行后面的代码,输出m,同时m减小1。运行完毕以后,程序跳回,继续判断条件m>n。
要注意的是,如果条件永远为True,while循环会进入无限循环,里面的代码块会一直运行。
>>> while True:
... print('1')
运行以后,程序会一直输出1,直到按Ctrl+C快捷键强制中断。无限循环通常用于开设服务器,处理客户端实时请求。
按Ctrl+C快捷键可以手动中断程序,也可以用代码中断循环或跳出循环,如continue语句和break语句。使用continue语句,Python将跳出当前循环块中的剩余语句,继续进行下一轮循环。而使用break语句则会终止整个循环。
有时候会看到pass语句,这个是占位用的,表示什么也不做,直接跳过。我们在编程时,可以先将结构搭建好,用pass语句占位,然后再来写结构体内部的语句。
2.3 函数(function)
我们编写一段程序实现了某个功能,如果要在其他地方实现相同的功能,可以把代码块复制过去。但是,如果代码需要修改,就会遇到很大困难,需要把每一处复制粘贴的代码都修改一遍。
这时候,我们就可以使用更高效的工具——函数。函数可以将一段代码封装起来,然后可以被其他Python程序重复使用。这样做的好处很明显:一是调用时只需要写函数名称,而不用复制整段代码;二是修改代码时只需要修改函数。
2.3.1 函数的定义与调用
Python中的函数和数学中的函数非常类似。看一个数学函数的例子。
f(x,y) = x+y
这就是一个加法函数,给定两个数x、y,求二者之和。
在Python中可以写成下面的语句。
>>> def f(x,y):
... return x+y
查看f的类型。
>>> f
<function f at 0x000000000435C0D0>
>>> type(f)
<class 'function'>
关键字def表示定义一个函数,f是函数的名字,x、y是函数的参数,关键字return指定了调用函数时输出的值,称为函数的返回值。所以,Python中的函数就是一个拥有名称、参数和返回值的代码块。
函数参数和返回值是可以选的,如果函数只是简单地执行某段代码,并不需要与外部进行交互,那么参数和返回值都可以省略。
定义了函数,后面就可以调用函数了。
>>> f(1,2)
3
>>> f('a', 'b')
'ab'
2.3.2 函数的应用举例
在程序设计过程中,我们经常需要调试程序。我们需要了解程序运行到哪一步了,这时候可以通过输出特定的符号来标识。下面定义一个函数,用来输出特定的符号。
>>> def f():
... print('**********')
...
>>> f()
**********
这是一个没有参数和返回值的函数,每调用一次函数,都会固定输出10个星号。
当然,我们也可以设置参数,决定输出星号的个数。
>>> def f(n):
... print('*' * n)
...
>>> f(10)
**********
>>> f(20)
********************
我们还可以将输出的符号和个数都设为参数,这样就更灵活一些,不仅可以输出星号,还可以输出其他字符。
>>> def f(x,n):
... print(x * n)
...
>>> f('*',20)
********************
>>> f('-',20)
--------------------
参数越多,函数的功能就越强大,同时又增加了调用时书写参数的麻烦。有时候,如果我们忘记给参数赋值,那么函数将产生错误。为了避免出现这种情形,Python允许创建带默认值的函数。由于我们经常需要输出20个星号,那么在创建函数的时候可以将其设为默认值。
>>> def f(x='*',n=20):
... print(x * n)
...
>>> f()
********************
>>> f('+',30)
++++++++++++++++++++++++++++++
我们多次使用的print其实也是函数,它是Python的内置函数,用于打印输出。之前我们都只向其提供一个参数,其实,它的参数有很多。
在Python里还有个内置函数help,使用它可以查看帮助信息。
>>> help(print)
Help on built-in function print in module builtins:
print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
以下是print函数的完整语法。
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
其中,参数value表示输出的内容。...表示该参数个数不固定,也就是一次可以输出一个,也可以输出多个,输出多个内容时,需要用,分隔。
参数sep用来间隔多个对象,默认值是一个空格。
参数end用来设定以什么结尾。默认值是换行符\n,也可以换成其他字符串。
参数file要写入的文件对象。sys.stdout是Python中的标准输出流,默认是映射到打开脚本的窗口中,所以,print操作会把字符输出到屏幕上。我们也可以修改默认参数,将输出字符保存到文件中。
参数flush输出是否被缓存,后面文件处理章节会介绍。
下面设置file参数,将字符串保存到本地文件中。
>>> f=open('test.txt','w')
>>> print('Hello World!',file=f)
>>> print('Hello Python!',file=f)
>>> f.close()
通过设置file参数,字符串被保存到本地文件中,而不是输出在屏幕上,如图2-2所示。
图2-2
下面设置sep参数,将多个字符串的分隔符设置为-。
>>> print('www','www','www',sep='-')
www-www-www
可以通过Python中内置的dir函数获取所有内置函数。
>>> dir(__builtins__)
[...,'abs','all','any','ascii','bin','bool','breakpoint','bytearray','bytes','callable','chr','classmethod',
'compile','complex','copyright','credits','delattr','dict','dir','divmod','enumerate','eval','exec', 'exit',
'filter','float','format','frozenset','getattr','globals','hasattr','hash','help','hex','id','input','int',
'isinstance','issubclass','iter','len','license','list','locals','map','max','memoryview', 'min','next',
'object','oct','open','ord','pow','print','property','quit','range','repr','reversed','round', 'set','setattr',
'slice','sorted','staticmethod','str','sum','super','tuple','type','vars','zip']
常用的内置函数包括help、print、input、type、dir、len、int、list、str、range、open、sum等,这些构成了编程的起点。尤其是type、dir、help这3个函数,大家要熟练掌握。
2.4 类(class)
初级的编程,用到数据和函数就足够了,为什么要使用类?
一个大型的程序可能有成百上千的函数。这么多函数放在一处,名称很容易混淆,于是我们需要把函数归类,将解决某类问题的函数放在一起,调用函数前先找到它所在的类。
Python是一门面向对象编程语言(Object Oriented Programming,OOP),类则是面向对象编程的基础。面向对象编程有三大特征:封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。
其实,前面我们已经用到了封装。将各种类型的数据放进列表中,这是数据层面的封装;把常用的代码块放入一个函数,这是语句层面的封装。类则是一种更复杂的封装,它把多个函数和数据封装在一起。如果没有封装,我们要完成一个任务,就要复制一段代码,整个代码文件就会非常臃肿。如果代码有错需要修改,就要修改多处。有了封装,我们就不用再一遍一遍地复制粘贴代码,而是调用列表、函数、类。代码文件也就更加简洁、清晰、易于维护。封装的另一个优点是安全,将各种代码和技术细节隐藏起来,让使用者只能通过事先定义好的方法来访问。封装无处不在,例如计算机对普通用户来说就是一个“黑箱”,封装和隐藏了所有细节,但是对外提供了一堆按键,这些按键也正是“接口”的概念。然后我们可以在接口附加上操作的限制,这样就不会误操作损害硬件。封装是一种编程的思想,具体到我们写程序时,当多处都要用到一段相同的代码时,我们就应该把它封装起来。
2.4.1 类的基础用法
1.类的定义
举个例子,每个人都是不一样的,有不同的性格、爱好(也可以称为属性),不同的行为特征(如吃饭、说话、工作、学习等)。
我们定义一个Person类。
>>> class Person:
... def __init__(self,name,age,like):
... self.name=name
... self.age=age
... self.like=like
... def eat(self):
... print(self.name+'开始吃饭!')
... def speak(self):
... print('%s说: 我%d岁了,我爱好%s。' % (self.name, self.age, self.like))
定义后查看Person,可以看到它是一个类。
>>> Person
<class '__main__.Person'>
我们定义了类,但这只是一个“模板”,我们还不能使用它。正如定义了函数,如果没有给参数,那么代入数据是无法完成计算的。为了将类变成有用的东西,我们必须代入数据,创建实例(instance)。
>>> p=Person('小明',5, '画画')
现在再看一下p是什么?
>>> p
<__main__.Person object at 0x0000000003919828>
p是一个对象(object),或者称为Person类的实例化对象,它在内存中的地址为0x0000000003919828。可以这么理解,Person是抽象的“人类”,而p是“人类”的一个具体的例子——小明。
类的定义和函数非常类似,Person('小明',5,'画画')看上去像一次函数的调用,但这其实是一个类的实例化,我们创建了一个Person类的实例p。
创建实例p,它会先自动执行初始化函数__init__,在p里面存放数据('小明',5, '画画')。self是固定写法,代表实例的内存地址,通过self就可以找到实例。
现在使用下面的语法查看p里面存放的数据。
>>> p.name,p.age,p.like
('小明', 5, '画画')
这些数据称为对象的属性(attribute)值。
p里面不仅存放了属性,还存放了类里面定义的eat、speak函数。
>>> p.eat
<bound method Person.eat of <__main__.Person object at 0x0000000003931358>>
>>> p.speak
<bound method Person.speak of <__main__.Person object at 0x000000000286BDA0>>
p不再是一个普通的变量,而是一个包含属性(name、age、like)和函数(eat、speak)的对象(object)。
看一下类里面函数eat的定义,除了第一个参数是self外,其他和普通函数类似。同时,它还使用了数据self.name。由于self代表实例的内存地址,self.name就是实例的属性值。在类的内部定义的能访问实例数据的函数,我们称之为方法(method)。方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据。
下面使用类中定义的函数eat。类里面的函数和普通函数的区别在于,使用类的时候前面要加上类名.,例如Person.eat(self)。参数self是实例对象,使用时,将其替换为实例名称p即可运行。
>>> Person.eat(p)
小明开始吃饭!
更常见的是写成下面的形式,效果是一样的。
>>> p.eat()
小明开始吃饭!
由于p中存储了数据(self.name='小明'),所以p.eat()就可以带着数据调用函数eat,完成相应的任务。要注意的是,和函数一样,方法后面不带圆括号则返回方法本身,如果带上圆括号,它就会运行。
2.继承
我们可以通过Python内置的dir函数查看对象p的属性和方法。
>>> dir(p)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'eat', 'like', 'name', 'speak']
dir函数其实是调用对象p的__dir__方法,并对该方法返回的属性名和方法名做了排序。p的属性包含我们创建的name、like、age,方法包含eat、speak。
dir函数还输出了很多前面带双下划线的属性方法,这是Python自动创建的,这里就涉及继承的概念。继承是OOP另一个重要概念。如果一种语言不支持继承,类就没有什么意义。Python能在一个类的基础上“继承”其方法和属性,构建另一个类。
在Python3里面,我们创建的类都继承了object类,所以它自带了object类的属性和方法。object类是其他类的父类,其他类都是object类的子类。
Python为所有类都提供了一个__bases__属性,通过该属性可以查看该类的所有直接父类,该属性返回由所有直接父类组成的元组。
>>> Person.__bases__
(<class 'object'>,)
下面创建一个object类的对象,查看其属性和方法。
>>> o=object()
>>> dir(o)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
对比一下对象o和对象p的属性和方法列表,可以发现后者增加了以下项目:__dict__、__module__、__weakref__、name、like、age、eat、speak。
我们分别查看。
>>> p.__dict__
{'name': '小李', 'age': 5, 'like': '画画'}
>>> p.__module__
'__main__'
>>> print(p.__weakref__)
None
>>> p.name,p.age,p.like
('小李', 5, '画画')
我们可以修改实例的属性值。
>>> p.name='小明';p.age=30;p.like='写作'
使用对象的方法,方法后面有圆括号。
>>> p.speak()
小明说: 我30岁了,我爱好写作。
和函数一样,假如不使用圆括号,它只会返回方法本身,不会运行方法。
>>> p.speak
<bound method Person.speak of <__main__.Person object at 0x0000000003931358>>
我们创建了Person类,显然一个类不够用。我们需要针对不同的人群特征设计不同的程序功能,将人群按照职业划分为学生和工人,进一步创建类。由于学生和工人都属于Person类,他们可以继承Person类的方法和属性。这样一来,我们创建Student类和Staff类,就没有必要从零开始重新写代码。
>>> class Student(Person):
... def __init__(self,name,age,like,grade):
... Person.__init__(self,name,age,like)
... self.grade=grade
... def speak(self):
... print('%s说: 我%d岁了,我爱好%s,我在读%d年级。'% (self.name,self.age, self.like,self.grade))
... def learn(self):
... print(self.name+'开始学习!')
通过在类名后面加上Person,就可以继承Person类的方法和属性。我们给Student类增加了grade属性,增加了learn方法,改写了speak方法。所以,继承不只是简单的封装和调用,继承还可以扩展已存在的代码模块。
子类继承了父类,同时还可以扩展,这一点上类就比函数更强大。写一个函数,若要升级、扩展,就需要修改原函数,也就容易出错,而且如果原函数已经被调用了,对它进行修改的影响就比较大。用类来扩展,更加方便、清晰。
我们创建了Student类,就可以基于它创建实例。
>>> p2=Student('小云',8,'唱歌',4)
>>> p2.eat()
小云开始吃饭!
>>> p2.speak()
小云说: 我8岁了,我爱好唱歌,我在读4年级。
>>> p2.learn()
小云开始学习!
尽管我们没有在Student类里面写eat方法,但还是可以调用,这就是继承。
同样地,我们可以创建Staff类。
>>> class Staff(Person):
... def __init__(self,name,age,like,job):
... Person.__init__(self,name,age,like)
... self.job=job
... def speak(self):
... print('%s说: 我%d岁了,我爱好%s,我的职业是%s。'% (self.name, self.age, self.like,self.job))
... def work(self):
... print(self.name+'开始工作!')
实例化一个对象。
>>> p3=Staff('小李',25,'打球','工程师')
>>> p3.eat()
小李开始吃饭!
>>> p3.speak()
小李说: 我25岁了,我爱好打球,我的职业是工程师。
>>> p3.work()
小李开始工作!
Staff类是Person类的子类,后者是前者的父类。子类还可以继续细分,如职员类可以分为白领(ClericalStaff)类、蓝领(ManualStaff)类等,子类的子类依然是父类的子类。通过继承机制,所有子类都可以继承父类的方法和属性,这样我们就可以少写很多代码。
3.多态
在上面的代码中,p.speak()、p2.speak()、p3.speak()的运行结果是不同的。不同类的对象,可以使用同一个函数名speak得到各自想要的结果,这就是最简单的多态。
没有多态机制,会出现什么麻烦呢?假如我们增加一个幼儿(Kid)子类,它的speak方法是输出“我在上幼儿园!”,由于函数名speak在代码文件中出现了很多次,修改speak方法就会牵一发而动全身,那就只能把之前用到的函数名speak重命名为speak1、speak2、speak3……
有了多态,我们只需要在新增的Kid子类里面改写父类的speak方法,而不需要改动其他子类或者已写好的代码。
下面创建子类Kid。
>>> class Kid(Person):
... def speak(self):
... print('我在上幼儿园!')
创建一个Kid类的实例化对象。
>>> p4=Kid('小胖',3,'喝奶')
>>> p4.eat()
小胖开始吃饭!
>>> p4.speak()
我在上幼儿园!
为了更清楚地看到这一点,我们可以在类外面定义一个函数speak,它的参数是对象,它每次运行给定对象的speak方法。
>>> def speak_obj(obj):
... obj.speak()
我们调用该函数,运行一下。
>>> speak_obj(p)
小张说: 我30岁了,我爱好写作。
>>> speak_obj(p2)
小云说: 我8岁了,我爱好唱歌,我在读4年级。
>>> speak_obj(p3)
小李说: 我25岁了,我爱好打球,我的职业是工程师。
>>> speak_obj(p4)
我在上幼儿园!
可以看到,代入不同的对象参数,运行的结果是不同的,但是他们的接口都是统一的。这就增加了程序的灵活性,以不变应万变。尽管子类中的speak方法写得千差万别,但都可以用一个统一的形式speak_obj(obj)来调用。同时,多态增加了程序的可扩展性,子类想增加新的功能时,都不需要修改其他类的代码。
假如没有多态,我们该如何处理呢?假如我们只有一个函数speak_obj,由于每类人说的话都是不一样的,因此每增加了一个类型,我们就要修改一次函数speak_obj,在里面放很多判断语句。一般来说,修改代码是很危险的,容易造成错误,我们尽量不要修改已经存在的、运行良好的代码,而应该尽量去派生和拓展现有程序的功能。
多态的用途非常广泛,前面我们对数字和字符串都进行了加法运算,其结果是完全不同的,数字是求和,字符串是连接。运算符+有多种含义,究竟执行哪种运算取决于参加运算的操作数类型。
2.4.2 类的高级用法
在Python里,类无处不在。以最简单的加法运算为例。
>>> x,y=1,2
>>> x+y
3
Python在执行x+y的过程中,首先是识别出变量x的类型整型。
>>> type(x)
<class 'int'>
整型类有__add__方法,加号调用了该方法,x+y实际上调用的是x.__add__(y)。
>>> x.__add__(y)
3
同样地,字符串的加法运算是调用字符串类的__add__方法。
>>> 'Hello'+'World!'
'HelloWorld!'
>>> 'Hello'.__add__('World!')
'HelloWorld!'
又如列表。
>>> list_1=['a',1,1.0,'b']
>>> type(list_1)
<class 'list'>
>>> list_1.pop()
list_1是list类的一个实例,它可以使用list类的pop方法。
为什么变量list_1会是list类的实例呢?那是因为右边的['a',1,1.0,'b']符合列表特征,Python程序将其识别为列表的实例对象。
前面说到字符串和列表索引,都是在对象后面使用方括号,底层是调用对象的__getitem__方法。
>>> 'Beautiful'[5]
'i'
>>> 'Beautiful'.__getitem__(5)
'i'
>>> list_1[2]
1.0
>>> list_1.__getitem__(2)
1.0
从字符串中提取元素和从列表中提取元素,底层的代码肯定是不一样的,但是他们都可以使用统一的接口——方括号,这也是多态的运用。
在Python里,对象后面使用圆括号,调用的是__call__方法,当然前提是类里面定义了该方法,否则会报错“object is not callable”。
例如,我们定义一个教师类Teacher,它是工人类Staff的子类,给它添加__call__方法。
>>> class Teacher(Staff):
... def __call__(self):
... self.speak()
>>> p5=Teacher('小李',35,'打球','教师')
>>> p5()
小李说: 我35岁了,我爱好打球,我的职业是教师。
在Python的语法里面,函数名后面使用圆括号将运行该函数。函数也是类,圆括号其实也是在调用__call__方法。
>>> def f(x,y):
... return x+y
>>> f(2,3)
5
>>> f,type(f)
(<function f at 0x0000000003919730>, <class 'function'>)
>>> f.__call__(2,3)
5
我们遇到陌生的对象时,常常在提示符>>>后输入对象名称,按Enter键,可以查看该对象的信息,通常返回的是“类名+object at+内存地址”信息。它调用了对象的__repr__方法。
>>> p5
<__main__.Teacher at 0x0000026E502SCF28>
>>> p5.__repr__()
'<__main__.Teacher object at 0x0000026E502SCF28>'
以双下划线开头的方法,通常是不直接使用的。但是有的程序员往往能通过这些方法实现一些特殊的效果。例如,我们通常使用方括号进行索引,然而通过__call__方法的设置,也可以让对象通过圆括号进行“索引”。这些底层的方法,决定了类和对象的行为特征。
在本书后面的内容里,我们并不需要编写类,而主要是运用类,因此要能够看得懂别人写的类。编程的时候,常常会遇到函数或方法返回对象,我们可以通过Python内置的type函数查询对象所属的类,使用dir函数查询对象的方法和属性,使用help函数查询方法的参数和语法。
还记得我们统计字符串中的字母出现次数,用到count方法吗?
>>> 'Beautiful'.count('B')
1
问题是我们如何知道这个方法的用法?难道要死记硬背?下面我们解释一下代码的含义。首先,'Beautiful'是一个对象,我们使用Python内置的type函数查看对象的类型。
>>> type('Beautiful')
<class 'str'>
我们看到了,它是一个字符串对象。使用dir函数查询该对象的方法和属性。
>>> dir('Beautiful')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__','__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
我们看到了这个字符串对象有count方法。使用help函数查询count方法的语法。
>>> help('Beautiful'.count)
Help on built-in function count:
count(...) method of builtins.str instance
S.count(sub[, start[, end]]) -> int
Return the number of non-overlapping occurrences of substring sub in
string S[start:end]. Optional arguments start and end are
interpreted as in slice notation.
在本书后面的章节大量使用各种第三方库,里面都有很多类。我们通过大量示例演示类、对象、属性、方法这些概念的实际应用,使读者加深对“面向对象编程”这一思想的理解。
2.5(略)
Python办公自动化你需要读哪本书?
Python编程快速上手 让繁琐工作自动化 第2版
如果你曾经花几小时来重命名文件或更新成千上万个电子表格的单元格,你就知道这样的任务有多繁琐了。如果可以让计算机替你完成呢?
在本书中,你将学习利用Python编程在几分钟内完成手动需要几小时的工作,无须事先具备编程经验。通过阅读本书,你会学习Python的基本知识,探索Python丰富的模块库,并完成特定的任务(例如,从网站抓取数据,读取PDF和Word文档等)。本书还包括有关输入验证的实现方法,以及自动更新CSV文件的技巧。一旦掌握了编程的基础知识,你就可以毫不费力地创建Python程序,自动化地完成很多繁琐的工作,包括:
- 在一个文件或多个文件中搜索并保存同类文本;
- 创建、更新、移动和重命名成百上千个文件和文件夹;
- 下载搜索结果和处理Web在线内容;
- 快速地批量化处理电子表格;
- 拆分、合并PDF文件,以及为其加水印和加密;
- 向特定人群发送提醒邮件和文本通知;
- 同时裁剪、调整、编辑成千上万张图片。
本书手把手地教你完成每个程序,并通过每章(除第1、2章外)末尾的实践项目帮你改进这些程序,使你能用所学的新技能来自动化地完成类似的任务。
学Python,不加班——轻松实现办公自动化
这是一本关于如何利用Python提高日常办公效率的书,书中凝聚了作者多年的实践经验和独特思考,旨在帮助读者准确、高效地完成大量高重复度的工作。
本书汇集了日常办公和处理文档时常见的问题,通过实例的演示与讲解,帮助读者灵活有效地使用Python处理工作中遇到的问题。全书共11章,涵盖Python的各种应用场景,具体包括文件管理自动化,网络信息自动获取,TXT、XLS/XLSX、DOC/DOCX、PPT、PDF、图片文件的自动化处理,模拟鼠标、键盘操控本地软件,自动化运行管理等。本书力图淡化编程中的抽象概念,贴合工作场景,注重实战效果,通过对Python技术的巧妙讲解,帮助读者成为高效率的办公室“超人”。
本书适合任何想要学习Python编程的读者,尤其适合缺乏编程经验的初学者。同时本书提供所有案例的源代码文件,方便读者边学边练,爱上Python编程。