# 第1章 Python语法基础
Python 是一种面向对象的、解释型的、通用的、开源的脚本编程语言。
Python是由荷兰数学和计算机科学研究学会的Guido van Rossum 于1990 年代初设计出来的。Python最大的特点就是书写简单,因此随着时间的推移,获得了更多人的青睐。Python是一种解释型语言,这使得它具有了跨平台的优势,随着近些年来版本的不断更新,功能逐渐完善,越来越多的大型项目开始选择Python。
目前主流的Python版本已经到3.x,从2020年开始,Python官方已不再对Python2进行维护。官方建议开发者尽快迁移到Python 3上,需要注意的是,虽然两个版本的语法十分接近,但Python 3并不兼容Python 2,也就是说此前使用Python 2编写的程序并不能保证可以在Python 3环境下正常运行。对于开发者来说,入门学习自然是要紧跟官方建议,本书的语法内容也是基于Python 3编写而成的。
## 1.1 初识Python
Python因为其语法简洁、优雅、易于理解,一直被认为是最“简单”的开发语言。从入门角度来说,这样的评价不无道理,对于使用过其他语言的开发者来说,Python的基础语法只需要几天就能够轻松上手了。但是想要真正地掌握Python这门语言,仅仅熟悉语法基础还远远不够。
### 1.1.1 为什么要学习Python
人生苦短,我用Python!这是入坑Python之后听到的第一句名言,Python的优势在于丰富的第三方库,尤其是人工智能方向。事实证明,当一门语言和某个火热的行业绑定时,它必将前途无量!正如10年前Android的崛起带动Java的再次起飞,最近几年人工智能技术的火热使得Python也变得炙手可热起来。
下图1-1是截至2021年10月的TIOBE编程语言排行榜。

图1-1-1 TIOBE编程语言排行榜
从排行榜上可以看出,时至今日,Python已经力压C和Java成为最受欢迎的编程语言。
无数的开发者为Python提供了庞大的第三方开源类库,为了实现一个功能,在其他语言中往往需要几十数百行的代码,在Python中也许一句类库的调用就足够了。Python以其优雅和易于理解的语法著称,就连《Thinking in C++》和《Thinking in Java》的作者Bruce Eckel也曾感叹:“Life is short,you need Python(人生苦短,你该用Python)”。作为一门脚本语言,它具有极强的适应性,也许是你正在使用的PC上,也许是在公司的服务器上,也许就只是在一台Open Wrt路由器上,Python程序都可以无需编译就直接运行。
可能很多读者会问,Python是不是更适合搞人工智能,不适合区块链行业呢?其实不然。正如前面提到的,人生苦短,你需要使用Python,在区块链领域也没有问题。区块链系统就像是数据库一样,会为大多数开发者提供各种语言的SDK,作为这么受欢迎的语言,任何一个区块链平台都不会放弃Python的市场。就拿以太坊来说,它的节点客户端就有Go语言、Java、C++、Python等语言的不同实现版本。以太坊创始人维塔利克·布特林正在努力开发vyperlang,它是一款类似Python的智能合约开发语言。由此可见,Python可以和区块链很好的结合,Python+区块链绝对是强强组合。
### 1.1.2 Python开发环境搭建
Python官网(https://www.python.org/) 已经为各个主流操作系统提供了安装文件,按照自己的系统下载对应的版本即可。

图1-1-2 官网Python下载
在Python官网主页的Downloads下拉框中,会根据你当前正在使用的系统,在右侧提供最新版Python安装文件的下载链接,如果读者需要下载指定版本或指定平台的安装文件,可以在左侧的列表中结合实际情况自行选择。
本书以Windows系统为例,点击下载Python3.10.0后双击安装文件,开始安装。

图1-1-3 开始安装Python
Install Now会自动安装到默认目录中,Customize installation可以自定义Python的安装目录,修改是否下载附加组件等内容。
安装完成后,点击键盘的Win+R,输入 cmd 进入Windows命令行模式。
输入:
```python
python --version
```
如果命令行返回
```python
Python 3.10.0
```
说明你的Python环境已经安装成功。
### 1.1.3 选择一个适合的IDE
工欲善其事,必先利其器。想要快速开发Python程序,一款合适的IDE是非常关键的。下面,我们将介绍几款用于开发Python代码的IDE(Integrated Development Environment),即集成开发环境。
* 文本编辑器
文本编辑器其实不能称之为IDE,作为初学者,最开始学习Python基础语法时,使用类记事本的编辑器也未尝不是一个好的选择。当然这里我们还是不建议使用windows自带的记事本程序,因为该程序在保存时,会默认在文本的开头部分添加一个头部标识(UTF-8 BOM),这会导致程序在运行时出现一些莫名其妙的问题。
就轻量级的文本编辑器而言,推荐使用Notepad++或Editra等第三方文本编辑器。
* IDLE
IDLE是Python默认提供的集成开发环境,当我们安装好Python后,也会默认安装上IDLE。IDLE具备基本的IDE功能,具有自动缩进、语法高亮、关键词自动完成等功能。在这些功能的帮助下,可以有效地提高开发者阅读和编写代码的效率。但是一般情况下不建议直接使用IDLE作为日常开发Python的IDE工具,相较于下面介绍的几种IDE来说,IDLE还是略显简易。
* PyCharm
使用文本编辑器写一些简单的小程序当然是没有问题的,但是到了大型项目中,往往需要同时管理数百个文件,各个文件之间还存在相互引用关系,此时使用智能IDE就成了必然的选择。如果有Java、JS等语言的编程经验,想必一定听说过Jetbrains公司的大名,PyCharm就是他们为Python开发者提供的一款IDE工具,这款IDE为开发者提供了代码编写、调试和测试的整套工具链。
你可以通过这个地址下载Pycharm (https://www.jetbrains.com/pycharm/download/),Jetbrains提供了Professional(专业版)和Community(社区版)两种版本,相比于免费的社区版,专业版内置了连接Sql数据库,在线编写代码,编辑Html、JS等强大功能,但是对于大多数Python开发者而言,社区版的功能就可以满足大多数的开发场景。
* VS Code
VS Code是微软推出的一款跨平台、支持插件扩展的智能IDE,它会在安装后自动检测系统中是否已经安装过Python的开发环境,并提示你安装所需插件,同时支持语法高亮、自动补全。比文本编辑器功能完善,又比PyCharm轻量级。
本书中接下来的Python代码都将在PyCharm中完成,这里我们也建议读者朋友们使用PyCharm来作为开发Python的首选IDE。
### 1.1.4 写下你的第一个Python程序
所有学习编程的开发者都是从"Hello World"开始了编程之旅,学习Python当然也不例外。
打开IDLE,输入print('Hello World'),系统将返回Hello World。执行结果如下:
```python
>>> print('Hello World')
Hello World
```
## 1.2 Python基础语法
Python 作为一种计算机编程语言,和Java,JS 和 C等高级语言十分相似。都是定义了一套完整的语法体系,确保编写的内容不会产生歧义。Python的基础语法主要涉及数据类型的定义与划分,操作符的使用,流程控制语句,下面将一一介绍。
### 1.2.1 基础数据类型
Python 3 为了便于开发者编写程序,支持多种数据类型。主要由六类标准的数据类型构成,它们分别是:Number(数字)、String(字符串)、List(列表)、Tuple(元组)、Set(集合)、Dictionary(字典)。
Python通过对多个数据元素进行组合,为开发者提供了几个非常有特色的复合数据类型。List、Tuple、Set、Dictionary都是非常典型的复合数据类型。String本质上也是一种复合数据类型,String中的每个字符都被视为一个数据元素。
- Number (数字)
数字是编程过程中最基本的类型。python 3 支持两种典型的数字类型:整数和浮点数。
整数的数据类型表示为 int,例如:1、3、50、100,都是比较典型的整数。通常情况下我们定义一个整数,默认是十进制的。python 3 在此基础上还支持2进制整数、8进制整数和16进制整数。
浮点数表示含有小数部分的数字,类型表示为float。例如:3.14、333.333、4.0,都是比较典型的浮点数。在python 3 中允许开发者们使用科学计数法表示浮点数,写法也非常简单,两个数字中间加一个字母 e,不区分大小写,e或E都可以。例如:5e3、6E4。其中字母e后边的数字表示10的幂次,换算后,乘以前面的数字得到实际数字。
$$
5e3 表示 5*10^3。
$$
```python
>>> i = 5e3
>>> print(i)
5000.0
>>> j = 3E-3
>>> print(j)
0.003
```
- String(字符串)
除了数字,Python支持的另一种常见数据类型为字符串,定义一个字符串类型的数据是非常容易的。使用单引号 ' 或双引号 " 将内容圈起来,引号里的内容即为字符串本身。Python没有单独定义字符类型,一个字符就是一个只有一个字符的字符串。
```python
>>> str1 = '床前明月光'
>>> print(str1)
床前明月光
>>> str2 = "疑是地上霜"
>>> print(str2)
疑是地上霜
```
那么如果在字符串中出现单引号或双引号该怎么办?python提供了反斜线 \ 来对其中的引号进行转义。避免和用于定义字符串所使用的引号混淆。
```python
>>> str3 = '床前\'明月\'光'
>>> print(str3)
床前'明月'光
```
如果我们要表达的字符串内容是多行的,可以采用三个单引号 ''' 或三个双引号 """ 将多行内容圈起来。当然也可以使用特定字符 \r\n 来实现换行效果。
```python
>>> str4 = '''床前明月光
疑是地上霜
举头望明月
低头思故乡'''
>>> print(str4)
床前明月光
疑是地上霜
举头望明月
低头思故乡
>>> str5 = """床前明月光
疑是地上霜
举头望明月
低头思故乡"""
>>> print(str5)
床前明月光
疑是地上霜
举头望明月
低头思故乡
>>> str6 = '床前明月光\r\n疑是地上霜\r\n举头望明月\r\n低头思故乡'
>>> print(str6)
床前明月光
疑是地上霜
举头望明月
低头思故乡
```
### 1.2.2 学会使用list和tuple
List(列表)和Tuple(元组)是非常常用的复合数据类型,它们都是将多个元素有序的集合在一起。
- List(列表)
List(列表)是 python 提供的一种复合数据类型,它将多个元素集合起来并用逗号分隔,再由方括号 [] 括起来。例如:List1 = [’A’, ’B’, ’C’] 。Python并没有对List中的元素约束数据类型,所以一个 List 中的不同元素可以是不同的数据类型。
List是一种有序的集合。可以通过索引的方式,访问List指定位置的元素。其中第一个元素的索引值为0,即从0开始计数。另外索引还支持反向查找,-1表示最后一个元素,以此类推-2表示倒数第二个元素。
以List1 = [ ’A’ , ’B’ , ’C’ ] 为例:
| 元素 | ’A’ | ’B’ | ’C’ |
| -------- | :---------- | ----------- | ----------- |
| 正向索引 | List1[ 0 ] | List1[ 1 ] | List1[ 2 ] |
| 反向索引 | List1[ -3 ] | List1[ -2 ] | List1[ -1 ] |
List支持基本的增加、删除、修改操作,以List1 = ['A', 'B', 'C'] 、List2 = [ 'P', 'Q'] 为例:
| 操作 | 命令 | 结果 | 说明 |
| :--- | ------------------------ | ------------------------------ | ---------------------- |
| 增加 | List1.append( 'D' ) | ['A' , 'B' , 'C' , 'D' ] | 末尾添加单个元素 |
| 增加 | List1.extend( List2 ) | ['A' , 'B' , 'C' , 'P' , 'Q' ] | 末尾添加多个元素 |
| 增加 | List1.insert( 1 , 'P' ) | ['A' , 'P' , 'B' , 'C' ] | 指定位置添加元素 |
| 删除 | List1.pop() | ['A' , 'B' ] | 删除末尾元素 |
| 删除 | List1.pop(1) | ['A' , 'C' ] | 删除指定位置元素 |
| 删除 | del List1[1] | ['A' , 'C' ] | 删除指定位置元素 |
| 删除 | del List1[1:] | ['A' ] | 删除指定范围内的元素 |
| 删除 | List1.remove('C') | ['A' , 'B' ] | 删除第一次匹配到的元素 |
| 修改 | List1[1]=’K’ | ['A' , 'K' , 'C' ] | 修改指定位置元素 |
可以通过python 3 的内置函数len()获得当前List中的元素个数。
```python
>>> List3 = [1, 2, 3]
>>> len(List3)
3
>>> List4 = ['A', 1, True]
>>> len(List4)
3
```
List自身的成员函数count()统计某个元素在List中出现的次数。例如List5 = [ 1 , 2 , 3 , 1 , 3 ]中的元素1出现了两次,通过调用count(),我们可以看到返回的结果为2。
```python
>>> List5 = [ 1 , 2 , 3 , 1 , 3 ]
>>> List5.count(1)
2
>>> List5.count(2)
1
```
如上,我们知道List5 = [ 1 , 2 , 3 , 1 , 3 ]中的元素1出现了两次,那么如何找到它第一次出现的索引值呢?List提供了index()函数用于确定某个元素在List中第一次出现时的索引。例如List5中的元素3,第一次出现的索引值为2。而List5中的元素1,第一次出现的索引值为0。
```python
>>> List5 = [ 1 , 2 , 3 , 1 , 3 ]
>>> List5.index(3)
2
>>> List5.index(1)
0
```
为了使代码编写更简洁,List还支持加号 + 运算符。加号实现多个List的顺序拼接。
例如:已知两个列表List6 = [1 , 2]和List7 = [3 , 4],现在想把它们的元素合并到一起,生成一个新的序列[1 , 2 , 3 , 4 ],该序列中List6、List7各自元素内部顺序保持不变,且List6的元素排在List7前边。
```python
>>> List6 = [ 1 , 2 ]
>>> List7 = [ 3 , 4 ]
>>> List6 + List7
[1, 2, 3, 4]
```
List还支持乘法 \* 运算符。乘法运算符后边追加数字n,实现将List的元素顺序拼接n次。例如将List6 = [1 , 2]复制三次并顺次拼接到一起可以表示为List6*3,即可得到列表[1, 2, 1, 2, 1, 2]。
```python
>>> List6 = [1, 2]
>>> List6*3
[1, 2, 1, 2, 1, 2]
```
- Tuple(元组)
和List(列表)相似,Tuple(元组)同样是一种复合数据类型,而且Tuple也是有序集合。与List最大的不同是Tuple中的元素一旦确定就不能再修改。Tuple的写法和List十分相似,将多个元素集合起来并用逗号分隔,再由小括号 () 括起来。例如:Tuple1 = (’A’, ’B’, ’C’)。
定义只有一个元素的Tuple不能用Tuple1 = (1),此时 () 表示数学公式中的小括号,相当于给变量Tuple1赋值1,而并没有将Tuple1视作Tuple。正确的定义方法应该在第一个元素后边加一个逗号。例如:Tuple2 = (1,)
python 3提供的type()方法能够查询变量的数据类型,如下面的例子所示,当Tuple1 = (1)时,Tuple1的数据类型为int,即为整数。而当Tuple2 = (1,)时,Tuple2的数据类型为Tuple。
```python
>>> Tuple1 = (1)
>>> type(Tuple1)
>>> Tuple2 = (1,)
>>> type(Tuple2)
```
实际上,Python在显示只有1个元素的Tuple类型数据时,也会加一个逗号避免误会。
```python
>>> print(Tuple2)
(1,)
```
### 1.2.3 学会使用dict和set
Dictionary(字典)和 Set(集合)同样是非常常用的复合数据类型,它们与 List 和 Tuple 最大的不同在于其内部的元素是无序的。其中 Set(集合)是一组无序元素构成的集合,而Dictionary(字典)是一组无序的键值对构成的集合。
- Dictionary(字典)
Dictionary(字典)是一组元素的集合,每一个元素都是键值对(key-value)的形式,元素间用逗号分隔,最后用大括号 {} 将所有元素括起来。 例如:Dict1 = { 'A' : 1 , 'B' : 2 , 'C' : 3 }。其中 'A' : 1 是一个键值对,也是 Dict1 的一个元素,'A' 是该键值对的键(key),1是该键值对的值(value),彼此间用冒号: 分隔。
Dictionary 中的元素是无序的。所有元素的key值是唯一且不可变的。Number、String 是不可变的,可以作为key。而 List 是可变的,不能作为 key。
为了便于书写,后边的内容我们把 Dictionary(字典)简写为Dict。
Python 3 同样为 Dict 提供了很多方法,便于开发者使用。通过 Dict 的 key 值,可以直接获得对应的 value 值,如 Dict1[ 'A' ] 将获得数值 1。
```python
>>> Dict1 = {'A': 1, 'B': 2, 'C': 3}
>>> Dict1['A']
1
```
Dict 支持基本的增加、删除、修改操作,以 Dict1 = { 'A' : 1 , 'B' : 2 , 'C' : 3 } 为例:
| 操作 | 命令 | 结果 | 说明 |
| ---- | ---------------- | ----------------------------------------- | ---------------------- |
| 增加 | Dict1[ 'D' ] = 4 | { 'A' : 1 , 'B' : 2 , 'C' : 3 , 'D' : 4 } | 添加key-value |
| 删除 | Dict1.pop( 'C' ) | { 'A' : 1 , 'B' : 2 } | 删除指定的key及其value |
| 删除 | del Dict1[ 'C' ] | { 'A' : 1 , 'B' : 2 } | 删除指定的key及其value |
| 修改 | Dict1[ 'C' ] = 4 | { 'A' : 1 , 'B' : 2 , 'C' : 4 } | 修改已有key对应的value |
判断key在当前Dict中是否存在主要有两种方法:in和get()。
通过in来判断key是否存在,存在返回True,不存在返回False。
```python
>>> Dict1 = { 'A' : 1 , 'B' : 2 , 'C' : 3 }
>>> 'A' in Dict1
True
>>> 'D' in Dict1
False
```
通过内置方法get()来判断key是否存在,存在返回key对应的value,不存在则返回get()函数中预先定义的值,没有预先定义值则返回None。
```python
>>> Dict1 = { 'A' : 1 , 'B' : 2 , 'C' : 3 }
>>> Dict1.get('A' , -1)
1
>>> Dict1.get('D' , -1)
-1
>>> print(Dict1.get('D'))
None
```
如上面的例子所示,'A'是Dict1中的key,其对应的value为1,所以Dict1.get('A' , -1)的返回值为1。'D'不是Dict1中的key,所以Dict1.get('D' , -1)的返回值为预先定义的-1。如果get()方法没有做预先定义,如Dict1.get('D'),则返回值为None。
实际开发中,经常会有将两个Dict合并成一个Dict的情况。Python 3 提供了很多种办法。
以两个Dict为例:
Dict3 = {1: [1, 11, 111], 2: [2, 22, 222]}
Dict4 = {3: [3, 33, 333], 4: [4, 44, 444]}
期望合并后的结果为:
Dict5 = {1: [1, 11, 111], 2: [2, 22, 222], 3: [3, 33, 333], 4: [4, 44, 444]}
下面为大家介绍三种方法实现Dict的合并。
方法1:
Dict的copy()方法可以将Dict中的数据拷贝出来。
Dict的update()方法实现了对Dict中键值对的更新,不仅可以修改现有key对应的value,还可以添加新的键值对到Dict中。
```python
>>> Dict3 = {1: [1, 11, 111], 2: [2, 22, 222]}
>>> Dict4 = {3: [3, 33, 333], 4: [4, 44, 444]}
>>> Dict5=Dict3.copy()
>>> Dict5.update(Dict4)
>>> Dict5
{1: [1, 11, 111], 2: [2, 22, 222], 3: [3, 33, 333], 4: [4, 44, 444]}
```
方法2:
构造函数dict()可以直接将一组键值对创建为Dict。
```python
>>> Dict3 = {1: [1, 11, 111], 2: [2, 22, 222]}
>>> Dict4 = {3: [3, 33, 333], 4: [4, 44, 444]}
>>> Dict5 = dict(Dict3)
>>> Dict5.update(Dict4)
>>> Dict5
{1: [1, 11, 111], 2: [2, 22, 222], 3: [3, 33, 333], 4: [4, 44, 444]}
```
方法3:
Dict的items()方法可将键值对提取出来。
构造函数list()可以直接将一组数据创建为List。
Dict5 = dict( list( Dict3.items() ) + list( Dict4.items() ) )
```python
>>> Dict3 = {1: [1, 11, 111], 2: [2, 22, 222]}
>>> Dict4 = {3: [3, 33, 333], 4: [4, 44, 444]}
>>> Dict5 = dict(list(Dict3.items()) + list(Dict4.items()))
>>> Dict5
{1: [1, 11, 111], 2: [2, 22, 222], 3: [3, 33, 333], 4: [4, 44, 444]}
```
- Set(集合)
Set(集合)是一个无序、无重复元素的集合。元素间用逗号分隔,再用大括号 {} 将所有元素括起来。例如:Set1 = {1, 2, 3}。当Set中无元素时要用set()表示,而不是用{}表示,因为这会和空的Dict混淆。 为了便于和 Dict 作区分,可以把 Set 看作是一组key的集合,只存储key,不存储value。由于key是无序且不重复的,Set中的元素也都遵从此约束。
在创建一个Set时,Python 3 会自动对Set中的元素进行去重,确保Set中无重复记录。
例如用构造函数set()创建一个Set时,当我们提供的数据有重复时,Set2 = set([1, 1, 2, 2, 4, 4]),最终生成的Set2为 {1, 2, 4}。
```python
>>> Set2 = set([1, 1, 2, 2, 4, 4])
>>> Set2
{1, 2, 4}
```
Set支持基本的增加、删除操作,以Set1 = {1, 2, 3}为例:
| 操作 | 命令 | 结果 |
| ---- | -------------- | ------------ |
| 增加 | Set1.add(4) | {1, 2, 3, 4} |
| 删除 | Set1.remove(3) | {1, 2} |
通过in来判断元素是否在Set中,存在返回True,不存在返回False。
```python
>>> Set1 = set([1, 2, 3])
>>> 1 in Set1
True
>>> 4 in Set1
False
```
Set作为集合,支持典型的集合运算。以Set1 = {1, 2, 3}、Set2 = {1, 2, 4}为例:
| 操作 | 命令 | 结果 |
| -------- | ------------------------------- | --------------- |
| 交集 | Set1 & Set2 | {1 , 2} |
| 并集 | Set1 \| Set2 | {1 , 2 , 3 , 4} |
| 差集 | Set1 - Set2 | {3} |
| 对称差集 | Set1 ^ Set2 | {3 , 4} |
| 交集 | Set1.intersection(Set2) | {1 , 2} |
| 并集 | Set1.union(Set2) | {1 , 2 , 3 , 4} |
| 差集 | Set1.difference(Set2) | {3} |
| 对称差集 | Set1.symmetric_difference(Set2) | {3 , 4} |
### 1.2.4 常用操作符
除了多种数据类型和对应的处理方法,Python还提供了多种操作符,使开发者能够方便地组合运用,实现程序业务逻辑。
- 注释
注释是开发者编写和阅读程序时的好帮手。可以大大提高代码的可读性。在Python 3 中使用井号 # 来标识注释,一段注释需要以井号开头,井号所在行后边所有的内容都被视作注释的内容。所以注释既可以发生在一行代码的开头,也可以发生在中间。
```python
>>> #注释内容1
>>> print('文本内容1')
文本内容1
>>> str1 = '文本内容2'
>>> #注释内容2
>>> print(str1)
文本内容2
```
通常单行注释使用井号。当需要注释的内容涉及多行时,采用三个单引号 ''' 或三个双引号 """来标记注释内容 ,即在注释的多行内容的首尾添加该符号。这种情况一般被叫做块注释或批量注释。这样的注释还常常被称为文档字符串。
- 转义
在Python中使用反斜杠\转义特定字符,倘若不想转义,只需在字符串前加r或R。就可以在字符串中显示原始的反斜线符号。
```python
>>> print( '转义\n字符' )
转义
字符
>>> print( r'转义\n字符' )
转义\n字符
>>> print( R'转义\n字符' )
转义\n字符
```
以下为一些比较常用的转义字符:
| 转义字符 | 说明 |
| -------- | ---------------------------- |
| \\\ | 生成一个反斜杠 \ |
| \\' | 生成一个单引号 ' |
| \\" | 生成一个双引号 " |
| \t | tab |
| \r | 回车 |
| \n | 换行 |
| \000 | NULL,空值,\0和\000效果一致 |
### 1.2.5 流程控制语句
python通过条件判断和循环的方式实现程序代码执行流程的控制。常用的条件判断语句为if,常用的循环语句为for和while。
- 条件判断
Python提供的完整if判断语句,由if、elif、else三部分组成。 其中elif子句相当于其他编程语言常出现的else if。完整的if条件判断语句编写样式如下所示:
```python
if condition1:
statement1
elif condition2:
statement2
else:
statement3
```
if 子句后边的 condition1 是该子句的判断条件。
当 condition1 为 True 时,执行 if 子句对应的代码块 statement1。
当 condition1 为 False 时,跳过 if 子句,执行 elif 子句。
elif 子句后边的 condition2 是该子句的判断条件。
当 condition2 为 True 时,执行 elif 子句对应的代码块 statement2。
当 condition2 为 False 时,跳过 elif 子句,执行 else 子句对应的代码块 statement3。
if判断语句的if、elif、else三个子句在使用上要遵守一定的原则:
if 子句必须出现且为第一个判断子句。
elif 子句可以不使用或使用多次。
else子句可以不使用,如果使用必须作为最后一个判断子句。
```python
#多个elif
if condition1:
statement1
elif condition2:
statement2
elif condition3:
statement3
else:
statement4
#缺失elif
if condition1:
statement1
else:
statement2
#缺失else
if condition1:
statement1
elif condition2:
statement2
#仅有if
if condition1:
statement1
```
由于 Python 3 中并没有提供 switch、case 类的条件判断语句,所以开发者可以通过在 if 子句后边追加多个 elif 子句的方法实现类似效果。
```python
score = 60
if score < 60:
print('不及格')
elif score < 85:
print('还不错')
elif score <= 100:
print('优秀')
else:
print('数据异常')
```
- while 循环
while 语句是构建一个循环的最简单方法,只需要定义一个条件,当条件满足时,就执行 while 子句中的内容。当条件不满足时循环终止,并执行 else 子句中的内容。
完整的 while 循环语句编写样式如下所示:
```python
while condition:
statement1
else:
statement2
```
while 后边的 condition 是循环的判断条件。
当 condition 为 True 时,执行 while 子句的代码块 statement1。
当 condition 为 False 时,跳出 while 循环。执行 else 子句的代码块 statement2。
由于else子句可缺省,在没有else子句的情况下,当condition条件不满足时,直接跳出while循环。
```python
while condition:
statement
```
以下代码通过 while 循环来计算 5 个 10 相加之和:
```python
num = 1
res = 0
while num <= 5:
res += 10
num += 1
else:
print(res)
```
- for 循环
for 语句在构建一个循环时,需要指定用于循环的序列,通常是一个List或者Tuple。
完整的 for 循环语句编写样式如下所示:
```python
for i in seq:
statement1
else:
statement2
```
变量 i 遍历序列 seq 中的每一个元素,并在遍历的过程中不断重复执行代码块 statement1。
当 i 遍历完 seq 中的所有元素后,for 循环结束。执行 else 子句对应的代码块 statement2。
以下代码通过 for 循环来计算 5 个 10 相加之和:
```python
res = 0
for i in [10, 10, 10, 10, 10]:
res = res + i
else:
print(res)
```
由于else子句可缺省,在没有else子句的情况下,for循环可简化成以下形式:
```python
for i in seq:
statement
```
开发者在编写程序时遇到的情况往往是多种多样的。为了让循环更灵活,python 提供了循环控制命令。
| 命令 | 说明 |
| -------- | ---------------------------------------------------------- |
| break | 终止循环。 用break结束循环后,循环对应的else 子句不会执行 |
| continue | 跳过本次循环剩余操作,直接进入下次循环 |
| pass | 占位语句,没有实际意义 |
### 1.2.6 了解Python的编码风格
不同的开发语言有各自不同特点,这也导致各自的开发规范不尽相同。
Python的语法比较简单,和其他语言相比定义了相对较少的关键字,并采用缩进的方式来控制代码块之间的逻辑关系,这也是Python的最大特色。所以代码看起来显得结构清晰,简单易懂。
- 缩进
使用缩进来划分语句块,缩进的空格数是可变的,相同缩进程度的的语句隶属于同一个语句块。Python摒弃了其他开发语言常用的大括号 {},代码看起来更简洁。一般建议使用4个空格缩进,避免使用制表符。
单行代码不宜过长,最好保持在几十个字符以内。需要注释时,尽量保证注释为单独的一行。
- 变量命名
Python 规定变量的命名只能是字母、数字、下划线 _ 的组合。且变量名的首位只能是字母或者下划线。例如:num_1、\_num1 是合法变量名,而 1num_ 就是非法的。
命名中的字母,并不局限于26个英文字母,还可以使用中文字符、日文字符等。当然,这里建议大家给变量命名时,尽量在字母这一块只使用26个英文字母。
需要注意的是,系统定义的关键字不能作为变量名。另外 Python 是大小写敏感的,这就意味着同一个字母大小写不同时,代表着不同的意义。例如:Num 和 num 表示不同的变量。
```python
>>> Num = 1
>>> num = 2
>>> print(Num)
1
>>> print(num)
2
```
Python简化了变量的定义过程。变量和变量的数据类型不需要事先声明,在给变量赋值时,既定义了变量名,又定义了变量的数据类型。即表示你给变量赋值了什么类型的数据,变量就是什么数据类型。
- 关键字
Python 3 定义的关键字并不多,且关键字本身也很简洁。我们可以通过 keyword 模块的 kwlist 查看到所有的关键词。
```python
>>> import keyword
>>> 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']
```
## 1.3 输入输出(IO)
在软件开发过程中,必然要涉及到输入输出。Python在这方面的设计也非常简便。input() 方法用于支持标准输入,print() 方法用于支持标准输出。
input() 能够接受一个标准输入数据。比如下面例子中 input() 方法获取到键盘输入的 good morning,并将其转成字符串。
```python
>>> input()
good morning
'good morning'
```
print() 方法将接收到的 'good morning' 显示输出。
```python
>>> str = 'good morning'
>>> print(str)
good morning
```
### 1.3.1 文件的打开与读取
读写文件也是常见的IO操作,Python 3 内置了相关的方法。通过内置方法 open() 打开文件,返回 file 对象,通过 file 对象提供的方法读取并解析文件内容。
```python
>>> open(file, mode='r')
```
调用open() 方法比较常见的形式如上所示:
file:打开的文件
mode:打开文件的模式,默认模式为只读 r ,可缺省。
mode的模式也有很多,常见的几种模式如下:
| 方法 | 说明 |
| ---- | ------------------------------------------------------------ |
| r | 打开一个文件用于只读,文件指针将会放在文件的开头。 |
| r+ | 打开一个文件用于读写,文件指针将会放在文件的开头。 |
| w | 打开一个文件用于只写,如果该文件已存在,则将其覆盖。如果该文件不存在,创建新文件。 |
| w+ | 打开一个文件用于读写。如果该文件已存在,则将其覆盖。如果该文件不存在,创建新文件。 |
| a | 打开一个文件用于只写。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件。 |
| a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件。 |
open() 方法打开一个文件后,返回file对象,需要操作file对象从而获取文件的内容。
file 对象的属性如下:
| 属性 | 说明 |
| ----------- | --------------------------- |
| file.closed | 文件关闭为True,否则为False |
| file.mode | 文件访问模式 |
| file.name | 文件名称 |
file 对象的主要方法如下:
| 方法 | 说明 |
| ------------------------ | ------------------------------------------------------------ |
| file.close() | 关闭文件 |
| file.truncate() | 清空文件内容,必须是在可写的模式下 |
| file.flush() | 把缓冲区内容写入文件 |
| file.read( [size] ) | 读取文件,返回结果的数据类型为List,每个字节作为一个list元素。size参数用来说明读取的字节数,可缺省 |
| file.readline() | 读取一行,包括每行结尾的 \n 符。返回结果的数据类型为List,每个字节作为一个list元素 |
| file.readlines( [size] ) | 读取多行,返回结果的数据类型为List,文件的每一行作为一个list元素。 参数size读取的行数,默认读取全部行,可缺省 |
| file.write( str ) | 把str写入文件 |
| file.writelines( list ) | 把list中的所有元素写入文件 |
### 1.3.2 文件与目录操作
在操作中,文件都是放在某个目录下的,Python 3 内置的os模块可以让开发者们轻松地处理文件、目录和文件路径。
在介绍os模块之前我们想先来了解一下文件、目录和文件路径三者的关系。用公式表示就是:
文件路径 = 目录 + 文件
下面的介绍中我们将用path表示文件路径,dir表示目录,file表示文件。path = dir + file
os模块提供的主要方法如下:
| 方法 | 说明 |
| ---------------------------------------- | -------------------------------------------- |
| os.getcwd() | 获取当前工作目录 |
| os.listdir( dir ) | 返回指定目录dir下的所有文件和文件夹名 |
| os.remove(path) | 删除指定文件 |
| os.rmdir(dir) | 删除单级目录,要求该目录下无内容 |
| os.removedirs(dir) | 删除多级目录,要求每级目录下无内容 |
| os.mkdir(dir) | 创建单级目录 |
| os.makedirs(dir) | 创建多级目录,当多级文件夹都不存在,同时创建 |
| os.rename (oldpath/file, newpath/file) | 文件或目录的移动或重命名 |
| os.walk( dir ) | 遍历指定目录 |
os.sep 给出当前操作系统的路径分隔符,windows下为 \\。
os.linesep 给出当前操作系统的行终止符。Windows下为 \r\n。
下面我们通过例子详细介绍一下walk()方法:os.walk( dir )。
dir为指定的目录,返回值为tuple(dir, subdirs, files)。
dir:起始目录。类型为string。
subdirs:起始文件路径下的所有子目录。类型为list。
filenames:起始文件路径下的所有文件。类型为list。
假设存在以下目录结构:
E:\data
part1\
4.txt
5.txt
part2\
8.txt
9.txt
1.txt
则os.walk()方法的结果如下:
```python
>>> import os
>>> for i in os.walk('E:\\data'):
print(i)
# 输出结果为:
('E:\\data', ['part1', 'part2'], ['1.txt'])
('E:\\data\\part1', [], ['4.txt', '5.txt'])
('E:\\data\\part2', [], ['8.txt', '9.txt'])
```
显示目录下所有子目录和文件路径:
```python
>>> import os
>>> for root, dirs, files in os.walk("E:\\data"):
for dir in dirs:
print(os.path.join(root, dir))
for file in files:
print(os.path.join(root, file))
# 输出结果为:
E:\data\part1
E:\data\part2
E:\data\1.txt
E:\data\part1\4.txt
E:\data\part1\5.txt
E:\data\part2\8.txt
E:\data\part2\9.txt
```
os.path 模块提供针对文件更详细的方法:
| 方法 | 说明 |
| ----------------------------- | ------------------------------------------------------------ |
| os.path.isdir(dir) | 判断是否是目录 |
| os.path.isfile(path) | 判断是否是文件 |
| os.path.exists(path/dir) | 判断文件路径/目录是否存在 |
| os.path.dirname(path) | 返回目录 |
| os.path.basename(path) | 返回文件名
os.path.basename("E:\\Data\\1.txt") 返回值:'1.txt' |
| os.path.join( dir, file/dir ) | 拼接目录与文件名/目录
os.path.join("E:\\Data","1.txt") 返回值:'E:\\Data\\1.txt' os.path.join("E:\\Data","1") 返回值:'E:\\Data\\1' |
| os.path.getsize(path) | 返回文件大小 |
| os.path.split(path) | 返回一个目录和文件构成的tuple
os.path.split("E:\\Data\\1.txt") 返回值:('E:\\Data', '1.txt') |
| os.path.splitext(path) | 返回一个文件路径和文件后缀名构成的tuple
os.path.splitext("E:\\Data\\1.txt") 返回值:('E:\\Data\\1', '.txt') |
### 1.3.3 JSON格式处理
JSON (JavaScript Object Notation) 是目前软件开发中非常受欢迎的一种数据交换格式。Python3 中提供了 json 模块来对 JSON 格式的数据进行编解码,主要用到两个方法,dumps()方法实现编码。loads()方法实现解码。
```python
>>> import json
>>> j = { 1:'attr1', 2:'attr2', 3:'attr3' }
>>> json.dumps(j)
'{"1": "attr1", "2": "attr2", "3": "attr3"}'
```
通过dumps()方法将输入的 j 转换成 JSON 格式:'{"1": "attr1", "2": "attr2", "3": "attr3"}'。需要注意的是,标准JSON格式中的字符串必须使用双引号,而不能使用单引号 。
## 1.4 函数
函数是组织好的,可重复使用的,用来实现单一或相关联功能的代码段。在软件开发过程中,开发者经常需要针对不同业务开发一些函数,接下来的开发工作中便可以重复使用这些函数。函数帮助程序实现模块化,便于代码的复用,提高了代码的可读性。
我们前面用到的print(),open()等方法,就是非常典型的函数,他们都是由Python预先准备好的,被称为内建函数。
### 1.4.1 函数的基本定义
定义函数使用 def 关键字,一般格式如下:
```python
def func( p1, p2...pn):
codes
return res1, res2...resn
```
函数以关键字def开头,后边的func表示函数名称,圆括号 () 中的 p1, p2...pn 表示函数的输入参数,简称入参。函数的入参必须放在圆括号内,且支持多个入参。
圆括号 () 后必须跟冒号 ,冒号后边都是函数的内容,通常称为函数体。本例中,从第二行开始表示函数的具体内容,需要相对第一行进行缩进。
关键词return后跟res1, res2...resn,表示函数的返回值为res1, res2...resn,定义函数时可以没有关键字return,此时函数返回值为 None。return是一个函数结束的标志,即执行完return整个函数就会结束运行。res可以是任意数据类型,且return的返回值没有个数限制,当返回多个值时,返回值之间用逗号分隔。
### 1.4.2 函数调用
在程序中调用某个函数,只需要知道该函数的名称和参数限制即可。
```python
>>> def func(i):
print(i)
# 调用函数func
>>> func('Hello')
Hello
```
可以在定义函数时给指定的输入参数赋默认值。当调用函数时,如果没有为该参数赋值,则会使用默认值。
```python
>>> def func(i, j = 'World'):
print(i)
print(j)
# j不赋值
>>> func('Hello')
Hello
World
# j赋值
>>> func('Hello', 'Lily')
Hello
Lily
```
如例子所示,函数func()的参数j定义了默认值 'World'。当调用函数func()时,如果没有给参数j赋值,实际执行中j的取值为默认值 'World'。
### 1.4.3 递归函数
在软件开发过程中,递归通常是指,函数内部调用函数本身。这样的函数在Python中被称为递归函数。递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以改写成循环的形式。
下面我们来实现一个生成斐波那契数的函数:
```python
def func(i):
if i <= 1:
return i
else:
return(func(i-1) + func(i-2))
# 执行结果:
>>> func(0)
0
>>> func(1)
1
>>> func(2)
1
>>> func(3)
2
>>> func(6)
8
```
我们都知道斐波那契数指的是这样一个数列:0、1、1、2、3、5、8、13、21、34……其中第0条为0,第1条为1,第2条为1,第3条为2,且从第2条开始,每一条都等于前两条之和。
func()函数根据输入的i值计算出斐波那契数第i条的值,且为了实现第i条都等于前两条之和,func()函数在函数内部递归调用了自己,用func(i-1) + func(i-2)表示前两条之和。
### 1.4.4 匿名函数
python 提供了一种特别的函数称之为匿名函数。不同于普通的函数,匿名函数在定义时不使用关键字def。而是采用关键词lambda。具体写法如下:
```python
lambda p1, p2... pn: expression
```
匿名函数通常只能在一行里实现。关键字lambda后边是函数的参数p1, p2... pn,然后是冒号和函数的主体expression。不同于普通的函数体,这里的expression不是代码块,而仅仅是一个表达式。匿名函数不需要指定return的内容,返回值就是表达式的结果。
以实现两个数字相加的函数为例,普通函数和匿名函数写法如下:
```python
# 普通函数:
def func(i, j):
return i+j
# 匿名函数:
lambda i, j: i+j
```
### 1.4.5 装饰器
装饰器(Decorator)本质上就是一个函数,能够在不修改某个函数内部代码的前提下,扩展该函数的功能。
```python
def func1():
print('hello')
# 执行结果:
>>> func1()
hello
```
在不修改func1()的情况下,增加显示输出Lily,就可以用装饰器来实现。
首先我们定义一个简单的装饰器语法如下:用关键词def定义wrapper函数,输入参数是func函数,内嵌inner函数,最后返回inner函数。再通过@wrapper命令完成对func1() 函数的装饰。
将 @wrapper放在func1() 前面,相当于执行了func1= wrapper(func1)。由于wrapper()是一个装饰器,返回inner函数,此后再调用func1函数相当于调用wrapper()函数中返回的inner()函数。
此后再执行func1函数时,就在hello的基础上增加显示输出Lily了。
```python
def wrapper(func):
def inner():
func()
print('Lily')
return inner
@wrapper
def func1():
print('hello')
# 执行结果:
>>> func1()
hello
Lily
```
## 1.5 异常处理
Python中一般会出现两种错误问题,第一种是不符合语言规范的语法错误,另外一种则是所谓的异常。
语法错误又称解析错误,也是开发者在学习Python 时最常见的错误。
```python
# 1.5.1 语法错误,后一个单引号是中文字符,这也是很多初学编程的开发者经常犯的错误
>>> def func()
File "", line 1
def func()
^
SyntaxError: invalid syntax
```
如上面的例子中所示,定义函数func时缺少了冒号,IDLE会在错误位置下方标记一个“箭头”。 文件名和行号也会被输出,以便开发者快速定位到错误位置。
在实际开发过程中,即使代码在语法上是正确的,不存在解析错误,执行时也可能发生错误。这种在执行时检测到的错误,被称为异常。
例如下面这段代码,程序期望num是一个数字,计算num的一半是多少。
```python
num = "50"
print(num/2)
```
但是num却是一个字符串,这样在运行时就会出现异常。
```python
# 执行结果:
Traceback (most recent call last):
File "", line 1, in
print(num/2)
TypeError: unsupported operand type(s) for /: 'str' and 'int'
```
可以看到,编译器会提示我们“unsupported operand type(s) for /: 'str' and 'int'”,不支持对str(字符型)和int(整型)做除法运算。
程序运行过程中出现预期之外的情况,导致后续的流程无法正常执行。这样的异常在大型项目中经常发生。提前预判此类情况并加以处理,确保程序的正常运行,就是所谓的异常处理。
Python提供了完善的异常检查机制,可以将异常扼杀在摇篮里。
### 1.5.1 错误处理思想
Python中已经预设了多种异常情况,具体如下表所示:
| 异常名称 | 描述 |
| :------------------------ | :------------------------------------------------- |
| BaseException | 所有异常的基类 |
| SystemExit | 解释器请求退出 |
| KeyboardInterrupt | 用户中断执行(通常是输入^C) |
| Exception | 常规错误的基类 |
| StopIteration | 迭代器没有更多的值 |
| GeneratorExit | 生成器(generator)发生异常来通知退出 |
| StandardError | 所有的内建标准异常的基类 |
| ArithmeticError | 所有数值计算错误的基类 |
| FloatingPointError | 浮点计算错误 |
| OverflowError | 数值运算超出最大限制 |
| ZeroDivisionError | 除(或取模)零 (所有数据类型) |
| AssertionError | 断言语句失败 |
| AttributeError | 对象没有这个属性 |
| EOFError | 没有内建输入,到达EOF 标记 |
| EnvironmentError | 操作系统错误的基类 |
| IOError | 输入/输出操作失败 |
| OSError | 操作系统错误 |
| WindowsError | 系统调用失败 |
| ImportError | 导入模块/对象失败 |
| LookupError | 无效数据查询的基类 |
| IndexError | 序列中没有此索引(index) |
| KeyError | 映射中没有这个键 |
| MemoryError | 内存溢出错误(对于Python 解释器不是致命的) |
| NameError | 未声明/初始化对象 (没有属性) |
| UnboundLocalError | 访问未初始化的本地变量 |
| ReferenceError | 弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
| RuntimeError | 一般的运行时错误 |
| NotImplementedError | 尚未实现的方法 |
| SyntaxError | Python 语法错误 |
| IndentationError | 缩进错误 |
| TabError | Tab 和空格混用 |
| SystemError | 一般的解释器系统错误 |
| TypeError | 对类型无效的操作 |
| ValueError | 传入无效的参数 |
| UnicodeError | Unicode 相关的错误 |
| UnicodeDecodeError | Unicode 解码时的错误 |
| UnicodeEncodeError | Unicode 编码时错误 |
| UnicodeTranslateError | Unicode 转换时错误 |
| Warning | 警告的基类 |
| DeprecationWarning | 关于被弃用的特征的警告 |
| FutureWarning | 关于构造将来语义会有改变的警告 |
| OverflowWarning | 旧的关于自动提升为长整型(long)的警告 |
| PendingDeprecationWarning | 关于特性将会被废弃的警告 |
| RuntimeWarning | 可疑的运行时行为(runtime behavior)的警告 |
| SyntaxWarning | 可疑的语法的警告 |
| UserWarning | 用户代码生成的警告 |
Python主要通过预设在代码中的try-except语句来捕获可能发生的异常,except后对应的是异常类型,当出现的异常同except预设的异常类型一致时,该异常就会被捕获。Python同样支持自定义异常类型而不具体处理,通过raise将该异常抛出,由调用方通过try-except来捕获这个异常。
### 1.5.2 try语句使用
Python的异常捕获语句和Java语法极为相似,try代码块内是检测异常的区域,在该区域内存在except中定义的异常,会直接跳转到对应的except代码块内。其中异常类型后的as是对该异常定义的别名,开发者可以通过该别名获取到异常的详细信息。
```python
try:
num = '50'
print(num/2)
except TypeError as e:
print("捕获到异常:", e)
```
这样就可以捕获到前面提到的异常,示例中程序在捕获到异常后会打印出异常的信息。
如果在一段程序中,无论是否出现异常,都期望程序能执行一些特定的操作,可以通过try-except-finally来实现。finally代码段中的内容,无论是否出现异常都会执行。
```python
try:
num = '50'
print(num/2)
except TypeError as e:
print("捕获到异常:", e)
finally:
print("无论是否发生异常,这里都会执行")
```
这种情况多数用在一些对数据资源的访问上,例如打开一个文件,无论是否在读取过程中出错,都需要最终关闭这个文件以释放资源,这个操作就可以在finally中实现。
如果想要在一个except中同时捕获多个预定义的异常,有两种方式来处理
* 多个并行的except
```python
try:
num = '50'
print(num / 2)
except TypeError as e:
print("捕获到的字符串与数字数学运算的异常:", e)
except ZeroDivisionError as e:
print("捕获到的除数为0的异常", e)
finally:
print("无论是否发生异常,这里都会执行")
```
* 一个except中包含多个类型的异常
```python
try:
num = 50
print(num / 0)
except (TypeError, ZeroDivisionError) as e:
print("捕获到的的异常:", e)
finally:
print("无论是否发生异常,这里都会执行")
```
代码执行时出现上面定义的任意一种异常,都会被对应的except语句捕获到。并行捕获异常的方式可以针对不同的异常类型采取相对应的处理方法,一个except对应多种异常则可以针对类似的几种异常采取相同的处理策略。
* 不指定异常类型
如果不确定try代码块中的异常类型,也可以不指定异常类型,或者捕获所有异常的父类BaseException,这样捕获到的任何异常都会在except代码块中处理。
```python
try:
num = 50
print(num / 0)
except:
print("捕获到的任意异常")
finally:
print("无论是否发生异常,这里都会执行")
```
* 捕获异常的基类
默认情况下所有异常都是继承自BaseException,所以捕获BaseException就可以匹配到所有的异常类型。
```python
try:
num = 50
print(num / 0)
except BaseException as e:
print("捕获到的任意异常:", e)
finally:
print("无论是否发生异常,这里都会执行")
```
可以看到,相比于不指定异常类型,捕获BaseException的优点在于,可以获取到导致程序异常的具体信息,并在except中对异常做相应的判断和处理。
* 使用raise向外抛出异常
使用raise可以不对异常做处理,而是将该异常抛给上一层,也就是调用方,由调用方来处理这个异常。这样的处理多是在方法定义中,可以让外部的调用方有更灵活的异常处理方式。
```python
def addNum():
num1Str = input("请输入第1个数字:")
if(not num1Str.isdigit()):
raise Exception("您输入的不是数字!", num1Str)
num2Str = input("请输入第2个数字:")
if(not num2Str.isdigit()):
raise Exception("您输入的不是数字!", num2Str)
floatNum1 = float(num1Str)
floatNum2 = float(num2Str)
return floatNum1 + floatNum2
try:
addNum()
except BaseException as e:
print(e)
```
上面的示例中,我们定义了一个加法函数,提示用户输入两个数字,函数会自动计算两个数字的和。如果用户输入的内容不是数字,那么这个加法操作流程必然无法执行,因此我们需要提前判断用户输入的内容。
在Python中,通过input获取到的输入内容都是字符串,我们通过字符串内置的isdigit()方法来判断其是否为数字,如果不是,函数会通过raise向外抛出一个Exception异常,并提示用户“您输入的不是数字!”,这样在调用该函数时,通过try-except就可以实时判断用户输入的内容是否符合函数的要求。
### 1.5.3 断言语句
断言一般应用在单元测试中,判断一个表达式,以及这个表达式为False时触发的异常。在Python中,断言的语法为
```python
assert expression, arguments
```
当assert中的条件为假时,就会将arguments以异常信息的形式输出。其中arguments可缺省。
```python
num1Str = 'm'
assert(num1Str.isdigit(), 'num1Str不是数字!')
```
等价于
```python
num1Str = "m"
if(num1Str.isdigit()):
pass
else:
raise AssertionError('num1Str不是数字!')
```
需要注意的是,断言只是调试程序的一种手段,一般是用来发现Python脚本中的异常。断言所检测的异常,在执行Python代码时是可以被忽略的,因此通过断言定位并修改程序问题后,该断言便可以删除。
## 1.6 面向对象编程
几乎所有的高级语言都是面向对象的,相比于面向过程,面向对象的编程方式更适合人类的思维模式。在中大型项目中,面向对象的编程风格也可以极大地节约代码量。
Python支持面向对象的编程,有着完善的类、对象以及方法的定义和使用规范。
### 1.6.1 面向对象的编程思想
面向对象最重要的三个特点即为封装、继承、多态。
封装是将拥有相同属性和方法的事物包装在一起成为一个类,对外尽可能隐藏具体的属性,外部通过给定的方法来修改类的属性或执行业务,各个类之间通过对外暴露的方法相互交流。
继承是使用已经存在的类定义新的类,新类中可以增加新的属性或方法,也可以使用父类定义的属性和方法,继承可以实现代码的可重用性。
多态则是为了解决现实生活中的多样性问题, 相同的指令对应不同的子对象,该对象具有特定的执行方式。
举一个简单的例子,我们可以将学生这个群体封装为一个学生类,该类中有age、grade两个属性,还有一个study方法。学生分为小学生、中学生和大学生,后三者就是前者的子类,它们除了可以继承学生定义的属性和方法,也可以定义各自的特点。例如大学生有不同的专业,这样的属性并非所有学生共有,只定义在大学生子类中即可。继承自父类中的study方法,在不同的子类中可以有不同的表现,小学生学习小学知识,大学生则学习自己的专业知识,这就是所谓的多态。
### 1.6.2 Python的面向对象特色
如下面的示例,在Python中定义了一个类:
```python
class Student(object):
def __init__(self, name, age, grade):
self.name = name
self.age = age
self.grade = grade
def study(self):
print(self.name+"在学习基础知识")
# 类的调用
baseStudent = Student("学生0", 10, 3)
print("baseStudent的name:", baseStudent.name)
print("baseStudent的age:", baseStudent.age)
print("baseStudent的grade:", baseStudent.grade)
baseStudent.study()
# 执行结果
baseStudent的name: 学生0
baseStudent的age: 10
baseStudent的grade: 3
学生0在学习基础知识
```
class是Python中用来定义类的标准字符,Student(object)说明当前定义的类继承自object类,如同Java中的类都是继承自Object类一样,Python中的类也都是继承自object类。Student类中的\_\_init\_\_方法是构造函数的定义,是这个类的对象被初始化时默认会调用的方法。
像\_\_init\_\_这样以前后双下划线的方式命名的方法名,是Python中的“魔术方法(magic method)”,对Python系统来说,这将确保不会与用户自定义的名称冲突。
Python中定义对象直接命名对象名即可,不能像Java那样,在对象名前显式指定类名。等号后的Student("学生0", 10, 3)就是对Student对象的实例化,这行代码会自动调用类构造函数,需要注意的是,在Python类定义的函数,第一个参数都是self,这个参数代表当前正在使用的对象,我们可以通过它获取到当前对象内的属性。
接下来我们定义Student的两个子类:PrimaryStudent和JuniorStudent,它们会各自实现自己的study方法
```python
class Student(object):
def __init__(self, name, age, grade):
self.name = name
self.age = age
self.grade = grade
def study(self):
print(self.name+"在学习基础知识")
class PrimaryStudent(Student):
def study(self):
print(self.name+"在学习小学知识")
class JuniorStudent(Student):
def study(self):
print(self.name+"在学习初中知识")
primaryStudent = PrimaryStudent("小学生", 7, 1)
print("primaryStudent的name:", primaryStudent.name)
print("primaryStudent的age:", primaryStudent.age)
print("primaryStudent的grade:", primaryStudent.grade)
primaryStudent.study()
juniorStudent = JuniorStudent("中学生", 13, 7)
print("juniorStudent的name:", juniorStudent.name)
print("juniorStudent的age:", juniorStudent.age)
print("juniorStudent的grade:", juniorStudent.grade)
juniorStudent.study()
# 执行结果
primaryStudent的name: 小学生
primaryStudent的age: 7
primaryStudent的grade: 1
小学生在学习小学知识
juniorStudent的name: 中学生
juniorStudent的age: 13
juniorStudent的grade: 7
中学生在学习初中知识
```
上面的示例中,PrimaryStudent和JuniorStudent虽然都没有在类中定义自己的age、grade以及name,但是因为在定义类时,继承了Student类,所以它们就继承了Student定义的这些属性。此时称Student类为父类,PrimaryStudent和JuniorStudent为子类。两个子类都重写了父类的study方法,调用时会执行子类中的方法,而非父类中的。
- 类属性和方法的访问限制
类属性和方法的访问限制,是通过名称来控制的。当名称以两个下划线开头时,则表示这是一个私有变量或私有方法。将上面的例子中的name改为\_\_name后,外部就无法访问到这个属性了。
```python
class Student(object):
def __init__(self, name, age, grade):
self.__name = name
self.age = age
self.grade = grade
def study(self):
print(self.__name+"在学习基础知识")
student = Student("小学生", 7, 1)
print("student的name:", student.__name)
```
执行上面的程序,会提示Student没有\_\_name这个属性,因为\_\_name对外部是不可见的。
```
AttributeError: 'Student' object has no attribute '__name'
```
在Python中,如果属性名或方法名以单个下划线开头,则说明这是一个受保护的属性或方法,这是一种约定俗成的做法,它表示“虽然我可以在外部被访问,但是最好不要这样做”。
## 疑难解答
### 1. 为什么建议使用python 3.x 而不是python 2.x?
Python 3.x 在设计时没有考虑向下兼容,且官方对于Python 2.x的维护截止到2020年。因此,为了能够在未来的开发中享受到Python的更新与改善。建议大家使用Python 3.x。
相较于 Python 2.x, Python 3.x 的Python解释器、io、pickle等模块在性能上都有了大幅度的提升。Python 3.x 新增了tracemalloc、enum等模块,新增了time.time_ns()、os.scandir()等方法。
Python 3.x 在使用上也做出了一些调整。去掉了print 语句,提供了print()函数。将源码文件的编码默认为 utf-8,去掉了不等运算符<>,只保留了不等运算符!=。去掉了 raw_input()函数,优化了input() 函数,使其可以接收任意类型输入,并将输入默认为字符串,返回值为字符串类型。
### 2. 为什么都说Python相比于其它语言更简单?
事实上想要真正精通Python一点也不简单,只是Python的入门基础知识比较简单,学习曲线较为平滑,而这些基础知识已经可以应付工作中的大多数场景。不像有些语言,初学者往往都倒在了入门的门槛上,根本都没有机会流畅地学完所有基础语法。
另外Python因为具备海量的第三方库,即便是刚刚入门的开发者,也能轻易地使用这些第三方库,“黏合”出一个看上去比较复杂的程序。就像造一辆汽车,C语言往往需要从如何炼铁开始,Java需要从如何拧螺丝钉开始,而Python则是直接提供了轮子、方向盘、底盘,你只需要按照自己的想法将这些组件搭建起来,就可以“造”出一辆汽车,这其中哪一个更简单就显而易见了。
学习者在学习Python时可以获得了更多的正反馈,这更容易让学习者产生Python相较其他语言更“简单”的感觉。
## 实训:如何用Python轻松处理excel
openpyxl 是一个支持读写xlsx、xlsm、xltx、xltm等文件格式的Python类库,下面我们尝试使用这个类库完成一个简单的Excel文件读写案例。
* 创建excel文件
```python
# 引入openpyxl模块
import openpyxl
# 创建一个工作簿(workbook),即一个excel文件。默认情况下为该工作簿创建一个名为'Sheet'的工作表
wb = openpyxl.Workbook()
# 获取当前工作表,即名为'Sheet'的工作表
ws = wb.active
# 为工作表的单元格赋值
# 第1行第A列的单元格赋值为'姓名'
# 第1行第B列的单元格赋值为'年龄'
ws['A1'] = '姓名'
ws['B1'] = '年龄'
# 通过追加的方式为工作表赋值
ws.append(['小明', '张三'])
# 保存工作簿,当系统不存在该文件时,新建文件。
wb.save('D:\\sample.xlsx')
wb.close()
```
下图为程序运行后的结果:

创建了一个名为sample的Excel文件,并为工作表增加了4条数据。
* 读取并修改excel文件
```python
# 引入openpyxl模块
import openpyxl
# 打开已有的excel文件
wb = openpyxl.load_workbook('D:\\sample.xlsx')
# 新建一个工作表,并命名为'NewSheet1',index控制创建新表的位置
wb.create_sheet(index = 0, title = 'NewSheet1')
# 新建一个工作表,并命名为'NewSheet2',index控制创建新表的位置
wb.create_sheet(index = 2, title = 'NewSheet2')
# 获取所有工作表名, names的值为:['NewSheet1', 'Sheet', 'NewSheet2']
names = wb.sheetnames
# 删除名为'Sheet'的工作表
wb.remove(wb['Sheet'])
# 打开名为'NewSheet1'的工作表
ws = wb['NewSheet1']
# 获取单元格A1中的值
a1 = ws['A1'].value
# 获取第5行第6列单元格中的值
cell56 = ws.cell(row=5, column=6).value
# 修改第3行第4列单元格的值为'X'
ws.cell(row=3, column=4, value='X')
# 将工作表'NewSheet1'改名为'New'
ws.title = 'New'
# 保存修改结果
wb.save('D:\\sample.xlsx')
wb.close()
```
下图为程序运行结果:

打开此前创建的Excel文件sample.xlsx,删除了名为Sheet的工作表,新增了名为New和NewSheet2的两个工作表。在工作表New的第3行D列的单元格内写入字符X。