diff --git a/Day01-15/01.初识Python.md b/Day01-15/01.初识Python.md index 6d02bf5..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环境 @@ -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/Day16-20/16-20.Python语言进阶.md b/Day16-20/16-20.Python语言进阶.md index 35a1486..c815a6e 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标记: + - <img src="http://latex.codecogs.com/gif.latex?O(c)" /> - 常量时间复杂度 - 布隆过滤器 / 哈希存储 + - <img src="http://latex.codecogs.com/gif.latex?O(log_2n)" /> - 对数时间复杂度 - 折半查找(二分查找) + - <img src="http://latex.codecogs.com/gif.latex?O(n)" /> - 线性时间复杂度 - 顺序查找 / 计数排序 + - <img src="http://latex.codecogs.com/gif.latex?O(n*log_2n)" /> - 对数线性时间复杂度 - 高级排序算法(归并排序、快速排序) + - <img src="http://latex.codecogs.com/gif.latex?O(n^2)" /> - 平方时间复杂度 - 简单排序算法(选择排序、插入排序、冒泡排序) + - <img src="http://latex.codecogs.com/gif.latex?O(n^3)" /> - 立方时间复杂度 - Floyd算法 / 矩阵乘法运算 + - <img src="http://latex.codecogs.com/gif.latex?O(2^n)" /> - 几何级数时间复杂度 - 汉诺塔 + - <img src="http://latex.codecogs.com/gif.latex?O(n!)" /> - 阶乘时间复杂度 - 旅行经销商问题 - 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'\<title\>(?P<title>.*)\<\/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/Day91-100/100.Python面试题集.md b/Day91-100/100.Python面试题集.md index 396d2b8..9e12dba 100644 --- a/Day91-100/100.Python面试题集.md +++ b/Day91-100/100.Python面试题集.md @@ -69,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)) @@ -84,8 +80,6 @@ reverse('hello') ``` - 或 - ```Python def reverse(content): return reverse(content[1:]) + content[0] if len(content) > 1 else content diff --git a/Day91-100/91.团队项目开发的问题和解决方案.md b/Day91-100/91.团队项目开发的问题和解决方案.md index 2495db5..df2ac87 100644 --- a/Day91-100/91.团队项目开发的问题和解决方案.md +++ b/Day91-100/91.团队项目开发的问题和解决方案.md @@ -427,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 ``` @@ -547,7 +547,7 @@ tar -xvf ZenTaoPMS.pro8.5.2.zbox_64.tar 对敏捷开发以及敏捷闭环工具不是特别了解的,可以参考[《基于JIRA的Scrum敏捷开发的项目管理》](<https://blog.51cto.com/newthink/1775427>)一文。 -#### Gitlab +#### GitLab 常用的代码托管平台和之前提到的Git私服Gitlab都提供了缺陷管理的功能,当我们要报告一个bug时,可以在如下图所示的界面创建一个新的问题票(issue ticket)。填写的内容包括: @@ -583,7 +583,7 @@ tar -xvf ZenTaoPMS.pro8.5.2.zbox_64.tar ![](./res/jenkins_new_project.png) -持续集成对于编译型语言的意义更大,对于Python这样的解释型语言,更多的时候是用于对接版本控制系统触发自动化测试并产生相应的报告,类似的功能也可以通过配置**Webhook**来完成。如果要通过Docker这样的虚拟化容器进行项目打包部署或者通过K8S进行容器管理,可以在持续集成平台安装对应的插件来支持这些功能。码云甚至可以直接对接[钉钉开放平台](<https://ding-doc.dingtalk.com/>)使用钉钉机器人来向项目相关人员发送即时消息。Gitlab也对CI和CD(持续交付)提供了支持,具体内容请大家参考[《GitLab CI/CD基础教程》](<https://blog.stdioa.com/2018/06/gitlab-cicd-fundmental/>)。 +持续集成对于编译型语言的意义更大,对于Python这样的解释型语言,更多的时候是用于对接版本控制系统触发自动化测试并产生相应的报告,类似的功能也可以通过配置**Webhook**来完成。如果要通过Docker这样的虚拟化容器进行项目打包部署或者通过K8S进行容器管理,可以在持续集成平台安装对应的插件来支持这些功能。码云甚至可以直接对接[钉钉开放平台](<https://ding-doc.dingtalk.com/>)使用钉钉机器人来向项目相关人员发送即时消息。GitLab也对CI和CD(持续交付)提供了支持,具体内容请大家参考[《GitLab CI/CD基础教程》](<https://blog.stdioa.com/2018/06/gitlab-cicd-fundmental/>)。 > **说明**: > diff --git a/Day91-100/92.Docker容器详解.md b/Day91-100/92.Docker容器详解.md index e1dd54e..17a125e 100644 --- a/Day91-100/92.Docker容器详解.md +++ b/Day91-100/92.Docker容器详解.md @@ -24,26 +24,49 @@ Docker属于对Linux容器技术(LXC)的一种封装(利用了Linux的name 下面以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的镜像文件。 @@ -376,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利用联合挂载技术将各层文件系统叠加到一起,最终的文件系统会包含有底层的文件和目录,这样的文件系统就是一个镜像**。 之前我们讲过了如何查找、列出镜像和拉取(下载)镜像,接下来看看构建镜像的两种方式: diff --git a/Day91-100/97.电商网站技术要点剖析.md b/Day91-100/97.电商网站技术要点剖析.md index 20943ed..bdf1911 100644 --- a/Day91-100/97.电商网站技术要点剖析.md +++ b/Day91-100/97.电商网站技术要点剖析.md @@ -305,7 +305,7 @@ if alipay.verify(params, params.pop('sign')): 1. 秒杀:秒杀是通常意味着要在很短的时间处理极高的并发,系统在短时间需要承受平时百倍以上的流量,因此秒杀架构是一个比较复杂的问题,其核心思路是流量控制和性能优化,需要从前端(通过JavaScript实现倒计时、避免重复提交和限制频繁刷新)到后台各个环节的配合。流量控制主要是限制只有少部分流量进入服务后端(毕竟最终只有少部分用户能够秒杀成功),同时在物理架构上使用缓存(一方面是因为读操作多写操作少;另外可以将库存放在Redis中,利用DECR原语实现减库存;同时也可以利用Redis来进行限流,道理跟限制频繁发送手机验证码是一样的)和消息队列(消息队列最为重要的作用就是“削峰”和“上下游节点解耦合”)来进行优化;此外还要采用无状态服务设计,这样才便于进行水平扩展(通过增加设备来为系统扩容)。 2. 超卖现象:比如某商品的库存为1,此时用户1和用户2并发购买该商品,用户1提交订单后该商品的库存被修改为0,而此时用户2并不知道的情况下提交订单,该商品的库存再次被修改为-1这就是超卖现象。解决超卖现象有三种常见的思路: - 悲观锁控制:查询商品数量的时候就用`select ... for update`对数据加锁,这样的话用户1查询库存时,用户2因无法读取库存数量被阻塞,直到用户1提交或者回滚了更新库存的操作后才能继续,从而解决了超卖问题。但是这种做法对并发访问量很高的商品来说性能太过糟糕,实际开发中可以在库存小于某个值时才考虑加锁,但是总的来说这种做法不太可取。 - - 乐观锁控制:查询商品数量不用加锁,更新库存的时候设定商品数量必须与之前查询数量相同才能更新,否则说明其他事务已经更新了库存,必须重新发出请求。这种做法要求事务隔离级别为可重复读(repeatable read),否则仍然会产生问题。 + - 乐观锁控制:查询商品数量不用加锁,更新库存的时候设定商品数量必须与之前查询数量相同才能更新,否则说明其他事务已经更新了库存,必须重新发出请求。 - 尝试减库存:将上面的查询(`select`)和更新(`update`)操作合并为一条SQL操作,更新库存的时候,在`where`筛选条件中加上`库存>=购买数量`或`库存-购买数量>=0`的条件,这种做法要求事务隔离级别为读提交(read committed)。 > 提示:有兴趣的可以自己在知乎上看看关于这类问题的讨论。 @@ -340,15 +340,15 @@ ElasticSearch的安装和配置可以参考[《ElasticSearch之Docker安装》]( 1. 使用Docker安装ElasticSearch。 ```Shell - docker pull elasticsearch:6.5.3 - docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms512m -Xmx512m" --name es elasticsearch:6.5.3 + docker pull elasticsearch:7.6.0 + docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms512m -Xmx512m" --name es elasticsearch:7.6.0 ``` > 说明:上面创建容器时通过`-e`参数指定了使用单机模式和Java虚拟机最小最大可用堆空间的大小,堆空间大小可以根据服务器实际能够提供给ElasticSearch的内存大小来决定,默认为2G。 2. 创建数据库。 - 请求:PUT - `http://1.2.3.4:9200/demo` + 请求:PUT - `http://1.2.3.4:9200/demo/` 响应: @@ -362,7 +362,7 @@ ElasticSearch的安装和配置可以参考[《ElasticSearch之Docker安装》]( 3. 查看创建的数据库。 - 请求:GET - `http://1.2.3.4:9200/demo` + 请求:GET - `http://1.2.3.4:9200/demo/` 响应: @@ -528,18 +528,19 @@ ElasticSearch的安装和配置可以参考[《ElasticSearch之Docker安装》]( 2. 下载和ElasticSearch版本对应的[ik](https://github.com/medcl/elasticsearch-analysis-ik)和[pinyin](https://github.com/medcl/elasticsearch-analysis-pinyin)插件。 ```Shell + yum install -y wget cd plugins/ mkdir ik cd ik - wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.5.3/elasticsearch-analysis-ik-6.5.3.zip - unzip elasticsearch-analysis-ik-6.5.3.zip - rm -f elasticsearch-analysis-ik-6.5.3.zip + wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.0/elasticsearch-analysis-ik-7.6.0.zip + unzip elasticsearch-analysis-ik-7.6.0.zip + rm -f elasticsearch-analysis-ik-7.6.0.zip cd .. mkdir pinyin cd pinyin - wget https://github.com/medcl/elasticsearch-analysis-pinyin/releases/download/v6.5.3/elasticsearch-analysis-pinyin-6.5.3.zip - unzip elasticsearch-analysis-pinyin-6.5.3.zip - rm -f elasticsearch-analysis-pinyin-6.5.3.zip + wget https://github.com/medcl/elasticsearch-analysis-pinyin/releases/download/v7.6.0/elasticsearch-analysis-pinyin-7.6.0.zip + unzip elasticsearch-analysis-pinyin-7.6.0.zip + rm -f elasticsearch-analysis-pinyin-7.6.0.zip ``` 3. 退出容器,重启ElasticSearch。 diff --git a/Day91-100/98.项目部署上线和性能调优.md b/Day91-100/98.项目部署上线和性能调优.md index bc00e5e..7fd6da2 100644 --- a/Day91-100/98.项目部署上线和性能调优.md +++ b/Day91-100/98.项目部署上线和性能调优.md @@ -65,30 +65,36 @@ 2. 下载Python源代码。 ```Shell - wget https://www.python.org/ftp/python/3.7.1/Python-3.7.1.tar.xz + wget https://www.python.org/ftp/python/3.7.6/Python-3.7.6.tar.xz ``` -3. 解压缩和解归档。 +3. 验证下载文件。 - ```Shell - xz -d Python-3.7.1.tar.xz - tar -xvf Python-3.7.1.tar + ```Bash + md5sum Python-3.7.6.tar.xz ``` -4. 执行配置生成Makefile(构建文件)。 +4. 解压缩和解归档。 ```Shell - cd Python-3.7.1 + xz -d Python-3.7.6.tar.xz + tar -xvf Python-3.7.6.tar + ``` + +5. 执行安装前的配置(生成Makefile文件)。 + + ```Shell + cd Python-3.7.6 ./configure --prefix=/usr/local/python37 --enable-optimizations ``` -5. 构建和安装。 +6. 构建和安装。 ```Shell make && make install ``` -6. 配置PATH环境变量(用户或系统环境变量)并激活。 +7. 配置PATH环境变量(用户或系统环境变量)并激活。 ```Shell vim ~/.bash_profile @@ -108,13 +114,13 @@ source /etc/profile ``` -7. 注册软链接(符号链接)- 这一步不是必须的,但通常会比较有用。 +8. 注册软链接(符号链接)- 这一步不是必须的,但通常会比较有用。 ```Shell ln -s /usr/local/python37/bin/python3 /usr/bin/python3 ``` -8. 测试Python环境是否更新成功(安装Python 3一定不能破坏原来的Python 2)。 +9. 测试Python环境是否更新成功(安装Python 3一定不能破坏原来的Python 2)。 ```Shell python3 --version @@ -286,7 +292,7 @@ pip install -r code/teamproject/requirements.txt ```Nginx # 配置用户 - user root; + user nginx; # 工作进程数(建议跟CPU的核数量一致) worker_processes auto; # 错误日志 @@ -530,7 +536,21 @@ root 上面创建Docker容器时使用的`-v`参数(`--volume`)表示映射数据卷,冒号前是宿主机的目录,冒号后是容器中的目录,这样相当于将宿主机中的目录挂载到了容器中。 -3. 创建和配置slave。 +3. 备份主表中的数据(如果需要的话)。 + + ```SQL + mysql> flush table with read lock; + ``` + + ```Bash + mysqldump -u root -p 123456 -A -B > /root/backup/mysql/mybak$(date +"%Y%m%d%H%M%S").sql + ``` + + ```SQL + mysql> unlock table; + ``` + +4. 创建和配置slave。 ```Shell docker run -d -p 3308:3306 --name mysql-slave-1 \ @@ -850,15 +870,15 @@ class MasterSlaveRouter(object): 2. 常用云服务。 - | 功能 | 可用的云服务 | - | -------------- | --------------------------------------- | - | 团队协作工具 | Teambition、钉钉 | - | 代码托管平台 | Github、Gitee、CODING | - | 邮件服务 | SendCloud | - | 云存储(CDN) | 七牛、OSS、LeanCloud、Bmob、又拍云、AWS | - | 移动端推送 | 极光、友盟、百度 | - | 即时通信 | 环信、融云 | - | 短信服务 | 云片、极光、Luosimao、又拍云 | - | 第三方登录 | 友盟、ShareSDK | - | 网站监控和统计 | 阿里云监控、监控宝、百度云观测、小鸟云 | + | 功能 | 可用的云服务 | + | -------------- | -------------------------------------- | + | 团队协作工具 | Teambition、钉钉 | + | 代码托管平台 | Github、Gitee、CODING | + | 邮件服务 | SendCloud | + | 云存储(CDN) | 七牛、OSS、LeanCloud、Bmob、又拍云、S3 | + | 移动端推送 | 极光、友盟、百度 | + | 即时通信 | 环信、融云 | + | 短信服务 | 云片、极光、Luosimao、又拍云 | + | 第三方登录 | 友盟、ShareSDK | + | 网站监控和统计 | 阿里云监控、监控宝、百度云观测、小鸟云 | diff --git a/README.md b/README.md index 48f0bdb..cc8c2ab 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ ## Python - 100天从新手到大师 -> 作者:骆昊 +> **作者**:骆昊 > -> 最近有很多想学习Python的小伙伴陆陆续续加入我们的交流群,目前我们的交流群人数已经超过一万人。我们的目标是打造一个优质的Python交流社区,一方面为Python初学者扫平入门过程中的重重障碍,另一方为新入行的开发者提供问道的门径,帮助他们迅速成长。我自己很希望更多有经验的开发者加入到这个平台,把自己的工作经验无偿分享或有偿提供出来,让大家最终都能够通过这个平台获取对自己有帮助的信息。我的知乎号是[Python-Jack](https://www.zhihu.com/people/jackfrued),之前是我的团队在协助运营,从现在开始我会自己管理这个知乎号,持续为大家提供高质量的文章和新的学习内容,也欢迎大家关注我在知乎的文章和问题回答。创作不易,感谢大家的打赏支持,这些钱不会用于购买咖啡而是通过腾讯公益平台捐赠给需要帮助的人([点击](./更新日志.md)了解捐赠情况)。 +> **说明**:从项目上线到获得8w+星标以来,一直收到反馈说基础部分(前15天的内容)对新手来说是比较困难的,建议有配套视频进行讲解。最近把基础部分的内容重新创建了一个名为[“Python-Core-50-Courses”](<https://github.com/jackfrued/Python-Core-50-Courses>)的项目,**用更为简单通俗的方式重写了这部分内容并附带了视频讲解**,初学者可以关注下这个新项目。国内用户如果访问GitHub比较慢的话,也可以关注我的知乎号[Python-Jack](https://www.zhihu.com/people/jackfrued)上的[“从零开始学Python”](<https://zhuanlan.zhihu.com/c_1216656665569013760>)专栏,专栏会持续更新,还有大家比较期待的“数据分析”的内容也即将上线,**欢迎大家关注我在知乎的专栏、文章和回答**。 +> +> 创作不易,感谢大家的打赏支持,这些钱基本不会用于购买咖啡,而是通过腾讯公益、美团公益、水滴筹等平台捐赠给需要帮助的人([点击](./更新日志.md)了解捐赠情况)。需要加入QQ交流群的可以扫描下面的二维码,交流群会为大家提供**学习资源**和**问题解答**,还会持续为大家带来**免费的线上Python体验课和行业公开课**,敬请关注。 ![](./res/python-qq-group.png) diff --git a/res/meituan.png b/res/meituan.png new file mode 100644 index 0000000..1234c86 Binary files /dev/null and b/res/meituan.png differ diff --git a/更新日志.md b/更新日志.md index 5a28229..158d3c2 100644 --- a/更新日志.md +++ b/更新日志.md @@ -1,5 +1,18 @@ ## 更新日志 +### 2020年4月2日 + +1. 将基础部分(第1天到第15天)内容重新创建了一个名为“Python-Core-50-Courses”的仓库,更新了部分内容。 +2. 更新了README.md文件。 + +### 2020年3月8日 + +1. 更新了最后10天的部分文档。 + +2. 通过美团公益将近期打赏捐助给受疫情影响儿童。 + + ![](res/meituan.png) + ### 2020年3月1日 1. 优化了项目中的部分图片资源。