diff --git a/Day01-15/01.初识Python.md b/Day01-15/01.初识Python.md
index 05acd85..50f7e3c 100644
--- a/Day01-15/01.初识Python.md
+++ b/Day01-15/01.初识Python.md
@@ -10,33 +10,31 @@
4. 2000年10月16日:Python 2.0发布,增加了完整的[垃圾回收](https://zh.wikipedia.org/wiki/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%B8)),提供了对[Unicode](https://zh.wikipedia.org/wiki/Unicode)的支持。与此同时,Python的整个开发过程更加透明,社区对开发进度的影响逐渐扩大,生态圈开始慢慢形成。
5. 2008年12月3日:Python 3.0发布,它并不完全兼容之前的Python代码,不过因为目前还有不少公司在项目和运维中使用Python 2.x版本,所以Python 3.x的很多新特性后来也被移植到Python 2.6/2.7版本中。
-目前我们使用的Python 3.7.x的版本是在2018年发布的,Python的版本号分为三段,形如A.B.C。其中A表示大版本号,一般当整体重写,或出现不向后兼容的改变时,增加A;B表示功能更新,出现新功能时增加B;C表示小的改动(例如:修复了某个Bug),只要有修改就增加C。如果对Python的历史感兴趣,可以阅读名为[《Python简史》](http://www.cnblogs.com/vamei/archive/2013/02/06/2892628.html)的博文。
+目前我们使用的Python 3.7.x的版本是在2018年发布的,Python的版本号分为三段,形如A.B.C。其中A表示大版本号,一般当整体重写,或出现不向后兼容的改变时,增加A;B表示功能更新,出现新功能时增加B;C表示小的改动(例如:修复了某个Bug),只要有修改就增加C。如果对Python的历史感兴趣,可以阅读名为[《Python简史》](http://www.cnblogs.com/vamei/archive/2013/02/06/2892628.html)的网络文章。
#### Python的优缺点
Python的优点很多,简单的可以总结为以下几点。
-1. 简单和明确,做一件事只有一种方法。
-2. 学习曲线低,跟其他很多语言相比,Python更容易上手。
-3. 开放源代码,拥有强大的社区和生态圈。
-4. 解释型语言,天生具有平台可移植性。
-5. 对两种主流的编程范式(面向对象编程和函数式编程)都提供了支持。
-6. 可扩展性和可嵌入性,例如在Python中可以调用C/C++代码。
-7. 代码规范程度高,可读性强,适合有代码洁癖和强迫症的人群。
+1. 简单明了,学习曲线低,比很多编程语言都容易上手。
+2. 开放源代码,拥有强大的社区和生态圈,尤其是在数据分析和机器学习领域。
+3. 解释型语言,天生具有平台可移植性,代码可以工作于不同的操作系统。
+4. 对两种主流的编程范式(面向对象编程和函数式编程)都提供了支持。
+5. 代码规范程度高,可读性强,适合有代码洁癖和强迫症的人群。
Python的缺点主要集中在以下几点。
-1. 执行效率稍低,因此计算密集型任务可以由C/C++编写。
+1. 执行效率稍低,对执行效率要求高的部分可以由其他语言(如:C、C++)编写。
2. 代码无法加密,但是现在很多公司都不销售卖软件而是销售服务,这个问题会被弱化。
3. 在开发时可以选择的框架太多(如Web框架就有100多个),有选择的地方就有错误。
#### Python的应用领域
-目前Python在Web应用开发、云基础设施、DevOps、网络数据采集(爬虫)、数据分析挖掘、机器学习等领域都有着广泛的应用,因此也产生了Web后端开发、数据接口开发、自动化运维、自动化测试、科学计算和可视化、数据分析、量化交易、机器人开发、自然语言处理、图像识别等一系列相关的职位。
+目前Python在Web应用后端开发、云基础设施建设、DevOps、网络数据采集(爬虫)、自动化测试、数据分析、机器学习等领域都有着广泛的应用。
### 安装Python解释器
-想要开始Python编程之旅,首先得在自己使用的计算机上安装Python解释器环境,下面将以安装官方的Python解释器为例,讲解如何在不同的操作系统上安装Python环境。官方的Python解释器是用C语言实现的,也是使用最为广泛的Python解释器,通常称之为CPython。除此之外,Python解释器还有Java语言实现的Jython、C#语言实现的IronPython以及PyPy、Brython、Pyston等版本,我们暂时不对这些内容进行介绍,有兴趣的读者可以自行了解。
+想要开始Python编程之旅,首先得在自己使用的计算机上安装Python解释器环境,下面将以安装官方的Python解释器为例,讲解如何在不同的操作系统上安装Python环境。官方的Python解释器是用C语言实现的,也是使用最为广泛的Python解释器,通常称之为CPython。除此之外,Python解释器还有Java语言实现的Jython、C#语言实现的IronPython以及PyPy、Brython、Pyston等版本,有兴趣的读者可以自行了解。
#### Windows环境
@@ -57,15 +55,15 @@ yum -y install wget gcc zlib-devel bzip2-devel openssl-devel ncurses-devel sqlit
2. 下载Python源代码并解压缩到指定目录。
```Shell
-wget https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tgz
-xz -d Python-3.7.3.tar.xz
-tar -xvf Python-3.7.3.tar
+wget https://www.python.org/ftp/python/3.7.6/Python-3.7.6.tar.xz
+xz -d Python-3.7.6.tar.xz
+tar -xvf Python-3.7.6.tar
```
3. 切换至Python源代码目录并执行下面的命令进行配置和安装。
```Shell
-cd Python-3.7.3
+cd Python-3.7.6
./configure --prefix=/usr/local/python37 --enable-optimizations
make && make install
```
@@ -104,13 +102,13 @@ macOS也自带了Python 2.x版本,可以通过[Python的官方网站](https://
```Shell
python --version
```
-或者是在Linux或macOS系统的终端中键入下面的命令。
+在Linux或macOS系统的终端中键入下面的命令。
```Shell
python3 --version
```
-当然也可以先输入python或python3进入交互式环境,再执行以下的代码检查Python的版本。
+当然也可以先输入`python`或`python3`进入交互式环境,再执行以下的代码检查Python的版本。
```Python
import sys
@@ -156,12 +154,8 @@ python3 hello.py
Version: 0.1
Author: 骆昊
"""
-
print('hello, world!')
-# print("你好,世界!")
-print('你好', '世界')
-print('hello', 'world', sep=', ', end='!')
-print('goodbye, world', end='!\n')
+# print("你好, 世界!")
```
### Python开发工具
@@ -174,7 +168,7 @@ IDLE是安装Python环境时自带的集成开发工具,如下图所示。但
#### IPython - 更好的交互式编程工具
-IPython是一种基于Python的交互式解释器。相较于原生的Python交互式环境,IPython提供了更为强大的编辑和交互功能。可以通过Python的包管理工具pip安装IPython和Jupyter,具体的操作如下所示。
+IPython是一种基于Python的交互式解释器。相较于原生的Python交互式环境,IPython提供了更为强大的编辑和交互功能。可以通过Python的包管理工具pip安装IPython,具体的操作如下所示。
```Shell
pip install ipython
@@ -220,7 +214,7 @@ pip3 install ipython
- Python PEP8 Autoformat - PEP8规范自动格式化插件。
- ConvertToUTF8 - 将本地编码转换为UTF-8。
-> 说明:事实上[Visual Studio Code]()可能是更好的选择,它不用花钱并提供了更为完整和强大的功能,有兴趣的读者可以自行研究。
+> **说明**:事实上[Visual Studio Code]()可能是更好的选择,它不用花钱并提供了更为完整和强大的功能,有兴趣的读者可以自行研究。
#### PyCharm - Python开发神器
@@ -236,12 +230,11 @@ PyCharm的安装、配置和使用在[《玩转PyCharm》](../玩转PyCharm.md)
import this
```
- > 说明:输入上面的代码,在Python的交互式环境中可以看到Tim Peter撰写的[“Python之禅”](../Python之禅.md),里面讲述的道理不仅仅适用于Python,也适用于其他编程语言。
- >
+ > **说明**:输入上面的代码,在Python的交互式环境中可以看到Tim Peter撰写的[“Python之禅”](../Python之禅.md),里面讲述的道理不仅仅适用于Python,也适用于其他编程语言。
2. 学习使用turtle在屏幕上绘制图形。
- > 说明:turtle是Python内置的一个非常有趣的模块,特别适合对计算机程序设计进行初体验的小伙伴,它最早是Logo语言的一部分,Logo语言是Wally Feurzig和Seymour Papert在1966发明的编程语言。
+ > **说明**:turtle是Python内置的一个非常有趣的模块,特别适合对计算机程序设计进行初体验的小伙伴,它最早是Logo语言的一部分,Logo语言是Wally Feurzig和Seymour Papert在1966发明的编程语言。
```Python
import turtle
@@ -260,4 +253,4 @@ PyCharm的安装、配置和使用在[《玩转PyCharm》](../玩转PyCharm.md)
turtle.mainloop()
```
- > 提示:本章提供的代码中还有画国旗和画小猪佩奇的代码,有兴趣的读者请自行研究。
+ > **提示**:本章提供的代码中还有画国旗和画小猪佩奇的代码,有兴趣的读者请自行研究。
diff --git a/Day01-15/02.语言元素.md b/Day01-15/02.语言元素.md
index 65d3dd9..bdb7f13 100644
--- a/Day01-15/02.语言元素.md
+++ b/Day01-15/02.语言元素.md
@@ -4,17 +4,17 @@
计算机的硬件系统通常由五大部件构成,包括:运算器、控制器、存储器、输入设备和输出设备。其中,运算器和控制器放在一起就是我们通常所说的中央处理器,它的功能是执行各种运算和控制指令以及处理计算机软件中的数据。我们通常所说的程序实际上就是指令的集合,我们程序就是将一系列的指令按照某种方式组织到一起,然后通过这些指令去控制计算机做我们想让它做的事情。今天我们大多数时候使用的计算机,虽然它们的元器件做工越来越精密,处理能力越来越强大,但究其本质来说仍然属于[“冯·诺依曼结构”](https://zh.wikipedia.org/wiki/%E5%86%AF%C2%B7%E8%AF%BA%E4%BC%8A%E6%9B%BC%E7%BB%93%E6%9E%84)的计算机。“冯·诺依曼结构”有两个关键点,一是指出要将存储设备与中央处理器分开,二是提出了将数据以二进制方式编码。二进制是一种“逢二进一”的计数法,跟我们人类使用的“逢十进一”的计数法没有实质性的区别,人类因为有十根手指所以使用了十进制(因为在数数时十根手指用完之后就只能进位了,当然凡事都有例外,玛雅人可能是因为长年光着脚的原因把脚趾头也算上了,于是他们使用了二十进制的计数法,在这种计数法的指导下玛雅人的历法就与我们平常使用的历法不一样,而按照玛雅人的历法,2012年是上一个所谓的“太阳纪”的最后一年,而2013年则是新的“太阳纪”的开始,后来这件事情被以讹传讹的方式误传为”2012年是玛雅人预言的世界末日“这种荒诞的说法,今天我们可以大胆的猜测,玛雅文明之所以发展缓慢估计也与使用了二十进制有关)。对于计算机来说,二进制在物理器件上来说是最容易实现的(高电压表示1,低电压表示0),于是在“冯·诺依曼结构”的计算机都使用了二进制。虽然我们并不需要每个程序员都能够使用二进制的思维方式来工作,但是了解二进制以及它与我们生活中的十进制之间的转换关系,以及二进制与八进制和十六进制的转换关系还是有必要的。如果你对这一点不熟悉,可以自行使用[维基百科](https://zh.wikipedia.org/wiki/%E4%BA%8C%E8%BF%9B%E5%88%B6)或者[百度百科](https://baike.baidu.com)科普一下。
-> 提示:近期关于**量子计算机**的研究已经被推倒了风口浪尖,量子计算机基于量子力学进行运算,使用量子瞬移的方式来传递信息。2018年6月,Intel宣布开发出新款量子芯片并通过了在接近绝对零度环境下的测试;2019年1月,IBM向全世界发布了首款商业化量子计算机。
+> **说明**:近期关于**量子计算机**的研究已经被推倒了风口浪尖,量子计算机基于量子力学进行运算,使用量子瞬移的方式来传递信息。2018年6月,Intel宣布开发出新款量子芯片并通过了在接近绝对零度环境下的测试;2019年,IBM和Google都推出了自己的量子计算机。
### 变量和类型
在程序设计中,变量是一种存储数据的载体。计算机中的变量是实际存在的数据或者说是存储器中存储数据的一块内存空间,变量的值可以被读取和修改,这是所有计算和控制的基础。计算机能处理的数据有很多种类型,除了数值之外还可以处理文本、图形、音频、视频等各种各样的数据,那么不同的数据就需要定义不同的存储类型。Python中的数据类型很多,而且也允许我们自定义新的数据类型(这一点在后面会讲到),我们先介绍几种常用的数据类型。
-- 整型:Python中可以处理任意大小的整数(Python 2.x中有int和long两种类型的整数,但这种区分对Python来说意义不大,因此在Python 3.x中整数只有int这一种了),而且支持二进制(如`0b100`,换算成十进制是4)、八进制(如`0o100`,换算成十进制是64)、十进制(`100`)和十六进制(`0x100`,换算成十进制是256)的表示法。
+- 整型:Python中可以处理任意大小的整数(Python 2.x中有`int`和`long`两种类型的整数,但这种区分对Python来说意义不大,因此在Python 3.x中整数只有int这一种了),而且支持二进制(如`0b100`,换算成十进制是4)、八进制(如`0o100`,换算成十进制是64)、十进制(`100`)和十六进制(`0x100`,换算成十进制是256)的表示法。
- 浮点型:浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的,浮点数除了数学写法(如`123.456`)之外还支持科学计数法(如`1.23456e2`)。
- 字符串型:字符串是以单引号或双引号括起来的任意文本,比如`'hello'`和`"hello"`,字符串还有原始字符串表示法、字节字符串表示法、Unicode字符串表示法,而且可以书写成多行的形式(用三个单引号或三个双引号开头,三个单引号或三个双引号结尾)。
- 布尔型:布尔值只有`True`、`False`两种值,要么是`True`,要么是`False`,在Python中,可以直接用`True`、`False`表示布尔值(请注意大小写),也可以通过布尔运算计算出来(例如`3 < 5`会产生布尔值`True`,而`2 == 1`会产生布尔值`False`)。
-- 复数型:形如`3+5j`,跟数学上的复数表示一样,唯一不同的是虚部的`i`换成了`j`。实际上,这个类型并不能算作常用类型,大家了解下就可以了。
+- 复数型:形如`3+5j`,跟数学上的复数表示一样,唯一不同的是虚部的`i`换成了`j`。实际上,这个类型并不常用,大家了解一下就可以了。
#### 变量命名
@@ -37,21 +37,17 @@
```Python
"""
-使用变量保存数据并进行算术运算
+使用变量保存数据并进行加减乘除运算
Version: 0.1
Author: 骆昊
"""
-
a = 321
-b = 123
-print(a + b)
-print(a - b)
-print(a * b)
-print(a / b)
-print(a // b)
-print(a % b)
-print(a ** b)
+b = 12
+print(a + b) # 333
+print(a - b) # 309
+print(a * b) # 3852
+print(a / b) # 26.75
```
在Python中可以使用`type`函数对变量的类型进行检查。程序设计中函数的概念跟数学上函数的概念是一致的,数学上的函数相信大家并不陌生,它包括了函数名、自变量和因变量。如果暂时不理解这个概念也不要紧,我们会在后续的章节中专门讲解函数的定义和使用。
@@ -62,19 +58,17 @@ print(a ** b)
Version: 0.1
Author: 骆昊
-Date: 2018-02-27
"""
-
a = 100
b = 12.345
c = 1 + 5j
d = 'hello, world'
e = True
-print(type(a)) #
-print(type(b)) #
-print(type(c)) #
-print(type(d)) #
-print(type(e)) #
+print(type(a)) #
+print(type(b)) #
+print(type(c)) #
+print(type(d)) #
+print(type(e)) #
```
可以使用Python中内置的函数对变量类型进行转换。
@@ -96,7 +90,6 @@ print(type(e)) #
Version: 0.1
Author: 骆昊
"""
-
a = int(input('a = '))
b = int(input('b = '))
print('%d + %d = %d' % (a, b, a + b))
@@ -133,7 +126,9 @@ Python支持多种运算符,下表大致按照优先级从高到低的顺序
>**说明:** 在实际开发中,如果搞不清楚运算符的优先级,可以使用括号来确保运算的执行顺序。
-下面的例子演示了赋值运算符和复合赋值运算符的使用。
+#### 赋值运算符
+
+赋值运算符应该是最为常见的运算符,它的作用是将右边的值赋给左边的变量。下面的例子演示了赋值运算符和复合赋值运算符的使用。
```Python
"""
@@ -142,40 +137,42 @@ Python支持多种运算符,下表大致按照优先级从高到低的顺序
Version: 0.1
Author: 骆昊
"""
-
a = 10
b = 3
-a += b # 相当于:a = a + b
-a *= a + 2 # 相当于:a = a * (a + 2)
-print(a) # 想想这里会输出什么
+a += b # 相当于:a = a + b
+a *= a + 2 # 相当于:a = a * (a + 2)
+print(a) # 算一下这里会输出什么
```
-下面的例子演示了比较运算符(关系运算符)、逻辑运算符和身份运算符的使用。
+### 比较运算符和逻辑运算符
+
+比较运算符有的地方也称为关系运算符,包括`==`、`!=`、`<`、`>`、`<=`、`>=`,我相信没有什么好解释的,大家一看就能懂,唯一需要提醒的是比较相等用的是`==`,请注意这个地方是两个等号,因为`=`是赋值运算符,我们在上面刚刚讲到过,`==`才是比较相等的比较运算符。比较运算符会产生布尔值,要么是`True`要么是`False`。
+
+逻辑运算符有三个,分别是`and`、`or`和`not`。`and`字面意思是“而且”,所以`and`运算符会连接两个布尔值,如果两个布尔值都是`True`,那么运算的结果就是`True`;左右两边的布尔值有一个是`False`,最终的运算结果就是`False`。相信大家已经想到了,如果`and`左边的布尔值是`False`,不管右边的布尔值是什么,最终的结果都是`False`,所以在做运算的时候右边的值会被跳过(短路处理),这也就意味着在`and`运算符左边为`False`的情况下,右边的表达式根本不会执行。`or`字面意思是“或者”,所以`or`运算符也会连接两个布尔值,如果两个布尔值有任意一个是`True`,那么最终的结果就是`True`。当然,`or`运算符也是有短路功能的,在它左边的布尔值为`True`的情况下,右边的表达式根本不会执行。`not`运算符的后面会跟上一个布尔值,它的作用是得到与该布尔值相反的值,也就是说,后面的布尔值如果是`True`运算结果就是`False`,而后面的布尔值如果是`False`则运算结果就是`True`。
```Python
"""
-比较、逻辑和算身份运算符的使用
+比较运算符和逻辑运算符的使用
Version: 0.1
Author: 骆昊
"""
-
flag0 = 1 == 1
flag1 = 3 > 2
flag2 = 2 < 1
flag3 = flag1 and flag2
flag4 = flag1 or flag2
flag5 = not (1 != 2)
-print('flag0 =', flag0) # flag0 = True
-print('flag1 =', flag1) # flag1 = True
-print('flag2 =', flag2) # flag2 = False
-print('flag3 =', flag3) # flag3 = False
-print('flag4 =', flag4) # flag4 = True
-print('flag5 =', flag5) # flag5 = False
-print(flag1 is True) # True
-print(flag2 is not False) # False
+print('flag0 =', flag0) # flag0 = True
+print('flag1 =', flag1) # flag1 = True
+print('flag2 =', flag2) # flag2 = False
+print('flag3 =', flag3) # flag3 = False
+print('flag4 =', flag4) # flag4 = True
+print('flag5 =', flag5) # flag5 = False
```
+> **说明**:比较运算符的优先级高于赋值运算符,所以`flag0 = 1 == 1`先做`1 == 1`产生布尔值`True`,再将这个值赋值给变量`flag0`。`print`函数可以输出多个值,多个值之间可以用`,`进行分隔,输出的内容之间默认以空格分开。
+
### 练习
#### 练习1:华氏温度转换为摄氏温度。
@@ -191,12 +188,17 @@ print(flag2 is not False) # False
Version: 0.1
Author: 骆昊
"""
-
f = float(input('请输入华氏温度: '))
c = (f - 32) / 1.8
print('%.1f华氏度 = %.1f摄氏度' % (f, c))
```
+> **说明**:在使用`print`函数输出时,也可以对字符串内容进行格式化处理,上面`print`函数中的字符串`%1.f`是一个占位符,稍后会由一个`float`类型的变量值替换掉它。同理,如果字符串中有`%d`,后面可以用一个`int`类型的变量值替换掉它,而`%s`会被字符串的值替换掉。除了这种格式化字符串的方式外,还可以用下面的方式来格式化字符串,其中`{f:.1f}`和`{c:.1f}`可以先看成是`{f}`和`{c}`,表示输出时会用变量`f`和变量`c`的值替换掉这两个占位符,后面的`:.1f`表示这是一个浮点数,小数点后保留1位有效数字。
+>
+> ```Python
+> print(f'{f:.1f}华氏度 = {c:.1f}摄氏度')
+> ```
+
#### 练习2:输入圆的半径计算计算周长和面积。
参考答案:
@@ -208,12 +210,9 @@ print('%.1f华氏度 = %.1f摄氏度' % (f, c))
Version: 0.1
Author: 骆昊
"""
-
-import math
-
radius = float(input('请输入圆的半径: '))
-perimeter = 2 * math.pi * radius
-area = math.pi * radius * radius
+perimeter = 2 * 3.1416 * radius
+area = 3.1416 * radius * radius
print('周长: %.2f' % perimeter)
print('面积: %.2f' % area)
```
@@ -229,11 +228,11 @@ print('面积: %.2f' % area)
Version: 0.1
Author: 骆昊
"""
-
year = int(input('请输入年份: '))
# 如果代码太长写成一行不便于阅读 可以使用\对代码进行折行
-is_leap = (year % 4 == 0 and year % 100 != 0) or \
- year % 400 == 0
+is_leap = year % 4 == 0 and year % 100 != 0 or \
+ year % 400 == 0
print(is_leap)
```
+> **说明**:比较运算符会产生布尔值,而逻辑运算符`and`和`or`会对这些布尔值进行组合,最终也是得到一个布尔值,闰年输出`True`,平年输出`False`。
\ No newline at end of file
diff --git a/Day01-15/03.分支结构.md b/Day01-15/03.分支结构.md
index e69246b..abd08e7 100644
--- a/Day01-15/03.分支结构.md
+++ b/Day01-15/03.分支结构.md
@@ -15,7 +15,6 @@
Version: 0.1
Author: 骆昊
"""
-
username = input('请输入用户名: ')
password = input('请输入口令: ')
# 用户名是admin且密码是123456则身份验证成功否则身份验证失败
@@ -25,9 +24,9 @@ else:
print('身份验证失败!')
```
-唯一需要说明的是和C/C++、Java等语言不同,Python中没有用花括号来构造代码块而是使用了缩进的方式来设置代码的层次结构,如果`if`条件成立的情况下需要执行多条语句,只要保持多条语句具有相同的缩进就可以了,换句话说连续的代码如果又保持了相同的缩进那么它们属于同一个代码块,相当于是一个执行的整体。
+需要说明的是和C/C++、Java等语言不同,Python中没有用花括号来构造代码块而是**使用了缩进的方式来表示代码的层次结构**,如果`if`条件成立的情况下需要执行多条语句,只要保持多条语句具有相同的缩进就可以了。换句话说**连续的代码如果又保持了相同的缩进那么它们属于同一个代码块**,相当于是一个执行的整体。**缩进**可以使用任意数量的空格,但**通常使用4个空格**,建议大家**不要使用制表键**或者**设置你的代码编辑工具自动将制表键变成4个空格**。
-当然如果要构造出更多的分支,可以使用`if…elif…else…`结构,例如下面的分段函数求值。
+当然如果要构造出更多的分支,可以使用`if...elif...else...`结构或者嵌套的`if...else...`结构,下面的代码演示了如何利用多分支结构实现分段函数求值。
![$$f(x)=\begin{cases} 3x-5&\text{(x>1)}\\x+2&\text{(-1}\leq\text{x}\leq\text{1)}\\5x+3&\text {(x<-1)}\end{cases}$$](./res/formula_1.png)
@@ -92,7 +91,6 @@ print('f(%.2f) = %.2f' % (x, y))
Version: 0.1
Author: 骆昊
"""
-
value = float(input('请输入长度: '))
unit = input('请输入单位: ')
if unit == 'in' or unit == '英寸':
@@ -116,7 +114,6 @@ else:
Version: 0.1
Author: 骆昊
"""
-
score = float(input('请输入成绩: '))
if score >= 90:
grade = 'A'
@@ -141,7 +138,6 @@ print('对应的等级是:', grade)
Version: 0.1
Author: 骆昊
"""
-
a = float(input('a = '))
b = float(input('b = '))
c = float(input('c = '))
diff --git a/Day01-15/04.循环结构.md b/Day01-15/04.循环结构.md
index 4415720..7bc33f9 100644
--- a/Day01-15/04.循环结构.md
+++ b/Day01-15/04.循环结构.md
@@ -2,9 +2,9 @@
### 应用场景
-如果在程序中我们需要重复的执行某条或某些指令,例如用程序控制机器人踢足球,如果机器人持球而且还没有进入射门范围,那么我们就要一直发出让机器人向球门方向奔跑的指令。当然你可能已经注意到了,刚才的描述中不仅仅有需要重复的动作,还需要用到上一章讲的分支结构。再举一个简单的例子,我们要实现一个每隔1秒中在屏幕上打印一次"hello, world"并持续打印一个小时的程序,我们肯定不能够直接把`print('hello, world')`这句代码写3600遍,如果真的要这样做,那么编程的工作就太无聊乏味了。因此,我们还需要了解一下循环结构,有了循环结构我们就可以轻松的控制某件事或者某些事重复、重复、再重复的去执行。
+我们在写程序的时候,一定会遇到需要重复执行某条或某些指令的场景。例如用程序控制机器人踢足球,如果机器人持球而且还没有进入射门范围,那么我们就要一直发出让机器人向球门方向移动的指令。在这个场景中,让机器人向球门方向移动就是一个需要重复的动作,当然这里还会用到上一课讲的分支结构来判断机器人是否持球以及是否进入射门范围。再举一个简单的例子,如果要实现每隔1秒中在屏幕上打印一次“hello, world”并持续打印一个小时,我们肯定不能够直接把`print('hello, world')`这句代码写3600遍,这里同样需要循环结构。
-在Python中构造循环结构有两种做法,一种是`for-in`循环,一种是`while`循环。
+循环结构就是程序中控制某条或某些指令重复执行的结构。在Python中构造循环结构有两种做法,一种是`for-in`循环,一种是`while`循环。
### for-in循环
@@ -24,11 +24,12 @@ for x in range(101):
print(sum)
```
-需要说明的是上面代码中的`range(101)`可以用来构造一个从0到100的取值范围,这样就可以构造出一个整数的序列并用于循环中,例如:
+需要说明的是上面代码中的`range(1, 101)`可以用来构造一个从1到100的范围,当我们把这样一个范围放到`for-in`循环中,就可以通过前面的循环变量`x`依次取出从1到100的整数。当然,`range`的用法非常灵活,下面给出了一个例子:
-- `range(101)`可以产生一个0到100的整数序列。
-- `range(1, 100)`可以产生一个1到99的整数序列。
-- `range(1, 100, 2)`可以产生一个1到99的奇数序列,其中2是步长,即数值序列的增量。
+- `range(101)`:可以用来产生0到100范围的整数,需要注意的是取不到101。
+- `range(1, 101)`:可以用来产生1到100范围的整数,相当于前面是闭区间后面是开区间。
+- `range(1, 101, 2)`:可以用来产生1到100的奇数,其中2是步长,即每次数值递增的值。
+- `range(100, 0, -2)`:可以用来产生100到1的偶数,其中-2是步长,即每次数字递减的值。
知道了这一点,我们可以用下面的代码来实现1~100之间的偶数求和。
@@ -46,7 +47,7 @@ for x in range(2, 101, 2):
print(sum)
```
-也可以通过在循环中使用分支结构的方式来实现相同的功能,代码如下所示。
+当然,也可以通过在循环中使用分支结构的方式来实现相同的功能,代码如下所示。
```Python
"""
@@ -63,20 +64,21 @@ for x in range(1, 101):
print(sum)
```
+> **说明**:相较于上面直接跳过奇数的做法,下面这种做法很明显并不是很好的选择。
+
### while循环
-如果要构造不知道具体循环次数的循环结构,我们推荐使用`while`循环。`while`循环通过一个能够产生或转换出`bool`值的表达式来控制循环,表达式的值为`True`循环继续,表达式的值为`False`循环结束。下面我们通过一个“猜数字”的小游戏(计算机出一个1~100之间的随机数,人输入自己猜的数字,计算机给出对应的提示信息,直到人猜出计算机出的数字)来看看如何使用`while`循环。
+如果要构造不知道具体循环次数的循环结构,我们推荐使用`while`循环。`while`循环通过一个能够产生或转换出`bool`值的表达式来控制循环,表达式的值为`True`则继续循环;表达式的值为`False`则结束循环。
+
+下面我们通过一个“猜数字”的小游戏来看看如何使用`while`循环。猜数字游戏的规则是:计算机出一个1到100之间的随机数,玩家输入自己猜的数字,计算机给出对应的提示信息(大一点、小一点或猜对了),如果玩家猜中了数字,计算机提示用户一共猜了多少次,游戏结束,否则游戏继续。
```Python
"""
猜数字游戏
-计算机出一个1~100之间的随机数由人来猜
-计算机根据人猜的数字分别给出提示大一点/小一点/猜对了
Version: 0.1
Author: 骆昊
"""
-
import random
answer = random.randint(1, 100)
@@ -147,6 +149,8 @@ else:
#### 练习2:输入两个正整数,计算它们的最大公约数和最小公倍数。
+> **提示**:两个数的最大公约数是两个数的公共因子中最大的那个数;两个数的最小公倍数则是能够同时被两个数整除的最小的那个数。
+
参考答案:
```Python
diff --git a/Day01-15/06.函数和模块的使用.md b/Day01-15/06.函数和模块的使用.md
index 4badd51..11d5099 100644
--- a/Day01-15/06.函数和模块的使用.md
+++ b/Day01-15/06.函数和模块的使用.md
@@ -17,7 +17,6 @@
Version: 0.1
Author: 骆昊
"""
-
m = int(input('m = '))
n = int(input('n = '))
fm = 1
@@ -26,10 +25,10 @@ for num in range(1, m + 1):
fn = 1
for num in range(1, n + 1):
fn *= num
-fmn = 1
+fm_n = 1
for num in range(1, m - n + 1):
- fmn *= num
-print(fm // fn // fmn)
+ fm_n *= num
+print(fm // fn // fm_n)
```
### 函数的作用
@@ -43,7 +42,13 @@ print(fm // fn // fmn)
在了解了如何定义函数后,我们可以对上面的代码进行重构,所谓重构就是在不影响代码执行结果的前提下对代码的结构进行调整,重构之后的代码如下所示。
```Python
-def factorial(num):
+"""
+输入M和N计算C(M,N)
+
+Version: 0.1
+Author: 骆昊
+"""
+def fac(num):
"""求阶乘"""
result = 1
for n in range(1, num + 1):
@@ -54,10 +59,10 @@ def factorial(num):
m = int(input('m = '))
n = int(input('n = '))
# 当需要计算阶乘的时候不用再写循环求阶乘而是直接调用已经定义好的函数
-print(factorial(m) // factorial(n) // factorial(m - n))
+print(fac(m) // fac(n) // fac(m - n))
```
-> **说明:** Python的`math`模块中其实已经有一个`factoria`l函数了,事实上要计算阶乘可以直接使用这个现成的函数而不用自己定义。下面例子中的一些函数在Python中也都是现成的,我们这里是为了讲解函数的定义和使用才把它们又实现了一遍,实际开发中不建议做这种低级的重复性的工作。
+> **说明:** Python的`math`模块中其实已经有一个名为`factorial`函数实现了阶乘运算,事实上求阶乘并不用自己定义函数。下面的例子中,我们讲的函数在Python标准库已经实现过了,我们这里是为了讲解函数的定义和使用才把它们又实现了一遍,**实际开发中并不建议做这种低级的重复劳动**。
### 函数的参数
@@ -267,7 +272,7 @@ def is_palindrome(num):
```Python
def is_prime(num):
"""判断一个数是不是素数"""
- for factor in range(2, num):
+ for factor in range(2, int(num ** 0.5) + 1):
if num % factor == 0:
return False
return True if num != 1 else False
@@ -286,6 +291,8 @@ if __name__ == '__main__':
> **注意**:通过上面的程序可以看出,当我们**将代码中重复出现的和相对独立的功能抽取成函数**后,我们可以**组合使用这些函数**来解决更为复杂的问题,这也是我们为什么要定义和使用函数的一个非常重要的原因。
+### 变量的作用域
+
最后,我们来讨论一下Python中有关变量作用域的问题。
```Python
diff --git a/Day01-15/res/TCP-IP-model.png b/Day01-15/res/TCP-IP-model.png
index 777af47..1c641fc 100644
Binary files a/Day01-15/res/TCP-IP-model.png and b/Day01-15/res/TCP-IP-model.png differ
diff --git a/Day01-15/res/after-browser.jpg b/Day01-15/res/after-browser.jpg
index 7cc9520..fb6b27c 100644
Binary files a/Day01-15/res/after-browser.jpg and b/Day01-15/res/after-browser.jpg differ
diff --git a/Day01-15/res/arpanet.png b/Day01-15/res/arpanet.png
index 65d2dd3..3a1f632 100644
Binary files a/Day01-15/res/arpanet.png and b/Day01-15/res/arpanet.png differ
diff --git a/Day01-15/res/ball-game.png b/Day01-15/res/ball-game.png
index 37b9f89..a0b175b 100644
Binary files a/Day01-15/res/ball-game.png and b/Day01-15/res/ball-game.png differ
diff --git a/Day01-15/res/ball.png b/Day01-15/res/ball.png
index 0a63bdc..496e6b5 100644
Binary files a/Day01-15/res/ball.png and b/Day01-15/res/ball.png differ
diff --git a/Day01-15/res/before-browser.jpg b/Day01-15/res/before-browser.jpg
index f816144..89c3acf 100644
Binary files a/Day01-15/res/before-browser.jpg and b/Day01-15/res/before-browser.jpg differ
diff --git a/Day01-15/res/browers.jpg b/Day01-15/res/browers.jpg
index 0be1d4d..2802a23 100644
Binary files a/Day01-15/res/browers.jpg and b/Day01-15/res/browers.jpg differ
diff --git a/Day01-15/res/browser-market-place.jpeg b/Day01-15/res/browser-market-place.jpeg
index 196b0f4..cb71d38 100644
Binary files a/Day01-15/res/browser-market-place.jpeg and b/Day01-15/res/browser-market-place.jpeg differ
diff --git a/Day01-15/res/fibonacci-blocks.png b/Day01-15/res/fibonacci-blocks.png
index a638bf1..be4fc13 100644
Binary files a/Day01-15/res/fibonacci-blocks.png and b/Day01-15/res/fibonacci-blocks.png differ
diff --git a/Day01-15/res/file-open-mode.png b/Day01-15/res/file-open-mode.png
index 2d01ad4..dd6b927 100644
Binary files a/Day01-15/res/file-open-mode.png and b/Day01-15/res/file-open-mode.png differ
diff --git a/Day01-15/res/formula_1.png b/Day01-15/res/formula_1.png
index 5a0a7e2..ec5e551 100644
Binary files a/Day01-15/res/formula_1.png and b/Day01-15/res/formula_1.png differ
diff --git a/Day01-15/res/formula_2.png b/Day01-15/res/formula_2.png
index c21c2ae..954d921 100644
Binary files a/Day01-15/res/formula_2.png and b/Day01-15/res/formula_2.png differ
diff --git a/Day01-15/res/formula_3.png b/Day01-15/res/formula_3.png
index b9a84ed..716835f 100644
Binary files a/Day01-15/res/formula_3.png and b/Day01-15/res/formula_3.png differ
diff --git a/Day01-15/res/formula_4.png b/Day01-15/res/formula_4.png
index 692fb08..c87452f 100644
Binary files a/Day01-15/res/formula_4.png and b/Day01-15/res/formula_4.png differ
diff --git a/Day01-15/res/formula_5.png b/Day01-15/res/formula_5.png
index 28fad34..29e0a58 100644
Binary files a/Day01-15/res/formula_5.png and b/Day01-15/res/formula_5.png differ
diff --git a/Day01-15/res/formula_6.png b/Day01-15/res/formula_6.png
index 86df5da..41e0dfa 100644
Binary files a/Day01-15/res/formula_6.png and b/Day01-15/res/formula_6.png differ
diff --git a/Day01-15/res/formula_7.png b/Day01-15/res/formula_7.png
index 92cd76d..34862f2 100644
Binary files a/Day01-15/res/formula_7.png and b/Day01-15/res/formula_7.png differ
diff --git a/Day01-15/res/formula_8.png b/Day01-15/res/formula_8.png
index a37f92b..f3f2e29 100644
Binary files a/Day01-15/res/formula_8.png and b/Day01-15/res/formula_8.png differ
diff --git a/Day01-15/res/how-data-is-processed.jpg b/Day01-15/res/how-data-is-processed.jpg
index 4359bcd..b1f5486 100644
Binary files a/Day01-15/res/how-data-is-processed.jpg and b/Day01-15/res/how-data-is-processed.jpg differ
diff --git a/Day01-15/res/image-crop.png b/Day01-15/res/image-crop.png
index bb4a9bf..fa19b5b 100644
Binary files a/Day01-15/res/image-crop.png and b/Day01-15/res/image-crop.png differ
diff --git a/Day01-15/res/image-filter.png b/Day01-15/res/image-filter.png
index aac571c..cc4e6e5 100644
Binary files a/Day01-15/res/image-filter.png and b/Day01-15/res/image-filter.png differ
diff --git a/Day01-15/res/image-paste.png b/Day01-15/res/image-paste.png
index 6c5c39d..df25e85 100644
Binary files a/Day01-15/res/image-paste.png and b/Day01-15/res/image-paste.png differ
diff --git a/Day01-15/res/image-putpixel.png b/Day01-15/res/image-putpixel.png
index 54712b7..4a4a7a8 100644
Binary files a/Day01-15/res/image-putpixel.png and b/Day01-15/res/image-putpixel.png differ
diff --git a/Day01-15/res/image-rotate.png b/Day01-15/res/image-rotate.png
index 5db7459..84af30b 100644
Binary files a/Day01-15/res/image-rotate.png and b/Day01-15/res/image-rotate.png differ
diff --git a/Day01-15/res/image-show.png b/Day01-15/res/image-show.png
index 396cb5a..15723fb 100644
Binary files a/Day01-15/res/image-show.png and b/Day01-15/res/image-show.png differ
diff --git a/Day01-15/res/image-thumbnail.png b/Day01-15/res/image-thumbnail.png
index 91c623f..a6d5239 100644
Binary files a/Day01-15/res/image-thumbnail.png and b/Day01-15/res/image-thumbnail.png differ
diff --git a/Day01-15/res/image-transpose.png b/Day01-15/res/image-transpose.png
index 685a280..43a8007 100644
Binary files a/Day01-15/res/image-transpose.png and b/Day01-15/res/image-transpose.png differ
diff --git a/Day01-15/res/ipython-timeit.png b/Day01-15/res/ipython-timeit.png
index 5b52382..4cd5a72 100644
Binary files a/Day01-15/res/ipython-timeit.png and b/Day01-15/res/ipython-timeit.png differ
diff --git a/Day01-15/res/macos-monitor.png b/Day01-15/res/macos-monitor.png
index 3d1b88b..cac5138 100644
Binary files a/Day01-15/res/macos-monitor.png and b/Day01-15/res/macos-monitor.png differ
diff --git a/Day01-15/res/object-feature.png b/Day01-15/res/object-feature.png
index 62c6057..538fa4d 100644
Binary files a/Day01-15/res/object-feature.png and b/Day01-15/res/object-feature.png differ
diff --git a/Day01-15/res/oop-zhihu.png b/Day01-15/res/oop-zhihu.png
index c473984..40e45ce 100644
Binary files a/Day01-15/res/oop-zhihu.png and b/Day01-15/res/oop-zhihu.png differ
diff --git a/Day01-15/res/osi_rm.gif b/Day01-15/res/osi_rm.gif
index 876960d..9228af5 100644
Binary files a/Day01-15/res/osi_rm.gif and b/Day01-15/res/osi_rm.gif differ
diff --git a/Day01-15/res/osimodel.png b/Day01-15/res/osimodel.png
index f0361c9..9c13968 100644
Binary files a/Day01-15/res/osimodel.png and b/Day01-15/res/osimodel.png differ
diff --git a/Day01-15/res/python-idle.png b/Day01-15/res/python-idle.png
index 1e90598..e55248f 100644
Binary files a/Day01-15/res/python-idle.png and b/Day01-15/res/python-idle.png differ
diff --git a/Day01-15/res/python-ipython.png b/Day01-15/res/python-ipython.png
index c3a9054..35d34a7 100644
Binary files a/Day01-15/res/python-ipython.png and b/Day01-15/res/python-ipython.png differ
diff --git a/Day01-15/res/python-jupyter-1.png b/Day01-15/res/python-jupyter-1.png
index 58cedc5..cad28e2 100644
Binary files a/Day01-15/res/python-jupyter-1.png and b/Day01-15/res/python-jupyter-1.png differ
diff --git a/Day01-15/res/python-jupyter-2.png b/Day01-15/res/python-jupyter-2.png
index 2724988..70102b0 100644
Binary files a/Day01-15/res/python-jupyter-2.png and b/Day01-15/res/python-jupyter-2.png differ
diff --git a/Day01-15/res/python-pycharm.png b/Day01-15/res/python-pycharm.png
index ffae8c3..57f2da5 100644
Binary files a/Day01-15/res/python-pycharm.png and b/Day01-15/res/python-pycharm.png differ
diff --git a/Day01-15/res/python-set.png b/Day01-15/res/python-set.png
index 2110598..0d59ce1 100644
Binary files a/Day01-15/res/python-set.png and b/Day01-15/res/python-set.png differ
diff --git a/Day01-15/res/python-sublime.png b/Day01-15/res/python-sublime.png
index c786d13..ce9590d 100644
Binary files a/Day01-15/res/python-sublime.png and b/Day01-15/res/python-sublime.png differ
diff --git a/Day01-15/res/tcpipprotocols.png b/Day01-15/res/tcpipprotocols.png
index 4acf216..bb62ed2 100644
Binary files a/Day01-15/res/tcpipprotocols.png and b/Day01-15/res/tcpipprotocols.png differ
diff --git a/Day01-15/res/tel-start-number.png b/Day01-15/res/tel-start-number.png
index b1be7a9..827522c 100644
Binary files a/Day01-15/res/tel-start-number.png and b/Day01-15/res/tel-start-number.png differ
diff --git a/Day01-15/res/telnet.png b/Day01-15/res/telnet.png
index 20c8653..1aa4d4d 100644
Binary files a/Day01-15/res/telnet.png and b/Day01-15/res/telnet.png differ
diff --git a/Day01-15/res/uml-components.png b/Day01-15/res/uml-components.png
index 0394cd7..00598b8 100644
Binary files a/Day01-15/res/uml-components.png and b/Day01-15/res/uml-components.png differ
diff --git a/Day01-15/res/uml-example.png b/Day01-15/res/uml-example.png
index 14e8e9c..1638fe7 100644
Binary files a/Day01-15/res/uml-example.png and b/Day01-15/res/uml-example.png differ
diff --git a/Day16-20/16-20.Python语言进阶.md b/Day16-20/16-20.Python语言进阶.md
index 35a1486..62dba5d 100644
--- a/Day16-20/16-20.Python语言进阶.md
+++ b/Day16-20/16-20.Python语言进阶.md
@@ -1,1353 +1,1396 @@
## Python语言进阶
-1. 数据结构和算法
-
- - 算法:解决问题的方法和步骤
-
- - 评价算法的好坏:渐近时间复杂度和渐近空间复杂度。
-
- - 渐近时间复杂度的大O标记:
- - - 常量时间复杂度 - 布隆过滤器 / 哈希存储
- - - 对数时间复杂度 - 折半查找(二分查找)
- - - 线性时间复杂度 - 顺序查找 / 桶排序
- - - 对数线性时间复杂度 - 高级排序算法(归并排序、快速排序)
- - - 平方时间复杂度 - 简单排序算法(选择排序、插入排序、冒泡排序)
- - - 立方时间复杂度 - Floyd算法 / 矩阵乘法运算
- - - 几何级数时间复杂度 - 汉诺塔
- - - 阶乘时间复杂度 - 旅行经销商问题 - NP
-
- ![](./res/algorithm_complexity_1.png)
-
- ![](./res/algorithm_complexity_2.png)
-
- - 排序算法(选择、冒泡和归并)和查找算法(顺序和折半)
-
- ```Python
- def select_sort(origin_items, comp=lambda x, y: x < y):
- """简单选择排序"""
- items = origin_items[:]
- for i in range(len(items) - 1):
- min_index = i
- for j in range(i + 1, len(items)):
- if comp(items[j], items[min_index]):
- min_index = j
- items[i], items[min_index] = items[min_index], items[i]
- return items
- ```
-
- ```Python
- def bubble_sort(origin_items, comp=lambda x, y: x > y):
- """高质量冒泡排序(搅拌排序)"""
- items = origin_items[:]
- for i in range(len(items) - 1):
- swapped = False
- for j in range(i, len(items) - 1 - i):
- if comp(items[j], items[j + 1]):
- items[j], items[j + 1] = items[j + 1], items[j]
- swapped = True
- if swapped:
- swapped = False
- for j in range(len(items) - 2 - i, i, -1):
- if comp(items[j - 1], items[j]):
- items[j], items[j - 1] = items[j - 1], items[j]
- swapped = True
- if not swapped:
- break
- return items
- ```
-
- ```Python
- def merge_sort(items, comp=lambda x, y: x <= y):
- """归并排序(分治法)"""
- if len(items) < 2:
- return items[:]
- mid = len(items) // 2
- left = merge_sort(items[:mid], comp)
- right = merge_sort(items[mid:], comp)
- return merge(left, right, comp)
-
-
- def merge(items1, items2, comp):
- """合并(将两个有序的列表合并成一个有序的列表)"""
- items = []
- index1, index2 = 0, 0
- while index1 < len(items1) and index2 < len(items2):
- if comp(items1[index1], items2[index2]):
- items.append(items1[index1])
- index1 += 1
- else:
- items.append(items2[index2])
- index2 += 1
- items += items1[index1:]
- items += items2[index2:]
- return items
- ```
-
- ```Python
- def seq_search(items, key):
- """顺序查找"""
- for index, item in enumerate(items):
- if item == key:
- return index
- return -1
- ```
-
- ```Python
- def bin_search(items, key):
- """折半查找"""
- start, end = 0, len(items) - 1
- while start <= end:
- mid = (start + end) // 2
- if key > items[mid]:
- start = mid + 1
- elif key < items[mid]:
- end = mid - 1
- else:
- return mid
- return -1
- ```
-
- - 使用生成式(推导式)语法
-
- ```Python
- prices = {
- 'AAPL': 191.88,
- 'GOOG': 1186.96,
- 'IBM': 149.24,
- 'ORCL': 48.44,
- 'ACN': 166.89,
- 'FB': 208.09,
- 'SYMC': 21.29
- }
- # 用股票价格大于100元的股票构造一个新的字典
- prices2 = {key: value for key, value in prices.items() if value > 100}
- print(prices2)
- ```
-
- > 说明:生成式(推导式)可以用来生成列表、集合和字典。
-
- - 嵌套的列表
-
- ```Python
- names = ['关羽', '张飞', '赵云', '马超', '黄忠']
- courses = ['语文', '数学', '英语']
- # 录入五个学生三门课程的成绩
- # 错误 - 参考http://pythontutor.com/visualize.html#mode=edit
- # scores = [[None] * len(courses)] * len(names)
- scores = [[None] * len(courses) for _ in range(len(names))]
- for row, name in enumerate(names):
- for col, course in enumerate(courses):
- scores[row][col] = float(input(f'请输入{name}的{course}成绩: '))
- print(scores)
- ```
-
- [Python Tutor](http://pythontutor.com/) - VISUALIZE CODE AND GET LIVE HELP
-
- - heapq、itertools等的用法
- ```Python
- """
- 从列表中找出最大的或最小的N个元素
- 堆结构(大根堆/小根堆)
- """
- import heapq
-
- list1 = [34, 25, 12, 99, 87, 63, 58, 78, 88, 92]
- list2 = [
- {'name': 'IBM', 'shares': 100, 'price': 91.1},
- {'name': 'AAPL', 'shares': 50, 'price': 543.22},
- {'name': 'FB', 'shares': 200, 'price': 21.09},
- {'name': 'HPQ', 'shares': 35, 'price': 31.75},
- {'name': 'YHOO', 'shares': 45, 'price': 16.35},
- {'name': 'ACME', 'shares': 75, 'price': 115.65}
- ]
- print(heapq.nlargest(3, list1))
- print(heapq.nsmallest(3, list1))
- print(heapq.nlargest(2, list2, key=lambda x: x['price']))
- print(heapq.nlargest(2, list2, key=lambda x: x['shares']))
- ```
-
- ```Python
- """
- 迭代工具 - 排列 / 组合 / 笛卡尔积
- """
- import itertools
-
- itertools.permutations('ABCD')
- itertools.combinations('ABCDE', 3)
- itertools.product('ABCD', '123')
- ```
-
- - collections模块下的工具类
-
- ```Python
- """
- 找出序列中出现次数最多的元素
- """
- from collections import Counter
-
- words = [
- 'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
- 'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around',
- 'the', 'eyes', "don't", 'look', 'around', 'the', 'eyes',
- 'look', 'into', 'my', 'eyes', "you're", 'under'
- ]
- counter = Counter(words)
- print(counter.most_common(3))
- ```
-
- - 常用算法:
-
- - 穷举法 - 又称为暴力破解法,对所有的可能性进行验证,直到找到正确答案。
- - 贪婪法 - 在对问题求解时,总是做出在当前看来
- - 最好的选择,不追求最优解,快速找到满意解。
- - 分治法 - 把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题,直到可以直接求解的程度,最后将子问题的解进行合并得到原问题的解。
- - 回溯法 - 回溯法又称为试探法,按选优条件向前搜索,当搜索到某一步发现原先选择并不优或达不到目标时,就退回一步重新选择。
- - 动态规划 - 基本思想也是将待求解问题分解成若干个子问题,先求解并保存这些子问题的解,避免产生大量的重复运算。
-
- 穷举法例子:百钱百鸡和五人分鱼。
-
- ```Python
- # 公鸡5元一只 母鸡3元一只 小鸡1元三只
- # 用100元买100只鸡 问公鸡/母鸡/小鸡各多少只
- for x in range(20):
- for y in range(33):
- z = 100 - x - y
- if 5 * x + 3 * y + z // 3 == 100 and z % 3 == 0:
- print(x, y, z)
-
- # A、B、C、D、E五人在某天夜里合伙捕鱼 最后疲惫不堪各自睡觉
- # 第二天A第一个醒来 他将鱼分为5份 扔掉多余的1条 拿走自己的一份
- # B第二个醒来 也将鱼分为5份 扔掉多余的1条 拿走自己的一份
- # 然后C、D、E依次醒来也按同样的方式分鱼 问他们至少捕了多少条鱼
- fish = 6
- while True:
- total = fish
- enough = True
- for _ in range(5):
- if (total - 1) % 5 == 0:
- total = (total - 1) // 5 * 4
- else:
- enough = False
- break
- if enough:
- print(fish)
- break
- fish += 5
- ```
-
- 贪婪法例子:假设小偷有一个背包,最多能装20公斤赃物,他闯入一户人家,发现如下表所示的物品。很显然,他不能把所有物品都装进背包,所以必须确定拿走哪些物品,留下哪些物品。
-
- | 名称 | 价格(美元) | 重量(kg) |
- | :----: | :----------: | :--------: |
- | 电脑 | 200 | 20 |
- | 收音机 | 20 | 4 |
- | 钟 | 175 | 10 |
- | 花瓶 | 50 | 2 |
- | 书 | 10 | 1 |
- | 油画 | 90 | 9 |
-
- ```Python
- """
- 贪婪法:在对问题求解时,总是做出在当前看来是最好的选择,不追求最优解,快速找到满意解。
- 输入:
- 20 6
- 电脑 200 20
- 收音机 20 4
- 钟 175 10
- 花瓶 50 2
- 书 10 1
- 油画 90 9
- """
- class Thing(object):
- """物品"""
-
- def __init__(self, name, price, weight):
- self.name = name
- self.price = price
- self.weight = weight
-
- @property
- def value(self):
- """价格重量比"""
- return self.price / self.weight
-
-
- def input_thing():
- """输入物品信息"""
- name_str, price_str, weight_str = input().split()
- return name_str, int(price_str), int(weight_str)
-
-
- def main():
- """主函数"""
- max_weight, num_of_things = map(int, input().split())
- all_things = []
- for _ in range(num_of_things):
- all_things.append(Thing(*input_thing()))
- all_things.sort(key=lambda x: x.value, reverse=True)
- total_weight = 0
- total_price = 0
- for thing in all_things:
- if total_weight + thing.weight <= max_weight:
- print(f'小偷拿走了{thing.name}')
- total_weight += thing.weight
- total_price += thing.price
- print(f'总价值: {total_price}美元')
-
-
- if __name__ == '__main__':
- main()
- ```
-
- 分治法例子:[快速排序](https://zh.wikipedia.org/zh/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F)。
-
- ```Python
- """
- 快速排序 - 选择枢轴对元素进行划分,左边都比枢轴小右边都比枢轴大
- """
- def quick_sort(origin_items, comp=lambda x, y: x <= y):
- items = origin_items[:]
- _quick_sort(items, 0, len(items) - 1, comp)
- return items
-
-
- def _quick_sort(items, start, end, comp):
- if start < end:
- pos = _partition(items, start, end, comp)
- _quick_sort(items, start, pos - 1, comp)
- _quick_sort(items, pos + 1, end, comp)
-
-
- def _partition(items, start, end, comp):
- pivot = items[end]
- i = start - 1
- for j in range(start, end):
- if comp(items[j], pivot):
- i += 1
- items[i], items[j] = items[j], items[i]
- items[i + 1], items[end] = items[end], items[i + 1]
- return i + 1
- ```
-
- 回溯法例子:[骑士巡逻](https://zh.wikipedia.org/zh/%E9%AA%91%E5%A3%AB%E5%B7%A1%E9%80%BB)。
-
- ```Python
- """
- 递归回溯法:叫称为试探法,按选优条件向前搜索,当搜索到某一步,发现原先选择并不优或达不到目标时,就退回一步重新选择,比较经典的问题包括骑士巡逻、八皇后和迷宫寻路等。
- """
- import sys
- import time
-
- SIZE = 5
- total = 0
-
-
- def print_board(board):
- for row in board:
- for col in row:
- print(str(col).center(4), end='')
- print()
-
-
- def patrol(board, row, col, step=1):
- if row >= 0 and row < SIZE and \
- col >= 0 and col < SIZE and \
- board[row][col] == 0:
- board[row][col] = step
- if step == SIZE * SIZE:
- global total
- total += 1
- print(f'第{total}种走法: ')
- print_board(board)
- patrol(board, row - 2, col - 1, step + 1)
- patrol(board, row - 1, col - 2, step + 1)
- patrol(board, row + 1, col - 2, step + 1)
- patrol(board, row + 2, col - 1, step + 1)
- patrol(board, row + 2, col + 1, step + 1)
- patrol(board, row + 1, col + 2, step + 1)
- patrol(board, row - 1, col + 2, step + 1)
- patrol(board, row - 2, col + 1, step + 1)
- board[row][col] = 0
-
-
- def main():
- board = [[0] * SIZE for _ in range(SIZE)]
- patrol(board, SIZE - 1, SIZE - 1)
-
-
- if __name__ == '__main__':
- main()
- ```
-
- 动态规划例子1:[斐波拉切数列]()。(不使用动态规划将会是几何级数复杂度)
-
- ```Python
- """
- 动态规划 - 适用于有重叠子问题和最优子结构性质的问题
- 使用动态规划方法所耗时间往往远少于朴素解法(用空间换取时间)
- """
- def fib(num, temp={}):
- """用递归计算Fibonacci数"""
- if num in (1, 2):
- return 1
- try:
- return temp[num]
- except KeyError:
- temp[num] = fib(num - 1) + fib(num - 2)
- return temp[num]
- ```
-
- 动态规划例子2:子列表元素之和的最大值。(使用动态规划可以避免二重循环)
-
- > 说明:子列表指的是列表中索引(下标)连续的元素构成的列表;列表中的元素是int类型,可能包含正整数、0、负整数;程序输入列表中的元素,输出子列表元素求和的最大值,例如:
- >
- > 输入:1 -2 3 5 -3 2
- >
- > 输出:8
- >
- > 输入:0 -2 3 5 -1 2
- >
- > 输出:9
- >
- > 输入:-9 -2 -3 -5 -3
- >
- > 输出:-2
-
- ```Python
- def main():
- items = list(map(int, input().split()))
- size = len(items)
- overall, partial = {}, {}
- overall[size - 1] = partial[size - 1] = items[size - 1]
- for i in range(size - 2, -1, -1):
- partial[i] = max(items[i], partial[i + 1] + items[i])
- overall[i] = max(partial[i], overall[i + 1])
- print(overall[0])
-
-
- if __name__ == '__main__':
- main()
- ```
-
-2. 函数的使用方式
-
- - 将函数视为“一等公民”
-
- - 函数可以赋值给变量
- - 函数可以作为函数的参数
- - 函数可以作为函数的返回值
-
- - 高阶函数的用法(`filter`、`map`以及它们的替代品)
-
- ```Python
- items1 = list(map(lambda x: x ** 2, filter(lambda x: x % 2, range(1, 10))))
- items2 = [x ** 2 for x in range(1, 10) if x % 2]
- ```
-
- - 位置参数、可变参数、关键字参数、命名关键字参数
-
- - 参数的元信息(代码可读性问题)
-
- - 匿名函数和内联函数的用法(`lambda`函数)
-
- - 闭包和作用域问题
-
- - Python搜索变量的LEGB顺序(Local --> Embedded --> Global --> Built-in)
-
- - `global`和`nonlocal`关键字的作用
-
- `global`:声明或定义全局变量(要么直接使用现有的全局作用域的变量,要么定义一个变量放到全局作用域)。
-
- `nonlocal`:声明使用嵌套作用域的变量(嵌套作用域必须存在该变量,否则报错)。
-
- - 装饰器函数(使用装饰器和取消装饰器)
-
- 例子:输出函数执行时间的装饰器。
-
- ```Python
- def record_time(func):
- """自定义装饰函数的装饰器"""
-
- @wraps(func)
- def wrapper(*args, **kwargs):
- start = time()
- result = func(*args, **kwargs)
- print(f'{func.__name__}: {time() - start}秒')
- return result
-
- return wrapper
- ```
-
- 如果装饰器不希望跟`print`函数耦合,可以编写带参数的装饰器。
-
- ```Python
- from functools import wraps
- from time import time
-
-
- def record(output):
- """自定义带参数的装饰器"""
-
- def decorate(func):
-
- @wraps(func)
- def wrapper(*args, **kwargs):
- start = time()
- result = func(*args, **kwargs)
- output(func.__name__, time() - start)
- return result
-
- return wrapper
-
- return decorate
- ```
-
- ```Python
- from functools import wraps
- from time import time
-
-
- class Record():
- """自定义装饰器类(通过__call__魔术方法使得对象可以当成函数调用)"""
-
- def __init__(self, output):
- self.output = output
-
- def __call__(self, func):
-
- @wraps(func)
- def wrapper(*args, **kwargs):
- start = time()
- result = func(*args, **kwargs)
- self.output(func.__name__, time() - start)
- return result
-
- return wrapper
- ```
-
- > 说明:由于对带装饰功能的函数添加了@wraps装饰器,可以通过`func.__wrapped__`方式获得被装饰之前的函数或类来取消装饰器的作用。
-
- 例子:用装饰器来实现单例模式。
-
- ```Python
- from functools import wraps
-
-
- def singleton(cls):
- """装饰类的装饰器"""
- instances = {}
-
- @wraps(cls)
- def wrapper(*args, **kwargs):
- if cls not in instances:
- instances[cls] = cls(*args, **kwargs)
- return instances[cls]
-
- return wrapper
-
-
- @singleton
- class President():
- """总统(单例类)"""
- pass
- ```
-
- > 说明:上面的代码中用到了闭包(closure),不知道你是否已经意识到了。还没有一个小问题就是,上面的代码并没有实现线程安全的单例,如果要实现线程安全的单例应该怎么做呢?
-
- ```Python
- from functools import wraps
- from threading import Lock
-
-
- def singleton(cls):
- """线程安全的单例装饰器"""
- instances = {}
- locker = Lock()
-
- @wraps(cls)
- def wrapper(*args, **kwargs):
- if cls not in instances:
- with locker:
- if cls not in instances:
- instances[cls] = cls(*args, **kwargs)
- return instances[cls]
-
- return wrapper
- ```
-
-3. 面向对象相关知识
-
- - 三大支柱:封装、继承、多态
-
- 例子:工资结算系统。
-
- ```Python
- """
- 月薪结算系统 - 部门经理每月15000 程序员每小时200 销售员1800底薪加销售额5%提成
- """
- from abc import ABCMeta, abstractmethod
-
-
- class Employee(metaclass=ABCMeta):
- """员工(抽象类)"""
-
- def __init__(self, name):
- self.name = name
-
- @abstractmethod
- def get_salary(self):
- """结算月薪(抽象方法)"""
- pass
-
-
- class Manager(Employee):
- """部门经理"""
-
- def get_salary(self):
- return 15000.0
-
-
- class Programmer(Employee):
- """程序员"""
-
- def __init__(self, name, working_hour=0):
- self.working_hour = working_hour
- super().__init__(name)
-
- def get_salary(self):
- return 200.0 * self.working_hour
-
-
- class Salesman(Employee):
- """销售员"""
-
- def __init__(self, name, sales=0.0):
- self.sales = sales
- super().__init__(name)
-
- def get_salary(self):
- return 1800.0 + self.sales * 0.05
-
-
- class EmployeeFactory():
- """创建员工的工厂(工厂模式 - 通过工厂实现对象使用者和对象之间的解耦合)"""
-
- @staticmethod
- def create(emp_type, *args, **kwargs):
- """创建员工"""
- emp_type = emp_type.upper()
- emp = None
- if emp_type == 'M':
- emp = Manager(*args, **kwargs)
- elif emp_type == 'P':
- emp = Programmer(*args, **kwargs)
- elif emp_type == 'S':
- emp = Salesman(*args, **kwargs)
- return emp
-
-
- def main():
- """主函数"""
- emps = [
- EmployeeFactory.create('M', '曹操'),
- EmployeeFactory.create('P', '荀彧', 120),
- EmployeeFactory.create('P', '郭嘉', 85),
- EmployeeFactory.create('S', '典韦', 123000),
- ]
- for emp in emps:
- print('%s: %.2f元' % (emp.name, emp.get_salary()))
-
-
- if __name__ == '__main__':
- main()
- ```
-
- - 类与类之间的关系
-
- - is-a关系:继承
- - has-a关系:关联 / 聚合 / 合成
- - use-a关系:依赖
-
- 例子:扑克游戏。
-
- ```Python
- """
- 经验:符号常量总是优于字面常量,枚举类型是定义符号常量的最佳选择
- """
- from enum import Enum, unique
-
- import random
-
-
- @unique
- class Suite(Enum):
- """花色"""
-
- SPADE, HEART, CLUB, DIAMOND = range(4)
-
- def __lt__(self, other):
- return self.value < other.value
-
-
- class Card():
- """牌"""
-
- def __init__(self, suite, face):
- """初始化方法"""
- self.suite = suite
- self.face = face
-
- def show(self):
- """显示牌面"""
- suites = ['♠️', '♥️', '♣️', '♦️']
- faces = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
- return f'{suites[self.suite.value]} {faces[self.face]}'
-
- def __str__(self):
- return self.show()
-
- def __repr__(self):
- return self.show()
-
-
- class Poker():
- """扑克"""
-
- def __init__(self):
- self.index = 0
- self.cards = [Card(suite, face)
- for suite in Suite
- for face in range(1, 14)]
-
- def shuffle(self):
- """洗牌(随机乱序)"""
- random.shuffle(self.cards)
- self.index = 0
-
- def deal(self):
- """发牌"""
- card = self.cards[self.index]
- self.index += 1
- return card
-
- @property
- def has_more(self):
- return self.index < len(self.cards)
-
-
- class Player():
- """玩家"""
-
- def __init__(self, name):
- self.name = name
- self.cards = []
-
- def get_one(self, card):
- """摸一张牌"""
- self.cards.append(card)
-
- def sort(self, comp=lambda card: (card.suite, card.face)):
- """整理手上的牌"""
- self.cards.sort(key=comp)
-
-
- def main():
- """主函数"""
- poker = Poker()
- poker.shuffle()
- players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
- while poker.has_more:
- for player in players:
- player.get_one(poker.deal())
- for player in players:
- player.sort()
- print(player.name, end=': ')
- print(player.cards)
-
-
- if __name__ == '__main__':
- main()
- ```
-
- > 说明:上面的代码中使用了Emoji字符来表示扑克牌的四种花色,在某些不支持Emoji字符的系统上可能无法显示。
-
- - 对象的复制(深复制/深拷贝/深度克隆和浅复制/浅拷贝/影子克隆)
-
- - 垃圾回收、循环引用和弱引用
-
- Python使用了自动化内存管理,这种管理机制以**引用计数**为基础,同时也引入了**标记-清除**和**分代收集**两种机制为辅的策略。
-
- ```C
- typedef struct_object {
- /* 引用计数 */
- int ob_refcnt;
- /* 对象指针 */
- struct_typeobject *ob_type;
- } PyObject;
- ```
-
- ```C
- /* 增加引用计数的宏定义 */
- #define Py_INCREF(op) ((op)->ob_refcnt++)
- /* 减少引用计数的宏定义 */
- #define Py_DECREF(op) \ //减少计数
- if (--(op)->ob_refcnt != 0) \
- ; \
- else \
- __Py_Dealloc((PyObject *)(op))
- ```
-
- 导致引用计数+1的情况:
-
- - 对象被创建,例如`a = 23`
- - 对象被引用,例如`b = a`
- - 对象被作为参数,传入到一个函数中,例如`f(a)`
- - 对象作为一个元素,存储在容器中,例如`list1 = [a, a]`
-
- 导致引用计数-1的情况:
-
- - 对象的别名被显式销毁,例如`del a`
- - 对象的别名被赋予新的对象,例如`a = 24`
- - 一个对象离开它的作用域,例如f函数执行完毕时,f函数中的局部变量(全局变量不会)
- - 对象所在的容器被销毁,或从容器中删除对象
-
- 引用计数可能会导致循环引用问题,而循环引用会导致内存泄露,如下面的代码所示。为了解决这个问题,Python中引入了“标记-清除”和“分代收集”。在创建一个对象的时候,对象被放在第一代中,如果在第一代的垃圾检查中对象存活了下来,该对象就会被放到第二代中,同理在第二代的垃圾检查中对象存活下来,该对象就会被放到第三代中。
-
- ```Python
- # 循环引用会导致内存泄露 - Python除了引用技术还引入了标记清理和分代回收
- # 在Python 3.6以前如果重写__del__魔术方法会导致循环引用处理失效
- # 如果不想造成循环引用可以使用弱引用
- list1 = []
- list2 = []
- list1.append(list2)
- list2.append(list1)
- ```
-
- 以下情况会导致垃圾回收:
-
- - 调用`gc.collect()`
- - gc模块的计数器达到阀值
- - 程序退出
-
- 如果循环引用中两个对象都定义了`__del__`方法,gc模块不会销毁这些不可达对象,因为gc模块不知道应该先调用哪个对象的`__del__`方法,这个问题在Python 3.6中得到了解决。
-
- 也可以通过`weakref`模块构造弱引用的方式来解决循环引用的问题。
-
- - 魔法属性和方法(请参考《Python魔法方法指南》)
-
- 有几个小问题请大家思考:
-
- - 自定义的对象能不能使用运算符做运算?
- - 自定义的对象能不能放到set中?能去重吗?
- - 自定义的对象能不能作为dict的键?
- - 自定义的对象能不能使用上下文语法?
-
- - 混入(Mixin)
-
- 例子:自定义字典限制只有在指定的key不存在时才能在字典中设置键值对。
-
- ```Python
- class SetOnceMappingMixin:
- """自定义混入类"""
- __slots__ = ()
-
- def __setitem__(self, key, value):
- if key in self:
- raise KeyError(str(key) + ' already set')
- return super().__setitem__(key, value)
-
-
- class SetOnceDict(SetOnceMappingMixin, dict):
- """自定义字典"""
- pass
-
-
- my_dict= SetOnceDict()
- try:
- my_dict['username'] = 'jackfrued'
- my_dict['username'] = 'hellokitty'
- except KeyError:
- pass
- print(my_dict)
- ```
-
- - 元编程和元类
-
- 例子:用元类实现单例模式。
-
- ```Python
- import threading
-
-
- class SingletonMeta(type):
- """自定义元类"""
-
- def __init__(cls, *args, **kwargs):
- cls.__instance = None
- cls.__lock = threading.Lock()
- super().__init__(*args, **kwargs)
-
- def __call__(cls, *args, **kwargs):
- if cls.__instance is None:
- with cls.__lock:
- if cls.__instance is None:
- cls.__instance = super().__call__(*args, **kwargs)
- return cls.__instance
-
-
- class President(metaclass=SingletonMeta):
- """总统(单例类)"""
-
- pass
- ```
-
- - 面向对象设计原则
-
- - 单一职责原则 (**S**RP)- 一个类只做该做的事情(类的设计要高内聚)
- - 开闭原则 (**O**CP)- 软件实体应该对扩展开发对修改关闭
- - 依赖倒转原则(DIP)- 面向抽象编程(在弱类型语言中已经被弱化)
- - 里氏替换原则(**L**SP) - 任何时候可以用子类对象替换掉父类对象
- - 接口隔离原则(**I**SP)- 接口要小而专不要大而全(Python中没有接口的概念)
- - 合成聚合复用原则(CARP) - 优先使用强关联关系而不是继承关系复用代码
- - 最少知识原则(迪米特法则,Lo**D**)- 不要给没有必然联系的对象发消息
-
- > 说明:上面加粗的字母放在一起称为面向对象的**SOLID**原则。
-
- - GoF设计模式
-
- - 创建型模式:单例、工厂、建造者、原型
- - 结构型模式:适配器、门面(外观)、代理
- - 行为型模式:迭代器、观察者、状态、策略
-
- 例子:可插拔的哈希算法。
-
- ```Python
- class StreamHasher():
- """哈希摘要生成器(策略模式)"""
-
- def __init__(self, alg='md5', size=4096):
- self.size = size
- alg = alg.lower()
- self.hasher = getattr(__import__('hashlib'), alg.lower())()
-
- def __call__(self, stream):
- return self.to_digest(stream)
-
- def to_digest(self, stream):
- """生成十六进制形式的摘要"""
- for buf in iter(lambda: stream.read(self.size), b''):
- self.hasher.update(buf)
- return self.hasher.hexdigest()
-
- def main():
- """主函数"""
- hasher1 = StreamHasher()
- with open('Python-3.7.1.tgz', 'rb') as stream:
- print(hasher1.to_digest(stream))
- hasher2 = StreamHasher('sha1')
- with open('Python-3.7.1.tgz', 'rb') as stream:
- print(hasher2(stream))
-
-
- if __name__ == '__main__':
- main()
- ```
-
-4. 迭代器和生成器
-
- - 和迭代器相关的魔术方法(`__iter__`和`__next__`)
-
- - 两种创建生成器的方式(生成器表达式和`yield`关键字)
-
- ```Python
- def fib(num):
- """生成器"""
- a, b = 0, 1
- for _ in range(num):
- a, b = b, a + b
- yield a
-
-
- class Fib(object):
- """迭代器"""
-
- def __init__(self, num):
- self.num = num
- self.a, self.b = 0, 1
- self.idx = 0
-
- def __iter__(self):
- return self
-
- def __next__(self):
- if self.idx < self.num:
- self.a, self.b = self.b, self.a + self.b
- self.idx += 1
- return self.a
- raise StopIteration()
- ```
-
-5. 并发编程
-
- Python中实现并发编程的三种方案:多线程、多进程和异步I/O。并发编程的好处在于可以提升程序的执行效率以及改善用户体验;坏处在于并发的程序不容易开发和调试,同时对其他程序来说它并不友好。
-
- - 多线程:Python中提供了Thread类并辅以Lock、Condition、Event、Semaphore和Barrier。Python中有GIL来防止多个线程同时执行本地字节码,这个锁对于CPython是必须的,因为CPython的内存管理并不是线程安全的,因为GIL的存在多线程并不能发挥CPU的多核特性。
-
- ```Python
- """
- 面试题:进程和线程的区别和联系?
- 进程 - 操作系统分配内存的基本单位 - 一个进程可以包含一个或多个线程
- 线程 - 操作系统分配CPU的基本单位
- 并发编程(concurrent programming)
- 1. 提升执行性能 - 让程序中没有因果关系的部分可以并发的执行
- 2. 改善用户体验 - 让耗时间的操作不会造成程序的假死
- """
- import glob
- import os
- import threading
-
- from PIL import Image
-
- PREFIX = 'thumbnails'
-
-
- def generate_thumbnail(infile, size, format='PNG'):
- """生成指定图片文件的缩略图"""
- file, ext = os.path.splitext(infile)
- file = file[file.rfind('/') + 1:]
- outfile = f'{PREFIX}/{file}_{size[0]}_{size[1]}.{ext}'
- img = Image.open(infile)
- img.thumbnail(size, Image.ANTIALIAS)
- img.save(outfile, format)
-
-
- def main():
- """主函数"""
- if not os.path.exists(PREFIX):
- os.mkdir(PREFIX)
- for infile in glob.glob('images/*.png'):
- for size in (32, 64, 128):
- # 创建并启动线程
- threading.Thread(
- target=generate_thumbnail,
- args=(infile, (size, size))
- ).start()
-
-
- if __name__ == '__main__':
- main()
- ```
-
- 多个线程竞争资源的情况
-
- ```Python
- """
- 多线程程序如果没有竞争资源处理起来通常也比较简单
- 当多个线程竞争临界资源的时候如果缺乏必要的保护措施就会导致数据错乱
- 说明:临界资源就是被多个线程竞争的资源
- """
- import time
- import threading
-
- from concurrent.futures import ThreadPoolExecutor
-
-
- class Account(object):
- """银行账户"""
-
- def __init__(self):
- self.balance = 0.0
- self.lock = threading.Lock()
-
- def deposit(self, money):
- # 通过锁保护临界资源
- with self.lock:
- new_balance = self.balance + money
- time.sleep(0.001)
- self.balance = new_balance
-
-
- class AddMoneyThread(threading.Thread):
- """自定义线程类"""
-
- def __init__(self, account, money):
- self.account = account
- self.money = money
- # 自定义线程的初始化方法中必须调用父类的初始化方法
- super().__init__()
-
- def run(self):
- # 线程启动之后要执行的操作
- self.account.deposit(self.money)
-
- def main():
- """主函数"""
- account = Account()
- # 创建线程池
- pool = ThreadPoolExecutor(max_workers=10)
- futures = []
- for _ in range(100):
- # 创建线程的第1种方式
- # threading.Thread(
- # target=account.deposit, args=(1, )
- # ).start()
- # 创建线程的第2种方式
- # AddMoneyThread(account, 1).start()
- # 创建线程的第3种方式
- # 调用线程池中的线程来执行特定的任务
- future = pool.submit(account.deposit, 1)
- futures.append(future)
- # 关闭线程池
- pool.shutdown()
- for future in futures:
- future.result()
- print(account.balance)
-
-
- if __name__ == '__main__':
- main()
- ```
-
- 修改上面的程序,启动5个线程向账户中存钱,5个线程从账户中取钱,取钱时如果余额不足就暂停线程进行等待。为了达到上述目标,需要对存钱和取钱的线程进行调度,在余额不足时取钱的线程暂停并释放锁,而存钱的线程将钱存入后要通知取钱的线程,使其从暂停状态被唤醒。可以使用`threading`模块的Condition来实现线程调度,该对象也是基于锁来创建的,代码如下所示:
-
- ```Python
- """
- 多个线程竞争一个资源 - 保护临界资源 - 锁(Lock/RLock)
- 多个线程竞争多个资源(线程数>资源数) - 信号量(Semaphore)
- 多个线程的调度 - 暂停线程执行/唤醒等待中的线程 - Condition
- """
- from concurrent.futures import ThreadPoolExecutor
- from random import randint
- from time import sleep
-
- import threading
-
-
- class Account():
- """银行账户"""
-
- def __init__(self, balance=0):
- self.balance = balance
- lock = threading.Lock()
- self.condition = threading.Condition(lock)
-
- def withdraw(self, money):
- """取钱"""
- with self.condition:
- while money > self.balance:
- self.condition.wait()
- new_balance = self.balance - money
- sleep(0.001)
- self.balance = new_balance
-
- def deposit(self, money):
- """存钱"""
- with self.condition:
- new_balance = self.balance + money
- sleep(0.001)
- self.balance = new_balance
- self.condition.notify_all()
-
-
- def add_money(account):
- while True:
- money = randint(5, 10)
- account.deposit(money)
- print(threading.current_thread().name,
- ':', money, '====>', account.balance)
- sleep(0.5)
-
-
- def sub_money(account):
- while True:
- money = randint(10, 30)
- account.withdraw(money)
- print(threading.current_thread().name,
- ':', money, '<====', account.balance)
- sleep(1)
-
-
- def main():
- account = Account()
- with ThreadPoolExecutor(max_workers=10) as pool:
- for _ in range(5):
- pool.submit(add_money, account)
- pool.submit(sub_money, account)
-
-
- if __name__ == '__main__':
- main()
- ```
-
- - 多进程:多进程可以有效的解决GIL的问题,实现多进程主要的类是Process,其他辅助的类跟threading模块中的类似,进程间共享数据可以使用管道、套接字等,在multiprocessing模块中有一个Queue类,它基于管道和锁机制提供了多个进程共享的队列。下面是官方文档上关于多进程和进程池的一个示例。
-
- ```Python
- """
- 多进程和进程池的使用
- 多线程因为GIL的存在不能够发挥CPU的多核特性
- 对于计算密集型任务应该考虑使用多进程
- time python3 example22.py
- real 0m11.512s
- user 0m39.319s
- sys 0m0.169s
- 使用多进程后实际执行时间为11.512秒,而用户时间39.319秒约为实际执行时间的4倍
- 这就证明我们的程序通过多进程使用了CPU的多核特性,而且这台计算机配置了4核的CPU
- """
- import concurrent.futures
- import math
-
- PRIMES = [
- 1116281,
- 1297337,
- 104395303,
- 472882027,
- 533000389,
- 817504243,
- 982451653,
- 112272535095293,
- 112582705942171,
- 112272535095293,
- 115280095190773,
- 115797848077099,
- 1099726899285419
- ] * 5
-
-
- def is_prime(n):
- """判断素数"""
- if n % 2 == 0:
- return False
-
- sqrt_n = int(math.floor(math.sqrt(n)))
- for i in range(3, sqrt_n + 1, 2):
- if n % i == 0:
- return False
- return True
-
-
- def main():
- """主函数"""
- with concurrent.futures.ProcessPoolExecutor() as executor:
- for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
- print('%d is prime: %s' % (number, prime))
-
-
- if __name__ == '__main__':
- main()
- ```
-
- > 说明:**多线程和多进程的比较**。
- >
- > 以下情况需要使用多线程:
- >
- > 1. 程序需要维护许多共享的状态(尤其是可变状态),Python中的列表、字典、集合都是线程安全的,所以使用线程而不是进程维护共享状态的代价相对较小。
- > 2. 程序会花费大量时间在I/O操作上,没有太多并行计算的需求且不需占用太多的内存。
- >
- > 以下情况需要使用多进程:
- >
- > 1. 程序执行计算密集型任务(如:字节码操作、数据处理、科学计算)。
- > 2. 程序的输入可以并行的分成块,并且可以将运算结果合并。
- > 3. 程序在内存使用方面没有任何限制且不强依赖于I/O操作(如:读写文件、套接字等)。
-
- - 异步处理:从调度程序的任务队列中挑选任务,该调度程序以交叉的形式执行这些任务,我们并不能保证任务将以某种顺序去执行,因为执行顺序取决于队列中的一项任务是否愿意将CPU处理时间让位给另一项任务。异步任务通常通过多任务协作处理的方式来实现,由于执行时间和顺序的不确定,因此需要通过回调式编程或者`future`对象来获取任务执行的结果。Python 3通过`asyncio`模块和`await`和`async`关键字(在Python 3.7中正式被列为关键字)来支持异步处理。
-
- ```Python
- """
- 异步I/O - async / await
- """
- import asyncio
-
-
- def num_generator(m, n):
- """指定范围的数字生成器"""
- yield from range(m, n + 1)
-
-
- async def prime_filter(m, n):
- """素数过滤器"""
- primes = []
- for i in num_generator(m, n):
- flag = True
- for j in range(2, int(i ** 0.5 + 1)):
- if i % j == 0:
- flag = False
- break
- if flag:
- print('Prime =>', i)
- primes.append(i)
-
- await asyncio.sleep(0.001)
- return tuple(primes)
-
-
- async def square_mapper(m, n):
- """平方映射器"""
- squares = []
- for i in num_generator(m, n):
- print('Square =>', i * i)
- squares.append(i * i)
-
- await asyncio.sleep(0.001)
- return squares
-
-
- def main():
- """主函数"""
- loop = asyncio.get_event_loop()
- future = asyncio.gather(prime_filter(2, 100), square_mapper(1, 100))
- future.add_done_callback(lambda x: print(x.result()))
- loop.run_until_complete(future)
- loop.close()
-
-
- if __name__ == '__main__':
- main()
- ```
-
- > 说明:上面的代码使用`get_event_loop`函数获得系统默认的事件循环,通过`gather`函数可以获得一个`future`对象,`future`对象的`add_done_callback`可以添加执行完成时的回调函数,`loop`对象的`run_until_complete`方法可以等待通过`future`对象获得协程执行结果。
-
- Python中有一个名为`aiohttp`的三方库,它提供了异步的HTTP客户端和服务器,这个三方库可以跟`asyncio`模块一起工作,并提供了对`Future`对象的支持。Python 3.6中引入了async和await来定义异步执行的函数以及创建异步上下文,在Python 3.7中它们正式成为了关键字。下面的代码异步的从5个URL中获取页面并通过正则表达式的命名捕获组提取了网站的标题。
-
- ```Python
- import asyncio
- import re
-
- import aiohttp
-
- PATTERN = re.compile(r'\(?P.*)\<\/title\>')
-
-
- async def fetch_page(session, url):
- async with session.get(url, ssl=False) as resp:
- return await resp.text()
-
-
- async def show_title(url):
- async with aiohttp.ClientSession() as session:
- html = await fetch_page(session, url)
- print(PATTERN.search(html).group('title'))
-
-
- def main():
- urls = ('https://www.python.org/',
- 'https://git-scm.com/',
- 'https://www.jd.com/',
- 'https://www.taobao.com/',
- 'https://www.douban.com/')
- loop = asyncio.get_event_loop()
- tasks = [show_title(url) for url in urls]
- loop.run_until_complete(asyncio.wait(tasks))
- loop.close()
-
-
- if __name__ == '__main__':
- main()
- ```
-
- > 说明:**异步I/O与多进程的比较**。
- >
- > 当程序不需要真正的并发性或并行性,而是更多的依赖于异步处理和回调时,asyncio就是一种很好的选择。如果程序中有大量的等待与休眠时,也应该考虑asyncio,它很适合编写没有实时数据处理需求的Web应用服务器。
-
- Python还有很多用于处理并行任务的三方库,例如:joblib、PyMP等。实际开发中,要提升系统的可扩展性和并发性通常有垂直扩展(增加单个节点的处理能力)和水平扩展(将单个节点变成多个节点)两种做法。可以通过消息队列来实现应用程序的解耦合,消息队列相当于是多线程同步队列的扩展版本,不同机器上的应用程序相当于就是线程,而共享的分布式消息队列就是原来程序中的Queue。消息队列(面向消息的中间件)的最流行和最标准化的实现是AMQP(高级消息队列协议),AMQP源于金融行业,提供了排队、路由、可靠传输、安全等功能,最著名的实现包括:Apache的ActiveMQ、RabbitMQ等。
-
- 要实现任务的异步化,可以使用名为Celery的三方库。Celery是Python编写的分布式任务队列,它使用分布式消息进行工作,可以基于RabbitMQ或Redis来作为后端的消息代理。
+### 重要知识点
+
+- 生成式(推导式)的用法
+
+ ```Python
+ prices = {
+ 'AAPL': 191.88,
+ 'GOOG': 1186.96,
+ 'IBM': 149.24,
+ 'ORCL': 48.44,
+ 'ACN': 166.89,
+ 'FB': 208.09,
+ 'SYMC': 21.29
+ }
+ # 用股票价格大于100元的股票构造一个新的字典
+ prices2 = {key: value for key, value in prices.items() if value > 100}
+ print(prices2)
+ ```
+
+ > 说明:生成式(推导式)可以用来生成列表、集合和字典。
+
+- 嵌套的列表的坑
+
+ ```Python
+ names = ['关羽', '张飞', '赵云', '马超', '黄忠']
+ courses = ['语文', '数学', '英语']
+ # 录入五个学生三门课程的成绩
+ # 错误 - 参考http://pythontutor.com/visualize.html#mode=edit
+ # scores = [[None] * len(courses)] * len(names)
+ scores = [[None] * len(courses) for _ in range(len(names))]
+ for row, name in enumerate(names):
+ for col, course in enumerate(courses):
+ scores[row][col] = float(input(f'请输入{name}的{course}成绩: '))
+ print(scores)
+ ```
+
+ [Python Tutor](http://pythontutor.com/) - VISUALIZE CODE AND GET LIVE HELP
+
+- `heapq`模块(堆排序)
+
+ ```Python
+ """
+ 从列表中找出最大的或最小的N个元素
+ 堆结构(大根堆/小根堆)
+ """
+ import heapq
+
+ list1 = [34, 25, 12, 99, 87, 63, 58, 78, 88, 92]
+ list2 = [
+ {'name': 'IBM', 'shares': 100, 'price': 91.1},
+ {'name': 'AAPL', 'shares': 50, 'price': 543.22},
+ {'name': 'FB', 'shares': 200, 'price': 21.09},
+ {'name': 'HPQ', 'shares': 35, 'price': 31.75},
+ {'name': 'YHOO', 'shares': 45, 'price': 16.35},
+ {'name': 'ACME', 'shares': 75, 'price': 115.65}
+ ]
+ print(heapq.nlargest(3, list1))
+ print(heapq.nsmallest(3, list1))
+ print(heapq.nlargest(2, list2, key=lambda x: x['price']))
+ print(heapq.nlargest(2, list2, key=lambda x: x['shares']))
+ ```
+
+- `itertools`模块
+
+ ```Python
+ """
+ 迭代工具模块
+ """
+ import itertools
+
+ # 产生ABCD的全排列
+ itertools.permutations('ABCD')
+ # 产生ABCDE的五选三组合
+ itertools.combinations('ABCDE', 3)
+ # 产生ABCD和123的笛卡尔积
+ itertools.product('ABCD', '123')
+ # 产生ABC的无限循环序列
+ itertools.cycle(('A', 'B', 'C'))
+ ```
+
+- `collections`模块
+
+ 常用的工具类:
+
+ - `namedtuple`:命令元组,它是一个类工厂,接受类型的名称和属性列表来创建一个类。
+ - `deque`:双端队列,是列表的替代实现。Python中的列表底层是基于数组来实现的,而deque底层是双向链表,因此当你需要在头尾添加和删除元素是,deque会表现出更好的性能,渐近时间复杂度为$O(1)$。
+ - `Counter`:`dict`的子类,键是元素,值是元素的计数,它的`most_common()`方法可以帮助我们获取出现频率最高的元素。`Counter`和`dict`的继承关系我认为是值得商榷的,按照CARP原则,`Counter`跟`dict`的关系应该设计为关联关系更为合理。
+ - `OrderedDict`:`dict`的子类,它记录了键值对插入的顺序,看起来既有字典的行为,也有链表的行为。
+ - `defaultdict`:类似于字典类型,但是可以通过默认的工厂函数来获得键对应的默认值,相比字典中的`setdefault()`方法,这种做法更加高效。
+
+ ```Python
+ """
+ 找出序列中出现次数最多的元素
+ """
+ from collections import Counter
+
+ words = [
+ 'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
+ 'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around',
+ 'the', 'eyes', "don't", 'look', 'around', 'the', 'eyes',
+ 'look', 'into', 'my', 'eyes', "you're", 'under'
+ ]
+ counter = Counter(words)
+ print(counter.most_common(3))
+ ```
+
+### 数据结构和算法
+
+- 算法:解决问题的方法和步骤
+
+- 评价算法的好坏:渐近时间复杂度和渐近空间复杂度。
+
+- 渐近时间复杂度的大O标记:
+ - - 常量时间复杂度 - 布隆过滤器 / 哈希存储
+ - - 对数时间复杂度 - 折半查找(二分查找)
+ - - 线性时间复杂度 - 顺序查找 / 计数排序
+ - - 对数线性时间复杂度 - 高级排序算法(归并排序、快速排序)
+ - - 平方时间复杂度 - 简单排序算法(选择排序、插入排序、冒泡排序)
+ - - 立方时间复杂度 - Floyd算法 / 矩阵乘法运算
+ - - 几何级数时间复杂度 - 汉诺塔
+ - - 阶乘时间复杂度 - 旅行经销商问题 - NPC
+
+ ![](./res/algorithm_complexity_1.png)
+
+ ![](./res/algorithm_complexity_2.png)
+
+- 排序算法(选择、冒泡和归并)和查找算法(顺序和折半)
+
+ ```Python
+ def select_sort(items, comp=lambda x, y: x < y):
+ """简单选择排序"""
+ items = items[:]
+ for i in range(len(items) - 1):
+ min_index = i
+ for j in range(i + 1, len(items)):
+ if comp(items[j], items[min_index]):
+ min_index = j
+ items[i], items[min_index] = items[min_index], items[i]
+ return items
+ ```
+
+ ```Python
+ def bubble_sort(items, comp=lambda x, y: x > y):
+ """冒泡排序"""
+ items = items[:]
+ for i in range(len(items) - 1):
+ swapped = False
+ for j in range(i, len(items) - 1 - i):
+ if comp(items[j], items[j + 1]):
+ items[j], items[j + 1] = items[j + 1], items[j]
+ swapped = True
+ if not swapped:
+ break
+ return items
+ ```
+
+ ```Python
+ def bubble_sort(items, comp=lambda x, y: x > y):
+ """搅拌排序(冒泡排序升级版)"""
+ items = items[:]
+ for i in range(len(items) - 1):
+ swapped = False
+ for j in range(i, len(items) - 1 - i):
+ if comp(items[j], items[j + 1]):
+ items[j], items[j + 1] = items[j + 1], items[j]
+ swapped = True
+ if swapped:
+ swapped = False
+ for j in range(len(items) - 2 - i, i, -1):
+ if comp(items[j - 1], items[j]):
+ items[j], items[j - 1] = items[j - 1], items[j]
+ swapped = True
+ if not swapped:
+ break
+ return items
+ ```
+
+ ```Python
+ def merge(items1, items2, comp=lambda x, y: x < y):
+ """合并(将两个有序的列表合并成一个有序的列表)"""
+ items = []
+ index1, index2 = 0, 0
+ while index1 < len(items1) and index2 < len(items2):
+ if comp(items1[index1], items2[index2]):
+ items.append(items1[index1])
+ index1 += 1
+ else:
+ items.append(items2[index2])
+ index2 += 1
+ items += items1[index1:]
+ items += items2[index2:]
+ return items
+
+
+ def merge_sort(items, comp=lambda x, y: x < y):
+ return _merge_sort(list(items), comp)
+
+
+ def _merge_sort(items, comp):
+ """归并排序"""
+ if len(items) < 2:
+ return items
+ mid = len(items) // 2
+ left = _merge_sort(items[:mid], comp)
+ right = _merge_sort(items[mid:], comp)
+ return merge(left, right, comp)
+ ```
+
+ ```Python
+ def seq_search(items, key):
+ """顺序查找"""
+ for index, item in enumerate(items):
+ if item == key:
+ return index
+ return -1
+ ```
+
+ ```Python
+ def bin_search(items, key):
+ """折半查找"""
+ start, end = 0, len(items) - 1
+ while start <= end:
+ mid = (start + end) // 2
+ if key > items[mid]:
+ start = mid + 1
+ elif key < items[mid]:
+ end = mid - 1
+ else:
+ return mid
+ return -1
+ ```
+
+- 常用算法:
+
+ - 穷举法 - 又称为暴力破解法,对所有的可能性进行验证,直到找到正确答案。
+ - 贪婪法 - 在对问题求解时,总是做出在当前看来
+ - 最好的选择,不追求最优解,快速找到满意解。
+ - 分治法 - 把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题,直到可以直接求解的程度,最后将子问题的解进行合并得到原问题的解。
+ - 回溯法 - 回溯法又称为试探法,按选优条件向前搜索,当搜索到某一步发现原先选择并不优或达不到目标时,就退回一步重新选择。
+ - 动态规划 - 基本思想也是将待求解问题分解成若干个子问题,先求解并保存这些子问题的解,避免产生大量的重复运算。
+
+ 穷举法例子:百钱百鸡和五人分鱼。
+
+ ```Python
+ # 公鸡5元一只 母鸡3元一只 小鸡1元三只
+ # 用100元买100只鸡 问公鸡/母鸡/小鸡各多少只
+ for x in range(20):
+ for y in range(33):
+ z = 100 - x - y
+ if 5 * x + 3 * y + z // 3 == 100 and z % 3 == 0:
+ print(x, y, z)
+
+ # A、B、C、D、E五人在某天夜里合伙捕鱼 最后疲惫不堪各自睡觉
+ # 第二天A第一个醒来 他将鱼分为5份 扔掉多余的1条 拿走自己的一份
+ # B第二个醒来 也将鱼分为5份 扔掉多余的1条 拿走自己的一份
+ # 然后C、D、E依次醒来也按同样的方式分鱼 问他们至少捕了多少条鱼
+ fish = 6
+ while True:
+ total = fish
+ enough = True
+ for _ in range(5):
+ if (total - 1) % 5 == 0:
+ total = (total - 1) // 5 * 4
+ else:
+ enough = False
+ break
+ if enough:
+ print(fish)
+ break
+ fish += 5
+ ```
+
+ 贪婪法例子:假设小偷有一个背包,最多能装20公斤赃物,他闯入一户人家,发现如下表所示的物品。很显然,他不能把所有物品都装进背包,所以必须确定拿走哪些物品,留下哪些物品。
+
+ | 名称 | 价格(美元) | 重量(kg) |
+ | :----: | :----------: | :--------: |
+ | 电脑 | 200 | 20 |
+ | 收音机 | 20 | 4 |
+ | 钟 | 175 | 10 |
+ | 花瓶 | 50 | 2 |
+ | 书 | 10 | 1 |
+ | 油画 | 90 | 9 |
+
+ ```Python
+ """
+ 贪婪法:在对问题求解时,总是做出在当前看来是最好的选择,不追求最优解,快速找到满意解。
+ 输入:
+ 20 6
+ 电脑 200 20
+ 收音机 20 4
+ 钟 175 10
+ 花瓶 50 2
+ 书 10 1
+ 油画 90 9
+ """
+ class Thing(object):
+ """物品"""
+
+ def __init__(self, name, price, weight):
+ self.name = name
+ self.price = price
+ self.weight = weight
+
+ @property
+ def value(self):
+ """价格重量比"""
+ return self.price / self.weight
+
+
+ def input_thing():
+ """输入物品信息"""
+ name_str, price_str, weight_str = input().split()
+ return name_str, int(price_str), int(weight_str)
+
+
+ def main():
+ """主函数"""
+ max_weight, num_of_things = map(int, input().split())
+ all_things = []
+ for _ in range(num_of_things):
+ all_things.append(Thing(*input_thing()))
+ all_things.sort(key=lambda x: x.value, reverse=True)
+ total_weight = 0
+ total_price = 0
+ for thing in all_things:
+ if total_weight + thing.weight <= max_weight:
+ print(f'小偷拿走了{thing.name}')
+ total_weight += thing.weight
+ total_price += thing.price
+ print(f'总价值: {total_price}美元')
+
+
+ if __name__ == '__main__':
+ main()
+ ```
+
+ 分治法例子:[快速排序](https://zh.wikipedia.org/zh/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F)。
+
+ ```Python
+ """
+ 快速排序 - 选择枢轴对元素进行划分,左边都比枢轴小右边都比枢轴大
+ """
+ def quick_sort(items, comp=lambda x, y: x <= y):
+ items = list(items)[:]
+ _quick_sort(items, 0, len(items) - 1, comp)
+ return items
+
+
+ def _quick_sort(items, start, end, comp):
+ if start < end:
+ pos = _partition(items, start, end, comp)
+ _quick_sort(items, start, pos - 1, comp)
+ _quick_sort(items, pos + 1, end, comp)
+
+
+ def _partition(items, start, end, comp):
+ pivot = items[end]
+ i = start - 1
+ for j in range(start, end):
+ if comp(items[j], pivot):
+ i += 1
+ items[i], items[j] = items[j], items[i]
+ items[i + 1], items[end] = items[end], items[i + 1]
+ return i + 1
+ ```
+
+ 回溯法例子:[骑士巡逻](https://zh.wikipedia.org/zh/%E9%AA%91%E5%A3%AB%E5%B7%A1%E9%80%BB)。
+
+ ```Python
+ """
+ 递归回溯法:叫称为试探法,按选优条件向前搜索,当搜索到某一步,发现原先选择并不优或达不到目标时,就退回一步重新选择,比较经典的问题包括骑士巡逻、八皇后和迷宫寻路等。
+ """
+ import sys
+ import time
+
+ SIZE = 5
+ total = 0
+
+
+ def print_board(board):
+ for row in board:
+ for col in row:
+ print(str(col).center(4), end='')
+ print()
+
+
+ def patrol(board, row, col, step=1):
+ if row >= 0 and row < SIZE and \
+ col >= 0 and col < SIZE and \
+ board[row][col] == 0:
+ board[row][col] = step
+ if step == SIZE * SIZE:
+ global total
+ total += 1
+ print(f'第{total}种走法: ')
+ print_board(board)
+ patrol(board, row - 2, col - 1, step + 1)
+ patrol(board, row - 1, col - 2, step + 1)
+ patrol(board, row + 1, col - 2, step + 1)
+ patrol(board, row + 2, col - 1, step + 1)
+ patrol(board, row + 2, col + 1, step + 1)
+ patrol(board, row + 1, col + 2, step + 1)
+ patrol(board, row - 1, col + 2, step + 1)
+ patrol(board, row - 2, col + 1, step + 1)
+ board[row][col] = 0
+
+
+ def main():
+ board = [[0] * SIZE for _ in range(SIZE)]
+ patrol(board, SIZE - 1, SIZE - 1)
+
+
+ if __name__ == '__main__':
+ main()
+ ```
+
+ 动态规划例子:子列表元素之和的最大值。
+
+ > 说明:子列表指的是列表中索引(下标)连续的元素构成的列表;列表中的元素是int类型,可能包含正整数、0、负整数;程序输入列表中的元素,输出子列表元素求和的最大值,例如:
+ >
+ > 输入:1 -2 3 5 -3 2
+ >
+ > 输出:8
+ >
+ > 输入:0 -2 3 5 -1 2
+ >
+ > 输出:9
+ >
+ > 输入:-9 -2 -3 -5 -3
+ >
+ > 输出:-2
+
+ ```Python
+ def main():
+ items = list(map(int, input().split()))
+ overall = partial = items[0]
+ for i in range(1, len(items)):
+ partial = max(items[i], partial + items[i])
+ overall = max(partial, overall)
+ print(overall)
+
+
+ if __name__ == '__main__':
+ main()
+ ```
+
+ > **说明**:这个题目最容易想到的解法是使用二重循环,但是代码的时间性能将会变得非常的糟糕。使用动态规划的思想,仅仅是多用了两个变量,就将原来$O(N^2)$复杂度的问题变成了$O(N)$。
+
+### 函数的使用方式
+
+- 将函数视为“一等公民”
+
+ - 函数可以赋值给变量
+ - 函数可以作为函数的参数
+ - 函数可以作为函数的返回值
+
+- 高阶函数的用法(`filter`、`map`以及它们的替代品)
+
+ ```Python
+ items1 = list(map(lambda x: x ** 2, filter(lambda x: x % 2, range(1, 10))))
+ items2 = [x ** 2 for x in range(1, 10) if x % 2]
+ ```
+
+- 位置参数、可变参数、关键字参数、命名关键字参数
+
+- 参数的元信息(代码可读性问题)
+
+- 匿名函数和内联函数的用法(`lambda`函数)
+
+- 闭包和作用域问题
+
+ - Python搜索变量的LEGB顺序(Local >>> Embedded >>> Global >>> Built-in)
+
+ - `global`和`nonlocal`关键字的作用
+
+ `global`:声明或定义全局变量(要么直接使用现有的全局作用域的变量,要么定义一个变量放到全局作用域)。
+
+ `nonlocal`:声明使用嵌套作用域的变量(嵌套作用域必须存在该变量,否则报错)。
+
+- 装饰器函数(使用装饰器和取消装饰器)
+
+ 例子:输出函数执行时间的装饰器。
+
+ ```Python
+ def record_time(func):
+ """自定义装饰函数的装饰器"""
+
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ start = time()
+ result = func(*args, **kwargs)
+ print(f'{func.__name__}: {time() - start}秒')
+ return result
+
+ return wrapper
+ ```
+
+ 如果装饰器不希望跟`print`函数耦合,可以编写可以参数化的装饰器。
+
+ ```Python
+ from functools import wraps
+ from time import time
+
+
+ def record(output):
+ """可以参数化的装饰器"""
+
+ def decorate(func):
+
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ start = time()
+ result = func(*args, **kwargs)
+ output(func.__name__, time() - start)
+ return result
+
+ return wrapper
+
+ return decorate
+ ```
+
+ ```Python
+ from functools import wraps
+ from time import time
+
+
+ class Record():
+ """通过定义类的方式定义装饰器"""
+
+ def __init__(self, output):
+ self.output = output
+
+ def __call__(self, func):
+
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ start = time()
+ result = func(*args, **kwargs)
+ self.output(func.__name__, time() - start)
+ return result
+
+ return wrapper
+ ```
+
+ > **说明**:由于对带装饰功能的函数添加了@wraps装饰器,可以通过`func.__wrapped__`方式获得被装饰之前的函数或类来取消装饰器的作用。
+
+ 例子:用装饰器来实现单例模式。
+
+ ```Python
+ from functools import wraps
+
+
+ def singleton(cls):
+ """装饰类的装饰器"""
+ instances = {}
+
+ @wraps(cls)
+ def wrapper(*args, **kwargs):
+ if cls not in instances:
+ instances[cls] = cls(*args, **kwargs)
+ return instances[cls]
+
+ return wrapper
+
+
+ @singleton
+ class President:
+ """总统(单例类)"""
+ pass
+ ```
+
+ > **提示**:上面的代码中用到了闭包(closure),不知道你是否已经意识到了。还没有一个小问题就是,上面的代码并没有实现线程安全的单例,如果要实现线程安全的单例应该怎么做呢?
+
+ 线程安全的单例装饰器。
+
+ ```Python
+ from functools import wraps
+ from threading import RLock
+
+
+ def singleton(cls):
+ """线程安全的单例装饰器"""
+ instances = {}
+ locker = RLock()
+
+ @wraps(cls)
+ def wrapper(*args, **kwargs):
+ if cls not in instances:
+ with locker:
+ if cls not in instances:
+ instances[cls] = cls(*args, **kwargs)
+ return instances[cls]
+
+ return wrapper
+ ```
+
+ > **提示**:上面的代码用到了`with`上下文语法来进行锁操作,因为锁对象本身就是上下文管理器对象(支持`__enter__`和`__exit__`魔术方法)。在`wrapper`函数中,我们先做了一次不带锁的检查,然后再做带锁的检查,这样做比直接加锁检查性能要更好,如果对象已经创建就没有必须再去加锁而是直接返回该对象就可以了。
+
+### 面向对象相关知识
+
+- 三大支柱:封装、继承、多态
+
+ 例子:工资结算系统。
+
+ ```Python
+ """
+ 月薪结算系统 - 部门经理每月15000 程序员每小时200 销售员1800底薪加销售额5%提成
+ """
+ from abc import ABCMeta, abstractmethod
+
+
+ class Employee(metaclass=ABCMeta):
+ """员工(抽象类)"""
+
+ def __init__(self, name):
+ self.name = name
+
+ @abstractmethod
+ def get_salary(self):
+ """结算月薪(抽象方法)"""
+ pass
+
+
+ class Manager(Employee):
+ """部门经理"""
+
+ def get_salary(self):
+ return 15000.0
+
+
+ class Programmer(Employee):
+ """程序员"""
+
+ def __init__(self, name, working_hour=0):
+ self.working_hour = working_hour
+ super().__init__(name)
+
+ def get_salary(self):
+ return 200.0 * self.working_hour
+
+
+ class Salesman(Employee):
+ """销售员"""
+
+ def __init__(self, name, sales=0.0):
+ self.sales = sales
+ super().__init__(name)
+
+ def get_salary(self):
+ return 1800.0 + self.sales * 0.05
+
+
+ class EmployeeFactory:
+ """创建员工的工厂(工厂模式 - 通过工厂实现对象使用者和对象之间的解耦合)"""
+
+ @staticmethod
+ def create(emp_type, *args, **kwargs):
+ """创建员工"""
+ all_emp_types = {'M': Manager, 'P': Programmer, 'S': Salesman}
+ cls = all_emp_types[emp_type.upper()]
+ return cls(*args, **kwargs) if cls else None
+
+
+ def main():
+ """主函数"""
+ emps = [
+ EmployeeFactory.create('M', '曹操'),
+ EmployeeFactory.create('P', '荀彧', 120),
+ EmployeeFactory.create('P', '郭嘉', 85),
+ EmployeeFactory.create('S', '典韦', 123000),
+ ]
+ for emp in emps:
+ print(f'{emp.name}: {emp.get_salary():.2f}元')
+
+
+ if __name__ == '__main__':
+ main()
+ ```
+
+- 类与类之间的关系
+
+ - is-a关系:继承
+ - has-a关系:关联 / 聚合 / 合成
+ - use-a关系:依赖
+
+ 例子:扑克游戏。
+
+ ```Python
+ """
+ 经验:符号常量总是优于字面常量,枚举类型是定义符号常量的最佳选择
+ """
+ from enum import Enum, unique
+
+ import random
+
+
+ @unique
+ class Suite(Enum):
+ """花色"""
+
+ SPADE, HEART, CLUB, DIAMOND = range(4)
+
+ def __lt__(self, other):
+ return self.value < other.value
+
+
+ class Card():
+ """牌"""
+
+ def __init__(self, suite, face):
+ """初始化方法"""
+ self.suite = suite
+ self.face = face
+
+ def show(self):
+ """显示牌面"""
+ suites = ['♠︎', '♥︎', '♣︎', '♦︎']
+ faces = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
+ return f'{suites[self.suite.value]}{faces[self.face]}'
+
+ def __repr__(self):
+ return self.show()
+
+
+ class Poker():
+ """扑克"""
+
+ def __init__(self):
+ self.index = 0
+ self.cards = [Card(suite, face)
+ for suite in Suite
+ for face in range(1, 14)]
+
+ def shuffle(self):
+ """洗牌(随机乱序)"""
+ random.shuffle(self.cards)
+ self.index = 0
+
+ def deal(self):
+ """发牌"""
+ card = self.cards[self.index]
+ self.index += 1
+ return card
+
+ @property
+ def has_more(self):
+ return self.index < len(self.cards)
+
+
+ class Player():
+ """玩家"""
+
+ def __init__(self, name):
+ self.name = name
+ self.cards = []
+
+ def get_one(self, card):
+ """摸一张牌"""
+ self.cards.append(card)
+
+ def sort(self, comp=lambda card: (card.suite, card.face)):
+ """整理手上的牌"""
+ self.cards.sort(key=comp)
+
+
+ def main():
+ """主函数"""
+ poker = Poker()
+ poker.shuffle()
+ players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
+ while poker.has_more:
+ for player in players:
+ player.get_one(poker.deal())
+ for player in players:
+ player.sort()
+ print(player.name, end=': ')
+ print(player.cards)
+
+
+ if __name__ == '__main__':
+ main()
+ ```
+
+ > **说明**:上面的代码中使用了Emoji字符来表示扑克牌的四种花色,在某些不支持Emoji字符的系统上可能无法显示。
+
+- 对象的复制(深复制/深拷贝/深度克隆和浅复制/浅拷贝/影子克隆)
+
+- 垃圾回收、循环引用和弱引用
+
+ Python使用了自动化内存管理,这种管理机制以**引用计数**为基础,同时也引入了**标记-清除**和**分代收集**两种机制为辅的策略。
+
+ ```C
+ typedef struct_object {
+ /* 引用计数 */
+ int ob_refcnt;
+ /* 对象指针 */
+ struct_typeobject *ob_type;
+ } PyObject;
+ ```
+
+ ```C
+ /* 增加引用计数的宏定义 */
+ #define Py_INCREF(op) ((op)->ob_refcnt++)
+ /* 减少引用计数的宏定义 */
+ #define Py_DECREF(op) \ //减少计数
+ if (--(op)->ob_refcnt != 0) \
+ ; \
+ else \
+ __Py_Dealloc((PyObject *)(op))
+ ```
+
+ 导致引用计数+1的情况:
+
+ - 对象被创建,例如`a = 23`
+ - 对象被引用,例如`b = a`
+ - 对象被作为参数,传入到一个函数中,例如`f(a)`
+ - 对象作为一个元素,存储在容器中,例如`list1 = [a, a]`
+
+ 导致引用计数-1的情况:
+
+ - 对象的别名被显式销毁,例如`del a`
+ - 对象的别名被赋予新的对象,例如`a = 24`
+ - 一个对象离开它的作用域,例如f函数执行完毕时,f函数中的局部变量(全局变量不会)
+ - 对象所在的容器被销毁,或从容器中删除对象
+
+ 引用计数可能会导致循环引用问题,而循环引用会导致内存泄露,如下面的代码所示。为了解决这个问题,Python中引入了“标记-清除”和“分代收集”。在创建一个对象的时候,对象被放在第一代中,如果在第一代的垃圾检查中对象存活了下来,该对象就会被放到第二代中,同理在第二代的垃圾检查中对象存活下来,该对象就会被放到第三代中。
+
+ ```Python
+ # 循环引用会导致内存泄露 - Python除了引用技术还引入了标记清理和分代回收
+ # 在Python 3.6以前如果重写__del__魔术方法会导致循环引用处理失效
+ # 如果不想造成循环引用可以使用弱引用
+ list1 = []
+ list2 = []
+ list1.append(list2)
+ list2.append(list1)
+ ```
+
+ 以下情况会导致垃圾回收:
+
+ - 调用`gc.collect()`
+ - `gc`模块的计数器达到阀值
+ - 程序退出
+
+ 如果循环引用中两个对象都定义了`__del__`方法,`gc`模块不会销毁这些不可达对象,因为gc模块不知道应该先调用哪个对象的`__del__`方法,这个问题在Python 3.6中得到了解决。
+
+ 也可以通过`weakref`模块构造弱引用的方式来解决循环引用的问题。
+
+- 魔法属性和方法(请参考《Python魔法方法指南》)
+
+ 有几个小问题请大家思考:
+
+ - 自定义的对象能不能使用运算符做运算?
+ - 自定义的对象能不能放到`set`中?能去重吗?
+ - 自定义的对象能不能作为`dict`的键?
+ - 自定义的对象能不能使用上下文语法?
+
+- 混入(Mixin)
+
+ 例子:自定义字典限制只有在指定的key不存在时才能在字典中设置键值对。
+
+ ```Python
+ class SetOnceMappingMixin:
+ """自定义混入类"""
+ __slots__ = ()
+
+ def __setitem__(self, key, value):
+ if key in self:
+ raise KeyError(str(key) + ' already set')
+ return super().__setitem__(key, value)
+
+
+ class SetOnceDict(SetOnceMappingMixin, dict):
+ """自定义字典"""
+ pass
+
+
+ my_dict= SetOnceDict()
+ try:
+ my_dict['username'] = 'jackfrued'
+ my_dict['username'] = 'hellokitty'
+ except KeyError:
+ pass
+ print(my_dict)
+ ```
+
+- 元编程和元类
+
+ 对象是通过类创建的,类是通过元类创建的,元类提供了创建类的元信息。所有的类都直接或间接的继承自`object`,所有的元类都直接或间接的继承自`type`。
+
+ 例子:用元类实现单例模式。
+
+ ```Python
+ import threading
+
+
+ class SingletonMeta(type):
+ """自定义元类"""
+
+ def __init__(cls, *args, **kwargs):
+ cls.__instance = None
+ cls.__lock = threading.RLock()
+ super().__init__(*args, **kwargs)
+
+ def __call__(cls, *args, **kwargs):
+ if cls.__instance is None:
+ with cls.__lock:
+ if cls.__instance is None:
+ cls.__instance = super().__call__(*args, **kwargs)
+ return cls.__instance
+
+
+ class President(metaclass=SingletonMeta):
+ """总统(单例类)"""
+
+ pass
+ ```
+
+- 面向对象设计原则
+
+ - 单一职责原则 (**S**RP)- 一个类只做该做的事情(类的设计要高内聚)
+ - 开闭原则 (**O**CP)- 软件实体应该对扩展开发对修改关闭
+ - 依赖倒转原则(DIP)- 面向抽象编程(在弱类型语言中已经被弱化)
+ - 里氏替换原则(**L**SP) - 任何时候可以用子类对象替换掉父类对象
+ - 接口隔离原则(**I**SP)- 接口要小而专不要大而全(Python中没有接口的概念)
+ - 合成聚合复用原则(CARP) - 优先使用强关联关系而不是继承关系复用代码
+ - 最少知识原则(迪米特法则,Lo**D**)- 不要给没有必然联系的对象发消息
+
+ > **说明**:上面加粗的字母放在一起称为面向对象的**SOLID**原则。
+
+- GoF设计模式
+
+ - 创建型模式:单例、工厂、建造者、原型
+ - 结构型模式:适配器、门面(外观)、代理
+ - 行为型模式:迭代器、观察者、状态、策略
+
+ 例子:可插拔的哈希算法(策略模式)。
+
+ ```Python
+ class StreamHasher():
+ """哈希摘要生成器"""
+
+ def __init__(self, alg='md5', size=4096):
+ self.size = size
+ alg = alg.lower()
+ self.hasher = getattr(__import__('hashlib'), alg.lower())()
+
+ def __call__(self, stream):
+ return self.to_digest(stream)
+
+ def to_digest(self, stream):
+ """生成十六进制形式的摘要"""
+ for buf in iter(lambda: stream.read(self.size), b''):
+ self.hasher.update(buf)
+ return self.hasher.hexdigest()
+
+ def main():
+ """主函数"""
+ hasher1 = StreamHasher()
+ with open('Python-3.7.6.tgz', 'rb') as stream:
+ print(hasher1.to_digest(stream))
+ hasher2 = StreamHasher('sha1')
+ with open('Python-3.7.6.tgz', 'rb') as stream:
+ print(hasher2(stream))
+
+
+ if __name__ == '__main__':
+ main()
+ ```
+
+### 迭代器和生成器
+
+- 迭代器是实现了迭代器协议的对象。
+
+ - Python中没有像`protocol`或`interface`这样的定义协议的关键字。
+ - Python中用魔术方法表示协议。
+ - `__iter__`和`__next__`魔术方法就是迭代器协议。
+
+ ```Python
+ class Fib(object):
+ """迭代器"""
+
+ def __init__(self, num):
+ self.num = num
+ self.a, self.b = 0, 1
+ self.idx = 0
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ if self.idx < self.num:
+ self.a, self.b = self.b, self.a + self.b
+ self.idx += 1
+ return self.a
+ raise StopIteration()
+ ```
+
+- 生成器是语法简化版的迭代器。
+
+ ```Python
+ def fib(num):
+ """生成器"""
+ a, b = 0, 1
+ for _ in range(num):
+ a, b = b, a + b
+ yield a
+ ```
+
+- 生成器进化为协程。
+
+ 生成器对象可以使用`send()`方法发送数据,发送的数据会成为生成器函数中通过`yield`表达式获得的值。这样,生成器就可以作为协程使用,协程简单的说就是可以相互协作的子程序。
+
+ ```Python
+ def calc_avg():
+ """流式计算平均值"""
+ total, counter = 0, 0
+ avg_value = None
+ while True:
+ value = yield avg_value
+ total, counter = total + value, counter + 1
+ avg_value = total / counter
+
+
+ gen = calc_avg()
+ next(gen)
+ print(gen.send(10))
+ print(gen.send(20))
+ print(gen.send(30))
+ ```
+
+### 并发编程
+
+Python中实现并发编程的三种方案:多线程、多进程和异步I/O。并发编程的好处在于可以提升程序的执行效率以及改善用户体验;坏处在于并发的程序不容易开发和调试,同时对其他程序来说它并不友好。
+
+- 多线程:Python中提供了`Thread`类并辅以`Lock`、`Condition`、`Event`、`Semaphore`和`Barrier`。Python中有GIL来防止多个线程同时执行本地字节码,这个锁对于CPython是必须的,因为CPython的内存管理并不是线程安全的,因为GIL的存在多线程并不能发挥CPU的多核特性。
+
+ ```Python
+ """
+ 面试题:进程和线程的区别和联系?
+ 进程 - 操作系统分配内存的基本单位 - 一个进程可以包含一个或多个线程
+ 线程 - 操作系统分配CPU的基本单位
+ 并发编程(concurrent programming)
+ 1. 提升执行性能 - 让程序中没有因果关系的部分可以并发的执行
+ 2. 改善用户体验 - 让耗时间的操作不会造成程序的假死
+ """
+ import glob
+ import os
+ import threading
+
+ from PIL import Image
+
+ PREFIX = 'thumbnails'
+
+
+ def generate_thumbnail(infile, size, format='PNG'):
+ """生成指定图片文件的缩略图"""
+ file, ext = os.path.splitext(infile)
+ file = file[file.rfind('/') + 1:]
+ outfile = f'{PREFIX}/{file}_{size[0]}_{size[1]}.{ext}'
+ img = Image.open(infile)
+ img.thumbnail(size, Image.ANTIALIAS)
+ img.save(outfile, format)
+
+
+ def main():
+ """主函数"""
+ if not os.path.exists(PREFIX):
+ os.mkdir(PREFIX)
+ for infile in glob.glob('images/*.png'):
+ for size in (32, 64, 128):
+ # 创建并启动线程
+ threading.Thread(
+ target=generate_thumbnail,
+ args=(infile, (size, size))
+ ).start()
+
+
+ if __name__ == '__main__':
+ main()
+ ```
+
+ 多个线程竞争资源的情况。
+
+ ```Python
+ """
+ 多线程程序如果没有竞争资源处理起来通常也比较简单
+ 当多个线程竞争临界资源的时候如果缺乏必要的保护措施就会导致数据错乱
+ 说明:临界资源就是被多个线程竞争的资源
+ """
+ import time
+ import threading
+
+ from concurrent.futures import ThreadPoolExecutor
+
+
+ class Account(object):
+ """银行账户"""
+
+ def __init__(self):
+ self.balance = 0.0
+ self.lock = threading.Lock()
+
+ def deposit(self, money):
+ # 通过锁保护临界资源
+ with self.lock:
+ new_balance = self.balance + money
+ time.sleep(0.001)
+ self.balance = new_balance
+
+
+ class AddMoneyThread(threading.Thread):
+ """自定义线程类"""
+
+ def __init__(self, account, money):
+ self.account = account
+ self.money = money
+ # 自定义线程的初始化方法中必须调用父类的初始化方法
+ super().__init__()
+
+ def run(self):
+ # 线程启动之后要执行的操作
+ self.account.deposit(self.money)
+
+ def main():
+ """主函数"""
+ account = Account()
+ # 创建线程池
+ pool = ThreadPoolExecutor(max_workers=10)
+ futures = []
+ for _ in range(100):
+ # 创建线程的第1种方式
+ # threading.Thread(
+ # target=account.deposit, args=(1, )
+ # ).start()
+ # 创建线程的第2种方式
+ # AddMoneyThread(account, 1).start()
+ # 创建线程的第3种方式
+ # 调用线程池中的线程来执行特定的任务
+ future = pool.submit(account.deposit, 1)
+ futures.append(future)
+ # 关闭线程池
+ pool.shutdown()
+ for future in futures:
+ future.result()
+ print(account.balance)
+
+
+ if __name__ == '__main__':
+ main()
+ ```
+
+ 修改上面的程序,启动5个线程向账户中存钱,5个线程从账户中取钱,取钱时如果余额不足就暂停线程进行等待。为了达到上述目标,需要对存钱和取钱的线程进行调度,在余额不足时取钱的线程暂停并释放锁,而存钱的线程将钱存入后要通知取钱的线程,使其从暂停状态被唤醒。可以使用`threading`模块的`Condition`来实现线程调度,该对象也是基于锁来创建的,代码如下所示:
+
+ ```Python
+ """
+ 多个线程竞争一个资源 - 保护临界资源 - 锁(Lock/RLock)
+ 多个线程竞争多个资源(线程数>资源数) - 信号量(Semaphore)
+ 多个线程的调度 - 暂停线程执行/唤醒等待中的线程 - Condition
+ """
+ from concurrent.futures import ThreadPoolExecutor
+ from random import randint
+ from time import sleep
+
+ import threading
+
+
+ class Account():
+ """银行账户"""
+
+ def __init__(self, balance=0):
+ self.balance = balance
+ lock = threading.Lock()
+ self.condition = threading.Condition(lock)
+
+ def withdraw(self, money):
+ """取钱"""
+ with self.condition:
+ while money > self.balance:
+ self.condition.wait()
+ new_balance = self.balance - money
+ sleep(0.001)
+ self.balance = new_balance
+
+ def deposit(self, money):
+ """存钱"""
+ with self.condition:
+ new_balance = self.balance + money
+ sleep(0.001)
+ self.balance = new_balance
+ self.condition.notify_all()
+
+
+ def add_money(account):
+ while True:
+ money = randint(5, 10)
+ account.deposit(money)
+ print(threading.current_thread().name,
+ ':', money, '====>', account.balance)
+ sleep(0.5)
+
+
+ def sub_money(account):
+ while True:
+ money = randint(10, 30)
+ account.withdraw(money)
+ print(threading.current_thread().name,
+ ':', money, '<====', account.balance)
+ sleep(1)
+
+
+ def main():
+ account = Account()
+ with ThreadPoolExecutor(max_workers=10) as pool:
+ for _ in range(5):
+ pool.submit(add_money, account)
+ pool.submit(sub_money, account)
+
+
+ if __name__ == '__main__':
+ main()
+ ```
+
+- 多进程:多进程可以有效的解决GIL的问题,实现多进程主要的类是`Process`,其他辅助的类跟`threading`模块中的类似,进程间共享数据可以使用管道、套接字等,在`multiprocessing`模块中有一个`Queue`类,它基于管道和锁机制提供了多个进程共享的队列。下面是官方文档上关于多进程和进程池的一个示例。
+
+ ```Python
+ """
+ 多进程和进程池的使用
+ 多线程因为GIL的存在不能够发挥CPU的多核特性
+ 对于计算密集型任务应该考虑使用多进程
+ time python3 example22.py
+ real 0m11.512s
+ user 0m39.319s
+ sys 0m0.169s
+ 使用多进程后实际执行时间为11.512秒,而用户时间39.319秒约为实际执行时间的4倍
+ 这就证明我们的程序通过多进程使用了CPU的多核特性,而且这台计算机配置了4核的CPU
+ """
+ import concurrent.futures
+ import math
+
+ PRIMES = [
+ 1116281,
+ 1297337,
+ 104395303,
+ 472882027,
+ 533000389,
+ 817504243,
+ 982451653,
+ 112272535095293,
+ 112582705942171,
+ 112272535095293,
+ 115280095190773,
+ 115797848077099,
+ 1099726899285419
+ ] * 5
+
+
+ def is_prime(n):
+ """判断素数"""
+ if n % 2 == 0:
+ return False
+
+ sqrt_n = int(math.floor(math.sqrt(n)))
+ for i in range(3, sqrt_n + 1, 2):
+ if n % i == 0:
+ return False
+ return True
+
+
+ def main():
+ """主函数"""
+ with concurrent.futures.ProcessPoolExecutor() as executor:
+ for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
+ print('%d is prime: %s' % (number, prime))
+
+
+ if __name__ == '__main__':
+ main()
+ ```
+
+ > **重点**:**多线程和多进程的比较**。
+ >
+ > 以下情况需要使用多线程:
+ >
+ > 1. 程序需要维护许多共享的状态(尤其是可变状态),Python中的列表、字典、集合都是线程安全的,所以使用线程而不是进程维护共享状态的代价相对较小。
+ > 2. 程序会花费大量时间在I/O操作上,没有太多并行计算的需求且不需占用太多的内存。
+ >
+ > 以下情况需要使用多进程:
+ >
+ > 1. 程序执行计算密集型任务(如:字节码操作、数据处理、科学计算)。
+ > 2. 程序的输入可以并行的分成块,并且可以将运算结果合并。
+ > 3. 程序在内存使用方面没有任何限制且不强依赖于I/O操作(如:读写文件、套接字等)。
+
+- 异步处理:从调度程序的任务队列中挑选任务,该调度程序以交叉的形式执行这些任务,我们并不能保证任务将以某种顺序去执行,因为执行顺序取决于队列中的一项任务是否愿意将CPU处理时间让位给另一项任务。异步任务通常通过多任务协作处理的方式来实现,由于执行时间和顺序的不确定,因此需要通过回调式编程或者`future`对象来获取任务执行的结果。Python 3通过`asyncio`模块和`await`和`async`关键字(在Python 3.7中正式被列为关键字)来支持异步处理。
+
+ ```Python
+ """
+ 异步I/O - async / await
+ """
+ import asyncio
+
+
+ def num_generator(m, n):
+ """指定范围的数字生成器"""
+ yield from range(m, n + 1)
+
+
+ async def prime_filter(m, n):
+ """素数过滤器"""
+ primes = []
+ for i in num_generator(m, n):
+ flag = True
+ for j in range(2, int(i ** 0.5 + 1)):
+ if i % j == 0:
+ flag = False
+ break
+ if flag:
+ print('Prime =>', i)
+ primes.append(i)
+
+ await asyncio.sleep(0.001)
+ return tuple(primes)
+
+
+ async def square_mapper(m, n):
+ """平方映射器"""
+ squares = []
+ for i in num_generator(m, n):
+ print('Square =>', i * i)
+ squares.append(i * i)
+
+ await asyncio.sleep(0.001)
+ return squares
+
+
+ def main():
+ """主函数"""
+ loop = asyncio.get_event_loop()
+ future = asyncio.gather(prime_filter(2, 100), square_mapper(1, 100))
+ future.add_done_callback(lambda x: print(x.result()))
+ loop.run_until_complete(future)
+ loop.close()
+
+
+ if __name__ == '__main__':
+ main()
+ ```
+
+ > **说明**:上面的代码使用`get_event_loop`函数获得系统默认的事件循环,通过`gather`函数可以获得一个`future`对象,`future`对象的`add_done_callback`可以添加执行完成时的回调函数,`loop`对象的`run_until_complete`方法可以等待通过`future`对象获得协程执行结果。
+
+ Python中有一个名为`aiohttp`的三方库,它提供了异步的HTTP客户端和服务器,这个三方库可以跟`asyncio`模块一起工作,并提供了对`Future`对象的支持。Python 3.6中引入了`async`和`await`来定义异步执行的函数以及创建异步上下文,在Python 3.7中它们正式成为了关键字。下面的代码异步的从5个URL中获取页面并通过正则表达式的命名捕获组提取了网站的标题。
+
+ ```Python
+ import asyncio
+ import re
+
+ import aiohttp
+
+ PATTERN = re.compile(r'\(?P.*)\<\/title\>')
+
+
+ async def fetch_page(session, url):
+ async with session.get(url, ssl=False) as resp:
+ return await resp.text()
+
+
+ async def show_title(url):
+ async with aiohttp.ClientSession() as session:
+ html = await fetch_page(session, url)
+ print(PATTERN.search(html).group('title'))
+
+
+ def main():
+ urls = ('https://www.python.org/',
+ 'https://git-scm.com/',
+ 'https://www.jd.com/',
+ 'https://www.taobao.com/',
+ 'https://www.douban.com/')
+ loop = asyncio.get_event_loop()
+ cos = [show_title(url) for url in urls]
+ loop.run_until_complete(asyncio.wait(cos))
+ loop.close()
+
+
+ if __name__ == '__main__':
+ main()
+ ```
+
+ > **重点**:**异步I/O与多进程的比较**。
+ >
+ > 当程序不需要真正的并发性或并行性,而是更多的依赖于异步处理和回调时,`asyncio`就是一种很好的选择。如果程序中有大量的等待与休眠时,也应该考虑`asyncio`,它很适合编写没有实时数据处理需求的Web应用服务器。
+
+ Python还有很多用于处理并行任务的三方库,例如:`joblib`、`PyMP`等。实际开发中,要提升系统的可扩展性和并发性通常有垂直扩展(增加单个节点的处理能力)和水平扩展(将单个节点变成多个节点)两种做法。可以通过消息队列来实现应用程序的解耦合,消息队列相当于是多线程同步队列的扩展版本,不同机器上的应用程序相当于就是线程,而共享的分布式消息队列就是原来程序中的Queue。消息队列(面向消息的中间件)的最流行和最标准化的实现是AMQP(高级消息队列协议),AMQP源于金融行业,提供了排队、路由、可靠传输、安全等功能,最著名的实现包括:Apache的ActiveMQ、RabbitMQ等。
+
+ 要实现任务的异步化,可以使用名为`Celery`的三方库。`Celery`是Python编写的分布式任务队列,它使用分布式消息进行工作,可以基于RabbitMQ或Redis来作为后端的消息代理。
\ No newline at end of file
diff --git a/Day16-20/res/algorithm_complexity_1.png b/Day16-20/res/algorithm_complexity_1.png
index 952889d..2d57aed 100644
Binary files a/Day16-20/res/algorithm_complexity_1.png and b/Day16-20/res/algorithm_complexity_1.png differ
diff --git a/Day16-20/res/algorithm_complexity_2.png b/Day16-20/res/algorithm_complexity_2.png
index 4c14249..0ff66f3 100644
Binary files a/Day16-20/res/algorithm_complexity_2.png and b/Day16-20/res/algorithm_complexity_2.png differ
diff --git a/Day21-30/21-30.Web前端概述.md b/Day21-30/21-30.Web前端概述.md
index 928da6a..0ea3daf 100644
--- a/Day21-30/21-30.Web前端概述.md
+++ b/Day21-30/21-30.Web前端概述.md
@@ -288,7 +288,7 @@
- `delete`关键字
- 标准对象
- `Number` / `String` / `Boolean` / `Symbol` / `Array` / `Function`
- - `Date` / `Error` / `Math` / `RegEx` / `Object` / `Map` / `Set`
+ - `Date` / `Error` / `Math` / `RegExp` / `Object` / `Map` / `Set`
- `JSON` / `Promise` / `Generator` / `Reflect` / `Proxy`
#### BOM
@@ -903,4 +903,4 @@ Bulma是一个基于Flexbox的现代化的CSS框架,其初衷就是移动优
3. 可视化
- ![](./res/bootstrap-layoutit.png)
\ No newline at end of file
+ ![](./res/bootstrap-layoutit.png)
\ No newline at end of file
diff --git a/Day21-30/code/垃圾分类查询/harmful-waste.png b/Day21-30/code/html/harmful-waste.png
similarity index 100%
rename from Day21-30/code/垃圾分类查询/harmful-waste.png
rename to Day21-30/code/html/harmful-waste.png
diff --git a/Day21-30/code/垃圾分类查询/垃圾分类.html b/Day21-30/code/html/index.html
similarity index 100%
rename from Day21-30/code/垃圾分类查询/垃圾分类.html
rename to Day21-30/code/html/index.html
diff --git a/Day21-30/code/垃圾分类查询/kitchen-waste.png b/Day21-30/code/html/kitchen-waste.png
similarity index 100%
rename from Day21-30/code/垃圾分类查询/kitchen-waste.png
rename to Day21-30/code/html/kitchen-waste.png
diff --git a/Day21-30/code/垃圾分类查询/other-waste.png b/Day21-30/code/html/other-waste.png
similarity index 100%
rename from Day21-30/code/垃圾分类查询/other-waste.png
rename to Day21-30/code/html/other-waste.png
diff --git a/Day21-30/code/垃圾分类查询/recyclable.png b/Day21-30/code/html/recyclable.png
similarity index 100%
rename from Day21-30/code/垃圾分类查询/recyclable.png
rename to Day21-30/code/html/recyclable.png
diff --git a/Day21-30/res/baidu_echarts.png b/Day21-30/res/baidu_echarts.png
index e583e89..7144a46 100644
Binary files a/Day21-30/res/baidu_echarts.png and b/Day21-30/res/baidu_echarts.png differ
diff --git a/Day21-30/res/bootstrap-layoutit.png b/Day21-30/res/bootstrap-layoutit.png
index ae39347..53b4d86 100644
Binary files a/Day21-30/res/bootstrap-layoutit.png and b/Day21-30/res/bootstrap-layoutit.png differ
diff --git a/Day21-30/res/browser-joke-1.png b/Day21-30/res/browser-joke-1.png
deleted file mode 100644
index 0e3efd8..0000000
Binary files a/Day21-30/res/browser-joke-1.png and /dev/null differ
diff --git a/Day21-30/res/browser-joke-2.png b/Day21-30/res/browser-joke-2.png
deleted file mode 100644
index b3ae531..0000000
Binary files a/Day21-30/res/browser-joke-2.png and /dev/null differ
diff --git a/Day21-30/res/browser-joke-3.png b/Day21-30/res/browser-joke-3.png
deleted file mode 100644
index bd6028f..0000000
Binary files a/Day21-30/res/browser-joke-3.png and /dev/null differ
diff --git a/Day21-30/res/dom-page.png b/Day21-30/res/dom-page.png
deleted file mode 100644
index 2777e9c..0000000
Binary files a/Day21-30/res/dom-page.png and /dev/null differ
diff --git a/Day21-30/res/dom-tree.png b/Day21-30/res/dom-tree.png
deleted file mode 100644
index ce5e74e..0000000
Binary files a/Day21-30/res/dom-tree.png and /dev/null differ
diff --git a/Day21-30/res/字体样式.png b/Day21-30/res/字体样式.png
index 9baacb6..8f592bb 100644
Binary files a/Day21-30/res/字体样式.png and b/Day21-30/res/字体样式.png differ
diff --git a/Day21-30/res/字符实体.png b/Day21-30/res/字符实体.png
index 4a1fd12..ff4a01a 100644
Binary files a/Day21-30/res/字符实体.png and b/Day21-30/res/字符实体.png differ
diff --git a/Day21-30/res/客户端对字体文件的支持.png b/Day21-30/res/客户端对字体文件的支持.png
index aa93eeb..1a0e5b0 100644
Binary files a/Day21-30/res/客户端对字体文件的支持.png and b/Day21-30/res/客户端对字体文件的支持.png differ
diff --git a/Day21-30/res/尺寸单位.png b/Day21-30/res/尺寸单位.png
index 3b71674..47968e0 100644
Binary files a/Day21-30/res/尺寸单位.png and b/Day21-30/res/尺寸单位.png differ
diff --git a/Day21-30/res/属性选择器.png b/Day21-30/res/属性选择器.png
index f6ae4dc..06fef27 100644
Binary files a/Day21-30/res/属性选择器.png and b/Day21-30/res/属性选择器.png differ
diff --git a/Day21-30/res/常用选择器.png b/Day21-30/res/常用选择器.png
index bf86166..fe55a35 100644
Binary files a/Day21-30/res/常用选择器.png and b/Day21-30/res/常用选择器.png differ
diff --git a/Day21-30/res/开始标签.png b/Day21-30/res/开始标签.png
index 29bd455..6f79eb4 100644
Binary files a/Day21-30/res/开始标签.png and b/Day21-30/res/开始标签.png differ
diff --git a/Day21-30/res/标签属性.png b/Day21-30/res/标签属性.png
index 21eb12f..7eb42f0 100644
Binary files a/Day21-30/res/标签属性.png and b/Day21-30/res/标签属性.png differ
diff --git a/Day21-30/res/样式属性.png b/Day21-30/res/样式属性.png
index 476ef50..9b63b89 100644
Binary files a/Day21-30/res/样式属性.png and b/Day21-30/res/样式属性.png differ
diff --git a/Day21-30/res/盒子模型.png b/Day21-30/res/盒子模型.png
index a2517f3..af181ff 100644
Binary files a/Day21-30/res/盒子模型.png and b/Day21-30/res/盒子模型.png differ
diff --git a/Day21-30/res/相对路径.png b/Day21-30/res/相对路径.png
index b0e6ec9..f9b75ff 100644
Binary files a/Day21-30/res/相对路径.png and b/Day21-30/res/相对路径.png differ
diff --git a/Day21-30/res/经典布局-1.png b/Day21-30/res/经典布局-1.png
index 42f4b94..16dfe2b 100644
Binary files a/Day21-30/res/经典布局-1.png and b/Day21-30/res/经典布局-1.png differ
diff --git a/Day21-30/res/经典布局-2.png b/Day21-30/res/经典布局-2.png
index 089133b..349c6d0 100644
Binary files a/Day21-30/res/经典布局-2.png and b/Day21-30/res/经典布局-2.png differ
diff --git a/Day21-30/res/结束标签.png b/Day21-30/res/结束标签.png
index c80a3c2..2479512 100644
Binary files a/Day21-30/res/结束标签.png and b/Day21-30/res/结束标签.png differ
diff --git a/Day21-30/res/网站地图.png b/Day21-30/res/网站地图.png
index 55a4daf..744e7d9 100644
Binary files a/Day21-30/res/网站地图.png and b/Day21-30/res/网站地图.png differ
diff --git a/Day21-30/res/衬线字体+非衬线字体+等宽字体.png b/Day21-30/res/衬线字体+非衬线字体+等宽字体.png
index 7693210..1fd0818 100644
Binary files a/Day21-30/res/衬线字体+非衬线字体+等宽字体.png and b/Day21-30/res/衬线字体+非衬线字体+等宽字体.png differ
diff --git a/Day21-30/res/选择器语法.png b/Day21-30/res/选择器语法.png
index 54c159d..75021ef 100644
Binary files a/Day21-30/res/选择器语法.png and b/Day21-30/res/选择器语法.png differ
diff --git a/Day31-35/31-35.玩转Linux操作系统.md b/Day31-35/31-35.玩转Linux操作系统.md
index c519043..97a1040 100644
--- a/Day31-35/31-35.玩转Linux操作系统.md
+++ b/Day31-35/31-35.玩转Linux操作系统.md
@@ -111,7 +111,7 @@ Linux系统的命令通常都是如下所示的格式:
Shell也被称为“壳”或“壳程序”,它是用户与操作系统内核交流的翻译官,简单的说就是人与计算机交互的界面和接口。目前很多Linux系统默认的Shell都是bash(Bourne Again SHell),因为它可以使用tab键进行命令和路径补全、可以保存历史命令、可以方便的配置环境变量以及执行批处理操作。
```Shell
- [root@izwz97tbgo9lkabnat2lo8z ~]# ps
+ [root ~]# ps
PID TTY TIME CMD
3531 pts/0 00:00:00 bash
3553 pts/0 00:00:00 ps
@@ -1283,7 +1283,7 @@ build environment:
### 计划任务
-1. 在指定的时间执行命令
+1. 在指定的时间执行命令。
- **at** - 将任务排队,在指定的时间执行。
- **atq** - 查看待执行的任务队列。
diff --git a/Day31-35/res/andrew.jpg b/Day31-35/res/andrew.jpg
index 8f001d3..41f37a5 100644
Binary files a/Day31-35/res/andrew.jpg and b/Day31-35/res/andrew.jpg differ
diff --git a/Day31-35/res/dmr.png b/Day31-35/res/dmr.png
index 2453340..2ca20e8 100644
Binary files a/Day31-35/res/dmr.png and b/Day31-35/res/dmr.png differ
diff --git a/Day31-35/res/file-mode.png b/Day31-35/res/file-mode.png
index 294139a..f80d500 100644
Binary files a/Day31-35/res/file-mode.png and b/Day31-35/res/file-mode.png differ
diff --git a/Day31-35/res/history-of-os.png b/Day31-35/res/history-of-os.png
deleted file mode 100644
index 22262ec..0000000
Binary files a/Day31-35/res/history-of-os.png and /dev/null differ
diff --git a/Day31-35/res/history-of-unix.png b/Day31-35/res/history-of-unix.png
index 401b706..1da969e 100644
Binary files a/Day31-35/res/history-of-unix.png and b/Day31-35/res/history-of-unix.png differ
diff --git a/Day31-35/res/ibm-col80-punched-card.png b/Day31-35/res/ibm-col80-punched-card.png
index 467ce12..d075ec8 100644
Binary files a/Day31-35/res/ibm-col80-punched-card.png and b/Day31-35/res/ibm-col80-punched-card.png differ
diff --git a/Day31-35/res/ken-and-dennis-pdp-11.png b/Day31-35/res/ken-and-dennis-pdp-11.png
index 28ad0f7..2a2b510 100644
Binary files a/Day31-35/res/ken-and-dennis-pdp-11.png and b/Day31-35/res/ken-and-dennis-pdp-11.png differ
diff --git a/Day31-35/res/ken_old.png b/Day31-35/res/ken_old.png
index 5d322c4..7fd7853 100644
Binary files a/Day31-35/res/ken_old.png and b/Day31-35/res/ken_old.png differ
diff --git a/Day31-35/res/ken_young.jpg b/Day31-35/res/ken_young.jpg
index 46e3009..4bf8fa6 100644
Binary files a/Day31-35/res/ken_young.jpg and b/Day31-35/res/ken_young.jpg differ
diff --git a/Day31-35/res/linus.png b/Day31-35/res/linus.png
index acbe3bf..b2e1620 100644
Binary files a/Day31-35/res/linus.png and b/Day31-35/res/linus.png differ
diff --git a/Day31-35/res/linux-network-config.png b/Day31-35/res/linux-network-config.png
index a3b01e6..1db952c 100644
Binary files a/Day31-35/res/linux-network-config.png and b/Day31-35/res/linux-network-config.png differ
diff --git a/Day31-35/res/pdp-11.jpg b/Day31-35/res/pdp-11.jpg
index 6b4486b..13cca8e 100644
Binary files a/Day31-35/res/pdp-11.jpg and b/Day31-35/res/pdp-11.jpg differ
diff --git a/Day31-35/res/pdp-7.png b/Day31-35/res/pdp-7.png
index 8e7ab9e..8246dd0 100644
Binary files a/Day31-35/res/pdp-7.png and b/Day31-35/res/pdp-7.png differ
diff --git a/Day31-35/res/vim-diff.png b/Day31-35/res/vim-diff.png
index 5fde982..5951e02 100644
Binary files a/Day31-35/res/vim-diff.png and b/Day31-35/res/vim-diff.png differ
diff --git a/Day31-35/res/vim-macro.png b/Day31-35/res/vim-macro.png
index 33b5eed..e80ef3e 100644
Binary files a/Day31-35/res/vim-macro.png and b/Day31-35/res/vim-macro.png differ
diff --git a/Day31-35/res/vim-multi-window.png b/Day31-35/res/vim-multi-window.png
index c6e17d7..85ef359 100644
Binary files a/Day31-35/res/vim-multi-window.png and b/Day31-35/res/vim-multi-window.png differ
diff --git a/Day36-40/36-38.关系型数据库MySQL.md b/Day36-40/36-38.关系型数据库MySQL.md
index ce1abf2..e733595 100644
--- a/Day36-40/36-38.关系型数据库MySQL.md
+++ b/Day36-40/36-38.关系型数据库MySQL.md
@@ -174,7 +174,7 @@ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行
alter user 'root'@'localhost' identified by '123456';
```
- > 说明:MySQL较新的版本默认不允许使用弱口令作为用户口令,所以我们通过上面的前两条命令修改了验证用户口令的策略和口令的长度。事实上我们不应该使用弱口令,因为存在用户口令被暴力破解的风险。近年来,攻击数据库窃取数据和劫持数据库勒索比特币的事件屡见不鲜,要避免这些潜在的风险,最为重要的一点是不要让数据库服务器暴露在公网上(最好的做法是将数据库置于内网,至少要做到不向公网开放数据库服务器的访问端口),另外要保管好`root`账号的口令,应用系统需要访问数据库时,通常不使用`root`账号进行访问,而是创建其他拥有适当权限的账号来访问。
+ > **说明**:MySQL较新的版本默认不允许使用弱口令作为用户口令,所以我们通过上面的前两条命令修改了验证用户口令的策略和口令的长度。事实上我们不应该使用弱口令,因为存在用户口令被暴力破解的风险。近年来,攻击数据库窃取数据和劫持数据库勒索比特币的事件屡见不鲜,要避免这些潜在的风险,最为重要的一点是不要让数据库服务器暴露在公网上(最好的做法是将数据库置于内网,至少要做到不向公网开放数据库服务器的访问端口),另外要保管好`root`账号的口令,应用系统需要访问数据库时,通常不使用`root`账号进行访问,而是创建其他拥有适当权限的账号来访问。
再次使用客户端工具连接MySQL服务器时,就可以使用新设置的口令了。在实际开发中,为了方便用户操作,可以选择图形化的客户端工具来连接MySQL服务器,包括:
@@ -936,15 +936,16 @@ drop index idx_student_name on tb_student;
创建视图。
```SQL
-create view vw_score
+create view vw_avg_score
as
- select sid, round(avg(score), 1) as avgscore from tb_record group by sid;
+ select sid, round(avg(score), 1) as avgscore
+ from tb_record group by sid;
create view vw_student_score
as
- select stuname, avgscore
- from tb_student, vw_score
- where stuid=sid;
+ select stuname, avgscore
+ from tb_student, vw_avg_score
+ where stuid=sid;
```
> **提示**:因为视图不包含数据,所以每次使用视图时,都必须执行查询以获得数据,如果你使用了连接查询、嵌套查询创建了较为复杂的视图,你可能会发现查询性能下降得很厉害。因此,在使用复杂的视图前,应该进行测试以确保其性能能够满足应用的需求。
@@ -1199,7 +1200,7 @@ insert into tb_emp values
# 1. 创建数据库连接对象
con = pymysql.connect(host='localhost', port=3306,
database='hrs', charset='utf8',
- user='root', password='123456')
+ user='yourname', password='yourpass')
try:
# 2. 通过连接对象获取游标
with con.cursor() as cursor:
@@ -1231,7 +1232,7 @@ insert into tb_emp values
no = int(input('编号: '))
con = pymysql.connect(host='localhost', port=3306,
database='hrs', charset='utf8',
- user='root', password='123456',
+ user='yourname', password='yourpass',
autocommit=True)
try:
with con.cursor() as cursor:
@@ -1263,7 +1264,7 @@ insert into tb_emp values
loc = input('所在地: ')
con = pymysql.connect(host='localhost', port=3306,
database='hrs', charset='utf8',
- user='root', password='123456',
+ user='yourname', password='yourpass',
autocommit=True)
try:
with con.cursor() as cursor:
@@ -1291,7 +1292,7 @@ insert into tb_emp values
def main():
con = pymysql.connect(host='localhost', port=3306,
database='hrs', charset='utf8',
- user='root', password='123456')
+ user='yourname', password='yourpass')
try:
with con.cursor(cursor=DictCursor) as cursor:
cursor.execute('select dno as no, dname as name, dloc as loc from tb_dept')
@@ -1334,7 +1335,7 @@ insert into tb_emp values
size = int(input('大小: '))
con = pymysql.connect(host='localhost', port=3306,
database='hrs', charset='utf8',
- user='root', password='123456')
+ user='yourname', password='yourpass')
try:
with con.cursor() as cursor:
cursor.execute(
diff --git a/Day36-40/39-40.NoSQL入门.md b/Day36-40/39-40.NoSQL入门.md
index c5fbe4c..35ab2ca 100644
--- a/Day36-40/39-40.NoSQL入门.md
+++ b/Day36-40/39-40.NoSQL入门.md
@@ -107,7 +107,7 @@ redis-server
方式一:通过参数指定认证口令和AOF持久化方式。
```Shell
-redis-server --requirepass 1qaz2wsx --appendonly yes
+redis-server --requirepass yourpass --appendonly yes
```
方式二:通过指定的配置文件来修改Redis的配置。
@@ -119,7 +119,7 @@ redis-server /root/redis-5.0.4/redis.conf
下面我们使用第一种方式来启动Redis并将其置于后台运行,将Redis产生的输出重定向到名为redis.log的文件中。
```Shell
-redis-server --requirepass 1qaz2wsx > redis.log &
+redis-server --requirepass yourpass > redis.log &
```
可以通过ps或者netstat来检查Redis服务器是否启动成功。
@@ -133,7 +133,7 @@ netstat -nap | grep redis-server
```Shell
redis-cli
-127.0.0.1:6379> auth 1qaz2wsx
+127.0.0.1:6379> auth yourpass
OK
127.0.0.1:6379> ping
PONG
@@ -274,7 +274,7 @@ python3
```Python
>>> import redis
->>> client = redis.Redis(host='1.2.3.4', port=6379, password='1qaz2wsx')
+>>> client = redis.Redis(host='1.2.3.4', port=6379, password='yourpass')
>>> client.set('username', 'admin')
True
>>> client.hset('student', 'name', 'hao')
diff --git a/Day36-40/code/contact/main.py b/Day36-40/code/contact/main.py
index 2e95a33..70ebb36 100644
--- a/Day36-40/code/contact/main.py
+++ b/Day36-40/code/contact/main.py
@@ -171,8 +171,8 @@ def find_contacters(con):
def main():
- con = pymysql.connect(host='120.77.222.217', port=3306,
- user='root', passwd='123456',
+ con = pymysql.connect(host='1.2.3.4', port=3306,
+ user='yourname', passwd='yourpass',
db='address', charset='utf8',
autocommit=True,
cursorclass=pymysql.cursors.DictCursor)
diff --git a/Day36-40/res/conceptual_model.png b/Day36-40/res/conceptual_model.png
index 2b93a21..2516ba3 100644
Binary files a/Day36-40/res/conceptual_model.png and b/Day36-40/res/conceptual_model.png differ
diff --git a/Day36-40/res/er_diagram.png b/Day36-40/res/er_diagram.png
index e851a30..434216e 100644
Binary files a/Day36-40/res/er_diagram.png and b/Day36-40/res/er_diagram.png differ
diff --git a/Day36-40/res/redis-aof.png b/Day36-40/res/redis-aof.png
index bdd13c6..567c841 100644
Binary files a/Day36-40/res/redis-aof.png and b/Day36-40/res/redis-aof.png differ
diff --git a/Day36-40/res/redis-bind-and-port.png b/Day36-40/res/redis-bind-and-port.png
index 1abc58f..2c2fb85 100644
Binary files a/Day36-40/res/redis-bind-and-port.png and b/Day36-40/res/redis-bind-and-port.png differ
diff --git a/Day36-40/res/redis-data-types.png b/Day36-40/res/redis-data-types.png
index bb6d74e..a45625c 100644
Binary files a/Day36-40/res/redis-data-types.png and b/Day36-40/res/redis-data-types.png differ
diff --git a/Day36-40/res/redis-databases.png b/Day36-40/res/redis-databases.png
index d076640..b6f8a92 100644
Binary files a/Day36-40/res/redis-databases.png and b/Day36-40/res/redis-databases.png differ
diff --git a/Day36-40/res/redis-hash.png b/Day36-40/res/redis-hash.png
index 29329af..0d86fd0 100644
Binary files a/Day36-40/res/redis-hash.png and b/Day36-40/res/redis-hash.png differ
diff --git a/Day36-40/res/redis-list.png b/Day36-40/res/redis-list.png
index 2ccd893..00f23f3 100644
Binary files a/Day36-40/res/redis-list.png and b/Day36-40/res/redis-list.png differ
diff --git a/Day36-40/res/redis-rdb-1.png b/Day36-40/res/redis-rdb-1.png
index 55dcc61..b868bc5 100644
Binary files a/Day36-40/res/redis-rdb-1.png and b/Day36-40/res/redis-rdb-1.png differ
diff --git a/Day36-40/res/redis-rdb-3.png b/Day36-40/res/redis-rdb-3.png
index fd9983e..958702f 100644
Binary files a/Day36-40/res/redis-rdb-3.png and b/Day36-40/res/redis-rdb-3.png differ
diff --git a/Day36-40/res/redis-replication.png b/Day36-40/res/redis-replication.png
index c1f67a0..297b953 100644
Binary files a/Day36-40/res/redis-replication.png and b/Day36-40/res/redis-replication.png differ
diff --git a/Day36-40/res/redis-security.png b/Day36-40/res/redis-security.png
index 44c2fee..3c70471 100644
Binary files a/Day36-40/res/redis-security.png and b/Day36-40/res/redis-security.png differ
diff --git a/Day36-40/res/redis-set.png b/Day36-40/res/redis-set.png
index 1962b16..b9f15c3 100644
Binary files a/Day36-40/res/redis-set.png and b/Day36-40/res/redis-set.png differ
diff --git a/Day36-40/res/redis-slow-logs.png b/Day36-40/res/redis-slow-logs.png
index 474a1db..6cc46ce 100644
Binary files a/Day36-40/res/redis-slow-logs.png and b/Day36-40/res/redis-slow-logs.png differ
diff --git a/Day36-40/res/redis-string.png b/Day36-40/res/redis-string.png
index 7cef9df..60d9b5b 100644
Binary files a/Day36-40/res/redis-string.png and b/Day36-40/res/redis-string.png differ
diff --git a/Day36-40/res/redis-zset.png b/Day36-40/res/redis-zset.png
index d3df613..c87b486 100644
Binary files a/Day36-40/res/redis-zset.png and b/Day36-40/res/redis-zset.png differ
diff --git a/Day41-55/41.快速上手.md b/Day41-55/41.快速上手.md
index 76a9af0..2ee1ec2 100644
--- a/Day41-55/41.快速上手.md
+++ b/Day41-55/41.快速上手.md
@@ -33,13 +33,13 @@ HTTP响应(响应行+响应头+空行+消息体):
![](./res/http-response.png)
-> 说明:这两张图是在2009年9月10日截取的,但愿这两张如同泛黄的照片般的截图能帮助你了解HTTP到底是什么样子的。
+> 说明:这两张图是在2009年9月10日凌晨获得的,但愿这两张如同泛黄的照片般的截图能帮助你了解HTTP到底是什么样子的。
### Django概述
-Python的Web框架有上百个,比它的关键字还要多。所谓Web框架,就是用于开发Web服务器端应用的基础设施(通常指封装好的模块和一系列的工具)。事实上,即便没有Web框架,我们仍然可以通过socket或[CGI](https://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E7%BD%91%E5%85%B3%E6%8E%A5%E5%8F%A3)来开发Web服务器端应用,但是这样做的成本和代价在实际开发中通常是不能接受的。通过Web框架,我们可以化繁为简,同时降低创建、更新、扩展应用程序的工作量。Python的Web框架中比较有名的有:Flask、Django、Tornado、Sanic、Pyramid、Bottle、Web2py、web.py等。
+Python的Web框架有上百个,比它的关键字还要多。所谓Web框架,就是用于开发Web服务器端应用的基础设施,说得通俗一点就是一系列封装好的模块和工具。事实上,即便没有Web框架,我们仍然可以通过socket或[CGI](https://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E7%BD%91%E5%85%B3%E6%8E%A5%E5%8F%A3)来开发Web服务器端应用,但是这样做的成本和代价在商业项目中通常是不能接受的。通过Web框架,我们可以化繁为简,降低创建、更新、扩展应用程序的工作量。刚才我们说到Python有上百个Web框架,这些框架包括Django、Flask、Tornado、Sanic、Pyramid、Bottle、Web2py、web.py等。
-在基于Python的Web框架中,Django是所有重量级选手中最有代表性的一位,开发者可以基于Django快速的开发可靠的Web应用程序,因为它减少了Web开发中不必要的开销,对常用的设计和开发模式进行了封装,并对MVC架构提供了支持(MTV)。许多成功的网站和App都是基于Django框架构建的,国内比较有代表性的网站包括:知乎、豆瓣网、果壳网、搜狐闪电邮箱、101围棋网、海报时尚网、背书吧、堆糖、手机搜狐网、咕咚、爱福窝、果库等。
+在上述Python的Web框架中,Django无疑是最有代表性的重量级选手,开发者可以基于Django快速的开发可靠的Web应用程序,因为它减少了Web开发中不必要的开销,对常用的设计和开发模式进行了封装,并对MVC架构提供了支持(Django中称之为MTV架构)。许多成功的网站和应用都是基于Django框架构建的,国内比较有代表性的网站包括:知乎、豆瓣网、果壳网、搜狐闪电邮箱、101围棋网、海报时尚网、背书吧、堆糖、手机搜狐网、咕咚、爱福窝、果库等。
![](./res/mvc.png)
@@ -51,7 +51,7 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目
1. 检查Python环境:Django 1.11需要Python 2.7或Python 3.4以上的版本;Django 2.0需要Python 3.4以上的版本;Django 2.1需要Python 3.5以上的版本。
- > 说明:我自己平时使用macOS做开发,macOS和Linux平台使用的命令跟Windows平台有较大的区别,这一点在之前也有过类似的说明,如果使用Windows平台做开发,替换一下对应的命令即可。
+ > 说明:我自己平时使用macOS和Linux系统做开发,macOS和Linux系统在命令的使用上跟Windows系统还是有一些差别,如果使用Windows平台做开发,要使用Windows平台对应的命令。
```Shell
$ python3 --version
@@ -64,66 +64,37 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目
>>> sys.version_info
```
-2. 创建项目文件夹并切换到该目录,例如我们要实例一个OA(办公自动化)项目。
+2. 更新包管理工具并安装Django管理工具。
```Shell
- $ mkdir oa
- $ cd oa
+ $ pip3 install -U pip
+ $ pip3 install django
```
-3. 创建并激活虚拟环境。
+3. 使用Django管理工具创建Django项目(项目名称为hellodjango)。
```Shell
- $ python3 -m venv venv
- $ source venv/bin/activate
+ $ django-admin startproject hellodjango
```
> 说明:上面使用了Python自带的venv模块完成了虚拟环境的创建,当然也可以使用virtualenv或pipenv这样的工具。要激活虚拟环境,在Windows环境下可以通过"venv/Scripts/activate"执行批处理文件来实现。
-4. 更新包管理工具pip。
+4. 进入项目文件夹,创建并激活虚拟环境。
```Shell
- (venv)$ pip install -U pip
+ $ cd hellodjango
+ $ python3 -m venv venv
+ $ source venv/bin/activate
```
- 或
+ > **提示**:上面使用了Python 3自带的`venv`模块来创建虚拟环境,当然也可以使用如`virtualenv`这样的三方工具来创建虚拟环境;激活虚拟环境后请注意终端中提示符的变化,在虚拟环境下使用Python解释器和包管理工具时,对应的命令是`python`和`pip`,而不再需要键入`python3`和`pip3`。
+
+5. 在虚拟环境中安装项目依赖项。
```Shell
- (venv)$ python -m pip install -U pip
- ```
- > 注意:请注意终端提示符发生的变化,前面的`(venv)`说明我们已经进入虚拟环境,而虚拟环境下的python和pip已经是Python 3的解释器和包管理工具了。
-
-5. 安装Django。
-
- ```Shell
- (venv)$ pip install django
+ (venv)$ pip install django mysqlclient django-redis pillow requests
```
- 或指定版本号来安装对应的Django的版本。
-
- ```Shell
- (venv)$ pip install django==2.1.8
- ```
-
-6. 检查Django的版本。
-
- ```Shell
- (venv)$ python -m django --version
- (venv)$ django-admin --version
- ```
-
- 或
-
- ```Shell
- (venv)$ python
- >>> import django
- >>> django.get_version()
- ```
- 当然,也可以通过pip来查看安装的依赖库及其版本,如:
-
- ```Shell
- (venv)$ pip freeze
- (venv)$ pip list
- ```
+ > **提示**:使用`pip`安装三方库时,可以通过如`django==1.11.27`的方式来指定三方库的版本。
下图展示了Django版本和Python版本的对应关系,如果在安装时没有指定版本号,将自动选择最新的版本(在写作这段内容时,Django最新的版本是2.2)。
@@ -135,25 +106,17 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目
| 2.0 | 3.4、3.5、3.6、3.7 |
| 2.1、2.2 | 3.5、3.6、3.7 |
-7. 使用`django-admin`创建项目,项目命名为oa。
-
- ```Shell
- (venv)$ django-admin startproject oa .
- ```
-
- > 注意:上面的命令最后的那个点,它表示在当前路径下创建项目。
-
- 执行上面的命令后看看生成的文件和文件夹,它们的作用如下所示:
+ 刚才创建的Django项目其文件和文件夹如下所示:
- `manage.py`: 一个让你可以管理Django项目的工具程序。
- - `oa/__init__.py`:一个空文件,告诉Python解释器这个目录应该被视为一个Python的包。
- - `oa/settings.py`:Django项目的配置文件。
- - `oa/urls.py`:Django项目的URL声明(URL映射),就像是你的网站的“目录”。
- - `oa/wsgi.py`:项目运行在WSGI兼容Web服务器上的接口文件。
+ - `hellodjango/__init__.py`:一个空文件,告诉Python解释器这个目录应该被视为一个Python的包。
+ - `hellodjango/settings.py`:Django项目的配置文件。
+ - `hellodjango/urls.py`:Django项目的URL声明(URL映射),就像是你的网站的“目录”。
+ - `hellodjango/wsgi.py`:项目运行在WSGI兼容Web服务器上的接口文件。
> 说明:WSGI全称是Web服务器网关接口,维基百科上给出的解释是“为Python语言定义的[Web服务器](https://zh.wikipedia.org/wiki/%E7%B6%B2%E9%A0%81%E4%BC%BA%E6%9C%8D%E5%99%A8)和[Web应用程序](https://zh.wikipedia.org/wiki/%E7%BD%91%E7%BB%9C%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F)或框架之间的一种简单而通用的接口”。
-8. 启动服务器运行项目。
+6. 启动Django自带的服务器运行项目。
```Shell
(venv)$ python manage.py runserver
@@ -161,21 +124,20 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目
在浏览器中输入访问我们的服务器,效果如下图所示。
+ > **说明1**:刚刚启动的是Django自带的用于开发和测试的服务器,它是一个用纯Python编写的轻量级Web服务器,但它并不是真正意义上的生产级别的服务器,千万不要将这个服务器用于和生产环境相关的任何地方。
+ >
+ > **说明2**:用于开发的服务器在需要的情况下会对每一次的访问请求重新载入一遍Python代码。所以你不需要为了让修改的代码生效而频繁的重新启动服务器。然而,一些动作,比如添加新文件,将不会触发自动重新加载,这时你得自己手动重启服务器。
+ >
+ > **说明3**:可以通过`python manage.py help`命令查看可用命令列表;在启动服务器时,也可以通过`python manage.py runserver 1.2.3.4:5678`来指定将服务器运行于哪个IP地址和端口。
+ >
+ > **说明4**:可以通过Ctrl+C来终止服务器的运行。
+
![](./res/django-index-1.png)
-
- > 说明1:刚刚启动的是Django自带的用于开发和测试的服务器,它是一个用纯Python编写的轻量级Web服务器,但它并不是真正意义上的生产级别的服务器,千万不要将这个服务器用于和生产环境相关的任何地方。
-
- > 说明2:用于开发的服务器在需要的情况下会对每一次的访问请求重新载入一遍Python代码。所以你不需要为了让修改的代码生效而频繁的重新启动服务器。然而,一些动作,比如添加新文件,将不会触发自动重新加载,这时你得自己手动重启服务器。
-
- > 说明3:可以通过`python manage.py help`命令查看可用命令列表;在启动服务器时,也可以通过`python manage.py runserver 1.2.3.4:5678`来指定将服务器运行于哪个IP地址和端口。
-
- > 说明4:可以通过Ctrl+C来终止服务器的运行。
-
-9. 接下来我们修改项目的配置文件settings.py,Django是一个支持国际化和本地化的框架,因此刚才我们看到的默认首页也是支持国际化的,我们将默认语言修改为中文,时区设置为东八区。
+7. 修改项目的配置文件settings.py,Django是一个支持国际化和本地化的框架,因此刚才我们看到的默认首页也是支持国际化的,我们将默认语言修改为中文,时区设置为东八区。
```Shell
- (venv)$ vim oa/settings.py
+ (venv)$ vim hellodjango/settings.py
```
```Python
@@ -189,9 +151,10 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目
# 此处省略下面的内容
```
-10. 刷新刚才的页面。
+ 刷新刚才的页面,可以看到修改语言代码和时区之后的结果。
+
+ ![](./res/django-index-2.png)
- ![](./res/django-index-2.png)
#### 动态页面
diff --git a/Day41-55/42.深入模型.md b/Day41-55/42.深入模型.md
index 3ba9035..8b07012 100644
--- a/Day41-55/42.深入模型.md
+++ b/Day41-55/42.深入模型.md
@@ -29,10 +29,10 @@
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'oa',
- 'HOST': '127.0.0.1',
+ 'HOST': '1.2.3.4',
'PORT': 3306,
- 'USER': 'root',
- 'PASSWORD': '123456',
+ 'USER': 'yourname',
+ 'PASSWORD': 'yourpass',
}
}
diff --git a/Day41-55/code/shop/shop/settings.py b/Day41-55/code/shop/shop/settings.py
index f90e37a..deffdda 100644
--- a/Day41-55/code/shop/shop/settings.py
+++ b/Day41-55/code/shop/shop/settings.py
@@ -81,8 +81,8 @@ DATABASES = {
'NAME': 'shop',
'HOST': 'localhost',
'PORT': 3306,
- 'USER': 'root',
- 'PASSWORD': '123456',
+ 'USER': 'yourname',
+ 'PASSWORD': 'yourpass',
}
}
diff --git a/Day41-55/code/shop_origin/shop/settings.py b/Day41-55/code/shop_origin/shop/settings.py
index 337b099..53db105 100644
--- a/Day41-55/code/shop_origin/shop/settings.py
+++ b/Day41-55/code/shop_origin/shop/settings.py
@@ -81,8 +81,8 @@ DATABASES = {
'NAME': 'Shop',
'HOST': 'localhost',
'PORT': 3306,
- 'USER': 'root',
- 'PASSWORD': '123456',
+ 'USER': 'yourname',
+ 'PASSWORD': 'yourpass',
}
}
diff --git a/Day41-55/res/CSRF.png b/Day41-55/res/CSRF.png
index 6e87482..3108524 100644
Binary files a/Day41-55/res/CSRF.png and b/Day41-55/res/CSRF.png differ
diff --git a/Day41-55/res/Django-Flowchart.png b/Day41-55/res/Django-Flowchart.png
index ef96033..2ed0cfe 100644
Binary files a/Day41-55/res/Django-Flowchart.png and b/Day41-55/res/Django-Flowchart.png differ
diff --git a/Day41-55/res/Django-MTV.png b/Day41-55/res/Django-MTV.png
index 76f330e..b075e93 100644
Binary files a/Day41-55/res/Django-MTV.png and b/Day41-55/res/Django-MTV.png differ
diff --git a/Day41-55/res/admin-login.png b/Day41-55/res/admin-login.png
index 15ea232..6ccecb0 100644
Binary files a/Day41-55/res/admin-login.png and b/Day41-55/res/admin-login.png differ
diff --git a/Day41-55/res/admin-model-create.png b/Day41-55/res/admin-model-create.png
index 5d07891..ce3c78e 100644
Binary files a/Day41-55/res/admin-model-create.png and b/Day41-55/res/admin-model-create.png differ
diff --git a/Day41-55/res/admin-model-delete-and-update.png b/Day41-55/res/admin-model-delete-and-update.png
index 87d709c..2d02419 100644
Binary files a/Day41-55/res/admin-model-delete-and-update.png and b/Day41-55/res/admin-model-delete-and-update.png differ
diff --git a/Day41-55/res/admin-model-depts.png b/Day41-55/res/admin-model-depts.png
index 7214544..3a26d75 100644
Binary files a/Day41-55/res/admin-model-depts.png and b/Day41-55/res/admin-model-depts.png differ
diff --git a/Day41-55/res/admin-model-emps-modified.png b/Day41-55/res/admin-model-emps-modified.png
index 27c6205..d607c3a 100644
Binary files a/Day41-55/res/admin-model-emps-modified.png and b/Day41-55/res/admin-model-emps-modified.png differ
diff --git a/Day41-55/res/admin-model-emps.png b/Day41-55/res/admin-model-emps.png
index b9b7953..7b19f1b 100644
Binary files a/Day41-55/res/admin-model-emps.png and b/Day41-55/res/admin-model-emps.png differ
diff --git a/Day41-55/res/admin-model-read.png b/Day41-55/res/admin-model-read.png
index 135437b..6ba71ae 100644
Binary files a/Day41-55/res/admin-model-read.png and b/Day41-55/res/admin-model-read.png differ
diff --git a/Day41-55/res/admin-model.png b/Day41-55/res/admin-model.png
index 12fa9e0..78862bf 100644
Binary files a/Day41-55/res/admin-model.png and b/Day41-55/res/admin-model.png differ
diff --git a/Day41-55/res/admin-welcome.png b/Day41-55/res/admin-welcome.png
index 2371ffe..6ab06cf 100644
Binary files a/Day41-55/res/admin-welcome.png and b/Day41-55/res/admin-welcome.png differ
diff --git a/Day41-55/res/captcha.png b/Day41-55/res/captcha.png
index 59b8065..a0a5ce5 100644
Binary files a/Day41-55/res/captcha.png and b/Day41-55/res/captcha.png differ
diff --git a/Day41-55/res/cookie_xstorage_indexeddb.png b/Day41-55/res/cookie_xstorage_indexeddb.png
index a08c257..e21643f 100644
Binary files a/Day41-55/res/cookie_xstorage_indexeddb.png and b/Day41-55/res/cookie_xstorage_indexeddb.png differ
diff --git a/Day41-55/res/django-index-1.png b/Day41-55/res/django-index-1.png
index df4ba60..65ccc75 100644
Binary files a/Day41-55/res/django-index-1.png and b/Day41-55/res/django-index-1.png differ
diff --git a/Day41-55/res/django-index-2.png b/Day41-55/res/django-index-2.png
index 6b5edb9..7c19786 100644
Binary files a/Day41-55/res/django-index-2.png and b/Day41-55/res/django-index-2.png differ
diff --git a/Day41-55/res/echarts_bar_graph.png b/Day41-55/res/echarts_bar_graph.png
index a77571f..d2f40ea 100644
Binary files a/Day41-55/res/echarts_bar_graph.png and b/Day41-55/res/echarts_bar_graph.png differ
diff --git a/Day41-55/res/er-graph.png b/Day41-55/res/er-graph.png
index 9072a9d..0046537 100644
Binary files a/Day41-55/res/er-graph.png and b/Day41-55/res/er-graph.png differ
diff --git a/Day41-55/res/http-request.png b/Day41-55/res/http-request.png
index aca9287..67d0fb6 100644
Binary files a/Day41-55/res/http-request.png and b/Day41-55/res/http-request.png differ
diff --git a/Day41-55/res/http-response.png b/Day41-55/res/http-response.png
index f2b8ae3..0de1144 100644
Binary files a/Day41-55/res/http-response.png and b/Day41-55/res/http-response.png differ
diff --git a/Day41-55/res/mvc.png b/Day41-55/res/mvc.png
index 7ba14ba..b12ee5a 100644
Binary files a/Day41-55/res/mvc.png and b/Day41-55/res/mvc.png differ
diff --git a/Day41-55/res/sessionid_from_cookie.png b/Day41-55/res/sessionid_from_cookie.png
index 6dfc76e..23b40aa 100644
Binary files a/Day41-55/res/sessionid_from_cookie.png and b/Day41-55/res/sessionid_from_cookie.png differ
diff --git a/Day41-55/res/show-depts.png b/Day41-55/res/show-depts.png
index ceaecc8..65023a2 100644
Binary files a/Day41-55/res/show-depts.png and b/Day41-55/res/show-depts.png differ
diff --git a/Day41-55/res/show_subjects.png b/Day41-55/res/show_subjects.png
index 9e7612d..e4175e1 100644
Binary files a/Day41-55/res/show_subjects.png and b/Day41-55/res/show_subjects.png differ
diff --git a/Day41-55/res/show_teachers.png b/Day41-55/res/show_teachers.png
index 031ac57..66ba00a 100644
Binary files a/Day41-55/res/show_teachers.png and b/Day41-55/res/show_teachers.png differ
diff --git a/Day41-55/res/web-application.png b/Day41-55/res/web-application.png
index 89d2dec..5a18949 100644
Binary files a/Day41-55/res/web-application.png and b/Day41-55/res/web-application.png differ
diff --git a/Day61-65/code/hello-tornado/example04.py b/Day61-65/code/hello-tornado/example04.py
index 7b9204c..405b00c 100644
--- a/Day61-65/code/hello-tornado/example04.py
+++ b/Day61-65/code/hello-tornado/example04.py
@@ -14,8 +14,10 @@ from tornado.options import define, options, parse_command_line
define('port', default=8888, type=int)
+# 请求天行数据提供的API数据接口
REQ_URL = 'http://api.tianapi.com/guonei/'
-API_KEY = '772a81a51ae5c780251b1f98ea431b84'
+# 在天行数据网站注册后可以获得API_KEY
+API_KEY = 'your_personal_api_key'
class MainHandler(tornado.web.RequestHandler):
diff --git a/Day61-65/code/hello-tornado/example05.py b/Day61-65/code/hello-tornado/example05.py
index e2f6117..5ae1ced 100644
--- a/Day61-65/code/hello-tornado/example05.py
+++ b/Day61-65/code/hello-tornado/example05.py
@@ -14,8 +14,10 @@ from tornado.options import define, options, parse_command_line
define('port', default=8888, type=int)
+# 请求天行数据提供的API数据接口
REQ_URL = 'http://api.tianapi.com/guonei/'
-API_KEY = '772a81a51ae5c780251b1f98ea431b84'
+# 在天行数据网站注册后可以获得API_KEY
+API_KEY = 'your_personal_api_key'
class MainHandler(tornado.web.RequestHandler):
diff --git a/Day61-65/code/hello-tornado/example06.py b/Day61-65/code/hello-tornado/example06.py
index 7143351..bcb02bf 100644
--- a/Day61-65/code/hello-tornado/example06.py
+++ b/Day61-65/code/hello-tornado/example06.py
@@ -15,13 +15,13 @@ define('port', default=8888, type=int)
async def connect_mysql():
return await aiomysql.connect(
- host='120.77.222.217',
+ host='1.2.3.4',
port=3306,
db='hrs',
charset='utf8',
use_unicode=True,
- user='root',
- password='123456',
+ user='yourname',
+ password='yourpass',
)
diff --git a/Day61-65/code/hello-tornado/example07.py b/Day61-65/code/hello-tornado/example07.py
index e0d454c..df38980 100644
--- a/Day61-65/code/hello-tornado/example07.py
+++ b/Day61-65/code/hello-tornado/example07.py
@@ -21,13 +21,13 @@ define('port', default=8888, type=int)
def get_mysql_connection():
return connect(
- host='120.77.222.217',
+ host='1.2.3.4',
port=3306,
db='hrs',
charset='utf8',
use_unicode=True,
- user='root',
- password='123456',
+ user='yourname',
+ password='yourpass',
)
diff --git a/Day61-65/code/project_of_tornado/backend_server.py b/Day61-65/code/project_of_tornado/backend_server.py
index 610d301..973242f 100644
--- a/Day61-65/code/project_of_tornado/backend_server.py
+++ b/Day61-65/code/project_of_tornado/backend_server.py
@@ -19,13 +19,13 @@ from service.handlers.handlers_for_charts import ChartHandler
async def connect_mysql():
return await aiomysql.connect(
- host='120.77.222.217',
+ host='1.2.3.4',
port=3306,
db='hrs',
charset='utf8',
use_unicode=True,
- user='root',
- password='123456',
+ user='yourname',
+ password='yourpass',
)
diff --git a/Day61-65/res/run-hello-world-app.png b/Day61-65/res/run-hello-world-app.png
index f744358..a4bd7eb 100644
Binary files a/Day61-65/res/run-hello-world-app.png and b/Day61-65/res/run-hello-world-app.png differ
diff --git a/Day61-65/res/websocket.png b/Day61-65/res/websocket.png
index 7973399..880d6b4 100644
Binary files a/Day61-65/res/websocket.png and b/Day61-65/res/websocket.png differ
diff --git a/Day61-65/res/ws_wss.png b/Day61-65/res/ws_wss.png
index ac4b0e5..c71d386 100644
Binary files a/Day61-65/res/ws_wss.png and b/Day61-65/res/ws_wss.png differ
diff --git a/Day66-75/res/api-image360.png b/Day66-75/res/api-image360.png
index 2e4a406..6d21497 100644
Binary files a/Day66-75/res/api-image360.png and b/Day66-75/res/api-image360.png differ
diff --git a/Day66-75/res/baidu-search-taobao.png b/Day66-75/res/baidu-search-taobao.png
index d013ba8..b551aab 100644
Binary files a/Day66-75/res/baidu-search-taobao.png and b/Day66-75/res/baidu-search-taobao.png differ
diff --git a/Day66-75/res/chrome-developer-tools.png b/Day66-75/res/chrome-developer-tools.png
index aad8fc7..c4be32c 100644
Binary files a/Day66-75/res/chrome-developer-tools.png and b/Day66-75/res/chrome-developer-tools.png differ
diff --git a/Day66-75/res/crawler-workflow.png b/Day66-75/res/crawler-workflow.png
index 9adf5fd..4175285 100644
Binary files a/Day66-75/res/crawler-workflow.png and b/Day66-75/res/crawler-workflow.png differ
diff --git a/Day66-75/res/douban-xpath.png b/Day66-75/res/douban-xpath.png
index 3ecb737..9af1046 100644
Binary files a/Day66-75/res/douban-xpath.png and b/Day66-75/res/douban-xpath.png differ
diff --git a/Day66-75/res/http-request.png b/Day66-75/res/http-request.png
index aca9287..67d0fb6 100644
Binary files a/Day66-75/res/http-request.png and b/Day66-75/res/http-request.png differ
diff --git a/Day66-75/res/http-response.png b/Day66-75/res/http-response.png
index f2b8ae3..0de1144 100644
Binary files a/Day66-75/res/http-response.png and b/Day66-75/res/http-response.png differ
diff --git a/Day66-75/res/image360-website.png b/Day66-75/res/image360-website.png
index ffbb0d1..e394332 100644
Binary files a/Day66-75/res/image360-website.png and b/Day66-75/res/image360-website.png differ
diff --git a/Day66-75/res/postman.png b/Day66-75/res/postman.png
index 0e9b6ed..54159c2 100644
Binary files a/Day66-75/res/postman.png and b/Day66-75/res/postman.png differ
diff --git a/Day66-75/res/redis-save.png b/Day66-75/res/redis-save.png
index d721dd8..8cf5e75 100644
Binary files a/Day66-75/res/redis-save.png and b/Day66-75/res/redis-save.png differ
diff --git a/Day66-75/res/scrapy-architecture.png b/Day66-75/res/scrapy-architecture.png
index 5fe393f..5a96766 100644
Binary files a/Day66-75/res/scrapy-architecture.png and b/Day66-75/res/scrapy-architecture.png differ
diff --git a/Day66-75/res/tesseract.gif b/Day66-75/res/tesseract.gif
index 4df18a7..c5476f3 100644
Binary files a/Day66-75/res/tesseract.gif and b/Day66-75/res/tesseract.gif differ
diff --git a/Day76-90/res/201205230001213115.png b/Day76-90/res/201205230001213115.png
deleted file mode 100644
index 48525f6..0000000
Binary files a/Day76-90/res/201205230001213115.png and /dev/null differ
diff --git a/Day76-90/res/201205230001238839.png b/Day76-90/res/201205230001238839.png
deleted file mode 100644
index 0d0be92..0000000
Binary files a/Day76-90/res/201205230001238839.png and /dev/null differ
diff --git a/Day76-90/res/20120523000125800.png b/Day76-90/res/20120523000125800.png
deleted file mode 100644
index 4133386..0000000
Binary files a/Day76-90/res/20120523000125800.png and /dev/null differ
diff --git a/Day76-90/res/result-in-jupyter.png b/Day76-90/res/result-in-jupyter.png
index d2c3847..a98c76d 100644
Binary files a/Day76-90/res/result-in-jupyter.png and b/Day76-90/res/result-in-jupyter.png differ
diff --git a/Day76-90/res/result1.png b/Day76-90/res/result1.png
index d455137..9b1feca 100644
Binary files a/Day76-90/res/result1.png and b/Day76-90/res/result1.png differ
diff --git a/Day76-90/res/result2.png b/Day76-90/res/result2.png
index 05140f9..0398869 100644
Binary files a/Day76-90/res/result2.png and b/Day76-90/res/result2.png differ
diff --git a/Day76-90/res/result3.png b/Day76-90/res/result3.png
index aa9c81d..a9ebe77 100644
Binary files a/Day76-90/res/result3.png and b/Day76-90/res/result3.png differ
diff --git a/Day76-90/res/result4.png b/Day76-90/res/result4.png
index 8081ea0..a02e7f1 100644
Binary files a/Day76-90/res/result4.png and b/Day76-90/res/result4.png differ
diff --git a/Day76-90/res/result5.png b/Day76-90/res/result5.png
index 53f0e1d..178ed9a 100644
Binary files a/Day76-90/res/result5.png and b/Day76-90/res/result5.png differ
diff --git a/Day76-90/res/result6.png b/Day76-90/res/result6.png
index 576c80d..63ac845 100644
Binary files a/Day76-90/res/result6.png and b/Day76-90/res/result6.png differ
diff --git a/Day76-90/res/result7.png b/Day76-90/res/result7.png
index 8f3d9fa..c8c2e26 100644
Binary files a/Day76-90/res/result7.png and b/Day76-90/res/result7.png differ
diff --git a/Day76-90/res/result8.png b/Day76-90/res/result8.png
index 44a2d8b..694b796 100644
Binary files a/Day76-90/res/result8.png and b/Day76-90/res/result8.png differ
diff --git a/Day76-90/res/result9.png b/Day76-90/res/result9.png
index d9a77c2..bb6bbc3 100644
Binary files a/Day76-90/res/result9.png and b/Day76-90/res/result9.png differ
diff --git a/Day91-100/100.Python面试题集.md b/Day91-100/100.Python面试题集.md
index c00a910..9e12dba 100644
--- a/Day91-100/100.Python面试题集.md
+++ b/Day91-100/100.Python面试题集.md
@@ -2,14 +2,24 @@
1. 说一说Python中的新式类和旧式类有什么区别。
+ 答:
+
2. Python中`is`运算符和`==`运算符有什么区别?
+ 答:请参考[《那些年我们踩过的那些坑》](../番外篇/那些年我们踩过的那些坑.md)。
+
3. Python中如何动态设置和获取对象属性?
+ 答:`setattr(object, name, value)`和`getattr(object, name[, default])`内置函数,其中`object`是对象,`name`是对象的属性名,`value`是属性值。这两个函数会调用对象的`__getattr__`和`__setattr__`魔术方法。
+
4. Python如何实现内存管理?有没有可能出现内存泄露的问题?
+ 答:
+
5. 阐述列表和集合的底层实现原理。
+ 答:
+
6. 现有字典`d = {'a': 24, 'g': 52, 'i': 12, 'k': 33}`,如何按字典中的值对字典进行排序得到排序后的字典。
答:
@@ -59,14 +69,10 @@
''.join(reversed('hello'))
```
- 或
-
```Python
'hello'[::-1]
```
- 或
-
```Python
def reverse(content):
return ''.join(content[i] for i in range(len(content) - 1, -1, -1))
@@ -74,8 +80,6 @@
reverse('hello')
```
- 或
-
```Python
def reverse(content):
return reverse(content[1:]) + content[0] if len(content) > 1 else content
@@ -98,6 +102,12 @@
11. 写一个返回bool值的函数,判断给定的非负整数是不是回文数。
+ 答:
+
+ ```Python
+
+ ```
+
12. 用一行代码实现求任意非负整数的阶乘。
答:
@@ -118,18 +128,56 @@
14. 删除列表中的重复元素并保留原有的顺序。
+ 答:
+
+ ```Python
+
+ ```
+
15. 找出两个列表中的相同元素和不同元素。
+ 答:
+
16. 列表中的某个元素出现次数占列表元素总数的半数以上,找出这个元素。
+ 答:
+
+ ```Python
+
+ ```
+
17. 实现对有序列表进行二分查找的算法。
+ 答:
+
+ ```Python
+
+ ```
+
18. 输入年月日,输出这一天是这一年的第几天。
+ 答:
+
+ ```Python
+
+ ```
+
19. 统计一个字符串中各个字符出现的次数。
+ 答:
+
+ ```Python
+
+ ```
+
20. 在Python中如何实现单例模式?
+ 答:
+
+ ```Python
+
+ ```
+
21. 下面的代码会输出什么。
```Python
@@ -151,8 +199,20 @@
22. 实现一个记录函数执行时间的装饰器。
+ 答:
+
+ ```Python
+
+ ```
+
23. 写一个遍历指定目录下指定后缀名的文件的函数。
+ 答:
+
+ ```Python
+
+ ```
+
24. 有如下所示的字典,请将其转换为CSV格式。
转换前:
@@ -199,30 +259,60 @@
27. 正则表达式对象的`search`和`match`方法有什么区别?
+ 答:
+
28. 当做个线程竞争一个对象且该对象并非线程安全的时候应该怎么办?
+ 答:
+
29. 说一下死锁产生的条件以及如何避免死锁的发生。
+ 答:
+
30. 请阐述TCP的优缺点。
+ 答:
+
31. HTTP请求的GET和POST有什么区别?
+ 答:
+
32. 说一些你知道的HTTP响应状态码。
+ 答:
+
33. 简单阐述HTTPS的工作原理。
+ 答:
+
34. 阐述Django项目中一个请求的生命周期。
+ 答:
+
35. Django项目中实现数据接口时如何解决跨域问题。
+ 答:
+
36. Django项目中如何对接Redis高速缓存服务。
+ 答:
+
37. 请说明Cookie和Session之间的关系。
+ 答:
+
38. 说一下索引的原理和作用。
+ 答:
+
39. 是否使用过Nginx实现负载均衡?用过哪些负载均衡算法?
+ 答:
+
40. 一个保存整数(int)的数组,除了一个元素出现过1次外,其他元素都出现过两次,请找出这个元素。
-41. 有12个外观相同的篮球,其中1个的重要和其他11个的重量不同(有可能轻有可能重),现在有一个天平可以使用,怎样才能通过最少的称重次数找出这颗与众不同的球。
\ No newline at end of file
+ 答:
+
+41. 有12个外观相同的篮球,其中1个的重要和其他11个的重量不同(有可能轻有可能重),现在有一个天平可以使用,怎样才能通过最少的称重次数找出这颗与众不同的球。
+
+ 答:
\ No newline at end of file
diff --git a/Day91-100/91.团队项目开发的问题和解决方案.md b/Day91-100/91.团队项目开发的问题和解决方案.md
index 428382d..df2ac87 100644
--- a/Day91-100/91.团队项目开发的问题和解决方案.md
+++ b/Day91-100/91.团队项目开发的问题和解决方案.md
@@ -382,6 +382,12 @@ Git不像SVN那样一定需要中央服务器才能工作,上面我们演示
git switch -c
```
+ 或
+
+ ```Shell
+ git checkout -b
+ ```
+
3. 在自己的分支上开发并在本地做版本控制。
4. 将自己的分支(工作成果)推到服务器。
@@ -421,21 +427,21 @@ Git不像SVN那样一定需要中央服务器才能工作,上面我们演示
创建`feature`分支:
```Shell
- git switch -c myfeature develop
+ git switch -c feature/user develop
```
或
```Shell
- git checkout -b myfeature develop
+ git checkout -b feature/user develop
```
- 将`feature`分支合并到`develop`分支:
+ 接下来就是在`feature`分支上进行开发并实施版本控制,这一段如何操作我们就不再赘述了。工作完成后,将`feature`分支合并到`develop`分支:
```Shell
git checkout develop
- git merge --no-ff myfeature
- git branch -d myfeature
+ git merge --no-ff feature/user
+ git branch -d feature/user
git push origin develop
```
@@ -444,7 +450,7 @@ Git不像SVN那样一定需要中央服务器才能工作,上面我们演示
创建`release`分支:
```Shell
- git switch -c release-0.1 develop
+ git checkout -b release-0.1 develop
git push -u origin release-0.1
... ... ...
git pull
@@ -497,7 +503,7 @@ Git不像SVN那样一定需要中央服务器才能工作,上面我们演示
git push --tags
```
-Git-flow流程比较容易控制各个分支的状况,但是在运用上github-flow要复杂得多,因此实际使用的时候通常会安装名为`gitflow`的命令行工具或者使用图形化的Git工具(如:SmartGit、SourceTree等)来简化操作,具体的可以参考[《git-flow 的工作流程》]()一文,因为这篇文章写得已经很好了,本文不再进行赘述。
+Git-flow流程比较容易控制各个分支的状况,但是在运用上github-flow要复杂得多,因此实际使用的时候通常会安装名为`gitflow`的命令行工具(Windows环境的Git自带了该工具)或者使用图形化的Git工具(如:SmartGit、SourceTree等)来简化操作,具体的可以参考[《git-flow 的工作流程》]()一文,因为这篇文章写得已经很好了,本文不再进行赘述。
### 缺陷管理
@@ -541,7 +547,7 @@ tar -xvf ZenTaoPMS.pro8.5.2.zbox_64.tar
对敏捷开发以及敏捷闭环工具不是特别了解的,可以参考[《基于JIRA的Scrum敏捷开发的项目管理》]()一文。
-#### Gitlab
+#### GitLab
常用的代码托管平台和之前提到的Git私服Gitlab都提供了缺陷管理的功能,当我们要报告一个bug时,可以在如下图所示的界面创建一个新的问题票(issue ticket)。填写的内容包括:
@@ -577,9 +583,7 @@ tar -xvf ZenTaoPMS.pro8.5.2.zbox_64.tar
![](./res/jenkins_new_project.png)
-持续集成对于编译型语言的意义更大,对于Python这样的解释型语言,更多的时候是用于对接版本控制系统触发自动化测试并产生相应的报告。类似的功能也可以通过在Git服务上配置**Webhook**来完成,码云甚至可以直接对接[钉钉开放平台]()使用钉钉机器人来向项目相关人员发送即时消息。Gitlab也对CI和CD(持续交付)提供了支持,具体内容请大家参考[《GitLab CI/CD基础教程》]()。
-
-
+持续集成对于编译型语言的意义更大,对于Python这样的解释型语言,更多的时候是用于对接版本控制系统触发自动化测试并产生相应的报告,类似的功能也可以通过配置**Webhook**来完成。如果要通过Docker这样的虚拟化容器进行项目打包部署或者通过K8S进行容器管理,可以在持续集成平台安装对应的插件来支持这些功能。码云甚至可以直接对接[钉钉开放平台]()使用钉钉机器人来向项目相关人员发送即时消息。GitLab也对CI和CD(持续交付)提供了支持,具体内容请大家参考[《GitLab CI/CD基础教程》]()。
> **说明**:
>
diff --git a/Day91-100/92.使用Docker部署应用.md b/Day91-100/92.Docker容器详解.md
similarity index 72%
rename from Day91-100/92.使用Docker部署应用.md
rename to Day91-100/92.Docker容器详解.md
index 143b110..ebea4c6 100644
--- a/Day91-100/92.使用Docker部署应用.md
+++ b/Day91-100/92.Docker容器详解.md
@@ -1,4 +1,6 @@
-## 使用Docker部署应用
+## Docker容器详解
+
+Docker是基于Go语言开发的开源应用容器引擎,遵从Apache Licence 2.0协议,可以让开发者打包应用以及应用的依赖包到一个可移植的容器中,然后发布到各种发行版本的Linux系统上。
### Docker简介
@@ -8,7 +10,7 @@
虚拟机(virtual machine)就是带环境安装的一种解决方案,它可以在一种操作系统里面运行另一种操作系统,比如在Windows系统里面运行Linux系统,在macOS上运行Windows,而应用程序对此毫无感知。使用过虚拟机的人都知道,虚拟机用起来跟真实系统一模一样,而对于虚拟机的宿主系统来说,虚拟机就是一个普通文件,不需要了就删掉,对宿主系统或者其他的程序并没有影响。但是虚拟机通常会占用较多的系统资源,启动和关闭也非常的缓慢,总之用户体验并没有想象中的那么好。
-Docker属于对Linux容器技术的一种封装(利用了Linux的namespace和cgroup技术),它提供了简单易用的容器使用接口,是目前最流行的 Linux 容器解决方案。Docker将应用程序与该程序的依赖打包在一个文件里面,运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。下图是虚拟机和容器的对比,左边是传统的虚拟机,右边是Docker。
+Docker属于对Linux容器技术(LXC)的一种封装(利用了Linux的namespace和cgroup技术),它提供了简单易用的容器使用接口,是目前最流行的 Linux 容器解决方案。Docker将应用程序与该程序的依赖打包在一个文件里面,运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。下图是虚拟机和容器的对比,左边是传统的虚拟机,右边是Docker。
![](./res/docker_vs_vm.png)
@@ -22,26 +24,49 @@ Docker属于对Linux容器技术的一种封装(利用了Linux的namespace和c
下面以CentOS为例讲解如何安装Docker,使用[Ubuntu](https://docs.docker.com/install/linux/docker-ce/ubuntu/)、[macOS](https://docs.docker.com/docker-for-mac/install/)或[Windows](https://docs.docker.com/docker-for-windows/install/)的用户可以通过点击对应的链接了解这些平台下如何进行安装。
-1. 确定操作系统内核版本(CentOS 7要求64位,内核版本3.10+;CentOS 6要求64位,内核版本2.6+),可以通过下面的命令确定Linux系统内核版本并更新底层库文件。
+1. 确定操作系统内核版本(CentOS 7要求64位,内核版本3.10+;CentOS 6要求64位,内核版本2.6+)。
-```Shell
-uname -r
-yum update
-```
+ ```Bash
+ uname -r
+ ```
-2. 在CentOS下使用yum安装Docker并启动。
+2. 更新系统底层的库文件(建议一定要执行,否则在使用Docker时可能会出现莫名其妙的问题)。
-```Shell
-yum -y install docker
-systemctl start docker
-```
+ ```Bash
+ yum update
+ ```
-3. 查看Docker的信息和版本。
+3. 移除可能存在的旧的Docker版本。
-```Shell
-docker version
-docker info
-```
+ ```Bash
+ yum erase -y docker docker-common docker-engine
+ ```
+
+4. 安装yum工具包和依赖项。
+
+ ```Bash
+ yum install -y yum-utils device-mapper-persistent-data lvm2
+ ```
+
+5. 通过yum工具包添加yum源(安装Docker-ce的源)。
+
+ ```Bash
+ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
+ ```
+
+6. 在CentOS下使用yum安装Docker-ce并启动。
+
+ ```Bash
+ yum -y install docker-ce
+ systemctl start docker
+ ```
+
+7. 查看Docker的信息和版本。
+
+ ```Shell
+ docker version
+ docker info
+ ```
接下来可以通过下载镜像和创建容器来看看Docker是否可以运转起来。可以使用下面的命令从Docker的镜像仓库下载名为hello-world的镜像文件。
@@ -299,7 +324,7 @@ select user, host, plugin, authentication_string from user where user='root';
接下来我们试一试运行多个容器并让多个容器之间通过网络通信。我们创建4个Redis容器来实现一主三从的主从复制结构。
```Shell
-docker run -d -p 6379:6379 --name redis-master redis redis-server
+docker run -d -p 6379:6379 --name redis-master redis
docker run -d -p 6380:6379 --name redis-slave-1 --link redis-master:redis-master redis redis-server --replicaof redis-master 6379
docker run -d -p 6381:6379 --name redis-slave-2 --link redis-master:redis-master redis redis-server --replicaof redis-master 6379
docker run -d -p 6382:6379 --name redis-slave-3 --link redis-master:redis-master redis redis-server --replicaof redis-master 6379
@@ -336,9 +361,7 @@ repl_backlog_histlen:1988
#### 运行GitLab
-GitLab是由GitLab Inc.开发的Git仓库管理工具,具有wiki、问题跟踪、持续集成等一系列的功能,分为社区版和企业版。通过Docker提供的虚拟化容器,我们可以安装社区版的Docker,命令如下所示。
-
-因为GitLab需要使用SSH协议进行安全连接,我们要暴露容器的22端口,所以可以先将宿主机SSH连接的22端口修改为其他端口(如:12345),然后再进行后续的操作。
+GitLab是由GitLab Inc.开发的Git仓库管理工具,具有wiki、问题跟踪、持续集成等一系列的功能,分为社区版和企业版。通过Docker提供的虚拟化容器,我们可以安装社区版的Docker。因为GitLab需要使用SSH协议进行安全连接,我们要暴露容器的22端口,所以可以先将宿主机SSH连接的22端口修改为其他端口(如:12345),然后再进行后续的操作。
```Shell
vim /etc/ssh/sshd_config
@@ -361,9 +384,7 @@ systemctl restart sshd
创建需要用于数据卷映射操作的文件夹。
```Shell
-mkdir -p /root/gitlab/config
-mkdir -p /root/gitlab/logs
-mkdir -p /root/gitlab/data
+mkdir -p /root/gitlab/{config,logs,data}
```
基于`gitlab/gitlab-ce`镜像创建容器,并暴露80端口(HTTP连接)和22端口(SSH连接)。
@@ -378,7 +399,7 @@ docker run -d -p 80:80 -p 22:22 --name gitlab -v /root/gitlab/config:/etc/gitlab
### 构建镜像
-通过上面的讲解,我们已经掌握了如何通过官方提供的镜像来创建容器。当然如果愿意,我们也可以用配置好的容器来生成镜像。简而言之,Docker镜像是由文件系统叠加而成的,系统的最底层是bootfs,相当于就是Linux内核的引导文件系统;接下来第二层是rootfs,这一层可以是一种或多种操作系统(如Debian或Ubuntu文件系统),Docker中的rootfs是只读状态的;Docker利用联合挂载技术将各层文件系统叠加到一起,最终的文件系统会包含有底层的文件和目录,这样的文件系统就是一个镜像。
+通过上面的讲解,我们已经掌握了如何通过官方提供的镜像来创建容器。当然如果愿意,我们也可以用配置好的容器来生成镜像。简而言之,**Docker镜像是由文件系统叠加而成的,系统的最底层是bootfs,相当于就是Linux内核的引导文件系统;接下来第二层是rootfs,这一层可以是一种或多种操作系统(如Debian或Ubuntu文件系统),Docker中的rootfs是只读状态的;Docker利用联合挂载技术将各层文件系统叠加到一起,最终的文件系统会包含有底层的文件和目录,这样的文件系统就是一个镜像**。
之前我们讲过了如何查找、列出镜像和拉取(下载)镜像,接下来看看构建镜像的两种方式:
@@ -438,25 +459,77 @@ jackfrued/mywebserver latest 795b294d265a 14 seconds ago 189 MB
Dockerfile使用DSL(Domain Specific Language)来构建一个Docker镜像,只要编辑好了Dockerfile文件,就可以使用`docker build`命令来构建一个新的镜像。
-我们先创建一个空文件夹并在文件夹下创建名为Dockerfile的文件。
+我们先创建一个名为myapp的文件夹来保存项目代码和Dockerfile的文件,如下所示:
```Shell
-touch Dockerfile
+[ECS-root temp]# tree myapp
+myapp
+├── api
+│ ├── app.py
+│ ├── requirements.txt
+│ └── start.sh
+└── Dockerfile
```
-编辑这个Dockerfile文件添加如下所示的内容。
+其中api是Flask项目的文件夹,其中包括了项目代码、依赖项以及启动脚本等文件,具体内容如下所示:
+
+app.py文件:
+
+```Python
+from flask import Flask
+from flask_restful import Resource, Api
+from flask_cors import CORS
+
+app = Flask(__name__)
+CORS(app, resources={r'/api/*': {'origins': '*'}})
+api = Api(app)
+
+
+class Product(Resource):
+
+ def get(self):
+ products = ['Ice Cream', 'Chocolate', 'Coca Cola', 'Hamburger']
+ return {'products': products}
+
+
+api.add_resource(Product, '/api/products')
+```
+
+requirements.txt文件:
+
+```INI
+flask
+flask-restful
+flask-cors
+gunicorn
+```
+
+start.sh文件:
```Shell
-vim Dockerfile
+#!/bin/bash
+exec gunicorn -w 4 -b 0.0.0.0:8000 app:app
```
+> **提示**:需要给start.sh文件以执行权限,可以使用`chmod 755 start.sh`命令来做到。
+
+Dockerfile文件:
+
```Dockerfile
-# version: 0.0.1
-FROM ubuntu:14.04
+# 指定基础镜像
+FROM python:3.7
+# 指定镜像的维护者
MAINTAINER jackfrued "jackfrued@126.com"
-RUN apt-get update && apt-get install -y nginx
-RUN echo 'hello, world!' > /usr/share/nginx/html/index.html
-EXPOSE 80
+# 将指定文件添加到容器中指定的位置
+ADD api/* /root/api/
+# 设置工作目录
+WORKDIR /root/api
+# 执行命令(安装Flask项目的依赖项)
+RUN pip install -r requirements.txt -i https://pypi.doubanio.com/simple/
+# 容器启动时要执行的命令
+ENTRYPOINT ["./start.sh"]
+# 暴露端口
+EXPOSE 8000
```
我们来解释一下上面的Dockerfile文件。Dockerfile文件通过特殊的指令来指定基础镜像(FROM指令)、创建容器后需要指定的命令(RUN指令)以及需要暴露的端口(EXPOSE)等信息。我们稍后会专门为大家介绍这些Dockfile中的指令。
@@ -464,7 +537,7 @@ EXPOSE 80
接下来我们可以使用`docker build`命令来创建镜像,如下所示。
```Shell
-docker build -t="jackfrued/webserver" .
+docker build -t "jackfrued/myapp" .
```
> 提示:上面的命令最后面的`.` 千万不要漏掉了哦,它表示从当前路径下寻找Dockerfile。
@@ -476,34 +549,31 @@ docker images
```
```
-REPOSITORY TAG IMAGE ID CREATED SIZE
-jackfrued/webserver latest 87d6cb096be2 23 minutes ago 222 MB
+REPOSITORY TAG IMAGE ID CREATED SIZE
+jackfrued/myapp latest 6d6f026a7896 5 seconds ago 930 MB
```
如果想知道镜像文件是如何创建出来的,可以使用下面的命令。
```Shell
-docker history jackfrued/webserver
+docker history jackfrued/myapp
```
```
-IMAGE CREATED CREATED BY SIZE
-87d6cb096be2 25 minutes ago /bin/sh -c #(nop) EXPOSE 80/tcp 0 B
-53d3bc3a123e 25 minutes ago /bin/sh -c service nginx start 3 B
-10646b63275e 25 minutes ago /bin/sh -c echo 'hello, world!' > /usr/sha... 14 B
-f3e3bf3e998e 25 minutes ago /bin/sh -c apt-get update && apt-get insta... 34.3 MB
-c98e22cf5a64 26 minutes ago /bin/sh -c #(nop) MAINTAINER jackfrued "j... 0 B
-2c5e00d77a67 3 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B
- 3 months ago /bin/sh -c mkdir -p /run/systemd && echo '... 7 B
- 3 months ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0 B
- 3 months ago /bin/sh -c set -xe && echo '#!/bin/sh' >... 195 kB
- 3 months ago /bin/sh -c #(nop) ADD file:1e01ab604c0cc30... 188 MB
+IMAGE CREATED CREATED BY SIZE COMMENT
+6d6f026a7896 31 seconds ago /bin/sh -c #(nop) EXPOSE 8000/tcp 0 B
+3f7739173a79 31 seconds ago /bin/sh -c #(nop) ENTRYPOINT ["./start.sh"] 0 B
+321e6bf09bf1 32 seconds ago /bin/sh -c pip install -r requirements.txt... 13 MB
+2f9bf2c89ac7 37 seconds ago /bin/sh -c #(nop) WORKDIR /root/api 0 B
+86119afbe1f8 37 seconds ago /bin/sh -c #(nop) ADD multi:4b76f9c9dfaee8... 870 B
+08d465e90d4d 3 hours ago /bin/sh -c #(nop) MAINTAINER jackfrued "j... 0 B
+fbf9f709ca9f 12 days ago /bin/sh -c #(nop) CMD ["python3"] 0 B
```
使用该镜像来创建容器运行Web服务器。
```Shell
-docker run -d -p 80:80 --name mywebserver jackfrued/webserver nginx -g "daemon off;"
+docker run -d -p 8000:8000 --name myapp jackfrued/myapp
```
如果希望将上面创建的镜像文件放到dockerhub仓库中,可以按照如下所示的步骤进行操作。
@@ -611,7 +681,7 @@ docker push jackfrued/webserver
USER nginx
```
-8. **VOLUME**:在创建容器时添加一个数据卷的挂载点。通过数据卷操作可以实现容器间数据的共享和重用,对卷所作的修改可以马上生效而不需要重新启动容器,我们之前创建容器时使用`—volume`参数就是为了实现数据卷的映射操作。
+8. **VOLUME**:在创建容器时添加一个数据卷的挂载点。通过数据卷操作可以实现容器间数据的共享和重用,对卷所作的修改可以马上生效而不需要重新启动容器,我们之前创建容器时使用`--volume`参数就是为了实现数据卷的映射操作。
```Dockerfile
VOLUME ["/路径1", "/路径2/子路径2.1/", ...]
@@ -638,7 +708,7 @@ docker push jackfrued/webserver
ONBUILD RUN cd /app/src && make
```
-### 容器编排
+### 多容器管理
我们的项目可能会使用了多个容器,容器多了之后管理容器的工作就会变得麻烦。如果要对多个容器进行自动配置使得容器可以相互协作甚至实现复杂的调度,这就需要进行容器编排。Docker原生对容器编排的支持非常弱,但是可以通过社区提供的工具来实现容器编排。
@@ -649,7 +719,7 @@ docker push jackfrued/webserver
1. 安装Docker Compose。
```Shell
- curl -L https://github.com/docker/compose/releases/download/1.25.0-rc2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
+ curl -L "https://github.com/docker/compose/releases/download/1.25.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
```
@@ -663,30 +733,31 @@ docker push jackfrued/webserver
2. 使用Docker Compose。
- 我们先创建一个名为`composeapp`的文件夹并在该文件夹下创建两个子文件夹`product-service`和`web-site`,如下所示。
+ 我们在刚才的Flask项目中引入缓存,然后再利用Flask提供的数据接口为前端页面提供数据,使用Vue.js进行页面渲染并将静态页面部署在Nginx服务器上。项目文件夹结构如下所示:
```Shell
- mkdir composeapp
- cd composeapp
- mkdir product-service
- mkdir web-site
+ [ECS-root ~]# tree temp
+ temp
+ ├── docker-compose.yml
+ ├── html
+ │ └── index.html
+ └── myapp
+ ├── api
+ │ ├── app.py
+ │ ├── requirements.txt
+ │ └── start.sh
+ └── Dockerfile
```
- 我们先在`product-service`文件夹下编写提供数据的API接口程序。
-
- ```Shell
- vim product-service/api.py
- ```
-
- 我们用Flask来实现一个非常简单的数据接口服务程序。
+ 修改后的app.py文件代码如下所示:
```Python
from pickle import dumps, loads
from flask import Flask
from flask_restful import Resource, Api
- from redis import Redis
from flask_cors import CORS
+ from redis import Redis
app = Flask(__name__)
CORS(app, resources={r'/api/*': {'origins': '*'}})
@@ -698,52 +769,18 @@ docker push jackfrued/webserver
def get(self):
data = redis.get('products')
- if not data:
+ if data:
+ products = loads(data)
+ else:
products = ['Ice Cream', 'Chocolate', 'Coca Cola', 'Hamburger']
redis.set('products', dumps(products))
- else:
- products = loads(data)
return {'products': products}
api.add_resource(Product, '/api/products')
-
- if __name__ == '__main__':
- app.run(host='0.0.0.0', port=8000, debug=True)
```
- 由于上面的项目需要依赖`flask`、 `flask-restful`等三方库,所以我们再添加一个指明依赖库的文件并将其命名为`requirements.txt`,其内容如下所示。
-
- ```Shell
- vim product-service/requirements.txt
- ```
-
- ```
- flask
- flask-restful
- flask-cors
- redis
- ```
-
- 稍后我们会将上面的接口服务放在一个容器中运行,为此我们先编写一个Dockerfile文件以便创建对应的镜像,其内容如下所示。
-
- ```Shell
- vim product-service/Dockerfile
- ```
-
- ```Dockerfile
- FROM python:3
- ADD . /root/product-service
- WORKDIR /root/product-service
- RUN pip install -r requirements.txt
- CMD ["python", "api.py"]
- ```
-
- 我们再去到`web-site`目录下创建一个页面,稍后我们会通一个容器来提供Nginx服务并运行该页面,而这个页面会访问我们刚才部署的数据接口服务获取数据并通过Vue.js将数据渲染到页面上。
-
- ```Shell
- vim web-site/index.html
- ```
+ html文件夹用来保存静态页面,稍后我们会通一个运行Nginx的容器来向浏览器提供静态页面。index.html文件的内容如下所示:
```HTML
@@ -777,68 +814,63 @@ docker push jackfrued/webserver