master
aolingwen 6 years ago
parent 51fc684d90
commit 8c09fd179a

@ -1,4 +1,4 @@
# 1.1.1HelloPython # 1.1.1 HelloPython
## Python为何物 ## Python为何物

@ -6,57 +6,148 @@
### 整数 ### 整数
`Python`可以处理任意大小的整数,当然包括负整数,在程序中的表示方法和数学上的写法一模一样,例如:`1100-80800`等等。
由于计算机使用二进制,所以,有时候用十六进制表示整数比较方便,十六进制用`0x`前缀和`0-9a-f`表示,例如:`0xff000xa5b4c3d2`等等。
Python 为什么这么流行呢?主要原因有两个: ### 浮点数
- 首先是因为本身语法简单而且提供了非常完善的基础代码库覆盖了如网络、文件、GUI、数据库、正则表达式等大量内容。您可别小看了这些基础代码库它会让我们在实现功能时减少我们的代码量将我们从底层代码中解放出来更加的关心我们需要实现的业务功能。 浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的,比如,`1.23x10^9`和`12.3x10^8`是完全相等的。浮点数可以用数学写法,如`1.233.14-9.01`等等。但是对于很大或很小的浮点数,就必须用科学计数法表示,把`10`用`e`替代,`1.23x10^9`就是`1.23e9`,或者`12.3e80.000012`可以写成`1.2e-5`等等。
- 其次是大家觉得 Python 这么好用,就会有很多具有开源精神的开发者去开发各种各样的第三方模块。接着大家可能会去尝试使用这些模块,然后发现这些模块挺好用的,然后就可能会把这些好用的模块或者 Python 语言推荐给自己的朋友、同事。就这样,不断地完善了 Python 的生态。以前用 C 语言想要实现一个功能可能需要 30 行代码,而用 Python 可能只需要 1 行代码。这也就使得编程的门槛越来越低,越来越流行。
事物都有两面性,既然 Python 有这么大的优点,那肯定也有不小的缺点。 Python 最为致命的确定就是运行速度慢。由于 Python 是解释型语言,您所编写的代码在执行时,解释器会一行一行的翻译成 CPU 能理解的机器码,这个翻译过程是非常耗时的,所以 Python 的运行速度很慢。而像 C 语言所编写的程序在运行前会直接将代码翻译成 CPU 能够理解的机器码,所以 C 语言编写的程序运行时非常快。
不过值得庆幸的是,大量的应用程序不需要这么快的运行速度,因为用户根本感觉不出来。例如开发一个下载 短视频 的网络应用程序C 程序的运行时间需要0.001 秒,而 Python 程序的运行时间需要 0.1 秒,慢了 100 倍,但由于网络更慢,需要等待 5 秒。您可以思考一下,用户真的能感觉到 5.001 秒和 5.1秒 的区别吗?
这就好比布加迪威龙和五菱宏光在北京三环路上行驶的道理一样,虽然理论时速高达 430 公里,但由于三环路堵车的时速只有 20 公里,因此,作为乘客,您感觉的时速是 20 公里。 ### 字符串
所以选择 Python 作为编程语言是一种比较明智的选额 字符串是以单引号`'`或双引号`"`括起来的任意文本,比如`'abc'"xyz"`等等。请注意,`''`或`""`本身只是一种表示方式,不是字符串的一部分,因此,字符串`'abc'`只有`a``b``c`这3个字符。如果`'`本身也是一个字符,那就可以用`""`括起来,比如`"I'm OK"`包含的字符是`I``'``m``空格``O``K`这6个字符
## 如何安装Python 如果字符串内部既包含`'`又包含`"`怎么办?可以用转义字符`\ `来标识,比如:`'I\'m \"OK\"!'`
相信现在您可能已经迫不及待地想要在自己的机器上安装 Python 并开始 Python 之旅了。不过在安装 Python 前还需要了解一个知识,就是 Python 其实有两个不兼容的版本,一个是 2.x 版,另一个是 3.x 版。但由于 2.x 版只维护到 2020 年,而且 Python 官方都直接建议直接使用 3.x 版。所以在这里建议您安装最新的Python 3.7 版。 表示的字符串内容是:
`I'm "OK"!`
想要安装 Python 3.7 可以 Python 官网([https://www.python.org/downloads/](https://www.python.org/downloads/))根据您机器的操作系统的类型下载对应的安装程序,然后运行安装即可。 转义字符`\`可以转义很多字符,比如`\n `表示换行,`\t `表示制表符,字符`\ `本身也要转义,所以`\\`表示的字符就是`\ `,可以在 Python 的交互式命令行用`print()`打印字符串看看:
![1](1.jpg) ```
>>> print('I\'m ok.')
I'm ok.
>>> print('I\'m learning\nPython.')
I'm learning
Python.
>>> print('\\\n\\')
\
\
>>> print('\tHello')
Hello
```
如果字符串里面有很多字符都需要转义,就需要加很多`\ `,为了简化, Python 还允许用`r''`表示`''`内部的字符串默认不转义,可以自己试试:
```
>>> print('\\\t\\')
\ \
>>> print(r'\\\t\\')
\\\t\\
```
部分转义字符:
|转义字符 | 描述 |
| ------------ | ------------ |
| \ | (在行尾时) 续行符 |
| \\\ | 反斜杠符号 |
| \' | 单引号 |
| \" | 双引号 |
| \a | 响铃 |
| \b | 退格(Backspace) |
|\n| 换行|
|\t| 横向制表符|
对于中文字符串的表示,为保证 Python 程序处理中文字符时不乱码,需要在 Python 程序最前面加上`#coding = utf-8`,例如:
```python
>>> #coding=utf-8
>>> a = "你好世界!"
>>> print(a)
你好世界!
```
######字符串运算符
字符串可以使用`+`号和`*`号进行运算。可以组合和复制,运算后会生成一个新的字符串。
## 喜闻乐见的HelloWorld |Python 表达式| 结果| 描述|
| ------------ | ------------ | ------------ |
|len("abcd")| 4| 计算元素个数|
|"123" + "456"| "123456"| 连接|
|"Hi!" * 4| 'Hi!Hi!Hi!Hi!'| 复制|
|"3" in "123"| True| 元素是否存在|
一般在学编程时都先学着写一个 “HelloWorld” 来表征一下自己的已经开始了编程之旅。那么我们来看一下怎样写 Python 版的 “HelloWorld” 。(本书使用的操作系统是 Windows ) ######字符串内置函数
首先在自己喜欢的一个目录中创建一个文本文件,并在文件中输入如下代码: 字符串包含了以下内置函数
| 函数名 | 描述 |
| ------------ | ------------ |
| len(list)|计算字符串长度|
| string.capitalize()|字母字符串首字母大写|
| string.split(str)|将字符串按`str`的形式分割|
| str(a)|将变量转换为字符串|
```python ```python
print('Hello, World') #使用逗号运算符给多个变量赋值
t1,t2 = "1-2-3-4","abcd"
print(len(t1))
print(t2.capitalize())
print(t1.split("-"))
#创建一个整型变量a
a = 123
print(str(a))
```
以上实例输出结果:
```pyhton
7
Abcd
['1','2','3','4']
"123"
``` ```
然后修改文件的名字,如 first.py。**(注意Python 代码文件的后缀名为 py )** ### 布尔值
接着进入命令行,进入到 first.py 所在的目录。例如 first.py 在 D:/code 目录下,所以需要在命令行输入: 布尔值和布尔代数的表示完全一致,一个布尔值只有`True、False`两种值,要么是`True`,要么是`False`在Python中可以直接用`True`、`False`表示布尔值(请注意大小写),也可以通过布尔运算计算出来
```shell ```python
cd d:/code >>> True
True
>>> False
False
>>> 3 > 2
True
>>> 3 > 5
False
``` ```
最后只要在命令行输入如下命令就可以运行我们写好的程序了。 布尔值可以用`and`、`or`和`not`运算
```shell `and`运算是`与运算`,只有所有都为`True``and`运算结果才是`True`
python first.py
```
>>> 5 > 3 and 3 > 1
True
``` ```
运行程序后看到的输出和我们的预期一致。 `or`运算是`或运算`,只要其中有一个为`True``or`运算结果就是`True`
![2](2.jpg) ```
>>> 5 > 3 or 1 > 3
True
```
`not`运算是`非运算`,它是一个单目运算符,把`True`变成`False``False`变成`True`
```
>>> not 1 > 2
True
```

@ -0,0 +1,491 @@
# 1.1.3 数据结构
数据结构:即人们抽象出来的描述现实世界实体的数学模型(非数值计算)及其上的操作(运算),在计算机上的表示和实现。
数据结构就是指按一定的逻辑结构组成的一批数据,使用某种存储结构将这批数据存储于计算机中,并在这些数据上定义了一个运算集合。
`Python`中内置了`4`种常用的数据结构,分别为:列表,元组,集合,字典。
## 列表
### 什么是列表
列表是最常用的`Python`数据类型,它可以作为一个方括号内的逗号分隔值出现。列表的数据项不需要具有相同的类型创建一个列表,只要把逗号分隔的不同的数据项使用方括号括起来即可。
简单来说列表是由一系列元素按特定顺序排列组成。你可以创建包含字母表中所有字母/数字`0-9`或一些字符串的列表;可以将其他数据类型放入列表中,甚至可以将另一个列表放在列表中。
在`Python`中,用方括号`[]`来表示列表,并用逗号来分隔其中的元素。例如:
```python
>>>a = [] #创建一个空的列表
>>>b = [1,2,3,4] #创建一个含数字的大小为 4 的列表
>>>c = [1,'a',2,'b',3,'c',4,'d'] #创建包含多种数据类型的列表
>>>a = [b,c] #创建包含其他列表的列表
>>>print(a)
[[1,2,3,4],[1,'a',2,'b',3,'c',4,'d']]
```
### 如何访问列表中的元素
先可以考虑前面所学的字符串类型的数据访问,例如有一字符串:`"abcd"`。如果我们需要看它的某个特定位置上的字符是什么,则只要知道它的索引位置就行了,索引位置如下图:
| 字符串 |a | b | c | d |
| ------------ | ------------ | ------------ | ------------ | ------------ |
| 索引 -> | 0 | 1 | 2 | 3 |
|索引 <-|0|-3|-2|-1|
计算机存储数据的位置都是从`0`号位置开始存储的,习惯使用从左往右进行访问,特殊情况可从右往左访问,即从`0`号位置开始,到`-1`号位置在最右边(最后)往左(前)访问。要访问上面那个字符串`"abcd"`中的字符`b`的话,我们可以用这样一种形式:
```python
"abcd"[1] #或 "abcd"[-3]
```
字符`b`的索引位置在字符串`"abcd"`中为`1`(或`-3`),所以可以通过加中括号`"[]"`(中括号内为索引位置)的形式访问。
通常我们习惯将这个字符串赋值给一个变量然后通过变量名进行操作:
```python
>>> a = "abcd"
>>> print(a[1]) #输出变量a储存的字符串索引位置为2的字符
b
```
与字符串的索引一样,列表索引从`0`开始。列表可以进行截取、组合等。使用索引位置来访问列表中的值,同样你也可以使用方括号的形式访问索引位置,如下所示:
```python
>>> list = ['physics', 'chemistry', 1997, 2000]
>>> print(list[0])
physics
>>> print(['physics', 'chemistry', 1997, 2000][0])
physics
```
以上两种形式都可以访问列表`list`索引位置为`0`的数据`physics`,第一个通过变量间接访问,第二个是直接访问,形式均为:
```
列表[索引位置]
```
可见当一个变量被赋值为某种数据类型时,该变量就相应变为了赋值的数据类型。例如:
```python
>>> a = 10 #此时a的数据类型为整数类型
>>> a = ['physics', 'chemistry'] #此时变为列表
>>> a[1] #对应 列表[索引位置] 的形式来访问特定位置
```
若要继续访问列表`['physics', 'chemistry']`中元素字符串`physics`的某个位置上的字符,可继续采用后面加`[]`的形式,例如:
```python
>>> a = 10 #此时a的数据类型为整数类型
>>> a = ['physics', 'chemistry'] #此时变为列表
>>> print(a[0]) #打印第一个元素(这里为字符串)
physics
>>> print(a[0][1]) #打印第一个元素的第二个位置上的字符
h
```
注意`[]`内的索引数字大小必须小于要访问元素长度,例如:`a = ['physics', 'chemistry']` 以`a[x]`访问列表元素,则`x`要小于列表元素个数`2`并且要大于`-3`,以`a[x][y]`访问列表元素(这里是字符串,单个数据元素不用此操作)内的元素时,则`y`要小于列表元素里的元素长度,例如列表`0`号元素`physics`长度为`7`,则`y`小于`7`,且大于`-8`依次类推。
### 列表的相关操作
列表的相关操作有以下几种:增加元素、删除元素、替换元素、列表运算符、列表内置函数
#### 增加元素
通过使用`append()`函数来在列表末尾处添加列表元素:
```python
>>> list = [] #创建空列表
>>> list.append('Google') #使用append()添加元素
>>> list.append('Runoob')
>>> print(list)
['Google', 'Runoob'] #结果
```
添加的元素按`append()`函数从上到下的先后顺序在列表中从左至右的顺序存放。
#### 删除元素
通过使用`pop()`函数来删除列表末尾处的列表元素:
```python
>>> list = ['Google', 'Runoob']
>>> list.pop()
>>> print(list)
['Google']
```
这里需要注意的是`pop()`函数也可以通过指定索引位置来删除列表特定位置的数据,例如:
```python
>>> list = ['Google', 'Runoob']
>>> list.pop(0)
>>> print(list)
['Runoob']
```
还可以使用`remove()`函数来删除指定的内容:
```python
>>> list = ['Google', 'Runoob']
>>> list.remove('Google')
>>> print(list)
['Runoob']
```
两种方式都可以将列表中的元素删除,可在不同情形下使用。
#### 替换元素
如果想要改变一个有数据的列表某个特定位置上的数据,我们可以通过类似赋值的方式进行:
```python
>>> list = ['Google', 'Runoob']
>>> list[0] = "Baidu"
>>> print(list)
['Baidu', 'Runoob']
```
#### 列表运算符
与字符串一样,列表之间可以使用 + 号和 * 号进行运算。这就意味着他们可以组合和复制,运算后会生成一个新的列表。
|Python 表达式| 结果| 描述|
| ------------ | ------------ | ------------ |
|len([1, 2, 3])| 3| 计算元素个数|
|[1, 2, 3] + [4, 5, 6]| [1, 2, 3, 4, 5, 6]| 连接|
|['Hi!',] * 4| ['Hi!', 'Hi!', 'Hi!', 'Hi!']| 复制|
|3 in [1, 2, 3]| True| 元素是否存在|
#### 列表内置函数
`Python`列表包含了以下内置函数
| 函数名 | 描述 |
| ------------ | ------------ |
| len(list)|计算列表元素个数。|
| max(list)|返回列表中元素最大值。|
| min(list)|返回列表中元素最小值。|
| list(str)|将字符串转换为列表。|
```python
#使用逗号运算符给多个变量赋值
t1,t2 = [10,30,50],[20,30,50]
print(len(t1))
print(min(t2))
print(max(t1))
#创建一个字符串a
a = "abcd"
print(list(a))
```
以上实例输出结果:
```pyhton
3
20
50
['a','b','c','d']
```
## 元组
`Python`的元组与列表类似,不同之处在于元组的元素不能修改。元组使用小括号,列表使用方括号。元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可。
### 创建元组
```python
tup1 = ()
```
元组中只包含一个元素时,需要在元素后面添加逗号,避免与括号运算符混淆。
```python
tup1 = (50,)
```
与列表一样,元组的创建也可以嵌套进行。
```pyhton
tup = (1,2,3,"abc",(1,2,3,"abc"),[1,2,3,"abc"])
```
### 访问元组
与列表一样元组可以使用索引位置来访问元组中的值,如下实例:
```python
tup1 = ('physics', 'chemistry', 1997, 2000)
tup2 = (1, 2, 3, 4, 5, 6, 7 )
print("tup1[0]: ", tup1[0])
print("tup2[-1]: ",tup2[-1])
```
以上实例输出结果:
```python
tup1[0]: physics
tup2[-1]: 7
```
嵌套的元组访问元素与列表一样,通过再后面添加中括号的形式。
```python
tup = (1,2,3,"abc",(10,20,30))
print("tup[3][1]: ",tup[3][1])
print("tup[4][0]: ",tup[4][0])
```
以上实例输出结果:
```python
tup[3][1]: b
tup[4][0]: 10
```
### 修改元组
元组中的元素值是不允许修改的,但我们可以对元组进行连接组合,如下实例:
```python
tup1 = (12, 34.56)
tup2 = ('abc', 'xyz')
# 以下修改元组元素操作是非法的。
# tup1[0] = 100
# 创建一个新的元组
tup3 = tup1 + tup2
print(tup3)
```
以上实例输出结果:
`(12, 34.56, 'abc', 'xyz')`
### 元组运算符
与列表一样,元组之间可以使用`+`号和`*`号进行运算。这就意味着他们可以组合和复制,运算后会生成一个新的元组。
|Python 表达式| 结果| 描述|
| ------------ | ------------ | ------------ |
|len((1, 2, 3))| 3| 计算元素个数|
|(1, 2, 3) + (4, 5, 6)| (1, 2, 3, 4, 5, 6)| 连接|
|('Hi!',) * 4| ('Hi!', 'Hi!', 'Hi!', 'Hi!')| 复制|
|3 in (1, 2, 3)| True| 元素是否存在|
### 元组内置函数
`Python`元组包含了以下内置函数
| 函数名 | 描述 |
| ------------ | ------------ |
| len(tuple)|计算元组元素个数。|
| max(tuple)|返回元组中元素最大值。|
| min(tuple)|返回元组中元素最小值。|
| tuple(list)|将列表转换为元组。|
```python
#使用逗号运算符给多个变量赋值
t1,t2 = (10,30,50),(20,30,50)
print(len(t1))
print(min(t2))
print(max(t1))
#创建一个列表a
a = [1,2,3]
print(tuple(a))
```
以上实例输出结果:
```pyhton
3
20
50
(1,2,3)
```
## 集合
集合(`set`)与数学意义相同,是一个无序的元素不重复的序列。
我们可以使用大括号 `{ }` 或者`set()`函数创建集合,注意:创建一个空集合必须用`set()`而不是 `{ }`,因为 `{ }` 是用来创建一个空字典。
创建格式:
`parame = {value01,value02,...}`
或者:
`set(value)`
```python
>>> a = {'apple', 'orange', 'pear'}
>>> a
{'apple', 'orange', 'pear'}
>>> s = set() #使用set()创建一个空的集合
```
### 集合去重
重复元素在`set`中自动被过滤,即去重功能:
```python
>>> fruit = {'apple', 'orange', 'apple', 'pear'}
>>> print(basket)
{'orange', 'pear', 'apple'} #集合是无序的
>>> s = set([1, 1, 2, 2, 3, 3]) #对列表去重
>>> s
{1, 2, 3}
```
###添加元素
通过`add(key)`方法可以添加元素到`set`中,可以重复添加,但不会有效果:
```python
>>> s = {1,2,3,4}
>>> s
{1, 2, 3, 4}
>>> s.add(4) #添加重复元素 4
>>> s
{1, 2, 3, 4}
```
###删除元素
通过`remove(key)`方法可以删除元素:
```python
>>> s.remove(4) #删除元素 4而不是索引位置 4
>>> s
{1, 2, 3}
```
### 集合运算
两个`set`可以做数学意义上的交集、并集等运算:
```python
>>> a = {1,2,3,4}
>>> b = {4,5,6,7}
>>> a - b # 只在集合a中包含元素
{1,2,3}
>>> a | b # 集合a或b中包含的所有元素
{1,2,3,4,5,6,7}
>>> a & b # 集合a和b中都包含了的元素
{4}
>>> a ^ b # 不同时包含于a和b的元素
{1,2,3,5,6,7}
```
### 常用操作
计算集合的大小可使用函数`len()`
```python
>>> thisset = {"Google", "Runoob", "Taobao"}
>>> len(thisset)
3
```
使用`in`判断元素是否在集合中:
```python
>>>thisset = {"Google", "Runoob", "Taobao"}
>>> "Runoob" in thisset
True
>>> "Facebook" in thisset
False
```
## 字典
字典是`Python`最强大的数据类型之一,通过`键-值(key-value)对`的方式建立数据对象之间的映射关系。字典的每个键-值对用冒号`:`分割,每个`键-值对`间用逗号`,`分隔开,字典是包含在`{}`中。
每个`键`都与一个值相关联,我们可以使用键来访问与之相关联的值。与`键`相关联的值可以是数字、字符串、列表乃至字典。事实上,可将任何`Python`对象用作字典中的值。
### 创建字典
字典的创建格式如下所示:
`d = {key1 : value1, key2 : value2 }`
`键`必须是唯一的,但值则不必。
值可以取任何数据类型,但`键`必须是不可变的,如字符串,数字或元组。
一个简单的字典实例:
```python
>>> dict = {'Alice': '2341', 'Beth': '9102'}
>>> print(dict)
{'Alice': '2341', 'Beth': '9102'}
```
### 访问字典中的值
要获取与`键`相关联的值,可依次指定字典名和放在方括号内的`键`。我们访问列表等其他类型是通过方括号`[]`内添加索引位置的形式,这里字典我们把索引位置用字典中的`键(key)`来代替。
把相应的键放入到方括号中(`name[key]`:
```python
>>> dict = {'Name': 'Runoob', 'Age': 7}
>>> print ("dict['Name']: ", dict['Name'])
dict['Name']: Runoob
>>> print ("dict['Age']: ", dict['Age'])
dict['Age']: 7
```
### 添加元素
字典是一种动态数据结构,可随时在字典中添加`键—值对`。要添加`键—值对`时,可依次指定字典名、`键`和`键`对应的值。
下面在字典`menu`中添加两道菜的菜名和价格:
```python
>>> #coding=utf-8
#创建并初始化menu字典
>>> menu = {'鱼':40, '猪肉':30, '番茄':15, '拉面':10}
#向menu字典中添加菜名和价格
>>> menu['果汁'] = 12
>>> menu['煎蛋'] = 2
######输出新的menu
>>> print(menu)
{'鱼': 40,'猪肉': 30,'番茄': 15,'拉面': 10, '果汁': 12,'煎蛋': 2}
```
新的`menu`字典包含`6`个`键-值对`,新增加的两个`键-值对`(菜名和对应价格)添加在了原有`键-值对`的后面,注意字典中`键-值对`的排列顺序和添加顺序没有必然联系Python 不关心字典中`键-值对`的排列顺序,而只关心键与值得对应关系。
同理,字典和列表一样,可以先创建一个空字典,然后可以不断向里面添加新的键-值对。
### 修改元素
字典和列表一样,里面的值都是可以修改的。要修改字典中的值,可直接指定字典中的键所对应的新值。例如,将`menu`中的`fish`价格改为`50`。
```python
>>> #coding = utf-8
#创建并初始化menu字典
>>> menu = {'鱼':40, '猪肉':30, '番茄':15, '拉面':10}
# 修改menu字典中菜fish的价格
>>> menu['鱼'] = 50
# 打印输出新的menu
>>> print(menu)
{'鱼': 50, '猪肉': 30, '番茄': 15, '拉面': 10}
```
### 删除元素
我们可以通过`del`方法删除字典中不需要的`键-值对`,使用`del`方法时,要指定字典名和要删除的`键`。
例如,在`menu`菜单中删除键`noodles`和它的值。
```python
###### 创建并初始化menu字典
>>> menu = {'鱼':40, '猪肉':30, '番茄':15, '拉面':10}
###### 删除noodles键值对
>>> del menu['拉面']
###### 打印输出新的menu
>>> print(menu)
{'鱼':40, '猪肉':30, '番茄':15}
```

@ -0,0 +1,90 @@
# 1.1.4 分支与循环
## 分支结构
`Python`中的分支结构又叫条件语句,`Python`条件语句是通过一条或多条语句的执行结果(`True`或者`False`)来决定执行的代码块。可以通过下图来简单了解条件语句的执行过程:
![](3.jpg)
`Python`语言中分支结构的使用if语句实现其一般的形式如下
```python
if <条件1>
<语句块1>
elif <条件2>
<语句块2>
elif <条件3>
<语句块3>
:
else:
<语句块n>
```
当`if`语句执行时,首先会测试`<条件1>`,如果结果为`True`,则执行`<语句块1>``<条件1>`为`False`,则会测试`<条件2>`,依此类推,`Python`会执行第一个测试结果为`True`的`<语句块>`,如果所有的`<条件>`均为`False`,则执行`else`后的`<语句块n>`。任何语句的部分也还可以嵌套`if`语句。
例如现在需要编写一个程序将学生的数学成绩转换成`A, B, C, D`三等,规则是:成绩高于`85`分则为`A`,成绩在`70到85分`之间的为`B`,成绩在`60到70分`之间的为`C`,成绩低于`60`分的为`D`.那么代码如下:
```python
score = 88
if score > 85:
print('A')
elif score >= 70:
print('B')
elif score >= 60:
print('C')
else
print('D')
```
会看到代码的输出为: `A`
## 循环结构
### for循环
`for`循环在`Python`中是一个通用的迭代器,可以遍历任何有序的序列对象内的元素。`for`语句可用于字符串、列表、元组、文件等可迭代对象。`for`循环格式如下:
```python
for <循环变量> in <遍历结构>:
<语句块1>
else:
<语句块2>
```
`for`循环也可称为“遍历循环”,是因为`for`语句的循环执行次数是根据遍历结构中元素个数确定的。遍历循环可以理解为从遍历结构中逐一提取元素,放在循环变量中,对于每个所提取的元素执行一次语句块。
### while循环
```python
while <条件>
<语句块1> #重复执行的部分,称为循环体
else: #可选部分
<语句块2>
```
如果不是执行`break`语句而结束循环,`<语句块2>`才会被执行。
## 循环控制语句
循环控制语句可以更改语句执行的顺序。`Python`支持以下循环控制语句:
| 控制语句 | 描述 |
| ------------ | ------------ |
|break语句 | 在语句块执行过程中终止循环,并且跳出整个循环 |
| continue语句 | 在语句块执行过程中终止当前循环,跳出该次循环,执行下一次循环。 |
| pass语句 | pass是空语句是为了保持程序结构的完整性。 |
如果要计算`1-100`的整数之和,从`1`写到`100`有点困难,幸好`Python`提供一个`range()`函数,可以生成一个整数序列,再通过`list()`函数可以转换为`list`。比如`range(5)`生成的序列是从`0`开始小于`5`的整数:`list(range(5))`
所以`range(101)`就可以生成`0-100`的整数序列,那么计算`1`到`100`的整数和代码如下:
```python
sum = 0
for x in range(101):
sum = sum + x
print(sum)
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@ -0,0 +1,200 @@
# 1.2.1 函数
## 函数是什么
函数一词来源于数学,但编程中的「函数」概念,与数学中的函数是有很大不同的,编程中的函数在英文中也有很多不同的叫法。在`BASIC`中叫做`subroutine`(子过程或子程序),在`Pascal`中叫做`procedure`(过程)和`function`,在`C`中只有`function`,在`Java`里面叫做`method`。
定义: 函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可。
## python中函数的定义
`Python`中函数是通过保留字`def`来进行定义。语法格式如下:
```python
def <函数名>([<形式参数>]):
<函数体>
return <返回值>
```
函数名可以是任何有效的`Python`标识符;参数是调用该函数时传递给它的值,可以有零个、一个或多个,当传递多个参数时各参数由逗号分隔,当没有参数时也要保留圆括号。
## 函数的参数
### 形参和实参
函数在调用的时候,可以传入参数,有形参和实参,简单点说,形参就是函数接收的参数,而实参就是你实际传入的参数。
**形参**:形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。
**实参**:实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。函数调用结束返回主调用函数后则不能再使用该形参变量。
```python
def calc(x,y):#定义一个函数参数有x和yx和y就是形参
print(x*y)#输出x乘以y的值
calc(5,2)#调用上面定义的函数5和2就是实参
```
### 函数的四种形参类型
#### 位置参数
位置参数,字面意思也就是按照参数的位置来进行传参,有几个位置参数在调用的时候就要传几个,否则就会报错了。
```python
# namesex为位置参数/必填参数
def my(name,sex):
print(name,sex)
return name
my('wwww','男')
```
#### 默认参数
默认参数就是在定义形参的时候,给函数默认赋一个值,比如说数据库的端口这样的,默认给它一个值,这样就算你在调用的时候没传入这个参数,它也是有值的。
```python
# port=3306为默认值参数
def connect(ip,port=3306):
print(ip,port)
#如果给一个port值则传新给的值
connect('118.1.1.1',3307)
#如果不填,则使用默认参数
connect('118.1.1.1')
```
#### 可变参数
可变参数有以下几种特点:
`1`、可变参数用`*`来接收,不是必传的;
`2`、它把传入的元素全部都放到了一个元祖里;
`3`、不显示参数个数,后面想传多少个参数就传多少个,它用在参数比较多的情况下
`4`、如果位置参数、默认值参数、可变参数一起使用的的话,可变参数必须在位置参数和默认值参数后面。
```python
#实例:发送报警短信 参数前面加*代表参数组
def send_sms(*phone_num):
#方法1返回的是元祖
print(phone_num)
#方法2用下面循环的方法不打印整个元祖而是打印每一个元素
# for p in phone_num:
# print(p)
send_sms()# 不传参数
send_sms(150)# 传1个
send_sms(151,152,153)# 传N个
# 执行后结果是:
# ()
# (150,)
# (151, 152, 153)
```
#### 关键字参数
关键字参数有以下几种特点:
`1`、关键字参数使用`**`来接收;
`2`、返回的是字典;
`3`、不限制参数个数,非必传;
`4`、当然也可以和上面的几种一起来使用,如果要一起使用的话,关键字参数必须在最后面。
```python
def send_sms2(**phone_num):
print(phone_num)
send_sms2()
send_sms2(name='xiaohei',sex='nan')
send_sms2(addr='北京',country='中国',aa='hahaha')
# 执行后结果是:
# {}
# {'name': 'xiaohei', 'sex': 'nan'}
# {'addr': '北京', 'country': '中国', 'aa': 'hahaha'}
```
### 函数的返回值-return语句
每个函数都有返回值,如果没有在函数里面指定返回值的话,在`Python`里面函数执行完之后,默认会返回一个`None`,函数也可以有多个返回值,如果有多个返回值的话,会把返回值都放到一个元组中,返回的是一个元组。
为什么要有返回值呢,是因为在这个函数操作完之后,它的结果在后面的程序里面需要用到。
函数中的返回值使用`return`,函数在遇到`return`就立即结束。
什么样的函数需要`return`,什么样的不需要?下面这个只是显示当前日期,就不需要`return`。例如:
```python
import datetime
def get_today():
print(datetime.datetime.today())
```
### 局部变量和全局变量
`1.` 局部变量意思就是在局部生效的,出了这个变量的作用域,这个变量就失效了。
`2.` 全局变量的意思就是在整个程序里面都生效的,在程序最前面定义的都是全局变量。
`3.` 全局变量如果要在函数中修改的话,需要加`global`关键字声明,如果是`list`、字典和集合的话,则不需要加`global`关键字,直接就可以修改。
`4.` 任何函数都可以修改,所以尽量少用全局变量,原因`1`不够安全。原因`2`全局变量一直占用内存。
```python
#如果两种变量都有,会先用局部变量,当没有局部变量则用全局变量
#全局变量,在函数的外面
name='wangcan'
def get_name():
# 声明修改全局变量
global name
# 局部变量,定义在函数内部
name='hailong'
print('函数里面的name'+name)
def get_name2():
print('get_name2',name)
get_name2() #第一调用打印wangcan
get_name() #第二调用打印函数内的name:hailong
print('函数外面的name'+name)# 最后打印,因为有函数声明了全局变量所以打印的是hailong
#最后的结果是
# get_name2 wangcan
# 函数里面的namehailong
# 函数外面的namehailong
```
### 递归调用
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
递归调用的意思就是,在这个函数内部自己调用自己,就有点循环的意思,写个递归,如下:
```python
#递归应用举例
def test1():
num = int(input('please enter a number'))
if num%2==0:#判断输入的数字是不是偶数
return True #如果是偶数的话程序就退出了返回true
print('不是偶数请重新输入!')
return test1()#如果不是偶数的话继续调用自己,输入值
print(test1())#调用test
```
递归调用的特性:
`1.` 必须有一个明确的结束条件。
`2.` 每次进入更深一层递归时,问题规模相比上次递归都应有所减少。
`3.` 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(`stack`)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)。

@ -0,0 +1,105 @@
# 1.2.2 模块
在`Python`程序的开发过程中,为了代码维护的方便,我们可以把函数进行分组,分别放到不同的`.py`文件里,这样,每个文件包含的代码就相对较少,这个`.py`文件就称之为一个模块(`Module`)。模块能够让我们有逻辑地组织`Python`代码段,模块中能够定义函数,类和变量,模块里也可以包含可执行的代码。
## 模块的引入
`Python`中要用关键字`import`来引入某个模块,比如要引用模块`math`,就要在文件的开头用`import math`来引入。在调用`math`模块中的函数时,引用格式为:
`模块名.函数名`
因为这种调用方式可以避免特殊情况的发生:比如在多个模块中可能含有相同名称的函数,这时如果只是通过函数名来调用,程序无法知道是要调用哪个函数。所以如果用上述方法引入模块的时候,调用函数必须加上模块名。
例如:
```python
import math
print(fabs(-2))
```
输出结果:
`NameError: name 'fabs' is not defined`
`fabs()`必须加上`math`前缀,例如:
```python
import math
print(math.fabs(-2))
```
输出结果:
`2`
有些时候我们只需要用到模块中的某个函数,这时不需要导入整个模块,只需要导入该函数即可,语句格式如下:
`from 模块名 import 函数名1,函数名2....`
通过这种方式导入函数的时候调用函数时就只能给出函数名而不能给出模块名了。这种方式导入函数的方法会有这种缺陷当两个模块中含有相同名称函数的时候后面一次导入的函数会覆盖前一次导入的函数。例如假如模块A中有函数`function()`模块B中也有函数`function()`如果先导入模块A中的`function()`、后导入模块B中的`function()`,那么当我们在后面调用`function()`函数的时候程序是去执行模块B中的`function()`函数。
如果想一次性引入模块`math`中所有的函数,可以通过如下方式导入:
`from math import *`
## 自定义模块
每个`Python`文件都可以看作一个模块,模块的名字就是`Python`文件的名字。所以我们完全可以自己写一个`Python`文件,就作为自己定义的模块。例如我们编写了`my_module.py`文件,里面定义了`plus()`函数:
```python
#my_module.py
def plus(a,b):
return a+b
```
之后我们就可以在其他`Python`文件中先`import my_module`,然后通过`my_module.plus(a,b)`来调用`my_module.py`文件中的`plus()`函数。我们也可以直接通过`from my_module import plus`来导入`plus()`函数。
## 内置模块中的内置函数
我们在安装好了`Python`后,也将`Python`本身带有的库也安装好了,`Python`自带的库也叫做`Python`的内置模块。`Python`的内置模块是`Python`编程的重要组织形式,内置模块中的内置函数也极大方便了编程过程中对函数等功能的使用。
`Python`中常见的内置模块如下:
1. `os`模块:(文件和目录)用于提供系统级别的操作。
2. `sys`模块:用于提供对解释器相关的操作。
3. `json`模块:处理`JSON`字符串。
4. `logging`: 用于便捷记录日志且线程安全的模块。
5. `time&datetime`模块:时间相关的操作,时间有三种表示方式。
6. `hashlib`模块:用于加密相关操作,代替了`md5`模块,主要是提供`SHA1``SHA224``SHA256``SHA384``SHA512``MD5`算法。
7. `random`模块:提供随机数。
`Python`的内置模块中也有很多使用十分方便的内置函数。
`dir()`函数是一个排好序的字符串列表,包含的内容是一个模块里定义过的名字,包含在一个模块里定义的所有模块,变量和函数。例如:
```python
# 导入内置math模块
import math
#调用math模块中的dir()函数
content = dir(math)
#输出math模块中所有模块、函数和变量的名字
print(content)
```
输出结果:
`['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs','factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'sin','sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc']`
程序输出了`math`模块中所有模块、函数和变量的名字。特殊字符串变量`__name__`是指向模块的名字,变量`__file__`是指向该模块的导入文件名。
`globals()`和`locals()`函数可被用来返回全局和局部命名空间里的名字。如果在函数内部调用的是`globals()`函数,那么返回的是所有在该函数里能够访问的全局名字。如果在函数内部调用的`locals()`函数,返回的是能够在该函数里访问的局部命名。`globals()`函数和`locals()`函数的返回类型都是字典。所以名字们能用`keys()`函数摘取。
当一个模块被导入到一个脚本中后,程序只会将模块顶层部分的代码执行一次。因此,如果我们想再次执行模块顶层部分的代码,可以用`reload()`函数。该函数便会重新将之前导入过的模块导入。格式如下:
`reload(module_name)`
在这里,`module_name`要直接放模块名,而不能是一个字符串形式。例如我们想重载`hello`模块:
`reload(hello)`

@ -0,0 +1,341 @@
# 1.2.3 类与对象
面向对象编程——`Object Oriented Programming`,简称`OOP`,是一种程序设计思想。`OOP`把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在`Python`中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(`Class`)的概念。
我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处。
假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个`dict`表示:
```python
std1 = { 'name': 'Michael', 'score': 98 }
std2 = { 'name': 'Bob', 'score': 81 }
```
而处理学生成绩可以通过函数实现,比如打印学生的成绩:
```python
def print_score(std):
print('%s: %s' % (std['name'], std['score']))
```
如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是`Student`这种数据类型应该被视为一个对象,这个对象拥有`name`和`score`这两个属性(`Property`)。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个`print_score`消息,让对象自己把自己的数据打印出来。
```python
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
```
给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(`Method`)。面向对象的程序写出来就像这样:
```python
bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()
```
面向对象的设计思想是从自然界中来的,因为在自然界中,类(`Class`)和对象(`Instance`)的概念是很自然的。`Class`是一种抽象概念,比如我们定义的`Class——Student`,是指学生这个概念,而对象(`Instance`)则是一个个具体的`Student`,比如,`Bart Simpson`和`Lisa Simpson`是两个具体的`Student`。
所以,面向对象的设计思想是抽象出`Class`,根据`Class`创建`Instance`。
面向对象的抽象程度又比函数要高,因为一个`Class`既包含数据,又包含操作数据的方法。
## 类和对象
面向对象最重要的概念就是类(`Class`)和实例(`Instance`),必须牢记类是抽象的模板,比如`Student`类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
仍以`Student`类为例,在`Python`中,定义类是通过`class`关键字:
```python
class Student(object):
pass
```
`class`后面紧接着是类名,即`Student`,类名通常是大写开头的单词,紧接着是(`object`),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用`object`类,这是所有类最终都会继承的类。
定义好了`Student`类,就可以根据`Student`类创建出`Student`的实例,创建实例是通过类名+()实现的:
```python
>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student
<class '__main__.Student'>
```
可以看到,变量`bart`指向的就是一个`Student`的实例,后面的`0x10a67a590`是内存地址,每个`object`的地址都不一样,而`Student`本身则是一个类。
可以自由地给一个实例变量绑定属性,比如,给实例`bart`绑定一个`name`属性:
```python
>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson'
```
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的`__init__`方法,在创建实例的时候,就把`name``score`等属性绑上去:
```python
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
```
注意到`__init__`方法的第一个参数永远是`self`,表示创建的实例本身,因此,在`__init__`方法内部,就可以把各种属性绑定到`self`,因为`self`就指向创建的实例本身。
有了`__init__`方法,在创建实例的时候,就不能传入空的参数了,必须传入与`__init__`方法匹配的参数,但`self`不需要传,`Python`解释器自己会把实例变量传进去:
```python
>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59
```
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量`self`,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
## 数据封装
面向对象编程的一个重要特点就是数据封装。在上面的`Student`类中,每个实例就拥有各自的`name`和`score`这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩:
```python
>>> def print_score(std):
... print('%s: %s' % (std.name, std.score))
...
>>> print_score(bart)
Bart Simpson: 59
```
但是,既然`Student`实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在`Student`类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和`Student`类本身是关联起来的,我们称之为类的方法:
```python
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
```
要定义一个方法,除了第一个参数是`self`外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了`self`不用传递,其他参数正常传入:
```python
>>> bart.print_score()
Bart Simpson: 59
```
这样一来,我们从外部看`Student`类,就只需要知道,创建实例需要给出`name`和`score`,而如何打印,都是在`Student`类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。
封装的另一个好处是可以给`Student`类增加新的方法,比如`get_grade`
```python
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 60:
return 'B'
else:
return 'C'
```
同样的,`get_grade`方法可以直接在实例变量上调用,不需要知道内部实现细节。
## 继承和多态
在`OOP`程序设计中,当我们定义一个`class`的时候,可以从某个现有的`class`继承,新的`class`称为子类(`Subclass`),而被继承的`class`称为基类、父类或超类(`Base class`、`Super class`)。
比如,我们已经编写了一个名为`Animal`的`class`,有一个`run()`方法可以直接打印:
```python
class Animal(object):
def run(self):
print('Animal is running...')
```
当我们需要编写`Dog`和`Cat`类时,就可以直接从`Animal`类继承:
```python
class Dog(Animal):
pass
class Cat(Animal):
pass
```
对于`Dog`来说,`Animal`就是它的父类,对于`Animal`来说,`Dog`就是它的子类。`Cat`和`Dog`类似。
继承有什么好处?最大的好处是子类获得了父类的全部功能。由于`Animial`实现了`run()`方法,因此,`Dog`和`Cat`作为它的子类,什么事也没干,就自动拥有了`run()`方法:
```python
dog = Dog()
dog.run()
cat = Cat()
cat.run()
```
运行结果如下:
```python
Animal is running...
Animal is running...
```
当然,也可以对子类增加一些方法,比如`Dog`类:
```python
class Dog(Animal):
def run(self):
print('Dog is running...')
def eat(self):
print('Eating meat...')
```
继承的第二个好处需要我们对代码做一点改进。你看到了,无论是`Dog`还是`Cat`,它们`run()`的时候,显示的都是`Animal is running...`,符合逻辑的做法是分别显示`Dog is running...`和`Cat is running...`,因此,对`Dog`和`Cat`类改进如下:
```python
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
```
再次运行,结果如下:
```python
Dog is running...
Cat is running...
```
当子类和父类都存在相同的`run()`方法时,我们说,子类的`run()`覆盖了父类的`run()`,在代码运行的时候,总是会调用子类的`run()`。这样,我们就获得了继承的另一个好处:多态。
要理解什么是多态,我们首先要对数据类型再作一点说明。当我们定义一个`class`的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和`Python`自带的数据类型,比如`str、list、dict`没什么两样:
```python
a = list() # a是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型
```
判断一个变量是否是某个类型可以用`isinstance()`判断:
```python
>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True
```
看来`a、b、c`确实对应着`list、Animal、Dog`这`3`种类型。
但是等等,试试:
```python
>>> isinstance(c, Animal)
True
```
看来`c`不仅仅是`Dog``c`还是`Animal`
不过仔细想想,这是有道理的,因为`Dog`是从`Animal`继承下来的,当我们创建了一个`Dog`的实例`c`时,我们认为`c`的数据类型是`Dog`没错,但`c`同时也是`Animal`也没错,`Dog`本来就是`Animal`的一种!
所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行:
```python
>>> b = Animal()
>>> isinstance(b, Dog)
False
```
`Dog`可以看成`Animal`,但`Animal`不可以看成`Dog`。
要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个`Animal`类型的变量:
```python
def run_twice(animal):
animal.run()
animal.run()
```
当我们传入`Animal`的实例时,`run_twice()`就打印出:
```python
>>> run_twice(Animal())
Animal is running...
Animal is running...
```
当我们传入`Dog`的实例时,`run_twice()`就打印出:
```python
>>> run_twice(Dog())
Dog is running...
Dog is running...
```
当我们传入`Cat`的实例时,`run_twice()`就打印出:
```python
>>> run_twice(Cat())
Cat is running...
Cat is running...
```
看上去没啥意思,但是仔细想想,现在,如果我们再定义一个`Tortoise`类型,也从`Animal`派生:
```python
class Tortoise(Animal):
def run(self):
print('Tortoise is running slowly...')
```
当我们调用`run_twice()`时,传入`Tortoise`的实例:
```python
>>> run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...
```
你会发现,新增一个`Animal`的子类,不必对`run_twice()`做任何修改,实际上,任何依赖`Animal`作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。
多态的好处就是,当我们需要传入`Dog、Cat、Tortoise……`时,我们只需要接收`Animal`类型就可以了,因为`Dog、Cat、Tortoise……`都是`Animal`类型,然后,按照`Animal`类型进行操作即可。由于`Animal`类型有`run()`方法,因此,传入的任意类型,只要是`Animal`类或者子类,就会自动调用实际类型的`run()`方法,这就是多态的意思:
对于一个变量,我们只需要知道它是`Animal`类型,无需确切地知道它的子类型,就可以放心地调用`run()`方法,而具体调用的`run()`方法是作用在`Animal、Dog、Cat`还是`Tortoise`对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种`Animal`的子类时,只要确保`run()`方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
对扩展开放:允许新增`Animal`子类;
对修改封闭:不需要修改依赖`Animal`类型的`run_twice()`等函数。
继承还可以一级一级地继承下来,就好比从爷爷到爸爸、再到儿子这样的关系。而任何类,最终都可以追溯到根类`object`,这些继承关系看上去就像一颗倒着的树。比如如下的继承树:
![](1.jpg)

@ -0,0 +1,151 @@
# 1.2.4 文件IO
我们现在生活在信息爆炸的时代,计算机中文本文件可存储的数据量多得难以置信,我们可以把各种信息都存储在文本文件中。每当我们需要利用程序去修改或分析存储在文本文件中的信息时,就先必须正确地读取文件。
要用`Python`程序去修改或分析文本文件中的信息,首先就需要将文本文件中的信息读取到内存中。而且我们既可以将文本文件中的内容一次性读取,也可以将信息按每次一行的方法逐步读取。
## 读取整个文件
一般我们读取的文件和编写的Python文件在同一目录下例如在当前目录下我们已经创建了要处理的文件`test.txt`,里面包含的内容为:
```markdown
Hello,world!
Hello,Python!
Hello,my brothers.
```
我们运行在同一目录下的`Python`文件`test.py`,代码如下:
```python
with open('test.txt') as file_object:
contents = file_object.read()
print(contents)
```
程序运行结果:
```markdown
Hello,world!
Hello,Python!
Hello,my brothers.
```
`test.py`文件中的第一行代码中有函数`open()`,用于打开文件,这是我们处理文件的第一步。函数`open()`中的参数`'test.txt'`就是要打开的文件。函数`open()`返回的是打开文件的对象,第一行代码就是把文本文件`test.txt`打开,并将其对象保存在`file_object`变量中。
关键字`with`的功能是在不再需要访问文件后自动将文件关闭。所以我们在这里只是`open()`打开了文件,但是没有加入`close()`代码关闭文件因为Python会在处理文件之后自动将文件关闭。
`test.py`文件中的第二行代码是使用`read()`方法读取文本文件`test.txt`的全部内容,并将内容保存在字符串变量`contents`中,然后通过`print()`将结果都输出。
如果我们要处理的文本文件和Python程序文件不在同一目录下那么我们就要在`open()`函数中传入文本文件的绝对路径。
## 逐行读取
我们也可以将文本文件中的数据进行逐行读取,我们要处理的文本文件还是上面的`test.txt`,这时我们可以逐行将`test.txt`的内容输出,代码如下:
```python
with open('test.txt') as file_object:
for line in file_object:
print(line)
```
输出结果:
```python
Hello,world!
Hello,Python!
Hello,my brothers.
```
我们会发现输出结果中每一行内容后面都多了一个空行,这时因为在文件中每一行的末尾都会有一个换行符,而每条`print()`语句也会加上一个换行符,所以每行末尾都有两个换行符,所以输出之后就会多一个空行。
我们可以采取`rstrip()`方法消除空行,代码如下:
我们会发现输出结果中每一行内容后面都多了一个空行,这时因为在文件中每一行的末尾都会有一个换行符,而每条`print()`语句也会加上一个换行符,所以每行末尾都有两个换行符,所以输出之后就会多一个空行。
我们可以采取`rstrip()`方法消除空行,代码如下:
```python
with open('test.txt') as file_object:
for line in file_object:
print(line.rstrip())
```
这时输出的结果中就没有多余的空行了:
```python
Hello,world!
Hello,Python!
Hello,my brothers.
```
## 写入空文件
要将信息写入文本文件中,我们依然用`open()`方法,只不过除了将文本文件名当作参数传入函数`open()`中去之外,还需要再传入写参数`w`,例如编写`Python`程序`test2.py`
```python
with open('test2.txt','w') as example:
example.write('Hello world!')
```
程序运行结果就是在`test2.py`文件所在目录生成了一个名为`test2.txt`的文本文件,文本文件中的内容为:
`Hello world!`
在这个例子中,调用函数`open()`,传入两个参数,一个是我们要创建的文件为`test2.txt`,还有一个就是参数`w`,代表的是写入命令。
我们也可以往`test2.txt`中传入多行信息,例如:
```python
with open('test2.txt','w') as example:
example.write('Hello world!\n')
example.write('Hello python!\n')
```
该段程序中第二行`write()`函数中还加入了换行符`\n`,这样就可以将加入的消息分行了。
程序运行之后生成的`test2.txt`的内容为:
```python
Hello world!
Hello python!
```
## 附件到文件
但我们要注意的是使用`w`命令时如果写入的文本文件原来已经存在了,`Python`将会在写入内容前将文本文件中原来的内容全部清空。所以我们如果要在已经存在的文本文件中添加信息内容,而不是将原来的信息内容都清除,就要采取附加模式打开文件。
我们用附加模式打开文件时,`Python`会将我们写入的行直接加入到文本文件原有信息的末尾,如果指定的文本文件不存在,才会新建一个文本文件。
例如现在已经存在了文本文件`test2.txt`,里面的内容为:
```python
Hello world!
Hello python!
```
我们现在想在文本文件`test2.txt`中再加入两句话:
`Hello my brothers!`
`Hello my sisters!`
实现代码如下:
```python
with open('test2.txt','a') as example:
example.write('Hello my brothers!\n')
example.write('Hello my sisters!\n')
```
我们在函数`open()`中传入了参数`a`,告诉`Python`程序我们是将信息加入到文本文件的末尾,而不是覆盖文件。
程序运行之后,文本文件`test2.txt`中的内容变为:
```python
Hello world!
Hello python!
Hello my brothers!
Hello my sisters!
```

@ -0,0 +1,88 @@
# 1.2.5 异常处理
## 异常的基本概念
究竟什么是异常呢?下面的例子可以让你茅塞顿开:
```python
x, y = 12, 5
a = x / y
print(A) #拼写错误Python对大小写敏感 并未定义变量A
```
![](2.jpg)
```python
1/0
```
![](3.jpg)
当`Python`检测到一个错误时,解释器就会指出当前程序流已经无法继续执行,这时候就出现了异常。当程序出现错误时`Python`会自动引发异常,程序员也可以通过`raise`语句显式地引发异常。
## try...except...结构
异常处理结构中最常见也最基本的结构是`try...except...`结构。`try`子句中的代码块包含可能出现异常的语句,而`except`子句用来捕捉相应的异常,`except`子句中的代码块用来处理异常。
```python
try:
try 块 #被监控的语句,可能引发异常
except Exception,e:
except 块 #处理异常的代码
```
如果`try`中的代码块没有出现异常,则跳过`excep`t块执行后面的代码如果出现异常并被`excep`t捕获则执行`except`块中的代码,如果出现异常但未被`except`捕获,则继续向外层抛出。如果所有层都没有捕获并处理该异常,则程序终止并将异常抛给用户。
## else与finally
另一种常用的结构是`try...except...else...`语句。如果`try`中的代码抛出异常,并被某个`except`捕捉则执行`except`块中的内容,这种情况下不执行`else`的内容,如果`try`中的代码没有发生异常,则执行`else`块。例如:
```python
a_list=['abc','def','ghi']
print('请输入字符串序号')
while True:
n=input()
try:
print a_list[n]
except IndexError:
print('输入不正确')
else:
break
```
还有一种异常处理结构是`try...except...finally...`结构。该结构中,`finally`子句中的代码无论异常是否发生都会被执行。它常用来做一些清理工作以释放资源。
例如:
```
try:
f=open('test.txt','r')
finally:
f.close()
```
## 自定义异常的方法
`Python`有许多内置异常类,如果需要的话也可以继承`Python`的内置异常类来实现自定义异常类,例如:
```python
class ShortInputException(Exception):
#自己定义的异常类
def __init__(self,length,atleast):
Exception.__init__(self)
self.length=length
self.atleast=atleast
def __str__(self):
return '输入的长度是 %d, 长度至少应是 %d' %(self.length, self.atleast)
try:
s=raw_input('请输入:')
if len(s) < 3:
raise ShortInputException(len(s), 3)
except ShortInputException as e:
print(e)
else:
print('无异常发生')
```

@ -0,0 +1,171 @@
# 1.2.6 正则表达式
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,来筛选出符合这个规则的内容。
可以简单理解为:一个强大的搜索工具中,正则表达式就是你要搜索内容的条件表达式。
## 正则表达式基础知识
字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在。比如判断一个字符串是否是合法的`Email`地址,虽然可以编程提取@前后的子串,再分别判断是否是单词和域名,但这样做不但麻烦,而且代码难以复用。
正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。
所以我们判断一个字符串是否是合法的Email的方法是
`1.` 创建一个匹配Email的正则表达式
`2.` 用该正则表达式去匹配用户的输入来判断是否合法。
因为正则表达式也是用字符串表示的,所以,我们要首先了解如何用字符来描述字符。
在正则表达式中,如果直接给出字符,就是精确匹配。用`\d`可以匹配一个数字,`\w`可以匹配一个字母或数字,所以:
- `'00\d'`可以匹配`'007'`,但无法匹配`'00A'`
- `'\d\d\d'`可以匹配`'010'`
- `'\w\w\d'`可以匹配`'py3'`
`.`可以匹配任意字符,所以:
- `'py.'`可以匹配`'pyc'、'pyo'、'py!'`等等。
要匹配变长的字符,在正则表达式中,用`*`表示任意个字符(包括`0`个),用`+`表示至少一个字符,用`?`表示`0`个或`1`个字符,用`{n}`表示`n`个字符,用`{n,m}`表示`n-m`个字符:
来看一个复杂的例子:`\d{3}\s+\d{3,8}`。
我们来从左到右解读一下:
- `\d{3}`表示匹配`3`个数字,例如`'010'`。
- `\s`可以匹配一个空格(也包括`Tab`等空白符),所以`\s+`表示至少有一个空格,例如匹配`' '' '`等。
- `\d{3,8}`表示`3-8`个数字,例如`'1234567'`。
综合起来,上面的正则表达式可以匹配以任意个空格隔开的带区号的电话号码。
如果要匹配`'010-12345'`这样的号码呢?由于`'-'`是特殊字符,在正则表达式中,要用`'\'`转义,所以,上面的正则是`\d{3}\-\d{3,8}`。
但是,仍然无法匹配`'010 - 12345'`,因为带有空格。所以我们需要更复杂的匹配方式。
## 正则表达式进阶知识
要做更精确地匹配,可以用`[]`表示范围,比如:
- `[0-9a-zA-Z\_]`可以匹配一个数字、字母或者下划线。
- `[0-9a-zA-Z\_]+`可以匹配至少由一个数字、字母或者下划线组成的字符串,比如`'a100','0_Z''Py3000'`等等。
- `[a-zA-Z\_][0-9a-zA-Z\_]*`可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是`Python`合法的变量。
- `[a-zA-Z\_][0-9a-zA-Z\_]{0, 19}`更精确地限制了变量的长度是`1-20`个字符(前面`1`个字符`+`后面最多`19`个字符)。
`A|B`可以匹配`A`或`B`,所以`(P|p)ython`可以匹配`'Python'`或者`'python'`。
`^`表示行的开头,`^\d`表示必须以数字开头。
`$`表示行的结束,`\d$`表示必须以数字结束。
你可能注意到了,`py`也可以匹配`'python'`,但是加上`^py$`就变成了整行匹配,就只能匹配`'py'`了。
## Python中的re模块
有了准备知识,我们就可以在`Python`中使用正则表达式了。`Python`提供`re`模块,包含所有正则表达式的功能。由于`Python`的字符串本身也用`\`转义,所以要特别注意:
```python
s = 'ABC\\-001' # Python的字符串
# 对应的正则表达式字符串变成:
# 'ABC\-001'
```
因此我们强烈建议使用`Python`的`r`前缀,就不用考虑转义的问题了:
```python
s = r'ABC\-001' # Python的字符串
# 对应的正则表达式字符串不变:
# 'ABC\-001'
```
先看看如何判断正则表达式是否匹配:
```python
>>> import re
>>> re.match(r'^\d{3}\-\d{3,8}$', '010-12345')
<_sre.SRE_Match object; span=(0, 9), match='010-12345'>
>>> re.match(r'^\d{3}\-\d{3,8}$', '010 12345')
>>>
```
`match()`方法判断是否匹配,如果匹配成功,返回一个`Match`对象,否则返回`None`。常见的判断方法就是:
```python
test = '用户输入的字符串'
if re.match(r'正则表达式', test):
print('ok')
else:
print('failed')
```
除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用`()`表示的就是要提取的分组(`Group`)。比如:
`^(\d{3})-(\d{3,8})$`分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:
```python
>>> m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
>>> m
<_sre.SRE_Match object; span=(0, 9), match='010-12345'>
>>> m.group(0)
'010-12345'
>>> m.group(1)
'010'
>>> m.group(2)
'12345'
```
如果正则表达式中定义了组,就可以在`Match`对象上用`group()`方法提取出子串来。注意到`group(0)`永远是原始字符串,`group(1)`、`group(2)` `……`表示第`1、2、……`个子串。
提取子串非常有用。来看一个更凶残的例子:
```python
>>> t = '19:05:30'
>>> m = re.match(r'^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$', t)
>>> m.groups()
('19', '05', '30')
```
这个正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也无法做到完全验证,比如识别日期:
```python
'^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$'
```
对于`'2-30'``'4-31'`这样的非法日期,用正则还是识别不了,或者说写出来非常困难,这时就需要程序配合识别了。
最后需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的`0`
```python
>>> re.match(r'^(\d+)(0*)$', '102300').groups()
('102300', '')
```
由于`\d+`采用贪婪匹配,直接把后面的`0`全部匹配了,结果`0*`只能匹配空字符串了。必须让`\d+`采用非贪婪匹配(也就是尽可能少匹配),才能把后面的`0`匹配出来,加个`?`就可以让`\d+`采用非贪婪匹配:
```python
>>> re.match(r'^(\d+?)(0*)$', '102300').groups()
('1023', '00')
```
当我们在`Python`中使用正则表达式时,`re`模块内部会干两件事情:
- 编译正则表达式,如果正则表达式的字符串本身不合法,会报错。
- 用编译后的正则表达式去匹配字符串。
如果一个正则表达式要重复使用几千次,出于效率的考虑,我们可以预编译该正则表达式,接下来重复使用时就不需要编译这个步骤了,直接匹配:
```python
>>> import re
# 编译:
>>> re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
# 使用:
>>> re_telephone.match('010-12345').groups()
('010', '12345')
>>> re_telephone.match('010-8086').groups()
('010', '8086')
```
编译后生成`Regular Expression`对象,由于该对象自己包含了正则表达式,所以调用对应的方法时不用给出正则字符串。

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

@ -1,2 +0,0 @@
# 第一章Python基础知识

@ -1,2 +0,0 @@
# 第一章Python基础知识

@ -1,2 +0,0 @@
# 第一章Python基础知识
Loading…
Cancel
Save