diff --git a/Day01-15/01.初识Python.md b/Day01-15/01.初识Python.md index b688213..50f7e3c 100644 --- a/Day01-15/01.初识Python.md +++ b/Day01-15/01.初识Python.md @@ -5,36 +5,36 @@ #### Python的历史 1. 1989年圣诞节:Guido von Rossum开始写Python语言的编译器。 -2. 1991年2月:第一个Python编译器(同时也是解释器)诞生,它是用C语言实现的(后面又出现了Java和C#实现的版本Jython和IronPython,除此之外还有PyPy、Brython、Pyston等其他实现),可以调用C语言的库函数。在最早的版本中,Python已经提供了对“类”,“函数”,“异常处理”等构造块的支持,还有对列表、字典等核心数据类型,同时支持以模块为基础来构造应用程序。 +2. 1991年2月:第一个Python编译器(同时也是解释器)诞生,它是用C语言实现的(后面),可以调用C语言的库函数。在最早的版本中,Python已经提供了对“类”,“函数”,“异常处理”等构造块的支持,还有对列表、字典等核心数据类型,同时支持以模块为基础来构造应用程序。 3. 1994年1月:Python 1.0正式发布。 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等版本,有兴趣的读者可以自行了解。 #### Windows环境 @@ -55,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 ``` @@ -93,7 +93,7 @@ source .bash_profile macOS也自带了Python 2.x版本,可以通过[Python的官方网站](https://www.python.org)提供的安装文件(pkg文件)安装Python 3.x的版本。默认安装完成后,可以通过在终端执行`python`命令来启动2.x版本的Python解释器,启动3.x版本的Python解释器需要执行`python3`命令。 -### 从终端运行Python程序 +### 运行Python程序 #### 确认Python的版本 @@ -102,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 @@ -139,7 +139,7 @@ python hello.py python3 hello.py ``` -### 代码中的注释 +#### 代码中的注释 注释是编程语言的一个重要组成部分,用于在源代码中解释代码的作用从而增强程序的可读性和可维护性,当然也可以将源代码中不需要参与运行的代码段通过注释来去掉,这一点在调试程序的时候经常用到。注释在随源代码进入预处理器或编译时会被移除,不会在目标代码中保留也不会影响程序的执行结果。 @@ -154,15 +154,11 @@ python3 hello.py Version: 0.1 Author: 骆昊 """ - print('hello, world!') -# print("你好,世界!") -print('你好', '世界') -print('hello', 'world', sep=', ', end='!') -print('goodbye, world', end='!\n') +# print("你好, 世界!") ``` -### 其他工具介绍 +### Python开发工具 #### IDLE - 自带的集成开发工具 @@ -172,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 @@ -188,11 +184,11 @@ pip3 install ipython ![](./res/python-ipython.png) -#### Sublime - 高级文本编辑器 +#### Sublime Text - 高级文本编辑器 ![](./res/python-sublime.png) -- 首先可以通过[官方网站](https://www.sublimetext.com/)下载安装程序安装Sublime 3或Sublime 2。 +- 首先可以通过[官方网站](https://www.sublimetext.com/)下载安装程序安装Sublime Text 3或Sublime Text 2。 - 安装包管理工具。 1. 通过快捷键Ctrl+`或者在View菜单中选择Show Console打开控制台,输入下面的代码。 @@ -207,9 +203,7 @@ pip3 install ipython ```Python import urllib2,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();os.makedirs(ipp)ifnotos.path.exists(ipp)elseNone;urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read());print('Please restart Sublime Text to finish installation') ``` - 2. 手动安装浏览器输入 https://sublime.wbond.net/Package%20Control.sublime-package 下载这个文件 - 下载好以后,打开sublime text,选择菜单Preferences->Browse Packages... 打开安装目录 - 此时会进入到一个叫做Packages的目录下,点击进入上一层目录Sublime Text3,在此目录下有一个文件夹叫做Installed Packages,把刚才下载的文件放到这里就可以了。然后重启sublime text3,观察Preferences菜单最下边是否有Package Settings 和Package Control两个选项,如果有,则代表安装成功了。 + 2. 在浏览器中输入 https://sublime.wbond.net/Package%20Control.sublime-package 下载包管理工具的安装包,并找到安装Sublime目录下名为"Installed Packages"的目录,把刚才下载的文件放到这个文件加下,然后重启Sublime Text就搞定了。 - 安装插件。通过Preference菜单的Package Control或快捷键Ctrl+Shift+P打开命令面板,在面板中输入Install Package就可以找到安装插件的工具,然后再查找需要的插件。我们推荐大家安装以下几个插件: @@ -220,7 +214,7 @@ pip3 install ipython - Python PEP8 Autoformat - PEP8规范自动格式化插件。 - ConvertToUTF8 - 将本地编码转换为UTF-8。 -> 说明:事实上[Visual Studio Code]()可能是更好的选择,它不用花钱并提供了更为完整和强大的功能,有兴趣的读者可以自行研究。 +> **说明**:事实上[Visual Studio Code]()可能是更好的选择,它不用花钱并提供了更为完整和强大的功能,有兴趣的读者可以自行研究。 #### PyCharm - Python开发神器 @@ -230,18 +224,17 @@ PyCharm的安装、配置和使用在[《玩转PyCharm》](../玩转PyCharm.md) ### 练习 -1. 在Python交互环境中查看下面的代码结果,并试着将这些内容翻译成中文。 +1. 在Python交互式环境中输入下面的代码并查看结果,请尝试将看到的内容翻译成中文。 ```Python import this ``` - > 说明:当前键入上面的命令后会在交互式环境中看到如下所示的输出,这段内容被称为“Python之禅”,里面讲述的道理不仅仅适用于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 @@ -259,3 +252,5 @@ PyCharm的安装、配置和使用在[《玩转PyCharm》](../玩转PyCharm.md) turtle.mainloop() ``` + + > **提示**:本章提供的代码中还有画国旗和画小猪佩奇的代码,有兴趣的读者请自行研究。 diff --git a/Day01-15/02.语言元素.md b/Day01-15/02.语言元素.md index 8e1e1f7..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,33 +37,59 @@ ```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`函数对变量的类型进行检查。程序设计中函数的概念跟数学上函数的概念是一致的,数学上的函数相信大家并不陌生,它包括了函数名、自变量和因变量。如果暂时不理解这个概念也不要紧,我们会在后续的章节中专门讲解函数的定义和使用。 + ```Python """ -使用input()函数获取键盘输入 -使用int()进行类型转换 -用占位符格式化输出的字符串 +使用type()检查变量的类型 Version: 0.1 Author: 骆昊 """ +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)) # +``` +可以使用Python中内置的函数对变量类型进行转换。 + +- `int()`:将一个数值或字符串转换成整数,可以指定进制。 +- `float()`:将一个字符串转换成浮点数。 +- `str()`:将指定的对象转换成字符串形式,可以指定编码。 +- `chr()`:将整数转换成该编码对应的字符串(一个字符)。 +- `ord()`:将字符串(一个字符)转换成对应的编码(整数)。 + +下面的代码通过键盘输入两个整数来实现对两个整数的算术运算。 + +```Python +""" +使用input()函数获取键盘输入(字符串) +使用int()函数将输入的字符串转换成整数 +使用print()函数输出带占位符的字符串 + +Version: 0.1 +Author: 骆昊 +""" a = int(input('a = ')) b = int(input('b = ')) print('%d + %d = %d' % (a, b, a + b)) @@ -75,38 +101,11 @@ print('%d %% %d = %d' % (a, b, a % b)) print('%d ** %d = %d' % (a, b, a ** b)) ``` -```Python -""" -使用type()检查变量的类型 - -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)) -``` - -在对变量类型进行转换时可以使用Python的内置函数。 - -- `int()`:将一个数值或字符串转换成整数,可以指定进制。 -- `float()`:将一个字符串转换成浮点数。 -- `str()`:将指定的对象转换成字符串形式,可以指定编码。 -- `chr()`:将整数转换成该编码对应的字符串(一个字符)。 -- `ord()`:将字符串(一个字符)转换成对应的编码(整数)。 +> **说明**:上面的print函数中输出的字符串使用了占位符语法,其中`%d`是整数的占位符,`%f`是小数的占位符,`%%`表示百分号(因为百分号代表了占位符,所以带占位符的字符串中要表示百分号必须写成`%%`),字符串之后的`%`后面跟的变量值会替换掉占位符然后输出到终端中,运行上面的程序,看看程序执行结果就明白啦。 ### 运算符 -Python支持多种运算符,下表大致按照优先级从高到低的顺序列出了所有的运算符,我们会陆续使用到它们。 +Python支持多种运算符,下表大致按照优先级从高到低的顺序列出了所有的运算符,运算符的优先级指的是多个运算符同时出现时,先做什么运算然后再做什么运算。除了我们之前已经用过的赋值运算符和算术运算符,我们稍后会陆续讲到其他运算符的使用。 | 运算符 | 描述 | | ------------------------------------------------------------ | ------------------------------ | @@ -127,61 +126,83 @@ Python支持多种运算符,下表大致按照优先级从高到低的顺序 >**说明:** 在实际开发中,如果搞不清楚运算符的优先级,可以使用括号来确保运算的执行顺序。 -下面的例子演示了运算符的使用。 +#### 赋值运算符 + +赋值运算符应该是最为常见的运算符,它的作用是将右边的值赋给左边的变量。下面的例子演示了赋值运算符和复合赋值运算符的使用。 ```Python """ -运算符的使用 +赋值运算符和复合赋值运算符 Version: 0.1 Author: 骆昊 """ +a = 10 +b = 3 +a += b # 相当于:a = a + b +a *= a + 2 # 相当于:a = a * (a + 2) +print(a) # 算一下这里会输出什么 +``` -a = 5 -b = 10 -c = 3 -d = 4 -e = 5 -a += b -a -= c -a *= d -a /= e -print("a = ", 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 flag1 -print("flag1 = ", flag1) -print("flag2 = ", flag2) -print("flag3 = ", flag3) -print("flag4 = ", flag4) -print("flag5 = ", flag5) -print(flag1 is True) -print(flag2 is not False) +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 ``` +> **说明**:比较运算符的优先级高于赋值运算符,所以`flag0 = 1 == 1`先做`1 == 1`产生布尔值`True`,再将这个值赋值给变量`flag0`。`print`函数可以输出多个值,多个值之间可以用`,`进行分隔,输出的内容之间默认以空格分开。 + ### 练习 -#### 练习1:华氏温度转摄氏温度。 +#### 练习1:华氏温度转换为摄氏温度。 + +> 提示:华氏温度到摄氏温度的转换公式为:$C=(F - 32) \div 1.8$。 + +参考答案: ```Python """ 将华氏温度转换为摄氏温度 -F = 1.8C + 32 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:输入圆的半径计算计算周长和面积。 +参考答案: + ```Python """ 输入半径计算圆的周长和面积 @@ -189,18 +210,17 @@ 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) ``` #### 练习3:输入年份判断是不是闰年。 +参考答案: + ```Python """ 输入年份 如果是闰年输出True 否则输出False @@ -208,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 08f2307..abd08e7 100644 --- a/Day01-15/03.分支结构.md +++ b/Day01-15/03.分支结构.md @@ -15,21 +15,18 @@ Version: 0.1 Author: 骆昊 """ - username = input('请输入用户名: ') password = input('请输入口令: ') -# 如果希望输入口令时 终端中没有回显 可以使用getpass模块的getpass函数 -# import getpass -# password = getpass.getpass('请输入口令: ') +# 用户名是admin且密码是123456则身份验证成功否则身份验证失败 if username == 'admin' and password == '123456': print('身份验证成功!') 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) @@ -83,7 +80,9 @@ print('f(%.2f) = %.2f' % (x, y)) ### 练习 -#### 练习1:英制单位与公制单位互换 +#### 练习1:英制单位英寸与公制单位厘米互换。 + +参考答案: ```Python """ @@ -92,7 +91,6 @@ print('f(%.2f) = %.2f' % (x, y)) Version: 0.1 Author: 骆昊 """ - value = float(input('请输入长度: ')) unit = input('请输入单位: ') if unit == 'in' or unit == '英寸': @@ -103,50 +101,19 @@ else: print('请输入有效的单位') ``` -#### 练习2:掷骰子决定做什么 +#### 练习2:百分制成绩转换为等级制成绩。 + +> **要求**:如果输入的成绩在90分以上(含90分)输出A;80分-90分(不含90分)输出B;70分-80分(不含80分)输出C;60分-70分(不含70分)输出D;60分以下输出E。 + +参考答案: ```Python """ -掷骰子决定做什么事情 +百分制成绩转换为等级制成绩 Version: 0.1 Author: 骆昊 """ - -from random import randint - -face = randint(1, 6) -if face == 1: - result = '唱首歌' -elif face == 2: - result = '跳个舞' -elif face == 3: - result = '学狗叫' -elif face == 4: - result = '做俯卧撑' -elif face == 5: - result = '念绕口令' -else: - result = '讲冷笑话' -print(result) -``` -> **说明:** 上面的代码中使用了random模块的randint函数生成指定范围的随机数来模拟掷骰子。 - -#### 练习3:百分制成绩转等级制 - -```Python -""" -百分制成绩转等级制成绩 -90分以上 --> A -80分~89分 --> B -70分~79分 --> C -60分~69分 --> D -60分以下 --> E - -Version: 0.1 -Author: 骆昊 -""" - score = float(input('请输入成绩: ')) if score >= 90: grade = 'A' @@ -160,71 +127,27 @@ else: grade = 'E' print('对应的等级是:', grade) ``` -#### 练习4:输入三条边长如果能构成三角形就计算周长和面积 +#### 练习3:输入三条边长,如果能构成三角形就计算周长和面积。 + +参考答案: ```Python """ -判断输入的边长能否构成三角形 -如果能则计算出三角形的周长和面积 +判断输入的边长能否构成三角形,如果能则计算出三角形的周长和面积 Version: 0.1 Author: 骆昊 """ - -import math - a = float(input('a = ')) b = float(input('b = ')) c = float(input('c = ')) if a + b > c and a + c > b and b + c > a: print('周长: %f' % (a + b + c)) p = (a + b + c) / 2 - area = math.sqrt(p * (p - a) * (p - b) * (p - c)) + area = (p * (p - a) * (p - b) * (p - c)) ** 0.5 print('面积: %f' % (area)) else: print('不能构成三角形') ``` -> **说明:** 上面的代码中使用了`math`模块的`sqrt`函数来计算平方根。用边长计算三角形面积的公式叫做[海伦公式](https://zh.wikipedia.org/zh-hans/海伦公式)。 +> **说明:** 上面使用的通过边长计算三角形面积的公式叫做[海伦公式](https://zh.wikipedia.org/zh-hans/海伦公式)。 -#### 练习5:个人所得税计算器。 - -```Python -""" -输入月收入和五险一金计算个人所得税 - -Version: 0.1 -Author: 骆昊 -""" - -salary = float(input('本月收入: ')) -insurance = float(input('五险一金: ')) -diff = salary - insurance - 3500 -if diff <= 0: - rate = 0 - deduction = 0 -elif diff < 1500: - rate = 0.03 - deduction = 0 -elif diff < 4500: - rate = 0.1 - deduction = 105 -elif diff < 9000: - rate = 0.2 - deduction = 555 -elif diff < 35000: - rate = 0.25 - deduction = 1005 -elif diff < 55000: - rate = 0.3 - deduction = 2755 -elif diff < 80000: - rate = 0.35 - deduction = 5505 -else: - rate = 0.45 - deduction = 13505 -tax = abs(diff * rate - deduction) -print('个人所得税: ¥%.2f元' % tax) -print('实际到手收入: ¥%.2f元' % (diff + 3500 - tax)) -``` ->**说明:** 上面的代码中使用了Python内置的`abs()`函数取绝对值来处理`-0`的问题。 diff --git a/Day01-15/04.循环结构.md b/Day01-15/04.循环结构.md index 0354abd..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`类型,`range`可以用来产生一个不变的数值序列,而且这个序列通常都是用在循环中的,例如: +需要说明的是上面代码中的`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) @@ -96,7 +98,7 @@ if counter > 7: print('你的智商余额明显不足') ``` -> **说明:** 上面的代码中使用了`break`关键字来提前终止循环,需要注意的是`break`只能终止它所在的那个循环,这一点在使用嵌套的循环结构(下面会讲到)需要引起注意。除了`break`之外,还有另一个关键字是`continue`,它可以用来放弃本次循环后续的代码直接让循环进入下一轮。 +上面的代码中使用了`break`关键字来提前终止循环,需要注意的是`break`只能终止它所在的那个循环,这一点在使用嵌套的循环结构(下面会讲到)需要引起注意。除了`break`之外,还有另一个关键字是`continue`,它可以用来放弃本次循环后续的代码直接让循环进入下一轮。 和分支结构一样,循环结构也是可以嵌套的,也就是说在循环中还可以构造循环结构。下面的例子演示了如何通过嵌套的循环来输出一个九九乘法表。 @@ -116,7 +118,11 @@ for i in range(1, 10): ### 练习 -#### 练习1:输入一个数判断是不是素数。 +#### 练习1:输入一个正整数判断是不是素数。 + +> **提示**:素数指的是只能被1和自身整除的大于1的整数。 + +参考答案: ```Python """ @@ -141,11 +147,15 @@ else: print('%d不是素数' % num) ``` -#### 练习2:输入两个正整数,计算最大公约数和最小公倍数。 +#### 练习2:输入两个正整数,计算它们的最大公约数和最小公倍数。 + +> **提示**:两个数的最大公约数是两个数的公共因子中最大的那个数;两个数的最小公倍数则是能够同时被两个数整除的最小的那个数。 + +参考答案: ```Python """ -输入两个正整数计算最大公约数和最小公倍数 +输入两个正整数计算它们的最大公约数和最小公倍数 Version: 0.1 Author: 骆昊 @@ -154,8 +164,11 @@ Date: 2018-03-01 x = int(input('x = ')) y = int(input('y = ')) +# 如果x大于y就交换x和y的值 if x > y: + # 通过下面的操作将y的值赋给x, 将x的值赋给y x, y = y, x +# 从两个数中较的数开始做递减的循环 for factor in range(x, 0, -1): if x % factor == 0 and y % factor == 0: print('%d和%d的最大公约数是%d' % (x, y, factor)) @@ -163,29 +176,37 @@ for factor in range(x, 0, -1): break ``` -#### 练习3:打印三角形图案。 - -```Python -""" -打印各种三角形图案 +#### 练习3:打印如下所示的三角形图案。 +``` * ** *** **** ***** +``` +``` * ** *** **** ***** +``` +``` * *** ***** ******* ********* +``` + +参考答案: + +```Python +""" +打印三角形图案 Version: 0.1 Author: 骆昊 diff --git a/Day01-15/05.构造程序逻辑.md b/Day01-15/05.构造程序逻辑.md index 6471d38..075583c 100644 --- a/Day01-15/05.构造程序逻辑.md +++ b/Day01-15/05.构造程序逻辑.md @@ -1,13 +1,130 @@ ## 构造程序逻辑 -分支和循环结构会帮助我们将程序中逻辑建立起来,将来我们的程序无论简单复杂,都是由顺序结构、分支结构、循环结构构成的。对于编程语言的初学者来说,首先要锻炼的是将人类自然语言描述的解决问题的步骤和方法翻译成代码的能力,其次就是熟练的运用之前学过的运算符、表达式以及最近的两个章节讲解的分支结构和循环结构的知识。有了这些基本的能力才能够通过计算机程序去解决各种各样的现实问题。所以,开始做练习吧! +学完前面的几个章节后,我觉得有必要在这里带大家做一些练习来巩固之前所学的知识,虽然迄今为止我们学习的内容只是Python的冰山一角,但是这些内容已经足够我们来构建程序中的逻辑。对于编程语言的初学者来说,在学习了Python的核心语言元素(变量、类型、运算符、表达式、分支结构、循环结构等)之后,必须做的一件事情就是尝试用所学知识去解决现实中的问题,换句话说就是锻炼自己把用人类自然语言描述的算法(解决问题的方法和步骤)翻译成Python代码的能力,而这件事情必须通过大量的练习才能达成。 -### 练习清单 +我们在本章为大家整理了一些经典的案例和习题,希望通过这些例子,一方面帮助大家巩固之前所学的Python知识,另一方面帮助大家了解如何建立程序中的逻辑以及如何运用一些简单的算法解决现实中的问题。 -1. 寻找[“水仙花数”](https://baike.baidu.com/item/%E6%B0%B4%E4%BB%99%E8%8A%B1%E6%95%B0)。 -2. 寻找[“完美数”](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E6%95%B0/370913)。 -3. [“百钱百鸡”](https://baike.baidu.com/item/%E7%99%BE%E9%B8%A1%E7%99%BE%E9%92%B1/5857320)问题。 -4. 生成[“斐波拉切数列”](https://baike.baidu.com/item/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97/99145)。 -5. Craps赌博游戏。 +### 经典的例子 -> **提示**:练习的参考答案在code/Day05目录下。 \ No newline at end of file +1. 寻找**水仙花数**。 + + > **说明**:水仙花数也被称为超完全数字不变数、自恋数、自幂数、阿姆斯特朗数,它是一个3位数,该数字每个位上数字的立方之和正好等于它本身,例如:$1^3 + 5^3+ 3^3=153$。 + + ```Python + """ + 找出所有水仙花数 + + Version: 0.1 + Author: 骆昊 + """ + + for num in range(100, 1000): + low = num % 10 + mid = num // 10 % 10 + high = num // 100 + if num == low ** 3 + mid ** 3 + high ** 3: + print(num) + ``` + + 在上面的代码中,我们通过整除和求模运算分别找出了一个三位数的个位、十位和百位,这种小技巧在实际开发中还是常用的。用类似的方法,我们还可以实现将一个正整数反转,例如:将12345变成54321,代码如下所示。 + + ```Python + """ + 正整数的反转 + + Version: 0.1 + Author: 骆昊 + """ + + num = int(input('num = ')) + reversed_num = 0 + while num > 0: + reversed_num = reversed_num * 10 + num % 10 + num //= 10 + print(reversed_num) + ``` + +2. **百钱百鸡**问题。 + + > **说明**:百钱百鸡是我国古代数学家[张丘建](https://baike.baidu.com/item/%E5%BC%A0%E4%B8%98%E5%BB%BA/10246238)在《算经》一书中提出的数学问题:鸡翁一值钱五,鸡母一值钱三,鸡雏三值钱一。百钱买百鸡,问鸡翁、鸡母、鸡雏各几何?翻译成现代文是:公鸡5元一只,母鸡3元一只,小鸡1元三只,用100块钱买一百只鸡,问公鸡、母鸡、小鸡各有多少只? + + ```Python + """ + 《百钱百鸡》问题 + + Version: 0.1 + Author: 骆昊 + """ + + for x in range(0, 20): + for y in range(0, 33): + z = 100 - x - y + if 5 * x + 3 * y + z / 3 == 100: + print('公鸡: %d只, 母鸡: %d只, 小鸡: %d只' % (x, y, z)) + ``` + + 上面使用的方法叫做**穷举法**,也称为**暴力搜索法**,这种方法通过一项一项的列举备选解决方案中所有可能的候选项并检查每个候选项是否符合问题的描述,最终得到问题的解。这种方法看起来比较笨拙,但对于运算能力非常强大的计算机来说,通常都是一个可行的甚至是不错的选择,而且问题的解如果存在,这种方法一定能够找到它。 + +3. **CRAPS赌博游戏**。 + + > **说明**:CRAPS又称花旗骰,是美国拉斯维加斯非常受欢迎的一种的桌上赌博游戏。该游戏使用两粒骰子,玩家通过摇两粒骰子获得点数进行游戏。简单的规则是:玩家第一次摇骰子如果摇出了7点或11点,玩家胜;玩家第一次如果摇出2点、3点或12点,庄家胜;其他点数玩家继续摇骰子,如果玩家摇出了7点,庄家胜;如果玩家摇出了第一次摇的点数,玩家胜;其他点数,玩家继续要骰子,直到分出胜负。 + + ```Python + """ + Craps赌博游戏 + 我们设定玩家开始游戏时有1000元的赌注 + 游戏结束的条件是玩家输光所有的赌注 + + Version: 0.1 + Author: 骆昊 + """ + from random import randint + + money = 1000 + while money > 0: + print('你的总资产为:', money) + needs_go_on = False + while True: + debt = int(input('请下注: ')) + if 0 < debt <= money: + break + first = randint(1, 6) + randint(1, 6) + print('玩家摇出了%d点' % first) + if first == 7 or first == 11: + print('玩家胜!') + money += debt + elif first == 2 or first == 3 or first == 12: + print('庄家胜!') + money -= debt + else: + needs_go_on = True + while needs_go_on: + needs_go_on = False + current = randint(1, 6) + randint(1, 6) + print('玩家摇出了%d点' % current) + if current == 7: + print('庄家胜') + money -= debt + elif current == first: + print('玩家胜') + money += debt + else: + needs_go_on = True + print('你破产了, 游戏结束!') + ``` + +###有用的练习 + +1. 生成**斐波那契数列**的前20个数。 + + > **说明**:斐波那契数列(Fibonacci sequence),又称黄金分割数列,是意大利数学家莱昂纳多·斐波那契(Leonardoda Fibonacci)在《计算之书》中提出一个在理想假设条件下兔子成长率的问题而引入的数列,所以这个数列也被戏称为"兔子数列"。斐波那契数列的特点是数列的前两个数都是1,从第三个数开始,每个数都是它前面两个数的和,形如:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...。斐波那契数列在现代物理、准晶体结构、化学等领域都有直接的应用。 + +2. 找出10000以内的**完美数**。 + + > **说明**:完美数又称为完全数或完备数,它的所有的真因子(即除了自身以外的因子)的和(即因子函数)恰好等于它本身。例如:6($6=1+2+3$)和28($28=1+2+4+7+14$)就是完美数。完美数有很多神奇的特性,有兴趣的可以自行了解。 + +3. 输出**100以内所有的素数**。 + + > **说明**:素数指的是只能被1和自身整除的正整数(不包括1)。 + +上面练习的参考答案在本章对应的代码目录中,如果需要帮助请读者自行查看参考答案。 \ No newline at end of file diff --git a/Day01-15/06.函数和模块的使用.md b/Day01-15/06.函数和模块的使用.md index 85017c8..11d5099 100644 --- a/Day01-15/06.函数和模块的使用.md +++ b/Day01-15/06.函数和模块的使用.md @@ -13,8 +13,10 @@ ```Python """ 输入M和N计算C(M,N) -""" +Version: 0.1 +Author: 骆昊 +""" m = int(input('m = ')) n = int(input('n = ')) fm = 1 @@ -23,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) ``` ### 函数的作用 @@ -40,13 +42,14 @@ print(fm // fn // fmn) 在了解了如何定义函数后,我们可以对上面的代码进行重构,所谓重构就是在不影响代码执行结果的前提下对代码的结构进行调整,重构之后的代码如下所示。 ```Python -def factorial(num): - """ - 求阶乘 - - :param num: 非负整数 - :return: num的阶乘 - """ +""" +输入M和N计算C(M,N) + +Version: 0.1 +Author: 骆昊 +""" +def fac(num): + """求阶乘""" result = 1 for n in range(1, num + 1): result *= n @@ -56,27 +59,22 @@ 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模块中其实已经有一个factorial函数了,事实上要计算阶乘可以直接使用这个现成的函数而不用自己定义。下面例子中的某些函数其实Python中也是内置了,我们这里是为了讲解函数的定义和使用才把它们又实现了一遍,实际开发中不建议做这种低级的重复性的工作。 +> **说明:** Python的`math`模块中其实已经有一个名为`factorial`函数实现了阶乘运算,事实上求阶乘并不用自己定义函数。下面的例子中,我们讲的函数在Python标准库已经实现过了,我们这里是为了讲解函数的定义和使用才把它们又实现了一遍,**实际开发中并不建议做这种低级的重复劳动**。 ### 函数的参数 -函数是绝大多数编程语言中都支持的一个代码的“构建块”,但是Python中的函数与其他语言中的函数还是有很多不太相同的地方,其中一个显著的区别就是Python对函数参数的处理。在Python中,函数的参数可以有默认值,也支持使用可变参数,所以Python并不需要像其他语言一样支持[函数的重载](https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B0%E9%87%8D%E8%BD%BD),因为我们在定义一个函数的时候可以让它有多种不同的使用方式,下面是两个小例子。 +函数是绝大多数编程语言中都支持的一个代码的"构建块",但是Python中的函数与其他语言中的函数还是有很多不太相同的地方,其中一个显著的区别就是Python对函数参数的处理。在Python中,函数的参数可以有默认值,也支持使用可变参数,所以Python并不需要像其他语言一样支持[函数的重载](https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B0%E9%87%8D%E8%BD%BD),因为我们在定义一个函数的时候可以让它有多种不同的使用方式,下面是两个小例子。 ```Python from random import randint def roll_dice(n=2): - """ - 摇色子 - - :param n: 色子的个数 - :return: n颗色子点数之和 - """ + """摇色子""" total = 0 for _ in range(n): total += randint(1, 6) @@ -84,6 +82,7 @@ def roll_dice(n=2): def add(a=0, b=0, c=0): + """三个数相加""" return a + b + c @@ -105,7 +104,6 @@ print(add(c=50, a=100, b=200)) ```Python # 在参数名前面的*表示args是一个可变参数 -# 即在调用add函数时可以传入0个或多个参数 def add(*args): total = 0 for val in args: @@ -113,6 +111,7 @@ def add(*args): return total +# 在调用add函数时可以传入0个或多个参数 print(add()) print(add(1)) print(add(1, 2)) @@ -139,21 +138,21 @@ foo() 当然上面的这种情况我们很容易就能避免,但是如果项目是由多人协作进行团队开发的时候,团队中可能有多个程序员都定义了名为`foo`的函数,那么怎么解决这种命名冲突呢?答案其实很简单,Python中每个文件就代表了一个模块(module),我们在不同的模块中可以有同名的函数,在使用函数的时候我们通过`import`关键字导入指定的模块就可以区分到底要使用的是哪个模块中的`foo`函数,代码如下所示。 -module1.py +`module1.py` ```Python def foo(): print('hello, world!') ``` -module2.py +`module2.py` ```Python def foo(): print('goodbye, world!') ``` -test.py +`test.py` ```Python from module1 import foo @@ -169,7 +168,7 @@ foo() 也可以按照如下所示的方式来区分到底要使用哪一个`foo`函数。 -test.py +`test.py` ```Python import module1 as m1 @@ -181,7 +180,7 @@ m2.foo() 但是如果将代码写成了下面的样子,那么程序中调用的是最后导入的那个`foo`,因为后导入的foo覆盖了之前导入的`foo`。 -test.py +`test.py` ```Python from module1 import foo @@ -191,7 +190,7 @@ from module2 import foo foo() ``` -test.py +`test.py` ```Python from module2 import foo @@ -201,9 +200,9 @@ from module1 import foo foo() ``` -需要说明的是,如果我们导入的模块除了定义函数之外还中有可以执行代码,那么Python解释器在导入这个模块时就会执行这些代码,事实上我们可能并不希望如此,因此如果我们在模块中编写了执行代码,最好是将这些执行代码放入如下所示的条件中,这样的话除非直接运行该模块,if条件下的这些代码是不会执行的,因为只有直接执行的模块的名字才是“\_\_main\_\_”。 +需要说明的是,如果我们导入的模块除了定义函数之外还中有可以执行代码,那么Python解释器在导入这个模块时就会执行这些代码,事实上我们可能并不希望如此,因此如果我们在模块中编写了执行代码,最好是将这些执行代码放入如下所示的条件中,这样的话除非直接运行该模块,if条件下的这些代码是不会执行的,因为只有直接执行的模块的名字才是"\_\_main\_\_"。 -module3.py +`module3.py` ```Python def foo(): @@ -223,7 +222,7 @@ if __name__ == '__main__': bar() ``` -test.py +`test.py` ```Python import module3 @@ -235,8 +234,11 @@ import module3 #### 练习1:实现计算求最大公约数和最小公倍数的函数。 +参考答案: + ```Python def gcd(x, y): + """求最大公约数""" (x, y) = (y, x) if x > y else (x, y) for factor in range(x, 0, -1): if x % factor == 0 and y % factor == 0: @@ -244,13 +246,17 @@ def gcd(x, y): def lcm(x, y): + """求最小公倍数""" return x * y // gcd(x, y) ``` #### 练习2:实现判断一个数是不是回文数的函数。 +参考答案: + ```Python def is_palindrome(num): + """判断一个数是不是回文数""" temp = num total = 0 while temp > 0: @@ -261,9 +267,12 @@ def is_palindrome(num): #### 练习3:实现判断一个数是不是素数的函数。 +参考答案: + ```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 @@ -271,6 +280,8 @@ def is_prime(num): #### 练习4:写一个程序判断输入的正整数是不是回文素数。 +参考答案: + ```Python if __name__ == '__main__': num = int(input('请输入正整数: ')) @@ -278,7 +289,9 @@ if __name__ == '__main__': print('%d是回文素数' % num) ``` -通过上面的程序可以看出,当我们将代码中重复出现的和相对独立的功能抽取成函数后,我们可以组合使用这些函数来解决更为复杂的问题,这也是我们为什么要定义和使用函数的一个非常重要的原因。 +> **注意**:通过上面的程序可以看出,当我们**将代码中重复出现的和相对独立的功能抽取成函数**后,我们可以**组合使用这些函数**来解决更为复杂的问题,这也是我们为什么要定义和使用函数的一个非常重要的原因。 + +### 变量的作用域 最后,我们来讨论一下Python中有关变量作用域的问题。 @@ -286,7 +299,8 @@ if __name__ == '__main__': def foo(): b = 'hello' - def bar(): # Python中可以在函数内部再定义函数 + # Python中可以在函数内部再定义函数 + def bar(): c = True print(a) print(b) @@ -302,7 +316,7 @@ if __name__ == '__main__': foo() ``` -上面的代码能够顺利的执行并且打印出100和“hello”,但我们注意到了,在`bar`函数的内部并没有定义`a`和`b`两个变量,那么`a`和`b`是从哪里来的。我们在上面代码的`if`分支中定义了一个变量`a`,这是一个全局变量(global variable),属于全局作用域,因为它没有定义在任何一个函数中。在上面的`foo`函数中我们定义了变量`b`,这是一个定义在函数中的局部变量(local variable),属于局部作用域,在`foo`函数的外部并不能访问到它;但对于`foo`函数内部的`bar`函数来说,变量`b`属于嵌套作用域,在`bar`函数中我们是可以访问到它的。`bar`函数中的变量`c`属于局部作用域,在`bar`函数之外是无法访问的。事实上,Python查找一个变量时会按照“局部作用域”、“嵌套作用域”、“全局作用域”和“内置作用域”的顺序进行搜索,前三者我们在上面的代码中已经看到了,所谓的“内置作用域”就是Python内置的那些隐含标识符`min`、`len`等都属于内置作用域)。 +上面的代码能够顺利的执行并且打印出100、hello和True,但我们注意到了,在`bar`函数的内部并没有定义`a`和`b`两个变量,那么`a`和`b`是从哪里来的。我们在上面代码的`if`分支中定义了一个变量`a`,这是一个全局变量(global variable),属于全局作用域,因为它没有定义在任何一个函数中。在上面的`foo`函数中我们定义了变量`b`,这是一个定义在函数中的局部变量(local variable),属于局部作用域,在`foo`函数的外部并不能访问到它;但对于`foo`函数内部的`bar`函数来说,变量`b`属于嵌套作用域,在`bar`函数中我们是可以访问到它的。`bar`函数中的变量`c`属于局部作用域,在`bar`函数之外是无法访问的。事实上,Python查找一个变量时会按照“局部作用域”、“嵌套作用域”、“全局作用域”和“内置作用域”的顺序进行搜索,前三者我们在上面的代码中已经看到了,所谓的“内置作用域”就是Python内置的那些标识符,我们之前用过的`input`、`print`、`int`等都属于内置作用域。 再看看下面这段代码,我们希望通过函数调用修改全局变量`a`的值,但实际上下面的代码是做不到的。 @@ -335,9 +349,9 @@ if __name__ == '__main__': 我们可以使用`global`关键字来指示`foo`函数中的变量`a`来自于全局作用域,如果全局作用域中没有`a`,那么下面一行的代码就会定义变量`a`并将其置于全局作用域。同理,如果我们希望函数内部的函数能够修改嵌套作用域中的变量,可以使用`nonlocal`关键字来指示变量来自于嵌套作用域,请大家自行试验。 -在实际开发中,我们应该尽量减少对全局变量的使用,因为全局变量的作用域和影响过于广泛,可能会发生意料之外的修改和使用,除此之外全局变量比局部变量拥有更长的生命周期,可能导致对象占用的内存长时间无法被[垃圾回收](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))。事实上,减少对全局变量的使用,也是降低代码之间耦合度的一个重要举措,同时也是对[迪米特法则](https://zh.wikipedia.org/zh-hans/%E5%BE%97%E5%A2%A8%E5%BF%92%E8%80%B3%E5%AE%9A%E5%BE%8B)的践行。减少全局变量的使用就意味着我们应该尽量让变量的作用域在函数的内部,但是如果我们希望将一个局部变量的生命周期延长,使其在函数调用结束后依然可以访问,这时候就需要使用[闭包](https://zh.wikipedia.org/wiki/%E9%97%AD%E5%8C%85_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)),这个我们在后续的内容中进行讲解。 +在实际开发中,我们应该尽量减少对全局变量的使用,因为全局变量的作用域和影响过于广泛,可能会发生意料之外的修改和使用,除此之外全局变量比局部变量拥有更长的生命周期,可能导致对象占用的内存长时间无法被[垃圾回收](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))。事实上,减少对全局变量的使用,也是降低代码之间耦合度的一个重要举措,同时也是对[迪米特法则](https://zh.wikipedia.org/zh-hans/%E5%BE%97%E5%A2%A8%E5%BF%92%E8%80%B3%E5%AE%9A%E5%BE%8B)的践行。减少全局变量的使用就意味着我们应该尽量让变量的作用域在函数的内部,但是如果我们希望将一个局部变量的生命周期延长,使其在定义它的函数调用结束后依然可以使用它的值,这时候就需要使用[闭包](https://zh.wikipedia.org/wiki/%E9%97%AD%E5%8C%85_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)),这个我们在后续的内容中进行讲解。 -> **说明:** 很多人经常会将“闭包”一词和[“匿名函数”](https://zh.wikipedia.org/wiki/%E5%8C%BF%E5%90%8D%E5%87%BD%E6%95%B0)混为一谈,但实际上它们是不同的概念,如果想提前了解这个概念,推荐看看[维基百科](https://zh.wikipedia.org/wiki/)或者[知乎](https://www.zhihu.com/)上对这个概念的讨论。 +> **说明:** 很多人经常会将“闭包”和[“匿名函数”](https://zh.wikipedia.org/wiki/%E5%8C%BF%E5%90%8D%E5%87%BD%E6%95%B0)混为一谈,但实际上它们并不是一回事,如果想了解这个概念,可以看看[维基百科](https://zh.wikipedia.org/wiki/)的解释或者[知乎](https://www.zhihu.com/)上对这个概念的讨论。 说了那么多,其实结论很简单,从现在开始我们可以将Python代码按照下面的格式进行书写,这一点点的改进其实就是在我们理解了函数和作用域的基础上跨出的巨大的一步。 diff --git a/Day01-15/07.字符串和常用数据结构.md b/Day01-15/07.字符串和常用数据结构.md index 934e7ec..57127b3 100644 --- a/Day01-15/07.字符串和常用数据结构.md +++ b/Day01-15/07.字符串和常用数据结构.md @@ -2,185 +2,249 @@ ### 使用字符串 -第二次世界大战促使了现代电子计算机的诞生,最初的目的用计算机来快速的完成导弹弹道的计算,因此在计算机刚刚诞生的那个年代,计算机处理的信息基本上都是数值型的信息,而世界上的第一台电子计算机ENIAC每秒钟能够完成约5000次浮点运算。随着时间的推移,虽然数值运算仍然是计算机日常工作中最为重要的事情之一,但是今天的计算机更多的时间需要处理的数据可能都是以文本的方式存在的,如果我们希望通过Python程序操作本这些文本信息,就必须要先了解字符串类型以及与它相关的知识。 +第二次世界大战促使了现代电子计算机的诞生,最初计算机被应用于导弹弹道的计算,而在计算机诞生后的很多年时间里,计算机处理的信息基本上都是数值型的信息。世界上的第一台电子计算机叫ENIAC(电子数值积分计算机),诞生于美国的宾夕法尼亚大学,每秒钟能够完成约5000次浮点运算。随着时间的推移,虽然数值运算仍然是计算机日常工作中最为重要的事情之一,但是今天的计算机处理得更多的数据可能都是以文本的方式存在的,如果我们希望通过Python程序操作本这些文本信息,就必须要先了解字符串类型以及与它相关的知识。 -所谓**字符串**,就是由零个或多个字符组成的有限序列,一般记为![$${\displaystyle s=a_{1}a_{2}\dots a_{n}(0\leq n \leq \infty)}$$](./res/formula_5.png)。 - -我们可以通过下面的代码来了解字符串的使用。 +所谓**字符串**,就是由零个或多个字符组成的有限序列,一般记为![$${\displaystyle s=a_{1}a_{2}\dots a_{n}(0\leq n \leq \infty)}$$](./res/formula_5.png)。在Python程序中,如果我们把单个或多个字符用单引号或者双引号包围起来,就可以表示一个字符串。 ```Python -def main(): - str1 = 'hello, world!' - # 通过len函数计算字符串的长度 - print(len(str1)) # 13 - # 获得字符串首字母大写的拷贝 - print(str1.capitalize()) # Hello, world! - # 获得字符串变大写后的拷贝 - print(str1.upper()) # HELLO, WORLD! - # 从字符串中查找子串所在位置 - print(str1.find('or')) # 8 - print(str1.find('shit')) # -1 - # 与find类似但找不到子串时会引发异常 - # print(str1.index('or')) - # print(str1.index('shit')) - # 检查字符串是否以指定的字符串开头 - print(str1.startswith('He')) # False - print(str1.startswith('hel')) # True - # 检查字符串是否以指定的字符串结尾 - print(str1.endswith('!')) # True - # 将字符串以指定的宽度居中并在两侧填充指定的字符 - print(str1.center(50, '*')) - # 将字符串以指定的宽度靠右放置左侧填充指定的字符 - print(str1.rjust(50, ' ')) - str2 = 'abc123456' - # 从字符串中取出指定位置的字符(下标运算) - print(str2[2]) # c - # 字符串切片(从指定的开始索引到指定的结束索引) - print(str2[2:5]) # c12 - print(str2[2:]) # c123456 - print(str2[2::2]) # c246 - print(str2[::2]) # ac246 - print(str2[::-1]) # 654321cba - print(str2[-3:-1]) # 45 - # 检查字符串是否由数字构成 - print(str2.isdigit()) # False - # 检查字符串是否以字母构成 - print(str2.isalpha()) # False - # 检查字符串是否以数字和字母构成 - print(str2.isalnum()) # True - str3 = ' jackfrued@126.com ' - print(str3) - # 获得字符串修剪左右两侧空格的拷贝 - print(str3.strip()) +s1 = 'hello, world!' +s2 = "hello, world!" +# 以三个双引号或单引号开头的字符串可以折行 +s3 = """ +hello, +world! +""" +print(s1, s2, s3, end='') +``` +可以在字符串中使用`\`(反斜杠)来表示转义,也就是说`\`后面的字符不再是它原来的意义,例如:`\n`不是代表反斜杠和字符n,而是表示换行;而`\t`也不是代表反斜杠和字符t,而是表示制表符。所以如果想在字符串中表示`'`要写成`\'`,同理想表示`\`要写成`\\`。可以运行下面的代码看看会输出什么。 -if __name__ == '__main__': - main() +```Python +s1 = '\'hello, world!\'' +s2 = '\n\\hello, world!\\\n' +print(s1, s2, end='') +``` + +在`\`后面还可以跟一个八进制或者十六进制数来表示字符,例如`\141`和`\x61`都代表小写字母`a`,前者是八进制的表示法,后者是十六进制的表示法。也可以在`\`后面跟Unicode字符编码来表示字符,例如`\u9a86\u660a`代表的是中文“骆昊”。运行下面的代码,看看输出了什么。 + +```Python +s1 = '\141\142\143\x61\x62\x63' +s2 = '\u9a86\u660a' +print(s1, s2) +``` + +如果不希望字符串中的`\`表示转义,我们可以通过在字符串的最前面加上字母`r`来加以说明,再看看下面的代码又会输出什么。 + +```Python +s1 = r'\'hello, world!\'' +s2 = r'\n\\hello, world!\\\n' +print(s1, s2, end='') +``` + +Python为字符串类型提供了非常丰富的运算符,我们可以使用`+`运算符来实现字符串的拼接,可以使用`*`运算符来重复一个字符串的内容,可以使用`in`和`not in`来判断一个字符串是否包含另外一个字符串(成员运算),我们也可以用`[]`和`[:]`运算符从字符串取出某个字符或某些字符(切片运算),代码如下所示。 + +```Python +s1 = 'hello ' * 3 +print(s1) # hello hello hello +s2 = 'world' +s1 += s2 +print(s1) # hello hello hello world +print('ll' in s1) # True +print('good' in s1) # False +str2 = 'abc123456' +# 从字符串中取出指定位置的字符(下标运算) +print(str2[2]) # c +# 字符串切片(从指定的开始索引到指定的结束索引) +print(str2[2:5]) # c12 +print(str2[2:]) # c123456 +print(str2[2::2]) # c246 +print(str2[::2]) # ac246 +print(str2[::-1]) # 654321cba +print(str2[-3:-1]) # 45 +``` + +在Python中,我们还可以通过一系列的方法来完成对字符串的处理,代码如下所示。 + +```Python +str1 = 'hello, world!' +# 通过内置函数len计算字符串的长度 +print(len(str1)) # 13 +# 获得字符串首字母大写的拷贝 +print(str1.capitalize()) # Hello, world! +# 获得字符串每个单词首字母大写的拷贝 +print(str1.title()) # Hello, World! +# 获得字符串变大写后的拷贝 +print(str1.upper()) # HELLO, WORLD! +# 从字符串中查找子串所在位置 +print(str1.find('or')) # 8 +print(str1.find('shit')) # -1 +# 与find类似但找不到子串时会引发异常 +# print(str1.index('or')) +# print(str1.index('shit')) +# 检查字符串是否以指定的字符串开头 +print(str1.startswith('He')) # False +print(str1.startswith('hel')) # True +# 检查字符串是否以指定的字符串结尾 +print(str1.endswith('!')) # True +# 将字符串以指定的宽度居中并在两侧填充指定的字符 +print(str1.center(50, '*')) +# 将字符串以指定的宽度靠右放置左侧填充指定的字符 +print(str1.rjust(50, ' ')) +str2 = 'abc123456' +# 检查字符串是否由数字构成 +print(str2.isdigit()) # False +# 检查字符串是否以字母构成 +print(str2.isalpha()) # False +# 检查字符串是否以数字和字母构成 +print(str2.isalnum()) # True +str3 = ' jackfrued@126.com ' +print(str3) +# 获得字符串修剪左右两侧空格之后的拷贝 +print(str3.strip()) +``` + +我们之前讲过,可以用下面的方式来格式化输出字符串。 + +```Python +a, b = 5, 10 +print('%d * %d = %d' % (a, b, a * b)) +``` + +当然,我们也可以用字符串提供的方法来完成字符串的格式,代码如下所示。 + +```Python +a, b = 5, 10 +print('{0} * {1} = {2}'.format(a, b, a * b)) +``` + +Python 3.6以后,格式化字符串还有更为简洁的书写方式,就是在字符串前加上字母`f`,我们可以使用下面的语法糖来简化上面的代码。 + +```Python +a, b = 5, 10 +print(f'{a} * {b} = {a * b}') ``` 除了字符串,Python还内置了多种类型的数据结构,如果要在程序中保存和操作数据,绝大多数时候可以利用现有的数据结构来实现,最常用的包括列表、元组、集合和字典。 ### 使用列表 -下面的代码演示了如何定义列表、使用下标访问列表元素以及添加和删除元素的操作。 +不知道大家是否注意到,刚才我们讲到的字符串类型(`str`)和之前我们讲到的数值类型(`int`和`float`)有一些区别。数值类型是标量类型,也就是说这种类型的对象没有可以访问的内部结构;而字符串类型是一种结构化的、非标量类型,所以才会有一系列的属性和方法。接下来我们要介绍的列表(`list`),也是一种结构化的、非标量类型,它是值的有序序列,每个值都可以通过索引进行标识,定义列表可以将列表的元素放在`[]`中,多个元素用`,`进行分隔,可以使用`for`循环对列表元素进行遍历,也可以使用`[]`或`[:]`运算符取出列表中的一个或多个元素。 + +下面的代码演示了如何定义列表、如何遍历列表以及列表的下标运算。 ```Python -def main(): - list1 = [1, 3, 5, 7, 100] - print(list1) - list2 = ['hello'] * 5 - print(list2) - # 计算列表长度(元素个数) - print(len(list1)) - # 下标(索引)运算 - print(list1[0]) - print(list1[4]) - # print(list1[5]) # IndexError: list index out of range - print(list1[-1]) - print(list1[-3]) - list1[2] = 300 - print(list1) - # 添加元素 - list1.append(200) - list1.insert(1, 400) - list1 += [1000, 2000] - print(list1) - print(len(list1)) - # 删除元素 - list1.remove(3) - if 1234 in list1: - list1.remove(1234) - del list1[0] - print(list1) - # 清空列表元素 - list1.clear() - print(list1) +list1 = [1, 3, 5, 7, 100] +print(list1) # [1, 3, 5, 7, 100] +# 乘号表示列表元素的重复 +list2 = ['hello'] * 3 +print(list2) # ['hello', 'hello', 'hello'] +# 计算列表长度(元素个数) +print(len(list1)) # 5 +# 下标(索引)运算 +print(list1[0]) # 1 +print(list1[4]) # 100 +# print(list1[5]) # IndexError: list index out of range +print(list1[-1]) # 100 +print(list1[-3]) # 5 +list1[2] = 300 +print(list1) # [1, 3, 300, 7, 100] +# 通过循环用下标遍历列表元素 +for index in range(len(list1)): + print(list1[index]) +# 通过for循环遍历列表元素 +for elem in list1: + print(elem) +# 通过enumerate函数处理列表之后再遍历可以同时获得元素索引和值 +for index, elem in enumerate(list1): + print(index, elem) +``` +下面的代码演示了如何向列表中添加元素以及如何从列表中移除元素。 -if __name__ == '__main__': - main() +```Python +list1 = [1, 3, 5, 7, 100] +# 添加元素 +list1.append(200) +list1.insert(1, 400) +# 合并两个列表 +# list1.extend([1000, 2000]) +list1 += [1000, 2000] +print(list1) # [1, 400, 3, 5, 7, 100, 200, 1000, 2000] +print(len(list1)) # 9 +# 先通过成员运算判断元素是否在列表中,如果存在就删除该元素 +if 3 in list1: + list1.remove(3) +if 1234 in list1: + list1.remove(1234) +print(list1) # [1, 400, 5, 7, 100, 200, 1000, 2000] +# 从指定的位置删除元素 +list1.pop(0) +list1.pop(len(list1) - 1) +print(list1) # [400, 5, 7, 100, 200, 1000] +# 清空列表元素 +list1.clear() +print(list1) # [] ``` 和字符串一样,列表也可以做切片操作,通过切片操作我们可以实现对列表的复制或者将列表中的一部分取出来创建出新的列表,代码如下所示。 ```Python -def main(): - fruits = ['grape', 'apple', 'strawberry', 'waxberry'] - fruits += ['pitaya', 'pear', 'mango'] - # 循环遍历列表元素 - for fruit in fruits: - print(fruit.title(), end=' ') - print() - # 列表切片 - fruits2 = fruits[1:4] - print(fruits2) - # fruit3 = fruits # 没有复制列表只创建了新的引用 - # 可以通过完整切片操作来复制列表 - fruits3 = fruits[:] - print(fruits3) - fruits4 = fruits[-3:-1] - print(fruits4) - # 可以通过反向切片操作来获得倒转后的列表的拷贝 - fruits5 = fruits[::-1] - print(fruits5) - - -if __name__ == '__main__': - main() +fruits = ['grape', 'apple', 'strawberry', 'waxberry'] +fruits += ['pitaya', 'pear', 'mango'] +# 列表切片 +fruits2 = fruits[1:4] +print(fruits2) # apple strawberry waxberry +# 可以通过完整切片操作来复制列表 +fruits3 = fruits[:] +print(fruits3) # ['grape', 'apple', 'strawberry', 'waxberry', 'pitaya', 'pear', 'mango'] +fruits4 = fruits[-3:-1] +print(fruits4) # ['pitaya', 'pear'] +# 可以通过反向切片操作来获得倒转后的列表的拷贝 +fruits5 = fruits[::-1] +print(fruits5) # ['mango', 'pear', 'pitaya', 'waxberry', 'strawberry', 'apple', 'grape'] ``` 下面的代码实现了对列表的排序操作。 ```Python -def main(): - list1 = ['orange', 'apple', 'zoo', 'internationalization', 'blueberry'] - list2 = sorted(list1) - # sorted函数返回列表排序后的拷贝不会修改传入的列表 - # 函数的设计就应该像sorted函数一样尽可能不产生副作用 - list3 = sorted(list1, reverse=True) - # 通过key关键字参数指定根据字符串长度进行排序而不是默认的字母表顺序 - list4 = sorted(list1, key=len) - print(list1) - print(list2) - print(list3) - print(list4) - # 给列表对象发出排序消息直接在列表对象上进行排序 - list1.sort(reverse=True) - print(list1) - - -if __name__ == '__main__': - main() +list1 = ['orange', 'apple', 'zoo', 'internationalization', 'blueberry'] +list2 = sorted(list1) +# sorted函数返回列表排序后的拷贝不会修改传入的列表 +# 函数的设计就应该像sorted函数一样尽可能不产生副作用 +list3 = sorted(list1, reverse=True) +# 通过key关键字参数指定根据字符串长度进行排序而不是默认的字母表顺序 +list4 = sorted(list1, key=len) +print(list1) +print(list2) +print(list3) +print(list4) +# 给列表对象发出排序消息直接在列表对象上进行排序 +list1.sort(reverse=True) +print(list1) ``` +### 生成式和生成器 + 我们还可以使用列表的生成式语法来创建列表,代码如下所示。 ```Python -import sys - - -def main(): - f = [x for x in range(1, 10)] - print(f) - f = [x + y for x in 'ABCDE' for y in '1234567'] - print(f) - # 用列表的生成表达式语法创建列表容器 - # 用这种语法创建列表之后元素已经准备就绪所以需要耗费较多的内存空间 - f = [x ** 2 for x in range(1, 1000)] - print(sys.getsizeof(f)) # 查看对象占用内存的字节数 - print(f) - # 请注意下面的代码创建的不是一个列表而是一个生成器对象 - # 通过生成器可以获取到数据但它不占用额外的空间存储数据 - # 每次需要数据的时候就通过内部的运算得到数据(需要花费额外的时间) - f = (x ** 2 for x in range(1, 1000)) - print(sys.getsizeof(f)) # 相比生成式生成器不占用存储数据的空间 - print(f) - for val in f: - print(val) - - -if __name__ == '__main__': - main() +f = [x for x in range(1, 10)] +print(f) +f = [x + y for x in 'ABCDE' for y in '1234567'] +print(f) +# 用列表的生成表达式语法创建列表容器 +# 用这种语法创建列表之后元素已经准备就绪所以需要耗费较多的内存空间 +f = [x ** 2 for x in range(1, 1000)] +print(sys.getsizeof(f)) # 查看对象占用内存的字节数 +print(f) +# 请注意下面的代码创建的不是一个列表而是一个生成器对象 +# 通过生成器可以获取到数据但它不占用额外的空间存储数据 +# 每次需要数据的时候就通过内部的运算得到数据(需要花费额外的时间) +f = (x ** 2 for x in range(1, 1000)) +print(sys.getsizeof(f)) # 相比生成式生成器不占用存储数据的空间 +print(f) +for val in f: + print(val) ``` 除了上面提到的生成器语法,Python中还有另外一种定义生成器的方式,就是通过`yield`关键字将一个普通函数改造成生成器函数。下面的代码演示了如何实现一个生成[斐波拉切数列](https://zh.wikipedia.org/wiki/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97)的生成器。所谓斐波拉切数列可以通过下面[递归](https://zh.wikipedia.org/wiki/%E9%80%92%E5%BD%92)的方法来进行定义: @@ -212,39 +276,34 @@ if __name__ == '__main__': ### 使用元组 -Python 的元组与列表类似,不同之处在于元组的元素不能修改,在前面的代码中我们已经不止一次使用过元组了。顾名思义,我们把多个元素组合到一起就形成了一个元组,所以它和列表一样可以保存多条数据。下面的代码演示了如何定义和使用元组。 +Python中的元组与列表类似也是一种容器数据类型,可以用一个变量(对象)来存储多个数据,不同之处在于元组的元素不能修改,在前面的代码中我们已经不止一次使用过元组了。顾名思义,我们把多个元素组合到一起就形成了一个元组,所以它和列表一样可以保存多条数据。下面的代码演示了如何定义和使用元组。 ```Python -def main(): - # 定义元组 - t = ('骆昊', 38, True, '四川成都') - print(t) - # 获取元组中的元素 - print(t[0]) - print(t[3]) - # 遍历元组中的值 - for member in t: - print(member) - # 重新给元组赋值 - # t[0] = '王大锤' # TypeError - # 变量t重新引用了新的元组原来的元组将被垃圾回收 - t = ('王大锤', 20, True, '云南昆明') - print(t) - # 将元组转换成列表 - person = list(t) - print(person) - # 列表是可以修改它的元素的 - person[0] = '李小龙' - person[1] = 25 - print(person) - # 将列表转换成元组 - fruits_list = ['apple', 'banana', 'orange'] - fruits_tuple = tuple(fruits_list) - print(fruits_tuple) - - -if __name__ == '__main__': - main() +# 定义元组 +t = ('骆昊', 38, True, '四川成都') +print(t) +# 获取元组中的元素 +print(t[0]) +print(t[3]) +# 遍历元组中的值 +for member in t: + print(member) +# 重新给元组赋值 +# t[0] = '王大锤' # TypeError +# 变量t重新引用了新的元组原来的元组将被垃圾回收 +t = ('王大锤', 20, True, '云南昆明') +print(t) +# 将元组转换成列表 +person = list(t) +print(person) +# 列表是可以修改它的元素的 +person[0] = '李小龙' +person[1] = 25 +print(person) +# 将列表转换成元组 +fruits_list = ['apple', 'banana', 'orange'] +fruits_tuple = tuple(fruits_list) +print(fruits_tuple) ``` 这里有一个非常值得探讨的问题,我们已经有了列表这种数据结构,为什么还需要元组这样的类型呢? @@ -260,96 +319,106 @@ Python中的集合跟数学上的集合是一致的,不允许有重复元素 ![](./res/python-set.png) +可以按照下面代码所示的方式来创建和使用集合。 + ```Python -def main(): - set1 = {1, 2, 3, 3, 3, 2} - print(set1) - print('Length =', len(set1)) - set2 = set(range(1, 10)) - print(set2) - set1.add(4) - set1.add(5) - set2.update([11, 12]) - print(set1) - print(set2) - set2.discard(5) - # remove的元素如果不存在会引发KeyError - if 4 in set2: - set2.remove(4) - print(set2) - # 遍历集合容器 - for elem in set2: - print(elem ** 2, end=' ') - print() - # 将元组转换成集合 - set3 = set((1, 2, 3, 3, 2, 1)) - print(set3.pop()) - print(set3) - # 集合的交集、并集、差集、对称差运算 - print(set1 & set2) - # print(set1.intersection(set2)) - print(set1 | set2) - # print(set1.union(set2)) - print(set1 - set2) - # print(set1.difference(set2)) - print(set1 ^ set2) - # print(set1.symmetric_difference(set2)) - # 判断子集和超集 - print(set2 <= set1) - # print(set2.issubset(set1)) - print(set3 <= set1) - # print(set3.issubset(set1)) - print(set1 >= set2) - # print(set1.issuperset(set2)) - print(set1 >= set3) - # print(set1.issuperset(set3)) +# 创建集合的字面量语法 +set1 = {1, 2, 3, 3, 3, 2} +print(set1) +print('Length =', len(set1)) +# 创建集合的构造器语法(面向对象部分会进行详细讲解) +set2 = set(range(1, 10)) +set3 = set((1, 2, 3, 3, 2, 1)) +print(set2, set3) +# 创建集合的推导式语法(推导式也可以用于推导集合) +set4 = {num for num in range(1, 100) if num % 3 == 0 or num % 5 == 0} +print(set4) +``` +向集合添加元素和从集合删除元素。 -if __name__ == '__main__': - main() +```Python +set1.add(4) +set1.add(5) +set2.update([11, 12]) +set2.discard(5) +if 4 in set2: + set2.remove(4) +print(set1, set2) +print(set3.pop()) +print(set3) +``` + +集合的成员、交集、并集、差集等运算。 + +```Python +# 集合的交集、并集、差集、对称差运算 +print(set1 & set2) +# print(set1.intersection(set2)) +print(set1 | set2) +# print(set1.union(set2)) +print(set1 - set2) +# print(set1.difference(set2)) +print(set1 ^ set2) +# print(set1.symmetric_difference(set2)) +# 判断子集和超集 +print(set2 <= set1) +# print(set2.issubset(set1)) +print(set3 <= set1) +# print(set3.issubset(set1)) +print(set1 >= set2) +# print(set1.issuperset(set2)) +print(set1 >= set3) +# print(set1.issuperset(set3)) ``` > **说明:** Python中允许通过一些特殊的方法来为某种类型或数据结构自定义运算符(后面的章节中会讲到),上面的代码中我们对集合进行运算的时候可以调用集合对象的方法,也可以直接使用对应的运算符,例如`&`运算符跟intersection方法的作用就是一样的,但是使用运算符让代码更加直观。 ### 使用字典 -字典是另一种可变容器模型,类似于我们生活中使用的字典,它可以存储任意类型对象,与列表、集合不同的是,字典的每个元素都是由一个键和一个值组成的“键值对”,键和值通过冒号分开。下面的代码演示了如何定义和使用字典。 +字典是另一种可变容器模型,Python中的字典跟我们生活中使用的字典是一样一样的,它可以存储任意类型对象,与列表、集合不同的是,字典的每个元素都是由一个键和一个值组成的“键值对”,键和值通过冒号分开。下面的代码演示了如何定义和使用字典。 ```Python -def main(): - scores = {'骆昊': 95, '白元芳': 78, '狄仁杰': 82} - # 通过键可以获取字典中对应的值 - print(scores['骆昊']) - print(scores['狄仁杰']) - # 对字典进行遍历(遍历的其实是键再通过键取对应的值) - for elem in scores: - print('%s\t--->\t%d' % (elem, scores[elem])) - # 更新字典中的元素 - scores['白元芳'] = 65 - scores['诸葛王朗'] = 71 - scores.update(冷面=67, 方启鹤=85) - print(scores) - if '武则天' in scores: - print(scores['武则天']) - print(scores.get('武则天')) - # get方法也是通过键获取对应的值但是可以设置默认值 - print(scores.get('武则天', 60)) - # 删除字典中的元素 - print(scores.popitem()) - print(scores.popitem()) - print(scores.pop('骆昊', 100)) - # 清空字典 - scores.clear() - print(scores) - - -if __name__ == '__main__': - main() +# 创建字典的字面量语法 +scores = {'骆昊': 95, '白元芳': 78, '狄仁杰': 82} +print(scores) +# 创建字典的构造器语法 +items1 = dict(one=1, two=2, three=3, four=4) +# 通过zip函数将两个序列压成字典 +items2 = dict(zip(['a', 'b', 'c'], '123')) +# 创建字典的推导式语法 +items3 = {num: num ** 2 for num in range(1, 10)} +print(items1, items2, items3) +# 通过键可以获取字典中对应的值 +print(scores['骆昊']) +print(scores['狄仁杰']) +# 对字典中所有键值对进行遍历 +for key in scores: + print(f'{key}: {scores[key]}') +# 更新字典中的元素 +scores['白元芳'] = 65 +scores['诸葛王朗'] = 71 +scores.update(冷面=67, 方启鹤=85) +print(scores) +if '武则天' in scores: + print(scores['武则天']) +print(scores.get('武则天')) +# get方法也是通过键获取对应的值但是可以设置默认值 +print(scores.get('武则天', 60)) +# 删除字典中的元素 +print(scores.popitem()) +print(scores.popitem()) +print(scores.pop('骆昊', 100)) +# 清空字典 +scores.clear() +print(scores) ``` ### 练习 -#### 练习1:在屏幕上显示跑马灯文字 +#### 练习1:在屏幕上显示跑马灯文字。 + +参考答案: ```Python import os @@ -373,6 +442,8 @@ if __name__ == '__main__': #### 练习2:设计一个函数产生指定长度的验证码,验证码由大小写字母和数字构成。 +参考答案: + ```Python import random @@ -396,6 +467,8 @@ def generate_code(code_len=4): #### 练习3:设计一个函数返回给定文件名的后缀名。 +参考答案: + ```Python def get_suffix(filename, has_dot=False): """ @@ -415,6 +488,8 @@ def get_suffix(filename, has_dot=False): #### 练习4:设计一个函数返回传入的列表中最大和第二大的元素的值。 +参考答案: + ```Python def max2(x): m1, m2 = (x[0], x[1]) if x[0] > x[1] else (x[1], x[0]) @@ -427,7 +502,9 @@ def max2(x): return m1, m2 ``` -#### 练习5:计算指定的年月日是这一年的第几天 +#### 练习5:计算指定的年月日是这一年的第几天。 + +参考答案: ```Python def is_leap_year(year): @@ -472,6 +549,8 @@ if __name__ == '__main__': #### 练习6:打印[杨辉三角](https://zh.wikipedia.org/wiki/%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92%E5%BD%A2)。 +参考答案: + ```Python def main(): num = int(input('Number of rows: ')) @@ -493,7 +572,7 @@ if __name__ == '__main__': ### 综合案例 -#### 案例1:双色球选号 +#### 案例1:双色球选号。 ```Python from random import randrange, randint, sample @@ -534,7 +613,7 @@ if __name__ == '__main__': > **说明:** 上面使用random模块的sample函数来实现从列表中选择不重复的n个元素。 -#### 综合案例2:[约瑟夫环问题](https://zh.wikipedia.org/wiki/%E7%BA%A6%E7%91%9F%E5%A4%AB%E6%96%AF%E9%97%AE%E9%A2%98) +#### 综合案例2:[约瑟夫环问题](https://zh.wikipedia.org/wiki/%E7%BA%A6%E7%91%9F%E5%A4%AB%E6%96%AF%E9%97%AE%E9%A2%98)。 ```Python """ @@ -564,7 +643,7 @@ if __name__ == '__main__': ``` -#### 综合案例3:[井字棋](https://zh.wikipedia.org/wiki/%E4%BA%95%E5%AD%97%E6%A3%8B)游戏 +#### 综合案例3:[井字棋](https://zh.wikipedia.org/wiki/%E4%BA%95%E5%AD%97%E6%A3%8B)游戏。 ```Python import os diff --git a/Day01-15/08.面向对象编程基础.md b/Day01-15/08.面向对象编程基础.md index d84d376..56a0fff 100644 --- a/Day01-15/08.面向对象编程基础.md +++ b/Day01-15/08.面向对象编程基础.md @@ -1,8 +1,8 @@ ## 面向对象编程基础 -活在当下的程序员应该都听过“面向对象编程”一词,也经常有人问能不能用一句话解释下什么是“面向对象编程”,我们先来看看比较正式的说法。 +活在当下的程序员应该都听过"面向对象编程"一词,也经常有人问能不能用一句话解释下什么是"面向对象编程",我们先来看看比较正式的说法。 -“把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)和泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派。” +"把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)和泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派。" 这样一说是不是更不明白了。所以我们还是看看更通俗易懂的说法,下面这段内容来自于[知乎](https://www.zhihu.com/)。 @@ -10,9 +10,9 @@ > **说明:** 以上的内容来自于网络,不代表作者本人的观点和看法,与作者本人立场无关,相关责任不由作者承担。 -之前我们说过“程序是指令的集合”,我们在程序中书写的语句在执行时会变成一条或多条指令然后由CPU去执行。当然为了简化程序的设计,我们引入了函数的概念,把相对独立且经常重复使用的代码放置到函数中,在需要使用这些功能的时候只要调用函数即可;如果一个函数的功能过于复杂和臃肿,我们又可以进一步将函数继续切分为子函数来降低系统的复杂性。但是说了这么多,不知道大家是否发现,所谓编程就是程序员按照计算机的工作方式控制计算机完成各种任务。但是,计算机的工作方式与正常人类的思维模式是不同的,如果编程就必须得抛弃人类正常的思维方式去迎合计算机,编程的乐趣就少了很多,“每个人都应该学习编程”这样的豪言壮语就只能说说而已。当然,这些还不是最重要的,最重要的是当我们需要开发一个复杂的系统时,代码的复杂性会让开发和维护工作都变得举步维艰,所以在上世纪60年代末期,“[软件危机](https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E5%8D%B1%E6%9C%BA)”、“[软件工程](https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B)”等一系列的概念开始在行业中出现。 +之前我们说过"**程序是指令的集合**",我们在程序中书写的语句在执行时会变成一条或多条指令然后由CPU去执行。当然为了简化程序的设计,我们引入了函数的概念,把相对独立且经常重复使用的代码放置到函数中,在需要使用这些功能的时候只要调用函数即可;如果一个函数的功能过于复杂和臃肿,我们又可以进一步将函数继续切分为子函数来降低系统的复杂性。但是说了这么多,不知道大家是否发现,所谓编程就是程序员按照计算机的工作方式控制计算机完成各种任务。但是,计算机的工作方式与正常人类的思维模式是不同的,如果编程就必须得抛弃人类正常的思维方式去迎合计算机,编程的乐趣就少了很多,"每个人都应该学习编程"这样的豪言壮语就只能说说而已。当然,这些还不是最重要的,最重要的是当我们需要开发一个复杂的系统时,代码的复杂性会让开发和维护工作都变得举步维艰,所以在上世纪60年代末期,"[软件危机](https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E5%8D%B1%E6%9C%BA)"、"[软件工程](https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B)"等一系列的概念开始在行业中出现。 -当然,程序员圈子内的人都知道,现实中并没有解决上面所说的这些问题的“[银弹](https://zh.wikipedia.org/wiki/%E6%B2%A1%E6%9C%89%E9%93%B6%E5%BC%B9)”,真正让软件开发者看到希望的是上世纪70年代诞生的[Smalltalk](https://zh.wikipedia.org/wiki/Smalltalk)编程语言中引入的面向对象的编程思想(面向对象编程的雏形可以追溯到更早期的[Simula](https://zh.wikipedia.org/wiki/Simula)语言)。按照这种编程理念,程序中的数据和操作数据的函数是一个逻辑上的整体,我们称之为“对象”,而我们解决问题的方式就是创建出需要的对象并向对象发出各种各样的消息,多个对象的协同工作最终可以让我们构造出复杂的系统来解决现实中的问题。 +当然,程序员圈子内的人都知道,现实中并没有解决上面所说的这些问题的"[银弹](https://zh.wikipedia.org/wiki/%E6%B2%A1%E6%9C%89%E9%93%B6%E5%BC%B9)",真正让软件开发者看到希望的是上世纪70年代诞生的[Smalltalk](https://zh.wikipedia.org/wiki/Smalltalk)编程语言中引入的面向对象的编程思想(面向对象编程的雏形可以追溯到更早期的[Simula](https://zh.wikipedia.org/wiki/Simula)语言)。按照这种编程理念,程序中的数据和操作数据的函数是一个逻辑上的整体,我们称之为“对象”,而我们解决问题的方式就是创建出需要的对象并向对象发出各种各样的消息,多个对象的协同工作最终可以让我们构造出复杂的系统来解决现实中的问题。 > **说明:** 当然面向对象也不是解决软件开发中所有问题的最后的“银弹”,所以今天的高级程序设计语言几乎都提供了对多种编程范式的支持,Python也不例外。 @@ -97,7 +97,7 @@ if __name__ == "__main__": main() ``` -但是,Python并没有从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来“妨碍”对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们,下面的代码就可以验证这一点。之所以这样设定,可以用这样一句名言加以解释,就是“We are all consenting adults here”。因为绝大多数程序员都认为开放比封闭要好,而且程序员要自己为自己的行为负责。 +但是,Python并没有从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来妨碍对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们,下面的代码就可以验证这一点。之所以这样设定,可以用这样一句名言加以解释,就是"**We are all consenting adults here**"。因为绝大多数程序员都认为开放比封闭要好,而且程序员要自己为自己的行为负责。 ```Python class Test: @@ -124,11 +124,13 @@ if __name__ == "__main__": ### 面向对象的支柱 -面向对象有三大支柱:封装、继承和多态。后面两个概念在下一个章节中进行详细的说明,这里我们先说一下什么是封装。我自己对封装的理解是“隐藏一切可以隐藏的实现细节,只向外界暴露(提供)简单的编程接口”。我们在类中定义的方法其实就是把数据和对数据的操作封装起来了,在我们创建了对象之后,只需要给对象发送一个消息(调用方法)就可以执行方法中的代码,也就是说我们只需要知道方法的名字和传入的参数(方法的外部视图),而不需要知道方法内部的实现细节(方法的内部视图)。 +面向对象有三大支柱:封装、继承和多态。后面两个概念在下一个章节中进行详细的说明,这里我们先说一下什么是封装。我自己对封装的理解是"隐藏一切可以隐藏的实现细节,只向外界暴露(提供)简单的编程接口"。我们在类中定义的方法其实就是把数据和对数据的操作封装起来了,在我们创建了对象之后,只需要给对象发送一个消息(调用方法)就可以执行方法中的代码,也就是说我们只需要知道方法的名字和传入的参数(方法的外部视图),而不需要知道方法内部的实现细节(方法的内部视图)。 ### 练习 -#### 练习1:定义一个类描述数字时钟 +#### 练习1:定义一个类描述数字时钟。 + +参考答案: ```Python from time import sleep @@ -180,6 +182,8 @@ if __name__ == '__main__': #### 练习2:定义一个类描述平面上的点并提供移动点和计算到另一个点距离的方法。 +参考答案: + ```Python from math import sqrt diff --git a/Day01-15/09.面向对象进阶.md b/Day01-15/09.面向对象进阶.md index 4e8cf6a..bbf7006 100644 --- a/Day01-15/09.面向对象进阶.md +++ b/Day01-15/09.面向对象进阶.md @@ -331,7 +331,7 @@ if __name__ == '__main__': ### 综合案例 -#### 案例1:奥特曼打小怪兽 +#### 案例1:奥特曼打小怪兽。 ```Python from abc import ABCMeta, abstractmethod @@ -521,7 +521,7 @@ if __name__ == '__main__': main() ``` -#### 案例2:扑克游戏 +#### 案例2:扑克游戏。 ```Python import random @@ -638,7 +638,7 @@ if __name__ == '__main__': >**说明:** 大家可以自己尝试在上面代码的基础上写一个简单的扑克游戏,例如21点(Black Jack),游戏的规则可以自己在网上找一找。 -#### 案例3:工资结算系统 +#### 案例3:工资结算系统。 ```Python """ diff --git a/Day01-15/code/Day01/flag.py b/Day01-15/code/Day01/flag.py new file mode 100644 index 0000000..2029b08 --- /dev/null +++ b/Day01-15/code/Day01/flag.py @@ -0,0 +1,74 @@ +""" +用Python的turtle模块绘制国旗 +""" +import turtle + + +def draw_rectangle(x, y, width, height): + """绘制矩形""" + turtle.goto(x, y) + turtle.pencolor('red') + turtle.fillcolor('red') + turtle.begin_fill() + for i in range(2): + turtle.forward(width) + turtle.left(90) + turtle.forward(height) + turtle.left(90) + turtle.end_fill() + + +def draw_star(x, y, radius): + """绘制五角星""" + turtle.setpos(x, y) + pos1 = turtle.pos() + turtle.circle(-radius, 72) + pos2 = turtle.pos() + turtle.circle(-radius, 72) + pos3 = turtle.pos() + turtle.circle(-radius, 72) + pos4 = turtle.pos() + turtle.circle(-radius, 72) + pos5 = turtle.pos() + turtle.color('yellow', 'yellow') + turtle.begin_fill() + turtle.goto(pos3) + turtle.goto(pos1) + turtle.goto(pos4) + turtle.goto(pos2) + turtle.goto(pos5) + turtle.end_fill() + + +def main(): + """主程序""" + turtle.speed(12) + turtle.penup() + x, y = -270, -180 + # 画国旗主体 + width, height = 540, 360 + draw_rectangle(x, y, width, height) + # 画大星星 + pice = 22 + center_x, center_y = x + 5 * pice, y + height - pice * 5 + turtle.goto(center_x, center_y) + turtle.left(90) + turtle.forward(pice * 3) + turtle.right(90) + draw_star(turtle.xcor(), turtle.ycor(), pice * 3) + x_poses, y_poses = [10, 12, 12, 10], [2, 4, 7, 9] + # 画小星星 + for x_pos, y_pos in zip(x_poses, y_poses): + turtle.goto(x + x_pos * pice, y + height - y_pos * pice) + turtle.left(turtle.towards(center_x, center_y) - turtle.heading()) + turtle.forward(pice) + turtle.right(90) + draw_star(turtle.xcor(), turtle.ycor(), pice) + # 隐藏海龟 + turtle.ht() + # 显示绘图窗口 + turtle.mainloop() + + +if __name__ == '__main__': + main() \ No newline at end of file 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 eabc351..62dba5d 100644 --- a/Day16-20/16-20.Python语言进阶.md +++ b/Day16-20/16-20.Python语言进阶.md @@ -1,1352 +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/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/code/old/html+css/example.html b/Day21-30/code/old/html+css/example.html index ae77ce5..1ffdc31 100644 --- a/Day21-30/code/old/html+css/example.html +++ b/Day21-30/code/old/html+css/example.html @@ -1,80 +1,80 @@ -<!DOCTYPE html> -<html> - <head> - <meta charset="UTF-8"> - <title> - - - - - - + + + + + + + + + + + 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 5effe50..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服务器,包括: @@ -243,21 +243,21 @@ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行 -- 创建学院表 create table tb_college ( - collid int auto_increment comment '编号', - collname varchar(50) not null comment '名称', - intro varchar(500) default '' comment '介绍', + collid int auto_increment comment '编号', + collname varchar(50) not null comment '名称', + collintro varchar(500) default '' comment '介绍', primary key (collid) ); -- 创建学生表 create table tb_student ( - stuid int not null comment '学号', - stuname varchar(20) not null comment '姓名', - sex boolean default 1 comment '性别', - birth date not null comment '出生日期', - addr varchar(255) default '' comment '籍贯', - collid int not null comment '所属学院', + stuid int not null comment '学号', + stuname varchar(20) not null comment '姓名', + stusex boolean default 1 comment '性别', + stubirth date not null comment '出生日期', + stuaddr varchar(255) default '' comment '籍贯', + collid int not null comment '所属学院', primary key (stuid), foreign key (collid) references tb_college (collid) ); @@ -265,10 +265,10 @@ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行 -- 创建教师表 create table tb_teacher ( - teaid int not null comment '工号', - teaname varchar(20) not null comment '姓名', - title varchar(10) default '助教' comment '职称', - collid int not null comment '所属学院', + teaid int not null comment '工号', + teaname varchar(20) not null comment '姓名', + teatitle varchar(10) default '助教' comment '职称', + collid int not null comment '所属学院', primary key (teaid), foreign key (collid) references tb_college (collid) ); @@ -276,10 +276,10 @@ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行 -- 创建课程表 create table tb_course ( - couid int not null comment '编号', - couname varchar(50) not null comment '名称', - credit int not null comment '学分', - teaid int not null comment '授课老师', + couid int not null comment '编号', + couname varchar(50) not null comment '名称', + coucredit int not null comment '学分', + teaid int not null comment '授课老师', primary key (couid), foreign key (teaid) references tb_teacher (teaid) ); @@ -287,11 +287,11 @@ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行 -- 创建选课记录表 create table tb_record ( - recid int auto_increment comment '选课记录编号', - sid int not null comment '选课学生', - cid int not null comment '所选课程', - seldate datetime default now() comment '选课时间日期', - score decimal(4,1) comment '考试成绩', + recid int auto_increment comment '选课记录编号', + sid int not null comment '选课学生', + cid int not null comment '所选课程', + seldate datetime default now() comment '选课时间日期', + score decimal(4,1) comment '考试成绩', primary key (recid), foreign key (sid) references tb_student (stuid), foreign key (cid) references tb_course (couid), @@ -548,13 +548,13 @@ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行 ```SQL -- 插入学院数据 - insert into tb_college (collname, intro) values + insert into tb_college (collname, collintro) values ('计算机学院', '创建于1956年是我国首批建立计算机专业。学院现有计算机科学与技术一级学科和网络空间安全一级学科博士学位授予权,其中计算机科学与技术一级学科具有博士后流动站。计算机科学与技术一级学科在2017年全国第四轮学科评估中评为A;2019 U.S.News全球计算机学科排名26名;ESI学科排名0.945‰,进入全球前1‰,位列第43位。'), ('外国语学院', '1998年浙江大学、杭州大学、浙江农业大学、浙江医科大学四校合并,成立新的浙江大学。1999年原浙江大学外语系、原杭州大学外国语学院、原杭州大学大外部、原浙江农业大学公外部、原浙江医科大学外语教学部合并,成立浙江大学外国语学院。2003年学院更名为浙江大学外国语言文化与国际交流学院。'), ('经济管理学院', '四川大学经济学院历史悠久、传承厚重,其前身是创办于1905年的四川大学经济科,距今已有100多年的历史。已故著名经济学家彭迪先、张与九、蒋学模、胡寄窗、陶大镛、胡代光,以及当代著名学者刘诗白等曾先后在此任教或学习。在长期的办学过程中,学院坚持以马克思主义的立场、观点、方法为指导,围绕建设世界一流经济学院的奋斗目标,做实“两个伟大”深度融合,不断提高党的建设质量与科学推进一流事业深度融合。'); -- 插入学生数据 - insert into tb_student (stuid, stuname, sex, birth, addr, collid) values + insert into tb_student (stuid, stuname, stusex, stubirth, stuaddr, collid) values (1001, '杨逍', 1, '1990-3-4', '四川成都', 1), (1002, '任我行', 1, '1992-2-2', '湖南长沙', 1), (1033, '王语嫣', 0, '1989-12-3', '四川成都', 1), @@ -571,10 +571,10 @@ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行 delete from tb_student where stuid=4040; -- 更新学生数据 - update tb_student set stuname='杨过', addr='湖南长沙' where stuid=1001; + update tb_student set stuname='杨过', stuaddr='湖南长沙' where stuid=1001; -- 插入老师数据 - insert into tb_teacher (teaid, teaname, title, collid) values + insert into tb_teacher (teaid, teaname, teatitle, collid) values (1122, '张三丰', '教授', 1), (1133, '宋远桥', '副教授', 1), (1144, '杨逍', '副教授', 1), @@ -582,7 +582,7 @@ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行 (3366, '韦一笑', '讲师', 3); -- 插入课程数据 - insert into tb_course (couid, couname, credit, teaid) values + insert into tb_course (couid, couname, coucredit, teaid) values (1111, 'Python程序设计', 3, 1122), (2222, 'Web前端开发', 2, 1122), (3333, '操作系统', 4, 1122), @@ -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; ``` > **提示**:因为视图不包含数据,所以每次使用视图时,都必须执行查询以获得数据,如果你使用了连接查询、嵌套查询创建了较为复杂的视图,你可能会发现查询性能下降得很厉害。因此,在使用复杂的视图前,应该进行测试以确保其性能能够满足应用的需求。 @@ -1004,19 +1005,29 @@ drop view vw_student_score; 下面的存储过程实现了查询某门课程的最高分、最低分和平均分。 ```SQL +drop procedure if exists sp_score_by_cid; + delimiter $$ -create procedure sp_get_score(courseId int, - out maxScore decimal(4,1), - out minScore decimal(4,1), - out avgScore decimal(4,1)) +create procedure sp_score_by_cid( + courseId int, + out maxScore decimal(4,1), + out minScore decimal(4,1), + out avgScore decimal(4,1) +) begin - select max(score) into maxScore from tb_record where cid=courseId; - select min(score) into minScore from tb_record where cid=courseId; - select avg(score) into avgScore from tb_record where cid=courseId; + select max(score) into maxScore from tb_record + where cid=courseId; + select min(score) into minScore from tb_record + where cid=courseId; + select avg(score) into avgScore from tb_record + where cid=courseId; end $$ delimiter ; + +call sp_score_by_cid(1111, @a, @b, @c); +select @a, @b, @c; ``` > 说明:在定义存储过程时,因为可能需要书写多条SQL,而分隔这些SQL需要使用分号作为分隔符,如果这个时候,仍然用分号表示整段代码结束,那么定义存储过程的SQL就会出现错误,所以上面我们用`delimiter $$`将整段代码结束的标记定义为`$$`,那么代码中的分号将不再表示整段代码的结束,需要马上执行,整段代码在遇到`end $$`时才输入完成并执行。在定义完存储过程后,通过`delimiter ;`将结束符重新改回成分号。 @@ -1026,7 +1037,7 @@ delimiter ; 调用存储过程。 ```SQL -call sp_get_score(1111, @a, @b, @c); +call sp_score_by_cid(1111, @a, @b, @c); ``` 获取输出参数的值。 @@ -1038,7 +1049,7 @@ select @a as 最高分, @b as 最低分, @c as 平均分; 删除存储过程。 ```SQL -drop procedure sp_get_score; +drop procedure sp_score_by_cid; ``` 在存储过程中,我们可以定义变量、条件,可以使用分支和循环语句,可以通过游标操作查询结果,还可以使用事件调度器,这些内容我们暂时不在此处进行介绍。虽然我们说了很多存储过程的好处,但是在实际开发中,如果过度的使用存储过程,将大量复杂的运算放到存储过程中,也会导致占用数据库服务器的CPU资源,造成数据库服务器承受巨大的压力。为此,我们一般会将复杂的运算和处理交给应用服务器,因为很容易部署多台应用服务器来分摊这些压力。 @@ -1189,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: @@ -1221,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: @@ -1253,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: @@ -1281,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') @@ -1324,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/HRS_create_and_init.sql b/Day36-40/code/HRS_create_and_init.sql index b8bc864..acedd3a 100644 --- a/Day36-40/code/HRS_create_and_init.sql +++ b/Day36-40/code/HRS_create_and_init.sql @@ -32,6 +32,7 @@ dno int comment '所在部门编号', primary key (eno) ); +alter table tb_emp add constraint fk_emp_mgr foreign key (mgr) references tb_emp (eno); alter table tb_emp add constraint fk_emp_dno foreign key (dno) references tb_dept (dno); insert into tb_emp values @@ -51,7 +52,7 @@ insert into tb_emp values (3588, '朱九真', '会计', 5566, 2500, null, 10); --- 查询月薪最高的员工姓名和工资 +-- 查询月薪最高的员工姓名和月薪 -- 查询员工的姓名和年薪((月薪+补贴)*13) @@ -59,14 +60,14 @@ insert into tb_emp values -- 查询所有部门的名称和人数 --- 查询月薪最高的员工(Boss除外)的姓名和工资 +-- 查询月薪最高的员工(Boss除外)的姓名和月薪 --- 查询薪水超过平均薪水的员工的姓名和工资 +-- 查询薪水超过平均薪水的员工的姓名和月薪 --- 查询薪水超过其所在部门平均薪水的员工的姓名、部门编号和工资 +-- 查询薪水超过其所在部门平均薪水的员工的姓名、部门编号和月薪 --- 查询部门中薪水最高的人姓名、工资和所在部门名称 +-- 查询部门中薪水最高的人姓名、月薪和所在部门名称 -- 查询主管的姓名和职位 --- 查询月薪排名4~6名的员工姓名和工资 +-- 查询月薪排名4~6名的员工姓名和月薪 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/polls_origin/images/captcha.jpg b/Day41-55/code/polls_origin/images/captcha.jpg new file mode 100644 index 0000000..01ba4d3 Binary files /dev/null and b/Day41-55/code/polls_origin/images/captcha.jpg differ diff --git a/Day41-55/code/polls_origin/images/hot-icon-small.png b/Day41-55/code/polls_origin/images/hot-icon-small.png new file mode 100644 index 0000000..307ef94 Binary files /dev/null and b/Day41-55/code/polls_origin/images/hot-icon-small.png differ diff --git a/Day41-55/code/polls_origin/images/luohao.png b/Day41-55/code/polls_origin/images/luohao.png new file mode 100644 index 0000000..208ac18 Binary files /dev/null and b/Day41-55/code/polls_origin/images/luohao.png differ diff --git a/Day41-55/code/polls_origin/images/wangdachui.png b/Day41-55/code/polls_origin/images/wangdachui.png new file mode 100644 index 0000000..5368f71 Binary files /dev/null and b/Day41-55/code/polls_origin/images/wangdachui.png differ diff --git a/Day41-55/code/polls_origin/images/weiyixiao.png b/Day41-55/code/polls_origin/images/weiyixiao.png new file mode 100644 index 0000000..cfbb281 Binary files /dev/null and b/Day41-55/code/polls_origin/images/weiyixiao.png differ diff --git a/Day41-55/code/polls_origin/images/xiaoshirong.png b/Day41-55/code/polls_origin/images/xiaoshirong.png new file mode 100644 index 0000000..c7f04eb Binary files /dev/null and b/Day41-55/code/polls_origin/images/xiaoshirong.png differ diff --git a/Day41-55/code/polls_origin/images/yuting.png b/Day41-55/code/polls_origin/images/yuting.png new file mode 100644 index 0000000..195af3a Binary files /dev/null and b/Day41-55/code/polls_origin/images/yuting.png differ diff --git a/Day41-55/code/polls_origin/images/zhangwuji.png b/Day41-55/code/polls_origin/images/zhangwuji.png new file mode 100644 index 0000000..ee01fa1 Binary files /dev/null and b/Day41-55/code/polls_origin/images/zhangwuji.png differ diff --git a/Day41-55/code/polls_origin/login.html b/Day41-55/code/polls_origin/login.html new file mode 100644 index 0000000..c30954b --- /dev/null +++ b/Day41-55/code/polls_origin/login.html @@ -0,0 +1,73 @@ + + + + + 用户登录 + + + +
+

用户登录

+
+
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+ 注册新用户 +
+ + \ No newline at end of file diff --git a/Day41-55/code/polls_origin/register.html b/Day41-55/code/polls_origin/register.html new file mode 100644 index 0000000..264f402 --- /dev/null +++ b/Day41-55/code/polls_origin/register.html @@ -0,0 +1,87 @@ + + + + + 用户注册 + + + +
+

用户注册

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+ + +
+
+ 返回登录 +
+ + \ No newline at end of file diff --git a/Day41-55/code/polls_origin/subjects.html b/Day41-55/code/polls_origin/subjects.html new file mode 100644 index 0000000..b2eccf5 --- /dev/null +++ b/Day41-55/code/polls_origin/subjects.html @@ -0,0 +1,67 @@ + + + + + 学科信息 + + + +
+

千锋教育成都校区所有学科

+
+
+
+
+ Python全栈+人工智能 + +
+
+ Python(英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/)是一种广泛使用的解释型、高级编程、通用型编程语言, + 由吉多·范罗苏姆创造,第一版发布于1991年。可以视之为一种改良(加入一些其他编程语言的优点,如面向对象)的LISP。 + Python的设计哲学强调代码的可读性和简洁的语法(尤其是使用空格缩进划分代码块,而非使用大括号或者关键词)。 + 相比于C++或Java,Python让开发者能够用更少的代码表达想法。不管是小型还是大型程序,该语言都试图让程序的结构清晰明了。 +
+
+
+
+ 全栈软件测试 +
+
+ 软件测试在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。 + 软件测试有许多方法,但对复杂的产品运行有效测试不仅仅是研究过程,更是创造并严格遵守某些呆板步骤的大事。 + 测试的其中一个定义:为了评估而质疑产品的过程;这里的“质疑”是测试员试着对产品做的事,而产品以测试者脚本行为反应作为回答。 +
+
+
+
+ JavaEE+分布式开发 + +
+
+ Java是一种广泛使用的计算机编程语言,拥有跨平台、面向对象、泛型编程的特性,广泛应用于企业级Web应用开发和移动应用开发。 + 该语言由当时任职于太阳微系统的詹姆斯·高斯林等人于1990年代初开发Java语言的雏形,最初被命名为Oak, + 目标设置在家用电器等小型系统的编程语言,应用在电视机、电话、闹钟、烤面包机等家用电器的控制和通信。 + 由于这些智能化家电的市场需求没有预期的高,太阳计算机系统(Sun公司)放弃了该项计划。 + 随着1990年代互联网的发展,Sun公司看见Oak在互联网上应用的前景,于是改造了Oak,于1995年5月以Java的名称正式发布。 + Java伴随着互联网的迅猛发展而发展,逐渐成为重要的网络编程语言。 +
+
+
+
+ + \ No newline at end of file diff --git a/Day41-55/code/polls_origin/teachers.html b/Day41-55/code/polls_origin/teachers.html new file mode 100644 index 0000000..55c31cc --- /dev/null +++ b/Day41-55/code/polls_origin/teachers.html @@ -0,0 +1,110 @@ + + + + + 老师信息 + + + +
+

Python全栈+人工智能学科的老师信息

+
+
+
+ +
+
+
+ 姓名:骆昊 + 性别:男 + 出生日期:1980年11月28日 +
+
+ 10年以上软硬件产品和系统设计、研发、架构和管理经验,2003年毕业于四川大学,四川大学Java技术俱乐部创始人, + 四川省优秀大学毕业生,在四川省网络通信技术重点实验室工作期间,参与了2项国家自然科学基金项目、 + 1项中国科学院中长期研究项目和多项四川省科技攻关项目,在国际会议和国内顶级期刊上发表多篇论文(1篇被SCI收录,3篇被EI收录), + 大规模网络性能测量系统DMC-TS的设计者和开发者,perf-TTCN语言的发明者。国内最大程序员社区CSDN的博客专家, + 在Github上参与和维护了多个高质量开源项目,精通C/C++、Java、Python、R、Swift、JavaScript等编程语言, + 擅长OOAD、系统架构、算法设计、协议分析和网络测量,主持和参与过电子政务系统、KPI考核系统、P2P借贷平台等产品的研发, + 一直践行“用知识创造快乐”的教学理念,善于总结,乐于分享。 +
+
+ 好评  (100) +      + 差评  (50) +
+
+
+
+
+ +
+
+
+ 姓名:余婷 + 性别:女 + 出生日期:1992年9月20日 +
+
+ 5年以上移动互联网项目开发经验和教学经验,曾担任上市游戏公司高级软件研发工程师和移动端(iOS)技术负责人, + 参了多个企业级应用和游戏类应用的移动端开发和后台服务器开发,拥有丰富的开发经验和项目管理经验, + 以个人开发者和协作开发者的身份在苹果的AppStore上发布过多款App。精通Python、C、Objective-C、Swift等开发语言, + 熟悉iOS原生App开发、RESTful接口设计以及基于Cocos2d-x的游戏开发。授课条理清晰、细致入微, + 性格活泼开朗、有较强的亲和力,教学过程注重理论和实践的结合,在学员中有良好的口碑。 +
+
+ 好评  (50) +      + 差评  (100) +
+
+
+
+ + \ No newline at end of file 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/常见反爬策略及应对方案.md b/Day66-75/常见反爬策略及应对方案.md similarity index 100% rename from 常见反爬策略及应对方案.md rename to Day66-75/常见反爬策略及应对方案.md diff --git a/Day76-90/code/.ipynb_checkpoints/1-pandas入门-checkpoint.ipynb b/Day76-90/code/.ipynb_checkpoints/1-pandas入门-checkpoint.ipynb new file mode 100644 index 0000000..b5b3acc --- /dev/null +++ b/Day76-90/code/.ipynb_checkpoints/1-pandas入门-checkpoint.ipynb @@ -0,0 +1,631 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from pandas import Series,DataFrame" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Math 120\n", + "Python 136\n", + "En 128\n", + "Chinese 99\n", + "dtype: int64" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# 创建\n", + "# Series是一维的数据\n", + "s = Series(data = [120,136,128,99],index = ['Math','Python','En','Chinese'])\n", + "s" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(4,)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([120, 136, 128, 99], dtype=int64)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v = s.values\n", + "v" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "numpy.ndarray" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(v)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "120.75" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s.mean()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "136" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s.max()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "15.903353943953666" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s.std()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Math 14400\n", + "Python 18496\n", + "En 16384\n", + "Chinese 9801\n", + "dtype: int64" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s.pow(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PythonEnMath
a11311675
b1914523
c57107113
d95366
e28121120
f14185132
h1243910
i803517
j689931
k741211
\n", + "
" + ], + "text/plain": [ + " Python En Math\n", + "a 113 116 75\n", + "b 19 145 23\n", + "c 57 107 113\n", + "d 95 3 66\n", + "e 28 121 120\n", + "f 141 85 132\n", + "h 124 39 10\n", + "i 80 35 17\n", + "j 68 99 31\n", + "k 74 12 11" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# DataFrame是二维的数据\n", + "# excel就非常相似\n", + "# 所有进行数据分析,数据挖掘的工具最基础的结果:行和列,行表示样本,列表示的是属性\n", + "df = DataFrame(data = np.random.randint(0,150,size = (10,3)),index = list('abcdefhijk'),columns=['Python','En','Math'])\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(10, 3)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[113, 116, 75],\n", + " [ 19, 145, 23],\n", + " [ 57, 107, 113],\n", + " [ 95, 3, 66],\n", + " [ 28, 121, 120],\n", + " [141, 85, 132],\n", + " [124, 39, 10],\n", + " [ 80, 35, 17],\n", + " [ 68, 99, 31],\n", + " [ 74, 12, 11]])" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v = df.values\n", + "v" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Python 79.9\n", + "En 76.2\n", + "Math 59.8\n", + "dtype: float64" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.mean()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Python 141\n", + "En 145\n", + "Math 132\n", + "dtype: int32" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.max()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PythonEnMath
a11311675
b1914523
c57107113
d95366
e28121120
f14185132
h1243910
i803517
j689931
k741211
\n", + "
" + ], + "text/plain": [ + " Python En Math\n", + "a 113 116 75\n", + "b 19 145 23\n", + "c 57 107 113\n", + "d 95 3 66\n", + "e 28 121 120\n", + "f 141 85 132\n", + "h 124 39 10\n", + "i 80 35 17\n", + "j 68 99 31\n", + "k 74 12 11" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Python 79.9\n", + "En 76.2\n", + "Math 59.8\n", + "dtype: float64" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.mean(axis = 0)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "a 101.333333\n", + "b 62.333333\n", + "c 92.333333\n", + "d 54.666667\n", + "e 89.666667\n", + "f 119.333333\n", + "h 57.666667\n", + "i 44.000000\n", + "j 66.000000\n", + "k 32.333333\n", + "dtype: float64" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.mean(axis = 1)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Day76-90/code/1-pandas入门.ipynb b/Day76-90/code/1-pandas入门.ipynb index 2f25670..b5b3acc 100644 --- a/Day76-90/code/1-pandas入门.ipynb +++ b/Day76-90/code/1-pandas入门.ipynb @@ -322,7 +322,7 @@ ], "source": [ "# DataFrame是二维的数据\n", - "# excel就非诚相似\n", + "# excel就非常相似\n", "# 所有进行数据分析,数据挖掘的工具最基础的结果:行和列,行表示样本,列表示的是属性\n", "df = DataFrame(data = np.random.randint(0,150,size = (10,3)),index = list('abcdefhijk'),columns=['Python','En','Math'])\n", "df" @@ -623,7 +623,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.7.3" } }, "nbformat": 4, 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 new file mode 100644 index 0000000..9e12dba --- /dev/null +++ b/Day91-100/100.Python面试题集.md @@ -0,0 +1,318 @@ +## Python面试题 + +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}`,如何按字典中的值对字典进行排序得到排序后的字典。 + + 答: + + ```Python + + ``` + +7. 实现将字符串`k1:v1|k2:v2|k3:v3`处理成字典`{'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}`。 + + 答: + + ```Python + {key: value for key, value in ( + item.split(':') for item in 'k1:v1|k2:v2|k3:v3'.split('|') + )} + ``` + +8. 写出生成从`m`到`n`公差为`k`的等差数列的生成器。 + + 答: + + ```Python + (value for value in range(m, n + 1, k)) + ``` + + 或 + + ```Python + def generate(m, n, k): + for value in range(m, n + 1, k): + yield value + ``` + + 或 + + ```Python + def generate(m, n, k): + yield from range(m, n + 1, k) + ``` + +9. 请写出你能想到的反转一个字符串的方式。 + + 答: + + ```Python + ''.join(reversed('hello')) + ``` + + ```Python + 'hello'[::-1] + ``` + + ```Python + def reverse(content): + return ''.join(content[i] for i in range(len(content) - 1, -1, -1)) + + reverse('hello') + ``` + + ```Python + def reverse(content): + return reverse(content[1:]) + content[0] if len(content) > 1 else content + + reverse('hello') + ``` + +10. 不使用任何内置函数,将字符串`'123'`转换成整数`123`。 + + 答: + + ```Python + nums = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9} + total = 0 + for ch in '123': + total *= 10 + total += nums[ch] + print(total) + ``` + +11. 写一个返回bool值的函数,判断给定的非负整数是不是回文数。 + + 答: + + ```Python + + ``` + +12. 用一行代码实现求任意非负整数的阶乘。 + + 答: + + ```Python + from functools import reduce + + (lambda num: reduce(int.__mul__, range(2, num + 1), 1))(5) + ``` + +13. 写一个函数返回传入的整数列表中第二大的元素。 + + 答: + + ```Python + + ``` + +14. 删除列表中的重复元素并保留原有的顺序。 + + 答: + + ```Python + + ``` + +15. 找出两个列表中的相同元素和不同元素。 + + 答: + +16. 列表中的某个元素出现次数占列表元素总数的半数以上,找出这个元素。 + + 答: + + ```Python + + ``` + +17. 实现对有序列表进行二分查找的算法。 + + 答: + + ```Python + + ``` + +18. 输入年月日,输出这一天是这一年的第几天。 + + 答: + + ```Python + + ``` + +19. 统计一个字符串中各个字符出现的次数。 + + 答: + + ```Python + + ``` + +20. 在Python中如何实现单例模式? + + 答: + + ```Python + + ``` + +21. 下面的代码会输出什么。 + + ```Python + class A: + + def __init__(self, value): + self.__value = value + + @property + def value(self): + return self.__value + + + a = A(1) + a.__value = 2 + print(a.__value) + print(a.value) + ``` + +22. 实现一个记录函数执行时间的装饰器。 + + 答: + + ```Python + + ``` + +23. 写一个遍历指定目录下指定后缀名的文件的函数。 + + 答: + + ```Python + + ``` + +24. 有如下所示的字典,请将其转换为CSV格式。 + + 转换前: + + ```Python + dict_corp = { + 'cn': {'id': 1, 'name': '土豆', 'desc': '土豆', 'price': {'gold': 20, 'kcoin': 20}}, + 'en': {'id': 1, 'name': 'potato', 'desc': 'potato', 'price': {'gold': 20, 'kcoin': 20}}, + 'kr': {'id': 1, 'name': '감자', 'desc':'감자', 'price': {'gold': 20, 'kcoin': 20}}, + 'jp': {'id': 1, 'name': 'ジャガイモ', 'desc': 'ジャガイモ', 'price': {'gold': 20, 'kcoin': 20}}, + } + ``` + + 转换后: + + ```CSV + ,id,name,desc,gold,kcoin + cn,1,土豆,土豆,20,20 + en,1,potato,potato,20,20 + kr,1,감자,감자,20,20 + jp,1,ジャガイモ,ジャガイモ,20,20 + ``` + +25. 有如下所示的日志文件,请用Python程序或Linux命令打印出独立IP并统计数量。 + + ``` + 221.228.143.52 - - [23/May/2019:08:57:42 +0800] ""GET /about.html HTTP/1.1"" 206 719996 + 218.79.251.215 - - [23/May/2019:08:57:44 +0800] ""GET /index.html HTTP/1.1"" 206 2350253 + 220.178.150.3 - - [23/May/2019:08:57:45 +0800] ""GET /index.html HTTP/1.1"" 200 2350253 + 218.79.251.215 - - [23/May/2019:08:57:52 +0800] ""GET /index.html HTTP/1.1"" 200 2350253 + 219.140.190.130 - - [23/May/2019:08:57:59 +0800] ""GET /index.html HTTP/1.1"" 200 2350253 + 221.228.143.52 - - [23/May/2019:08:58:08 +0800] ""GET /about.html HTTP/1.1"" 206 719996 + 221.228.143.52 - - [23/May/2019:08:58:08 +0800] ""GET /news.html HTTP/1.1"" 206 713242 + 221.228.143.52 - - [23/May/2019:08:58:09 +0800] ""GET /products.html HTTP/1.1"" 206 1200250 + ``` + +26. 请写出从HTML页面源代码中获取a标签href属性的正则表达式。 + + 答: + + ```Python + + ``` + +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 diff --git a/Day91-100/91.团队项目开发准备.md b/Day91-100/91.团队项目开发准备.md deleted file mode 100644 index d9acf2e..0000000 --- a/Day91-100/91.团队项目开发准备.md +++ /dev/null @@ -1,233 +0,0 @@ -## 团队项目开发准备 - -我们经常听到个人开发和团队开发这两个词,所谓个人开发就是一个人把控产品的所有内容;而团队开发则是由多个人组成团队并完成产品的开发。要实施团队开发以下几点是必不可少的: - -1. 必须对开发过程中的各种事件(例如:谁到什么时间完成了什么事情)进行管理和共享。 -2. 各类工作成果以及新的知识技巧等必须在团队内部共享。 -3. 管理工作成果的变更,既要防止成果被破坏,又要保证各个成员利用现有成果并行作业。 -4. 能够证明团队开发出的软件在任何时候都是可以正常运行的。 -5. 尽可能的使用自动化的工作流程,让团队成员能够正确的实施开发、测试和部署。 - -### 团队项目开发常见问题 - -#### 问题1:传统的沟通方式无法确定处理的优先级 - -例如:使用邮件进行沟通可能出现邮件数量太多导致重要的邮件被埋没,无法管理状态,不知道哪些问题已经解决,哪些问题尚未处理,如果用全文检索邮件的方式来查询相关问题效率过于低下。 - -解决方案:使用缺陷管理工具。 - -#### 问题2:没有能够用于验证的环境 - -例如:收到项目正式环境中发生的故障报告后,需要还原正式环境需要花费很长的时间。 - -解决方法:实施持续交付。 - -#### 问题3:用别名目录管理项目分支 - -解决方法:实施版本控制。 - -#### 问题4:重新制作数据库非常困难 - -例如:正式环境和开发环境中数据库表结构不一致或者某个表列的顺序不一致。 - -解决方法:实施版本控制。 - -#### 问题5:不运行系统就无法察觉问题 - -例如:解决一个bug可能引入其他的bug或者造成系统退化,不正确的使用版本系统覆盖了其他人的修改,修改的内容相互发生了干扰,如果问题不能尽早发现,那么等过去几个月后再想追溯问题就非常麻烦了。 - -解决方法:实施持续集成,将团队成员的工作成果经常、持续的进行构建和测试。 - -#### 问题6:覆盖了其他成员修正的代码 - -解决方法:实施版本控制。 - -#### 问题7:无法实施代码重构 - -重构:在不影响代码产生的结果的前提下对代码内部的构造进行调整。 - -例如:在实施代码重构时可能引发退化。 - -解决方法:大量的可重用的测试并实施持续集成。 - -#### 问题8:不知道bug的修正日期无法追踪退化 - -解决方法:版本控制系统、缺陷管理系统和持续集成之间需要交互,最好能够和自动化部署工具集成到一起来使用。 - -#### 问题9:发布过程太复杂 - -解决方法:实施持续交付。 - -基于对上述问题的阐述和分析,我们基本上可以得到以下的结论,在团队开发中版本控制、缺陷管理和持续集成都是非常重要且不可或缺的。 - -### 版本控制 - -针对上面提到的一些问题,在团队开发的首要前提就是实施版本控制,对必要的信息进行管理,需要管理的内容包括: - -1. 代码。 -2. 需求和设计的相关文档。 -3. 数据库模式和初始数据。 -4. 配置文件。 -5. 库的依赖关系定义。 - -#### Git简介 - -![](./res/git-logo.png) - -Git是诞生于2005年的一个开源分布式版本控制系统,最初是Linus Torvalds(Linux之父) 为了帮助管理Linux内核开发而开发的一个版本控制软件。Git与常用的版本控制工具Subversion等不同,它采用了分布式版本控制的方式,在没有中央服务器支持的环境下也能够实施版本控制。 - -对于有使用Subversion(以下简称为SVN)经验的人来说,Git和SVN一样摒弃了基于锁定模式的版本控制方案(早期的CVS和VSS使用的就是锁定模式)采用了合并模式,而二者的区别在于: - 1. Git是分布式的,SVN是集中式的,SVN需要中央服务器才能工作。 - 2. Git把内容按元数据方式存储,而SVN是按文件,即把文件的元信息隐藏在一个.svn文件夹里。 - 3. Git分支和SVN的分支不同。 - 4. Git没有一个全局版本号而SVN有。 - 5. Git的内容完整性要优于SVN,Git的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。 - -#### 安装Git - -可以在[Git官方网站](http://git-scm.com/)找到适合自己系统的Git下载链接并进行安装,安装成功后可以在终端中键入下面的命令检查自己的Git版本。 - -```Shell -git --version -``` - -如果之前完全没有接触过Git,可以先阅读[《git - 简易指南》](http://www.bootcss.com/p/git-guide/)来对Git有一个大致的了解。 - -#### 本地实施版本控制 - -可以使用下面的命令将目录创建为Git仓库。 -```Shell -git init -``` - -当你完成了上述操作后,本地目录就变成了下面的样子,左边是你正在操作的工作目录,而右边是你的本地仓库,中间是工作目录和本地仓库之间的一个暂存区(也称为缓存区)。 - -![](./res/git_repository.png) - -通过`git add`可以将文件添加到暂存区。 - -```Shell -git add ... -``` - -可以用下面的方式将暂存区的指定文件恢复到工作区。 - -```Shell -git checkout -- -``` - -通过下面的命令可以将暂存区的内容纳入本地仓库。 - -```Shell -git commit -m '本次提交的说明' -``` - -可以使用下面的命令查看文件状态和进行版本比较。 - -```Shell -git status -s -git diff -``` - -可以通过`git log`查看提交日志。 - -```Shell -git log -git log --graph --pretty=oneline --abbrev-commit -``` - -如果要回到历史版本,可以使用下面的命令。 - -```Shell -git reset --hard -git reset --hard HEAD^ -``` - -其他的一些命令可以参考阮一峰老师的[《常用Git命令清单》](http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html)或者是码云上的[《Git大全》](https://gitee.com/all-about-git)。 - -#### Git服务器概述 - -对于Git来说不像SVN那样一定需要一个中心服务器,刚才我们的操作都是在本地执行的,如果你想通过Git分享你的代码或者与其他人协作,那么就需要服务器的支持。Github为Git提供了远程仓库,它是一个基于Git的代码托管平台,企业用户(付费用户)可以创建仓库,普通用户只能创建公开仓库(代码是可以是他人可见的)。Github是在2008年4月创办的,它上面代码库惊人的增长速度已经证明了它是非常成功的,在2018年6月,微软以75亿美元的天价收购了Github。国内也有类似的代码托管平台,最有名的当属[码云](https://gitee.com/)和[CODING](https://coding.net/),目前码云和CODING对注册用户都提供了受限的使用私有仓库的功能,同时还提供了对Pull Request的支持(后面会讲到),而且目前提供代码托管服务的平台都集成了“缺陷管理”、“WebHook”等一系列的功能,让我们能做的事情不仅仅是版本控制。当然,如果公司需要也可以搭建自己的Git服务器,具体的方式我们就不在这里进行介绍了,有兴趣的可以自行了解。 - -![](./res/git_logo.png) - -我们可以在码云或者CODING上注册账号,也可以使用第三方登录(github账号、微信账号、新浪微博账号、CSDN账号等)的方式。登录成功后就可以创建项目,创建项目几乎是“傻瓜式”的,我们只说几个值得注意的地方。 - -1. 添加项目成员。创建项目后,可以在项目的“设置”或“管理”中找到“成员管理”功能,这样就可以将其他开发者设置为项目团队的成员,项目成员通常分为“所有者”、“管理者”、“普通成员”和“受限成员”几种角色。 - -2. 设置公钥实现免密操作。在项目的“设置”或“管理”中我们还可以找到“部署公钥管理”的选项,通过添加部署公钥,可以通过SSH(安全远程连接)的形式访问服务器而不用每次输入用户名和口令。可以使用`ssh-keygen`命令来创建密钥对。 - - ```Shell - ssh-keygen -t rsa -C "your_email@example.com" - ``` - -#### 使用Git进行开发 - -克隆服务器上的代码到本地机器。 - -```Shell -git clone -``` - -在自己的分支上进行开发。 - -```Shell -git branch -git checkout -``` - -或者 - -```Shell -git checkout -b -``` - -接下来可以先在本地实施版本控制(操作的方式与前面相同不再赘述),然后再将自己的分支Push到服务器。 - -```Shell -git push origin -``` - -最后,当工作完成时,可以发起一个Pull Request,请求将代码合并到master分支。 - -#### 分支策略的模式 - -上面讲解的方式,其实是一种称为github-flow的分支策略模式,这种模式的操作步骤包括: - -1. master的内容都是可以进行发布的内容。 -2. 开发时应该以master为基础建立新分支。 -3. 分支先在本地实施版本控制,然后以同名分支定期向服务器进行Push。 -4. 开发结束后向master发送Pull Request。 -5. Pull Request通过代码审查之后合并到master,并从master向正式环境发布。 - -在使用github-flow时的注意事项有以下三点: - -1. master是用于发布的,不能直接在master上进行修改。 -2. 开始日常开发工作时要首先建立分支。 -3. 工作完成后向master发送Pull Request。 - -除了上述的github-flow工作方式外,还有一种名为git-flow的分支策略模式,它借鉴了中央集权型版本控制系统的长处,为团队内部统一管理建立分支的方法、合并操作和关闭分支的方法。在这种模式下,项目有两个长线分支,分别是master和develop,其他的都是临时的、短暂的辅助分支,包括feature(开发特定功能的分支,开发结束后合并到develop)、release(从develop分离出来的为发布做准备的分支,发布结束后合并到master和develop)和hotfix(产品发布后出现问题时紧急建立的分支,直接从master分离,问题修复后合并到master并打上标签,同时还要合并到develop来避免将来的版本遗漏了这个修复工作,如果此时有正在发布中的release分支,还要合并到release分支)。这套方式分支策略简单清晰且容易理解,但是在运用上会稍微有些复杂,需要一些脚本来辅助版本控制的实施。 - -### 缺陷管理 - -没有好的团队管理工具必然导致项目进展不顺利,任务管理困难,而引入缺陷管理系统正好可以解决这些问题,通常一个缺陷管理系统都包含了以下的功能: - -1. 任务管理(包括必须做什么、谁来做、什么时候完成、现在处于什么状态等)。 -2. 直观而且可以检索过去发生的各种问题。 -3. 能够对信息进行统一的管理和共享。 -4. 能够生成各类报表。 -5. 能够关联到其他系统,具有可扩展性。 - -Redmine是基于Ruby on Rails框架的开源缺陷管理系统,提供了问题管理、代码管理、Wiki等必要的功能,而且支持插件系统,扩展起来也非常容易。 - -![](./res/redmine_new_issue.png) - -如果希望了解和使用Redmine,可以关注[Redmine中文网](http://www.redmine.org.cn/),上面提供了视频教程、经验分享以及其他的安装和使用上的指导。 - -### 持续集成 - -为了快速的产生高品质的软件,在团队开发中,持续集成(CI)也是一个非常重要的基础。按照经典的软件过程模型(瀑布模型),集成的工作一般要等到所有的开发工作都结束后才能开始,但这个时候如果发现了问题,修复问题的代价是非常具体的。基本上,集成实施得越晚,代码量越大,解决问题就越困难。持续集成将版本控制、自动化构建、代码测试融入到一起,让这些工作变得自动化和可协作。由于其频繁重复整个开发流程(在指定时间内多次pull源代码并运行测试代码),所以能帮助开发者提早发现问题。 - -在所有的CI工具中,Jenkins和TravisCI是最具有代表性的。 - -![](./res/jenkins_new_project.png)Jenkins 是基 Java的开源CI工具,其安装和操作都很简单。另外,Jenkins不仅能在面板上轻松看出任务成功或失败,还可以借助通知功能将结果以邮件或RSS订阅的形式发给用户。与此同时,Jenkins也允许通过插件进行功能扩展,所需功能可以随用随添加,而且还支持主从式集群,能够轻松的进行水平扩展。 \ No newline at end of file diff --git a/Day91-100/91.团队项目开发的问题和解决方案.md b/Day91-100/91.团队项目开发的问题和解决方案.md new file mode 100644 index 0000000..df2ac87 --- /dev/null +++ b/Day91-100/91.团队项目开发的问题和解决方案.md @@ -0,0 +1,593 @@ +## 团队项目开发的问题和解决方案 + +个人开发和团队开发这两个词相信对大家来说并不陌生。所谓个人开发就是一个人把控产品的所有内容;而团队开发则是由多个人组团并完成产品的开发。要实施团队开发以下几点是不可或缺的: + +1. 对开发过程中的各种事件(例如:谁到什么时间完成了什么事情)进行管理和共享。 +2. 在团队内部共享各类工作成果以及新的知识技巧等。 +3. 管理工作成果的变更,既要防止成果被破坏,又要保证各个成员利用现有成果并行作业。 +4. 证明团队开发出的软件在任何时候都是可以正常运行的。 +5. 使用自动化的工作流程,让团队成员能够正确的实施开发、测试和部署。 + +### 团队项目开发常见问题 + +团队开发相较于个人开发,容易遇到以下几个方面的问题。 + +#### 问题1:传统的沟通方式无法确定处理的优先级 + +例如:使用邮件进行沟通可能出现邮件数量太多导致重要的邮件被埋没,无法管理状态,不知道哪些问题已经解决,哪些问题尚未处理,如果用全文检索邮件的方式来查询相关问题效率过于低下。 + +解决方案:使用缺陷管理工具。 + +#### 问题2:没有能够用于验证的环境 + +例如:收到项目正式环境中发生的故障报告后,需要还原正式环境需要花费很长的时间。 + +解决方法:实施持续交付。 + +#### 问题3:用别名目录管理项目分支 + +解决方法:实施版本控制。 + +#### 问题4:重新制作数据库非常困难 + +例如:正式环境和开发环境中数据库表结构不一致或者某个表列的顺序不一致。 + +解决方法:实施版本控制。 + +#### 问题5:不运行系统就无法察觉问题 + +例如:解决一个bug可能引入其他的bug或者造成系统退化,不正确的使用版本系统覆盖了其他人的修改,修改的内容相互发生了干扰,如果问题不能尽早发现,那么等过去几个月后再想追溯问题就非常麻烦了。 + +解决方法:实施持续集成,将团队成员的工作成果经常、持续的进行构建和测试。 + +#### 问题6:覆盖了其他成员修正的代码 + +解决方法:实施版本控制。 + +#### 问题7:无法实施代码重构 + +例如:在实施代码重构(在不影响代码产生的结果的前提下对代码内部的构造进行调整)时可能引发退化。 + +解决方法:大量的可重用的测试并实施持续集成。 + +#### 问题8:不知道bug的修正日期无法追踪退化 + +解决方法:版本控制系统、缺陷管理系统和持续集成之间需要交互,最好能够和自动化部署工具集成到一起来使用。 + +#### 问题9:发布过程太复杂 + +解决方法:实施持续交付。 + +基于对上述问题的阐述和分析,我们基本上可以得到以下的结论,在团队开发中版本控制、缺陷管理和持续集成都是非常重要且不可或缺的。 + +### 版本控制 + +针对上面提到的一系列问题,我们可以得出一个简单的结论,版本控制是实施团队开发的首要前提,必须通过版本控制对产品研发过程中产生的各种信息进行管理,这些内容包括: + +1. 代码。 +2. 需求和设计的相关文档。 +3. 数据库模式和初始数据。 +4. 配置文件。 +5. 库的依赖关系定义。 + +#### Git简介 + +![](./res/git-logo.png) + +Git是诞生于2005年的一个开源分布式版本控制系统,最初是Linus Torvalds(Linux之父) 为了帮助管理Linux内核开发而开发的一个版本控制软件。Git与常用的版本控制工具Subversion等不同,它采用了分布式版本控制的方式,在没有中央服务器支持的环境下也能够实施版本控制。 + +对于有使用Subversion(以下简称为SVN)经验的人来说,Git和SVN的共同点是摒弃了传统的基于锁定模式的版本控制(早期的CVS和VSS使用了锁定模式,当一个开发者编辑一个文件时会锁定该文件,其他开发者在此期间无法编辑该文件),采用了更有效率的基于合并模式的版本控制,而二者的区别在于: + +1. Git是分布式的,SVN是集中式的,SVN需要中央服务器的支持才能工作。 +2. Git把内容按元数据方式存储,而SVN是按文件,即把文件的元信息隐藏在一个.svn文件夹里。 +3. Git分支和SVN的分支不同,SVN对分支的处理是相当“狗血”的。 +4. Git没有一个全局版本号,但是可以自己维护一个版本标签。 +5. Git的内容完整性要优于SVN,Git的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。 + +总而言之,**Git真的非常棒!!!** + +#### 安装Git + +可以在[Git官方网站](http://git-scm.com/)找到适合自己系统的Git下载链接并进行安装,macOS和Windows平台下安装Git都非常简单,Linux下如果要安装官方最新的版本,建议通过官方提供的Git源代码进行构建安装,步骤如下所示(以CentOS为例)。 + +下载Git源代码压缩文件。 + +```Shell +wget https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.23.0.tar.xz +``` + +解压缩和解归档。 + +```Shell +xz -d git-2.23.0.tar.xz +tar -xvf git-2.23.0.tar +``` + +安装底层依赖库。 + +```Shell +yum -y install libcurl-devel +``` + +> 说明:没有这个依赖库,git的网络功能将无法执行。 + +安装前的配置。 + +```Shell +cd git-2.23.0 +./configure --prefix=/usr/local +``` + +构建和安装。 + +```Shell +make && make install +``` + +安装成功后可以在终端中键入下面的命令检查自己的Git版本。 + +```Shell +git --version +``` + +如果之前完全没有接触过Git,可以先阅读[《git - 简易指南》](http://www.bootcss.com/p/git-guide/)来对Git有一个大致的了解。 + +#### Git本地操作 + +可以使用下面的命令将一个文件夹变成Git仓库。 +```Shell +git init +``` + +当你完成了上述操作后,本地目录就变成了下面的样子,下图左边是你的工作区(正在操作的工作目录),而右边是你的本地仓库,中间是工作区和本地仓库之间的暂存区(也称为缓存区)。 + +![](./res/git_repository.png) + +> **提示**:用`ls -la`查看所有文件会发现在执行完上面的命令后,文件夹下多了一个名为`.git`的隐藏文件夹,这个就是本地的Git版本仓库。 + +通过`git add`可以将指定的文件或所有文件添加到暂存区。 + +```Shell +git add +git add . +``` + +这个时候使用下面的命令可以查看工作区、暂存区和本地仓库的状态。 + +```Shell +git status +``` + +> **提示**:如果不希望将文件添加到暂存区,可以按照提示,使用`git rm --cached `命令将文件从暂存区放回到工作区。 + +如果这个时候对工作区的文件又进行了修改使得工作区和暂存区的内容并不相同了,再次执行`git status`可以看到哪个或哪些文件被修改了,如果希望用暂存区的内容恢复工作区,可以使用下面的命令。 + +```Shell +git restore +git restore . +``` + +> **注意**:上面的命令目前仍然处于试验性阶段,在Git较早的版本中对应的命令是`git checkout -- `。由于`git checkout`这个命令还可以用于切换分支,容易引起混淆,所以Git最新版本中将这个命令的两项功能分别赋予两个新的命令,一个就是上面的`git restore`,另一个是`git switch`。 + +如果第一次使用Git,需要配置用户名和邮箱,然后才能将代码提交到仓库。 + +```Shell +git config --global user.name "jackfrued" +git config --global user.email "jackfrued@126.com" +``` + +> **提示**:可以用`git config --list`来查看Git的配置信息。 + +通过下面的命令可以将暂存区的内容纳入本地仓库, + +```Shell +git commit -m '本次提交的说明' +``` + +可以通过`git log`查看每次提交对应的日志。 + +```Shell +git log +git log --graph --oneline --abbrev-commit +``` + +#### Git服务器概述 + +Git不像SVN那样一定需要中央服务器才能工作,上面我们演示的版本控制操作都是在本地执行的,但是对于企业开发多人协作这样的场景还是需要中央服务器的支持。通常,企业可以选择使用代码托管平台(如[GitHub](https://github.com))或自己搭建Git私服的方式来建立中央服务器(版本仓库),当然大多数的企业更倾向于后者。Github创办于2008年4月,目前是全世界最大的代码托管平台,支持企业用户(可以创建私有仓库,私有仓库内容不对外界公开)和普通用户(受限的使用私有仓库,不受限的使用公开仓库,公开仓库内容对他人可见)。Github上面代码库惊人的增长速度证明了它是非常成功的,在2018年6月被微软以75亿美元的天价收购。 + +国内也有不少类似Github的代码托管平台,最有名的当属[码云](https://gitee.com/)和[CODING](https://coding.net/),目前码云和CODING对注册用户都提供了受限的使用私有仓库的功能,支持**Pull Request**(一种对话机制,可以在提交你的工作成果时让相关人员或团队注意到这件事情),同时还提供了对**缺陷管理**、**Webhook**等功能支持,这些使得版本控制系统还具备了缺陷管理和持续集成的能力。当然,很多公司都不愿意将自己的商业代码托管于别人的平台,这样的公司可以用[Gitlab]()来搭建公司内部的Git私服,具体的做法在下一章为大家介绍。 + +![](./res/gitlab-about.png) + +这里我们直接以码云为例来说明使用Git服务器的一些注意事项。首先需要在码云上注册账号,当然也可以使用第三方登录(github账号、微信账号、新浪微博账号、CSDN账号等),登录成功后就可以创建项目,创建项目几乎是“傻瓜式”的,无需赘述,我们只对几个地方加以说明。 + +1. 创建项目时不建议勾选如下图所示的这些选项,编程语言可以暂时不做选择,而`.gitignore`模板也可以稍后自己编写或者通过更专业的工具(如:网站)自动生成。 + + ![](./res/gitee-create-project.png) + +2. 添加项目成员。创建项目后,可以在项目的“设置”或“管理”中找到“成员管理”功能,这样就可以将其他开发者设置为项目团队的成员,项目成员通常分为“所有者”、“管理者”、“普通成员”和“受限成员”几种角色。 + + ![](./res/gitee-add-members.png) + +3. 项目的分支。创建项目后,项目只有一个默认的**master**分支,应该将该分支设置为“保护分支”来避免项目管理者之外的成员修改该分支(不可直接提交)。当然,如果需要我们也可以在线创建新的代码分支。 + +4. 设置公钥实现免密访问。在项目的“设置”或“管理”中我们还可以找到“部署公钥管理”的选项,通过添加部署公钥,可以通过SSH(安全远程连接)的方式访问服务器而不用每次输入用户名和口令。可以使用`ssh-keygen`命令来创建密钥对。 + + ```Shell + ssh-keygen -t rsa -b 2048 -C "your_email@example.com" + ``` + + > **说明**:上面命令生成的密钥对在`~/.ssh`目录下,公钥文件默认的名字为`id_rsa.pub`,可以通过`cat id_rsa.pub`来查看自己的公钥。Windows用户在安装Git工具后,可以通过**Git Bash**来输入上面的命令。 + +#### Git远程操作 + +拥有了Git服务器之后,我们就可以通过Git的远程操作将自己的工作成果推到服务器的仓库中,也可以将他人的工作成果从服务器仓库更新到本地。我们以刚才在码云上创建的仓库(仓库名为`python`)为例来说明如何进行远程操作。可以在如下所示的页面上找到仓库的地址(URL),如果配置了**SSH Key**就使用SSH方式访问仓库,否则就用HTTPS方式,后者需要在进行远程操作时提供用户名和口令。 + +![](./res/gitee-project-index.png) + +1. 添加远程仓库(Git服务器)。 + + ```Shell + git remote add origin git@gitee.com:jackfrued/python.git + ``` + + 其中`git@gitee.com:jackfrued/python.git`是上图中显示的仓库的URL,而前面的`origin`是替代这个冗长的URL的字符串,简单的说`origin`就是服务器上仓库的别名(如果有多个Git服务器,这个简短的名字也会有多个)。可以用`git remote -v`来查看已经指定的Git服务,也可以用`git remote remove`来删除指定的Git服务器。 + +2. 将本地代码(工作成果)推送到远程仓库。 + + ```Shell + git push -u origin master:master + ``` + + 其中,`-u`是`--set-upstream`的缩写,用来指定推送的服务器仓库,后面的`origin`就是刚才给仓库起的简短的别名,冒号前面的`master`是本地分支名,冒号后面的`master`是远程分支名,如果本地分支`master`已经和远程分支`master`建立过关联,则冒号以及后面的部分可以省略。 + +3. 从远程仓库取回代码。 + + ```Shell + git pull origin master + ``` + +#### Git分支操作 + +1. **创建**和**切换**分支。下面的命令创建了名为`dev` 的分支并切换到该分支。 + + ```Shell + git branch + git switch + ``` + + 或 + + ```Shell + git switch -c + ``` + + > **注意**:在之前的Git版本中,切换分支使用`git checkout `命令,也可以通过`git checkout -b `来创建并切换分支。`git switch`命令目前仍然处于试验性阶段,但很明显这个命令更加清晰的表达了它要做的事情。 + +2. **关联远程**分支。例如:如果当前所在的分支还没有关联到远程分支,可以使用下面的命令为它们建立关联。 + + ```Shell + git branch --set-upstream-to origin/develop + ``` + + 如果需要为指定的分支关联远程分支,可以如下操作。 + + ```Shell + git branch --set-upstream-to origin/develop + ``` + + > 提示:上面的操作假设Git服务器上存在名为`develop`的分支,`--set-upstream-to`可以缩写为`-u`。 + + 当然,在创建分支时,如果使用了`--track`参数,也可以直接指定与本地分支关联的远程分支,如下所示。 + + ```Shell + git branch --track origin/develop + ``` + + 如果需要解除本地分支与远程分支的关联,可以使用下面的命令。 + + ```Shell + git branch --unset-upstream + ``` + +3. 分支**合并**。例如在`dev`分支上完成开发任务之后,如果希望将`dev`分支上的成果合并到`master`,可以先切回到`master`分支然后使用`git merge`来做分支合并,合并的结果如下图右上方所示。 + + ```Shell + git switch master + git merge --no-ff dev + ``` + + 使用`git merge`合并分支时,默认使用`Fast Forward`合并,这意味着如果删除了分支,分支上的信息就全都丢掉了,如果希望将分支上的历史版本保留下来,可以使用`--no-ff`参数来禁用`Fast Forward`。 + + 在合并分支时,没有冲突的部分Git会做自动合并。如果发生了冲突(如`dev`和`master`分支上都修改了同一个文件),会看到`CONFLICT (content): Merge conflict in . Automatic merge failed; fix conflicts and then commit the result`(自动合并失败,修复冲突之后再次提交)的提示,这个时候我们可以用`git diff`来查看产生冲突的内容。解决冲突通常需要当事人当面沟通之后才能决定保留谁的版本,冲突解决后需要重新提交代码。 + +4. 分支**变基**。分支合并操作可以将多个分支上的工作成果最终合并到一个分支上,但是再多次合并操作之后,分支可能会变得非常的混乱和复杂,为了解决这个问题,可以使用`git rebase`操作来实现分支变基。如下图所示,当我们希望将`master`和`dev`上的工作成果统一到一起的时候,也可以使用变基操作。 + + ![](./res/git-rebase.png) + + ```Shell + git rebase master + git switch master + git merge dev + ``` + + 当我们在`dev`分支执行`git rebase`命令时,将首先计算`dev`分支和`master`分支的差集,然后应用该差集到`dev`分支,最后我们切回到`master`分支并执行操作合并,这样就看到了如上图右下方所示的干净的分支。 + +5. **删除**分支。删除分支可以使用`git branch`加上`-d`参数,如果分支上的工作成果还没有合并,那么在删除分支时会看到`error: The branch '' is not fully merged.`这样的错误提示。如果希望强行删除分支,可以使用`-D`参数。删除分支的操作如下所示。 + + ```Shell + git branch -d + error: The branch '' is not fully merged. + If you are sure you want to delete it, run 'git branch -D '. + git branch -D + ``` + + 如果要删除远程分支,可以使用下面的命令,但是请慎重的操作。 + + ```Shell + git branch -r -d origin/develop + git push origin :develop + ``` + + 或者 + + ```Shell + git push origin --delete develop + ``` + +#### Git其他操作 + +1. `git fetch`:下载远程仓库的所有变动,可以将远程仓库下载到一个临时分支,然后再根据需要进行合并操作,`git fetch`命令和`git merge`命令可以看作是之前讲的`git pull`命令的分解动作。 + + ```Shell + git fetch origin master:temp + git merge temp + ``` + +2. `git diff`:常用于比较工作区和仓库、暂存区与仓库、两个分支之间有什么差别。 + +3. `git stash`:将当前工作区和暂存区发生的变动放到一个临时的区域,让工作区变干净。这个命令适用于手头工作还没有提交,但是突然有一个更为紧急的任务(如线上bug需要修正)需要去处理的场景。 + + ```Shell + git stash + git stash list + git stash pop + ``` + +4. `git reset`:回退到指定的版本。该命令主要有三个参数,如下图所示。 + + ![](./res/git-reset.png) + +5. `git cherry-pick`:挑选某个分支的单次提交并作为一个新的提交引入到你当前分支上。 + +6. `git revert`:撤回提交信息。 + +7. `git tag`:经常用于查看或新增一个标签。 + +#### Git工作流程(分支管理策略) + +既然Git是团队开发必备的工具,那么在团队协作时就必须有一个规范的工作流程,这样才能让团队高效的工作,让项目顺利的进展下去,否则工具再厉害但团队成员各自为战,冲突就会无处不在,协作更加无从谈起。我们仍然以刚才码云上创建的`python`项目为例,来说明Git的分支管理策略。 + +##### Github-flow + +1. 克隆服务器上的代码到本地。 + + ```Shell + git clone git@gitee.com:jackfrued/python.git + ``` + +2. 创建并切换到自己的分支。 + + ```Shell + git switch -c + ``` + + 或 + + ```Shell + git checkout -b + ``` + +3. 在自己的分支上开发并在本地做版本控制。 + +4. 将自己的分支(工作成果)推到服务器。 + + ```Shell + git push origin + ``` + +5. 在线发起一次合并请求(通常称之为**Pull Request**,有的地方称为**Merge Request**),请求将自己的工作成果合并到`master`分支,合并之后可以删除该分支。 + + ![](./res/gitee-pull-request.png) + +上面这种分支管理策略就是被称为**github-flow**或**PR**的流程,它非常简单容易理解,只需要注意以下几点: + +1. `master`的内容都是可以进行发布的内容(不能直接在`master`上进行修改)。 +2. 开发时应该以`master`为基础建立新分支(日常开发任务在自己的分支上进行)。 +3. 分支先在本地实施版本控制,然后以同名分支定期向服务器进行push操作。 +4. 开发任务完成后向`master`发送合并请求。 +5. 合并请求通过审查之后合并到`master`,并从`master`向正式环境发布。 + +当然,github-flow的缺点也很明显,`master`分支默认就是当前的线上代码,但是有的时候工作成果合并到`master`分支,并不代表它就能立刻发布,这样就会导致线上版本落后于`master`分支。 + +##### Git-flow + +除了上述的github-flow分支管理策略外,还有一种名为git-flow的分支管理策略,它也是大多数公司愿意使用的一套流程。Git-flow借鉴了中央集权型版本控制系统的长处,为团队内部统一建立、合并和关闭分支的方法,如下图所示。 + +![](./res/git-flow.png) + +在这种模式下,项目有两个长线分支,分别是`master`和`develop`,其他都是临时的的辅助分支,包括`feature`(开发特定功能的分支,开发结束后合并到`develop`)、`release`(从`develop`分离出来的为发布做准备的分支,发布结束后合并到`master`和`develop`)和`hotfix`(产品发布后出现问题时紧急建立的分支,直接从`master`分离,问题修复后合并到`master`并打上标签,同时还要合并到`develop`来避免将来的版本遗漏了这个修复工作,如果此时有正在发布中的`release`分支,还要合并到`release`分支)。具体的实施过程如下所示: + +![](./res/git-flow-detail.png) + +1. 最开始的时候只有`master`和`develop`分支,如上图左侧所示。 + +2. 从`develop`分支创建`feature`分支(上图右上),工作完成后将工作成果合并到`develop`分支(上图右中)。 + + 创建`feature`分支: + + ```Shell + git switch -c feature/user develop + ``` + + 或 + + ```Shell + git checkout -b feature/user develop + ``` + + 接下来就是在`feature`分支上进行开发并实施版本控制,这一段如何操作我们就不再赘述了。工作完成后,将`feature`分支合并到`develop`分支: + + ```Shell + git checkout develop + git merge --no-ff feature/user + git branch -d feature/user + git push origin develop + ``` + +3. 从`develop`分支创建`release`分支,发布结束后合并回`master`和`develop`分支。 + + 创建`release`分支: + + ```Shell + git checkout -b release-0.1 develop + git push -u origin release-0.1 + ... ... ... + git pull + git commit -a -m "............" + ``` + + 将`release`分支合并回`master`和`develop`分支: + + ```Shell + git checkout master + git merge --no-ff release-0.1 + git push + + git checkout develop + git merge --no-ff release-0.1 + git push + + git branch -d release-0.1 + git push --delete release-0.1 + git tag v0.1 master + git push --tags + ``` + +4. 从`master`分支创建`hotfix`分支,在修复bug后合并到`develop`和`master`分支(上图右下)。 + + 创建`hotfix`分支: + + ```Shell + git checkout -b hotfix-0.1.1 master + git push -u origin hotfix-0.1.1 + ... ... ... + git pull + git commit -a -m "............" + ``` + + 将`hotfix`分支合并回`develop`和`master`分支。 + + ```Shell + git checkout master + git merge --no-ff hotfix-0.1.1 + git push + + git checkout develop + git merge --no-ff hotfix-0.1.1 + git push + + git branch -d hotfix-0.1.1 + git push --delete hotfix-0.1.1 + git tag v0.1.1 master + git push --tags + ``` + +Git-flow流程比较容易控制各个分支的状况,但是在运用上github-flow要复杂得多,因此实际使用的时候通常会安装名为`gitflow`的命令行工具(Windows环境的Git自带了该工具)或者使用图形化的Git工具(如:SmartGit、SourceTree等)来简化操作,具体的可以参考[《git-flow 的工作流程》]()一文,因为这篇文章写得已经很好了,本文不再进行赘述。 + +### 缺陷管理 + +没有好的团队管理工具必然导致项目进展不顺利,任务管理困难,而引入缺陷管理系统正好可以解决这些问题,通常一个缺陷管理系统都包含了以下的功能: + +1. 任务管理(包括必须做什么、谁来做、什么时候完成、现在处于什么状态等)。 +2. 直观而且可以检索过去发生的各种问题。 +3. 能够对信息进行统一的管理和共享。 +4. 能够生成各类报表。 +5. 能够关联到其他系统,具有可扩展性。 + +#### 禅道 + +[禅道]()是国产的专业项目管理软件,它不仅仅是缺陷管理工具,它提供了完整软件生命周期管理功能,支持[Scrum敏捷开发](),能够实现需求管理、缺陷管理、任务管理等一系列的功能,而且拥有强大的扩展机制和丰富的功能插件。可以从禅道的官方网站提供的[下载链接]()来下载禅道,推荐使用一键安装包。 + +下面仍然以CentOS Linux为例,讲解如何利用官方提供的一键安装包来安装禅道。 + +```Shell +cd /opt +wget http://dl.cnezsoft.com/zentao/pro8.5.2/ZenTaoPMS.pro8.5.2.zbox_64.tar.gz +gunzip ZenTaoPMS.pro8.5.2.zbox_64.tar.gz +tar -xvf ZenTaoPMS.pro8.5.2.zbox_64.tar +``` + +我们在`/opt`目录下(官方推荐使用这个目录)下载了禅道的归档压缩文件,并进行了解压缩和解归档的操作,完成上述步骤后,会看到一个名为`zbox`的文件夹。一键安装包中内置了Apache、MySQL、PHP等应用,也就是说这些都不需要单独安装部署了,接下来我们通过下面的命令来启动禅道。 + +```Shell +/opt/zbox/zbox -ap 8080 -mp 3307 +/opt/zbox/zbox start +``` + +> 说明:上面使用`zbox`文件夹下的`zbox`命令,其中`-ap`是为了指定Apache服务器使用的端口,`-mp`是为了指定MySQL数据库使用的端口,这里使用3307端口是为了避开服务器上可能已经存在的MySQL服务的3306端口;`start`表示启动服务,`stop`可以用来停止服务。此外,需要打开防火墙8080端口以便访问禅道,注意**数据库的端口决不能暴露给公网**。 + +打开浏览器,输入服务器的公网IP地址就可以访问禅道,如果愿意,也可以通过DNS解析绑定一个域名来进行访问,禅道的首页如下图所示,默认的管理员是`admin`,口令是`123456`。 + +![](./res/zentao-login.png) + +第一次使用禅道时,建议通过点击用户名,然后通过“帮助”菜单的“新手教程”来迅速了解禅道。官方网站的文档链接中提供了[视频教程](),初学者也可以通过视频教程来上手。 + +![](./res/zentao-index.png) + +对敏捷开发以及敏捷闭环工具不是特别了解的,可以参考[《基于JIRA的Scrum敏捷开发的项目管理》]()一文。 + +#### GitLab + +常用的代码托管平台和之前提到的Git私服Gitlab都提供了缺陷管理的功能,当我们要报告一个bug时,可以在如下图所示的界面创建一个新的问题票(issue ticket)。填写的内容包括: + +1. **[必填]**出现问题的软件版本号、具体的使用环境(如操作系统)等相关信息。 +2. **[必填]**能够稳定重现该问题的相关步骤。 +3. **[必填]**描述此处期待的行为和实际的行为。 +4. **[可选]**你对这个bug的看法(产生bug的原因是什么)。 + +![](./res/gitlab-new-issue.png) + +如上图所示,我们在创建问题票时,还需要将问题指派给处理问题的人,如果不清楚应该由谁来修复这个bug,就指派给项目管理者,除此之外还要指定问题的优先级(十分紧急、紧急、普通、不紧急等)、问题的标签(功能缺陷、新特性、改进增强、前瞻研究等)、里程碑(通过里程碑可以将问题与某些特定的项目节点关联起来,之后可以查看每一个里程碑的进展,可以基于软件版本号来建立里程碑,也可以基于迭代周期来建立里程碑)以及需要在哪个时间点以前修复等信息。 + +有些敏捷团队使用问题票来管理产品的需求,称之为“问题驱动开发”(TiDD),也就是说新功能的开发是通过创建问题票来驱动的,具体的步骤包括:建立问题票、指定责任人、开发、提交、Push到代码库。如果要创建一个和需求相关的问题票,应该要填写以下的内容: + +1. **[必填]**简短的描述需求,并用它作为标题。 +2. **[必填]**这个需求是解决什么问题的。 +3. **[必填]**这个需求对软件现有功能会造成什么影响。 +4. **[必填]**这个需求应该实现什么样的功能。 +5. **[必填]**这个需求是否依赖其他模块提供相关支持。 +6. **[可选]**这个需求有哪些实现方式。 +7. **[可选]**这些可选的实现方式分别有哪些优缺点。 + +#### 其他产品 + +除了禅道和GitLab之外,[JIRA]()、[Redmine]()、Backlog等也是不错的缺陷管理系统。目前,这些系统大都不仅仅提供了缺陷管理的功能,更多的时候它们可以作为敏捷闭环工具来使用,关于敏捷闭环工具这个话题,请大家参考[《基于JIRA的Scrum敏捷开发的项目管理》]()一文。 + + +### 持续集成 + +为了快速的产出高质量的软件,在团队开发中持续集成(CI)是一个非常重要的环节。所谓CI,就是一种让计算机自动任意次重复编译、测试、汇报等工作的方法,通过CI可以帮助开发者提早发现问题,降低各种人为失误给项目带来的风险。按照经典的软件过程模型(瀑布模型),集成的工作一般要等到所有的开发工作都结束后才能开始,但这个时候如果发现了问题,修复问题的代价是非常具体的。基本上,集成实施得越晚,代码量越大,解决问题就越困难。持续集成将版本控制、自动化构建、代码测试融入到一起,让这些工作变得自动化和可协作。由于其频繁重复整个开发流程(在指定时间内多次pull源代码并运行测试代码),所以能帮助开发者提早发现问题。 + +在所有的CI工具中,Jenkins和[TravisCI]()是最具有代表性的,前者是基于 Java的开源CI工具,后者是新晋的在线CI工具,下图是Jenkins的工作面板。 + +![](./res/jenkins_new_project.png) + +持续集成对于编译型语言的意义更大,对于Python这样的解释型语言,更多的时候是用于对接版本控制系统触发自动化测试并产生相应的报告,类似的功能也可以通过配置**Webhook**来完成。如果要通过Docker这样的虚拟化容器进行项目打包部署或者通过K8S进行容器管理,可以在持续集成平台安装对应的插件来支持这些功能。码云甚至可以直接对接[钉钉开放平台]()使用钉钉机器人来向项目相关人员发送即时消息。GitLab也对CI和CD(持续交付)提供了支持,具体内容请大家参考[《GitLab CI/CD基础教程》]()。 + +> **说明**: +> +> 1. 关于敏捷开发的相关内容,有兴趣的读者可以阅读知乎上的[《这才是敏捷开发》]()一文。 +> +> 2. 本章中的部分插图来自于网易云课堂[《人人都会用Git》]()课程(免费哟),在此表示感谢。 + diff --git a/Day91-100/92.使用Docker部署服务.md b/Day91-100/92.Docker容器详解.md similarity index 64% rename from Day91-100/92.使用Docker部署服务.md rename to Day91-100/92.Docker容器详解.md index 33481d8..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,25 +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 -``` + ```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的镜像文件。 @@ -112,7 +138,7 @@ docker rmi hello-world > > 国内用户可以通过更换Ubuntu软件下载源来提升下载速度,具体请参照清华大学开源软件镜像站上的[《Ubuntu镜像使用帮助》]()。 -安装Docker后,由于直接访问[dockerhub](https://hub.docker.com/)下载镜像会非常缓慢,建议将服务器更换为国内镜像,可以通过修改 `/etc/docker/daemon.js` 文件来做到。 +安装Docker后,由于直接访问[dockerhub](https://hub.docker.com/)下载镜像会非常缓慢,建议将服务器更换为国内镜像,可以通过修改 `/etc/docker/daemon.json` 文件来做到。一般的云服务器会有自己专属的镜像,就不需要手动修改了。 ```JavaScript { @@ -125,9 +151,11 @@ docker rmi hello-world ### 使用Docker -#### 安装Nginx +想要玩转Docker,最简单的办法就是马上用Docker创建一些自己学习和工作中需要用到的容器,下面我们带着大家一起来创建这些容器。 -下面我们就基于Docker来创建一台HTTP服务器,我们选择用Nginx来搭建该服务,因为Nginx是高性能的Web服务器,同时也是做反向代理服务器的上佳选择。要做到这件事情,只需要使用下面的命令在Docker中创建一个容器即可。 +#### 运行Nginx + +Nginx是高性能的Web服务器,同时也是做反向代理服务器的上佳选择。使用Docker可以非常简单的创建一个运行Nginx的容器,命令如下所示。 ```Shell docker container run -d -p 80:80 --rm --name mynginx nginx @@ -150,7 +178,7 @@ docker container stop mynginx 然后用下面的命令重新创建容器。 ```Shell -docker container run -d -p 80:80 --rm --name mynginx --volume $PWD/html:/usr/share/nginx/html nginx +docker container run -d -p 80:80 --rm --name mynginx --volume /root/docker/nginx/html:/usr/share/nginx/html nginx ``` > 说明:上面创建容器和拷贝文件的命令中,`container`是可以省略的,也就是说`docker container run`和`docker run`是一样的,而`docker container cp`和`docker cp`是一样的。此外,命令中的`--volume`也可以缩写为`-v`,就如同`-d`是`--detach`的缩写,`-p`是`--publish`的缩写。`$PWD`代表宿主系统当前文件夹,这些对于使用过Unix或者Linux系统的人来说,应该是很容易理解的。 @@ -191,7 +219,7 @@ docker rm mynginx docker rm -f mynginx ``` -#### 安装MySQL +#### 运行MySQL 我们再来尝试用Docker安装一台MySQL服务器,首先可以先检查一下有没有MySQL的镜像文件。 @@ -228,10 +256,12 @@ docker.io/mysql 5.7 f6509bac4980 3 weeks ago 创建并运行MySQL容器。 ```Shell -docker run -d -p 3306:3306 --name mysql57 -v $PWD/mysql/conf:/etc/mysql/mysql.cnf.d -v $PWD/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7 +docker run -d -p 3306:3306 --name mysql57 -v /root/docker/mysql/conf:/etc/mysql/mysql.conf.d -v /root/docker/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7 ``` -注意,上面创建容器时我们又一次使用了数据卷操作,那是因为通常容器是随时创建随时删除的,而数据库中的数据却是需要保留下来的,所以上面的两个数据卷操作一个是映射了MySQL配置文件所在的文件夹,一个是映射了MySQL数据所在的文件夹,这里的数据卷操作非常重要。我们可以将MySQL的配置文件放在`$PWD/mysql/conf`目录下,配置文件的具体内容如下所示: +> **注意**:上面创建容器时我们又一次使用了数据卷操作,那是因为通常容器是随时创建随时删除的,而数据库中的数据却是需要保留下来的。 + +上面的两个数据卷操作一个是映射了MySQL配置文件所在的文件夹,一个是映射了MySQL数据所在的文件夹,这两个数据卷操作非常重要。我们可以将MySQL的配置文件放在`$PWD/mysql/conf`目录下,配置文件的具体内容如下所示: ```INI [mysqld] @@ -289,12 +319,12 @@ select user, host, plugin, authentication_string from user where user='root'; 在完成上面的步骤后,现在即便不更新客户端工具也可以连接MySQL 8.x了。 -#### 安装Redis +#### 运行Redis 接下来我们试一试运行多个容器并让多个容器之间通过网络通信。我们创建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 @@ -329,9 +359,47 @@ repl_backlog_first_byte_offset:1 repl_backlog_histlen:1988 ``` +#### 运行GitLab + +GitLab是由GitLab Inc.开发的Git仓库管理工具,具有wiki、问题跟踪、持续集成等一系列的功能,分为社区版和企业版。通过Docker提供的虚拟化容器,我们可以安装社区版的Docker。因为GitLab需要使用SSH协议进行安全连接,我们要暴露容器的22端口,所以可以先将宿主机SSH连接的22端口修改为其他端口(如:12345),然后再进行后续的操作。 + +```Shell +vim /etc/ssh/sshd_config +``` + +将其中定义端口的那行代码去掉注释并将端口修改为12345。 + +``` +Port 12345 +``` + +重新启动`sshd`服务。 + +```Shell +systemctl restart sshd +``` + +> **提示**:修改端口后应该确保防火墙上也开启对应的端口,否则无法使用SSH连接到Linux服务器。 + +创建需要用于数据卷映射操作的文件夹。 + +```Shell +mkdir -p /root/gitlab/{config,logs,data} +``` + +基于`gitlab/gitlab-ce`镜像创建容器,并暴露80端口(HTTP连接)和22端口(SSH连接)。 + +```Shell +docker run -d -p 80:80 -p 22:22 --name gitlab -v /root/gitlab/config:/etc/gitlab -v /root/gitlab/logs:/var/log/gitlab -v /root/gitlab/data:/var/opt/gitlab gitlab/gitlab-ce +``` + +> 说明:GitLab的启动比较缓慢,创建好容器后可能需要等待一段时间才能通过浏览器来进行访问。 + +首次进入GitLab访问界面会提示我们修改管理员密码,设置好管理员密码后就可以在登录界面输入用户名`root`和刚才设置的密码登录到管理员控制台,在使用上还是非常简单和人性化的。 + ### 构建镜像 -Docker镜像是由文件系统叠加而成的,系统的最底层是bootfs,相当于就是Linux内核的引导文件系统;接下来第二层是rootfs,这一层可以是一种或多种操作系统(如Debian或Ubuntu文件系统),Docker中的rootfs是只读状态的;Docker利用联合挂载技术将各层文件系统叠加到一起,最终的文件系统会包含有底层的文件和目录,这样的文件系统就是一个镜像,如下图所示。 +通过上面的讲解,我们已经掌握了如何通过官方提供的镜像来创建容器。当然如果愿意,我们也可以用配置好的容器来生成镜像。简而言之,**Docker镜像是由文件系统叠加而成的,系统的最底层是bootfs,相当于就是Linux内核的引导文件系统;接下来第二层是rootfs,这一层可以是一种或多种操作系统(如Debian或Ubuntu文件系统),Docker中的rootfs是只读状态的;Docker利用联合挂载技术将各层文件系统叠加到一起,最终的文件系统会包含有底层的文件和目录,这样的文件系统就是一个镜像**。 之前我们讲过了如何查找、列出镜像和拉取(下载)镜像,接下来看看构建镜像的两种方式: @@ -391,27 +459,77 @@ jackfrued/mywebserver latest 795b294d265a 14 seconds ago 189 MB Dockerfile使用DSL(Domain Specific Language)来构建一个Docker镜像,只要编辑好了Dockerfile文件,就可以使用`docker build`命令来构建一个新的镜像。 -我们先创建一个新的文件夹并在文件夹下创建名为Dockerfile的文件。 +我们先创建一个名为myapp的文件夹来保存项目代码和Dockerfile的文件,如下所示: ```Shell -mkdir test -cd test -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中的指令。 @@ -419,9 +537,11 @@ EXPOSE 80 接下来我们可以使用`docker build`命令来创建镜像,如下所示。 ```Shell -docker build -t="jackfrued/webserver" . +docker build -t "jackfrued/myapp" . ``` +> 提示:上面的命令最后面的`.` 千万不要漏掉了哦,它表示从当前路径下寻找Dockerfile。 + 通过下面的命令可以查看创建好的镜像。 ```Shell @@ -429,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仓库中,可以按照如下所示的步骤进行操作。 @@ -564,7 +681,7 @@ docker push jackfrued/webserver USER nginx ``` -8. **VOLUME**:在创建容器时添加一个数据卷的挂载点。通过数据卷操作可以实现容器间数据的共享和重用,对卷所作的修改可以马上生效而不需要重新启动容器,我们之前创建容器时使用`—volume`参数就是为了实现数据卷的映射操作。 +8. **VOLUME**:在创建容器时添加一个数据卷的挂载点。通过数据卷操作可以实现容器间数据的共享和重用,对卷所作的修改可以马上生效而不需要重新启动容器,我们之前创建容器时使用`--volume`参数就是为了实现数据卷的映射操作。 ```Dockerfile VOLUME ["/路径1", "/路径2/子路径2.1/", ...] @@ -591,7 +708,7 @@ docker push jackfrued/webserver ONBUILD RUN cd /app/src && make ``` -### 容器编排 +### 多容器管理 我们的项目可能会使用了多个容器,容器多了之后管理容器的工作就会变得麻烦。如果要对多个容器进行自动配置使得容器可以相互协作甚至实现复杂的调度,这就需要进行容器编排。Docker原生对容器编排的支持非常弱,但是可以通过社区提供的工具来实现容器编排。 @@ -602,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 ``` @@ -616,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': '*'}}) @@ -651,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 @@ -730,70 +814,63 @@ docker push jackfrued/webserver ``` - 接下来,我们要通过一个YAML文件来创建三个容器并指明容器之间的依赖关系。 - - ```Shell - vim docker-compose.yml - ``` + 接下来,我们要通过docker-compose.yml文件来创建三个容器并指明容器之间的依赖关系。 ```YAML version: '3' services: - - product-service: - build: ./product-service + api-server: + build: ./myapp ports: - '8000:8000' links: - redis-master - - web-site: + web-server: image: nginx ports: - '80:80' volumes: - - ./web-site:/usr/share/nginx/html - + - ./html:/usr/share/nginx/html redis-master: image: redis expose: - '6379' ``` - 有了这个YAML文件,我们就可以使用`docker-compose`命令来创建和管理这三个容器,其命令如下所示。 + 有了这个YAML文件,我们就可以使用`docker-compose`命令来创建容器运行项目,其命令如下所示: ```Shell - docker-compose up + [ECS-root temp]# docker-compose up + Creating network "temp_default" with the default driver + Creating temp_web-server_1 ... done + Creating temp_redis-master_1 ... done + Creating temp_api-server_1 ... done + Attaching to temp_redis-master_1, temp_web-server_1, temp_api-server_1 + redis-master_1 | 1:C 05 Dec 2019 11:57:26.828 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo + redis-master_1 | 1:C 05 Dec 2019 11:57:26.828 # Redis version=5.0.6, bits=64, commit=00000000, modified=0, pid=1, just started + redis-master_1 | 1:C 05 Dec 2019 11:57:26.828 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf + redis-master_1 | 1:M 05 Dec 2019 11:57:26.830 * Running mode=standalone, port=6379. + redis-master_1 | 1:M 05 Dec 2019 11:57:26.831 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. + redis-master_1 | 1:M 05 Dec 2019 11:57:26.831 # Server initialized + redis-master_1 | 1:M 05 Dec 2019 11:57:26.831 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. + redis-master_1 | 1:M 05 Dec 2019 11:57:26.831 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled. + redis-master_1 | 1:M 05 Dec 2019 11:57:26.831 * Ready to accept connections + api-server_1 | [2019-12-05 11:57:27 +0000] [1] [INFO] Starting gunicorn 20.0.4 + api-server_1 | [2019-12-05 11:57:27 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1) + api-server_1 | [2019-12-05 11:57:27 +0000] [1] [INFO] Using worker: sync + api-server_1 | [2019-12-05 11:57:27 +0000] [8] [INFO] Booting worker with pid: 8 + api-server_1 | [2019-12-05 11:57:27 +0000] [9] [INFO] Booting worker with pid: 9 + api-server_1 | [2019-12-05 11:57:27 +0000] [10] [INFO] Booting worker with pid: 10 + api-server_1 | [2019-12-05 11:57:27 +0000] [11] [INFO] Booting worker with pid: 11 ``` - ``` - Creating network "composeapp_default" with the default driver - Building product-service - Step 1/5 : FROM python:3 - ---> e497dabd8450 - Step 2/5 : ADD . /root/product-service - ---> fbe62813d595 - Removing intermediate container 6579e845565a - Step 3/5 : WORKDIR /root/product-service - ---> 3a722675e3b1 - Removing intermediate container 57fc490436ce - Step 4/5 : RUN pip install -r requirements.txt - ---> Running in cadc2d0c1b9b - ... ... - ---> fc747fc11f4a - Removing intermediate container cadc2d0c1b9b - Step 5/5 : CMD python api.py - ---> Running in ecbbd2a69906 - ---> 637e760f2e5b - Removing intermediate container ecbbd2a69906 - Successfully built 637e760f2e5b - WARNING: Image for service product-service was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`. - Creating composeapp_redis-master_1 ... done - Creating composeapp_web-site_1 ... done - Creating composeapp_product-service_1 ... done - Attaching to composeapp_redis-master_1, composeapp_web-site_1, composeapp_product-service_1 - ... ... + 要停止容器的运行,可以使用下面的命令。 + + ```Shell + docker-compose down ``` +#### Kubernetes(K8S) +实际的生产环境中常常需要部署和管理多个协同工作的容器,docker compose解决了多容器创建和管理的问题,但是实际项目中,我们还需要Kubernetes(以下都简称为K8S)来提供一个跨主机集群的容器调度平台。K8S可以进行自动化容器的部署、扩展和操作,从而提供以容器为中心的基础架构。该项目是谷歌在2014年启动的项目,建立在谷歌公司十余年运维经验的基础之上,而且谷歌自己的应用也是运行在容器上的。 diff --git a/Day91-100/93.MySQL性能优化.md b/Day91-100/93.MySQL性能优化.md index de8beed..27a7e6e 100644 --- a/Day91-100/93.MySQL性能优化.md +++ b/Day91-100/93.MySQL性能优化.md @@ -4,95 +4,267 @@ 在前面[《关系型数据库MySQL》](../Day36-40/36-38.关系型数据库MySQL.md)一文中,我们已经讲到过索引的相关知识,这里我们做一个简单的回顾。 -1. B-Tree索引 -2. HASH索引 -3. R-Tree索引(空间索引) -4. Full-text索引(全文索引) +#### 索引的设计原则 + +1. 创建索引的列并不一定是`select`操作中要查询的列,最适合做索引的列是出现在`where`子句中经常用作筛选条件或连表子句中作为表连接条件的列。 +2. 具有唯一性的列,索引效果好;重复值较多的列,索引效果差。 +3. 如果为字符串类型创建索引,最好指定一个前缀长度,创建短索引。短索引可以减少磁盘I/O而且在做比较时性能也更好,更重要的是MySQL底层的高速索引缓存能够缓存更多的键值。 +4. 创建一个包含N列的复合索引(多列索引)时,相当于是创建了N个索引,此时应该利用最左前缀进行匹配。 +5. 不要过度使用索引。索引并不是越多越好,索引需要占用额外的存储空间而且会影响写操作的性能(插入、删除、更新数据时索引也需要更新)。MySQL在生成执行计划时,要考虑各个索引的使用,这个也是需要耗费时间的。 +6. 要注意可能使索引失效的场景,例如:模糊查询使用了前置通配符、使用负向条件进行查询等。 ### 使用过程 -过程,通常也称之为存储过程。 - -```SQL -create procedure ... (params) -begin -... -end; - -call ... -``` - -```Python -cursor.callproc('...') -``` +过程,通常也称之为存储过程,它是事先编译好存储在数据库中的一组SQL的集合。调用存储过程可以简化应用程序开发人员的工作,减少与数据库服务器之间的通信,对于提升数据操作的性能是有帮助的,这些我们在之前的[《关系型数据库MySQL》](../Day36-40/36-38.关系型数据库MySQL.md)一文中已经提到过。 ### 数据分区 +MySQL支持做数据分区,通过分区可以存储更多的数据、优化查询,获得更大的吞吐量并快速删除过期的数据。关于这个知识点建议大家看看MySQL的[官方文档](https://dev.mysql.com/doc/refman/5.7/en/partitioning-overview.html)。数据分区有以下几种类型: +1. RANGE分区:基于连续区间范围,把数据分配到不同的分区。 + + ```SQL + CREATE TABLE tb_emp ( + eno INT NOT NULL, + ename VARCHAR(20) NOT NULL, + job VARCHAR(10) NOT NULL, + hiredate DATE NOT NULL, + dno INT NOT NULL + ) + PARTITION BY RANGE( YEAR(hiredate) ) ( + PARTITION p0 VALUES LESS THAN (1960), + PARTITION p1 VALUES LESS THAN (1970), + PARTITION p2 VALUES LESS THAN (1980), + PARTITION p3 VALUES LESS THAN (1990), + PARTITION p4 VALUES LESS THAN MAXVALUE + ); + ``` + +2. LIST分区:基于枚举值的范围,把数据分配到不同的分区。 + +3. HASH分区 / KEY分区:基于分区个数,把数据分配到不同的分区。 + + ```SQL + CREATE TABLE tb_emp ( + eno INT NOT NULL, + ename VARCHAR(20) NOT NULL, + job VARCHAR(10) NOT NULL, + hiredate DATE NOT NULL, + dno INT NOT NULL + ) + PARTITION BY HASH(dno) + PARTITIONS 4; + ``` ### SQL优化 -1. 通过`show status`了解各种SQL的执行频率。 +1. 定位低效率的SQL语句 - 慢查询日志。 + + - 查看慢查询日志相关配置 + + ```SQL + mysql> show variables like 'slow_query%'; + +---------------------------+----------------------------------+ + | Variable_name | Value | + +---------------------------+----------------------------------+ + | slow_query_log | OFF | + | slow_query_log_file | /mysql/data/localhost-slow.log | + +---------------------------+----------------------------------+ + + mysql> show variables like 'long_query_time'; + +-----------------+-----------+ + | Variable_name | Value | + +-----------------+-----------+ + | long_query_time | 10.000000 | + +-----------------+-----------+ + ``` + + - 修改全局慢查询日志配置。 + + ```SQL + mysql> set global slow_query_log='ON'; + mysql> set global long_query_time=1; + ``` + + 或者直接修改MySQL配置文件启用慢查询日志。 + + ```INI + [mysqld] + slow_query_log=ON + slow_query_log_file=/usr/local/mysql/data/slow.log + long_query_time=1 + ``` + +2. 通过`explain`了解SQL的执行计划。例如: ```SQL - show status like 'com_%'; - show status like 'innodb_%'; - show status like 'connections'; - show status like 'slow_queries'; + explain select ename, job, sal from tb_emp where dno=20\G + *************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: tb_emp + type: ref + possible_keys: fk_emp_dno + key: fk_emp_dno + key_len: 5 + ref: const + rows: 7 + Extra: NULL + 1 row in set (0.00 sec) ``` -2. 定位低效率的SQL语句 - 慢查询日志。 + - `select_type`:查询类型(SIMPLE - 简单查询、PRIMARY - 主查询、UNION - 并集、SUBQUERY - 子查询)。 + - `table`:输出结果集的表。 + - `type`:访问类型(ALL - 全表查询性能最差、index、range、ref、eq_ref、const、NULL)。 + - `possible_keys`:查询时可能用到的索引。 + - `key`:实际使用的索引。 + - `key_len`:索引字段的长度。 + - `rows`:扫描的行数,行数越少肯定性能越好。 + - `extra`:额外信息。 + +3. 通过`show profiles`和`show profile for query`分析SQL。 + + MySQL从5.0.37开始支持剖面系统来帮助用户了解SQL执行性能的细节,可以通过下面的方式来查看MySQL是否支持和开启了剖面系统。 ```SQL - show processlist + select @@have_profiling; + select @@profiling; ``` -3. 通过`explain`了解SQL的执行计划。 + 如果没有开启剖面系统,可以通过下面的SQL来打开它。 - - select_type:查询类型(simple、primary、union、subquery) - - table:输出结果集的表 - - type:访问类型(ALL、index、range、ref、eq_ref、const、NULL) - - possible_keys:查询时可能用到的索引 - - key:实际使用的索引 - - key_len:索引字段的长度 - - rows:扫描的行数 - - extra:额外信息 + ```SQL + set profiling=1; + ``` -4. 通过`show profiles`和`show profile for query`分析SQL。 + 接下来就可以通过剖面系统来了解SQL的执行性能,例如: -5. 优化CRUD操作。 + ```SQL + mysql> select count(*) from tb_emp; + +----------+ + | count(*) | + +----------+ + | 14 | + +----------+ + 1 row in set (0.00 sec) + + mysql> show profiles; + +----------+------------+-----------------------------+ + | Query_ID | Duration | Query | + +----------+------------+-----------------------------+ + | 1 | 0.00029600 | select count(*) from tb_emp | + +----------+------------+-----------------------------+ + 1 row in set, 1 warning (0.00 sec) + + mysql> show profile for query 1; + +----------------------+----------+ + | Status | Duration | + +----------------------+----------+ + | starting | 0.000076 | + | checking permissions | 0.000007 | + | Opening tables | 0.000016 | + | init | 0.000013 | + | System lock | 0.000007 | + | optimizing | 0.000005 | + | statistics | 0.000012 | + | preparing | 0.000010 | + | executing | 0.000003 | + | Sending data | 0.000070 | + | end | 0.000012 | + | query end | 0.000008 | + | closing tables | 0.000012 | + | freeing items | 0.000032 | + | cleaning up | 0.000013 | + +----------------------+----------+ + 15 rows in set, 1 warning (0.00 sec) + ``` + +4. 优化CRUD操作。 + + - 优化`insert`语句 + - 在`insert`语句后面跟上多组值进行插入在性能上优于分开`insert`。 + - 如果有多个连接向同一个表插入数据,使用`insert delayed`可以获得更好的性能。 + - 如果要从一个文本文件装载数据到表时,使用`load data infile`比`insert`性能好得多。 + + - 优化`order by`语句 + + - 如果`where`子句的条件和`order by`子句的条件相同,而且排序的顺序与索引的顺序相同,如果还同时满足排序字段都是升序或者降序,那么只靠索引就能完成排序。 + + - 优化`group by`语句 + + - 在使用`group by`子句分组时,如果希望避免排序带来的开销,可以用`order by null`禁用排序。 - - 优化insert语句 - - 优化order by语句 - - 优化group by语句 - 优化嵌套查询 + + - MySQL从4.1开始支持嵌套查询(子查询),这使得可以将一个查询的结果当做另一个查询的一部分来使用。在某些情况下,子查询可以被更有效率的连接查询取代,因为在连接查询时MySQL不需要在内存中创建临时表来完成这个逻辑上需要多个步骤才能完成的查询。 + - 优化or条件 + + - 如果条件之间是`or`关系,则只有在所有条件都用到索引的情况下索引才会生效。 + - 优化分页查询 + + - 分页查询时,一个比较头疼的事情是如同`limit 1000, 20`,此时MySQL已经排序出前1020条记录但是仅仅返回第1001到1020条记录,前1000条实际都用不上,查询和排序的代价非常高。一种常见的优化思路是在索引上完成排序和分页的操作,然后根据返回的结果做表连接操作来得到最终的结果,这样可以避免出现全表查询,也避免了外部排序。 + + ```SQL + select * from tb_emp order by ename limit 1000, 20; + select * from tb_emp t1 inner join (select eno from tb_emp order by ename limit 1000, 20) t2 on t1.eno=t2.eno; + ``` + + 上面的代码中,第2行SQL是优于第1行SQL的,当然我们的前提是已经在`ename`字段上创建了索引。 + - 使用SQL提示 - - USE INDEX - - IGNORE INDEX - - FORCE INDEX + - USE INDEX:建议MySQL使用指定的索引。 + - IGNORE INDEX:建议MySQL忽略掉指定的索引。 + - FORCE INDEX:强制MySQL使用指定的索引。 ### 配置优化 -1. 调整max_connections -2. 调整back_log -3. 调整table_open_cache -4. 调整thread_cache_size -5. 调整innodb_lock_wait_timeout +可以使用下面的命令来查看MySQL服务器配置参数的默认值。 + +```SQL +show variables; +show variables like 'key_%'; +show variables like '%cache%'; +show variables like 'innodb_buffer_pool_size'; +``` + +通过下面的命令可以了解MySQL服务器运行状态值。 + +```SQL +show status; +show status like 'com_%'; +show status like 'innodb_%'; +show status like 'connections'; +show status like 'slow_queries'; +``` + +1. 调整`max_connections`:MySQL最大连接数量,默认151。在Linux系统上,如果内存足够且不考虑用户等待响应时间这些问题,MySQL理论上可以支持到万级连接,但是通常情况下,这个值建议控制在1000以内。 +2. 调整`back_log`:TCP连接的积压请求队列大小,通常是max_connections的五分之一,最大不能超过900。 +3. 调整`table_open_cache`:这个值应该设置为max_connections的N倍,其中N代表每个连接在查询时打开的表的最大个数。 +4. 调整`innodb_lock_wait_timeout`:该参数可以控制InnoDB事务等待行锁的时间,默认值是50ms,对于反馈响应要求较高的应用,可以将这个值调小避免事务长时间挂起;对于后台任务,可以将这个值调大来避免发生大的回滚操作。 +5. 调整`innodb_buffer_pool_size`:InnoDB数据和索引的内存缓冲区大小,以字节为单位,这个值设置得越高,访问表数据需要进行的磁盘I/O操作就越少,如果可能甚至可以将该值设置为物理内存大小的80%。 ### 架构优化 -1. 通过拆分提高表的访问效率 +1. 通过拆分提高表的访问效率。 - 垂直拆分 - 水平拆分 -2. 逆范式理论 - - 数据表设计的规范程度称之为范式(Normal Form) - - 1NF:列不能再拆分 - - 2NF:所有的属性都依赖于主键 - - 3NF:所有的属性都直接依赖于主键(消除传递依赖) - - BCNF:消除非平凡多值依赖 -3. 使用中间表提高统计查询速度 -4. 主从复制和读写分离 -5. 配置MySQL集群 +2. 逆范式理论。数据表设计的规范程度称之为范式(Normal Form),要提升表的规范程度通常需要将大表拆分为更小的表,范式级别越高数据冗余越小,而且在插入、删除、更新数据时出问题的可能性会大幅度降低,但是节省了空间就意味着查询数据时可能花费更多的时间,原来的单表查询可能会变成连表查询。为此,项目实践中我们通常会进行逆范式操作,故意降低范式级别增加冗余来减少查询的时间开销。 + - 1NF:列不能再拆分 + - 2NF:所有的属性都依赖于主键 + - 3NF:所有的属性都直接依赖于主键(消除传递依赖) + - BCNF:消除非平凡多值依赖 + +3. 使用中间表提高统计查询速度。 + + 使用`insert into 中间表 select ... where ...`这样的语句先将需要的数据筛选出来放到中间表中,然后再对中间表进行统计,避免不必要的运算和处理。 + +4. 主从复制和读写分离,具体内容请参考[《项目部署上线和性能调优》](./98.项目部署上线和性能调优.md)。 + +5. 配置MySQL集群。 + + + +> **说明**:本章内容参考了网易出品的《深入浅出MySQL》一书,该书和《高性能MySQL》一样,都对MySQL进行了深入细致的讲解,虽然总体感觉后者更加高屋建瓴,但是前者也算得上是提升MySQL技能的佳作(作者的文字功底稍显粗糙,深度也不及后者),建议有兴趣的读者可以阅读这两本书。 \ No newline at end of file diff --git a/Day91-100/94.网络API接口设计.md b/Day91-100/94.网络API接口设计.md index df05a3f..36842a4 100644 --- a/Day91-100/94.网络API接口设计.md +++ b/Day91-100/94.网络API接口设计.md @@ -1,12 +1,22 @@ ## 网络API接口设计 -手机App以及使用了Ajax技术或做了前后端分离的页面都需要通过网络API(Application Programming Interface)和后台进行交互,所谓API,指的应用程序的编程接口;而网络API通畅指的是基于HTTP或HTTPS协议的一个URL(统一资源定位符),通过这个URL我们可以让服务器对某个资源进行操作并返回操作的结果。基于HTTP(S)协议最大的好处就在于访问起来非常的简单方便,而且没有编程语言和应用环境上的差别。 +目前许多的Web应用和移动应用都使用了前后端分离的开发模式,前后端分离简单的说就是前端或移动端通过网络API接口和后台进行交互,获得接口中提供的数据并负责用户界面的渲染。API是应用程序的编程接口的缩写,网络API通常指的是基于一个URL(统一资源定位符)可以访问到的资源,也就是说通过这个URL我们就可以请求服务器对某个资源进行操作并返回操作的结果。大家可以想想,网络API接口不也是一种封装吗,简单的说就是将复杂的业务逻辑隐藏在简单的API接口中。 + +URL的通用格式如下所示: + +``` +协议://用户名:口令@主机:端口/路径1/.../路径N/资源名 +``` + +> **说明**:URL中的用户名(有可能不需要提供用户名)、口令(有可能不需要提供口令)、端口(有可能使用默认端口)、路径(资源有可能直接位于根路径`/`下)并不是必需的部分,可以根据需要进行设置。 + +网络API通常基于HTTP或HTTPS进行访问,基于HTTP/HTTPS最大的好处就在于访问起来非常的简单方便,而且可以跨语言、跨应用进行访问和互操作。 ### 设计原则 #### 关键问题 -为移动端或者PC端设计网络API接口一个非常重要的原则是:根据业务实体而不是用户界面或操作来设计。如果API接口的设计是根据用户的操作或者界面上的功能设置来设计,随着需求的变更,用户界面也会进行调整,需要的数据也在发生变化,那么后端开发者就要不停的调整API,或者给一个API设计出多个版本,这些都会使项目的开发和维护成本增加。 +为移动端或者PC端设计网络API接口一个非常重要的原则是:**根据业务实体而不是用户界面或操作来设计API接口**。如果API接口的设计是根据用户的操作或者界面上的功能设置来设计,随着需求的变更,用户界面也会进行调整,需要的数据也在发生变化,那么后端开发者就要不停的调整API,或者给一个API设计出多个版本,这些都会使项目的开发和维护成本增加。我们可以将业务实体理解为服务器提供的资源,而URL就是资源的定位符(标识符),这种方式是最为简单自然的。对于相对复杂的用户操作,我们可以提供一个“门面”(设计模式中的“门面模式”),通过该“门面”把多个接口的功能组装起来即可。 下面是某个网站开放API的接口,可以看出API的设计是围绕业务实体来进行的,而且都做到了“见名知意”。 @@ -20,9 +30,9 @@ | comments/destroy | 删除一条评论 | | comments/reply | 回复一条评论 | -注意:上面的API接口并不是REST风格的,关于REST的知识,可以阅读阮一峰老师的[《理解RESTful架构》](http://www.ruanyifeng.com/blog/2011/09/restful.html)以及[《RESTful API设计指南》](http://www.ruanyifeng.com/blog/2014/05/restful_api.html)。 +需要说明的是,**上面的API接口并不是REST风格的**。REST是一种网络应用架构风格,被认为最适合分布式的网络应用。关于REST的知识,可以阅读阮一峰的[《理解RESTful架构》](http://www.ruanyifeng.com/blog/2011/09/restful.html)以及[《RESTful API设计指南》](http://www.ruanyifeng.com/blog/2014/05/restful_api.html),当然这两篇文章大家也要批判的阅读,因为上面阐述的观点并不完全正确,有些内容甚至是自相矛盾的。 -API接口返回的数据通常都是JSON或XML格式,我们这里不讨论后者。对于JSON格式的数据,我们需要做到不要返回null这的值,因为这样的值一旦处置失当,会给移动端的开发带来麻烦(移动端可能使用强类型语言)。要解决这个问题可以从源头入手,在设计数据库的时候,尽量给每个字段都加上“not null”约束或者设置合理的默认值约束。 +API接口返回的数据通常都是**JSON**或**XML**格式,XML这种数据格式目前基本已经被弃用了。对于JSON格式的数据,我们需要做到不要返回null这的值,因为这样的值一旦处置失当,会给前端和移动端开发带来不必要的麻烦(因为开发者有可能会使用强类型语言)。要解决这个问题可以从源头入手,在设计数据库的时候,尽量给每个字段都加上“not null”约束或者设置合理的默认值约束。 #### 其他问题 @@ -34,9 +44,7 @@ API接口返回的数据通常都是JSON或XML格式,我们这里不讨论后 下面以设计评论接口为例,简单说明接口文档应该如何撰写。 -#### 评论接口 - -全局返回状态码 +首先,我们可以定义全局返回状态码。 | 返回码 | 返回信息 | 说明 | | ------ | ------------ | ---------------------------------- | @@ -46,7 +54,9 @@ API接口返回的数据通常都是JSON或XML格式,我们这里不讨论后 | 10003 | 评论已被删除 | 查看评论时评论因不和谐因素已被删除 | | 10004 | …… | …… | -1. **GET** `/articles/{article-id}/comments/` +1. 获取文章评论。 + + URL:**GET** `/articles/{article-id}/comments/` 开发者:王大锤 @@ -95,7 +105,9 @@ API接口返回的数据通常都是JSON或XML格式,我们这里不讨论后 } ``` -2. **POST** `/articles/{article-id}/comments` +2. 新增文章评论。 + + **POST** `/articles/{article-id}/comments` 开发者:王大锤 @@ -129,3 +141,8 @@ API接口返回的数据通常都是JSON或XML格式,我们这里不讨论后 /* ... */ } ``` + + + +> **提示**:如果没有接口文档撰写经验,可以使用在线接口文档编辑平台[RAP2]()或[YAPI]()来进行接口文档撰写。 + diff --git a/Day91-100/95.使用Django开发商业项目.md b/Day91-100/95.使用Django开发商业项目.md index 26bdf40..5229ab1 100644 --- a/Day91-100/95.使用Django开发商业项目.md +++ b/Day91-100/95.使用Django开发商业项目.md @@ -160,23 +160,14 @@ python manage.py inspectdb > /models.py - `filter()` / `exclude()` - `exact` / `iexact`:精确匹配/忽略大小写的精确匹配查询 - - `contains` / `icontains` / `startswith / istartswith / endswith / iendswith`:基于`like`的模糊查询 - - `in`:集合运算 - - `gt` / `gte` / `lt` / `lte`:大于/大于等于/小于/小于等于关系运算 - - `range`:指定范围查询(SQL中的`between…and…`) - - `year` / `month` / `day` / `week_day` / `hour` / `minute` / `second`:查询时间日期 - - `isnull`:查询空值(`True`)或非空值(`False`) - - `search`:基于全文索引的全文检索 - - `regex` / `iregex`:基于正则表达式的模糊匹配查询 - - `aggregate()` / `annotate()` - `Avg` / `Count` / `Sum` / `Max` / `Min` @@ -268,9 +259,9 @@ python manage.py inspectdb > /models.py #### 如何设计视图函数 -1. 用户的每个操作(用户故事)对应一个视图函数。 +1. 用户的每个请求(用户故事)对应一个视图函数,当然也可以将用户要执行的业务逻辑封装到独立的函数中,也就是有专门的模块处理程序中的业务逻辑。 -2. [每个视图函数可以构成一个事务边界](https://docs.djangoproject.com/en/2.1/ref/settings/)。 +2. 用户的请求可能会包含多个(持久化)操作,这些操作有可能需要设计成不可分割的原子性操作,那么这里就形成了事务的边界。 - 事务的ACID特性。 @@ -356,7 +347,7 @@ python manage.py inspectdb > /models.py 4. 如果使用`url`函数捕获的路径参数都是字符串,`path`函数可以指定路径参数类型。 -5. 可以使用`include`函数引入其他URL配置,捕获的参数会向下传递。 +5. 可以使用`include`函数引入其他URL配置并指定`namespace`来解决命名冲突,捕获的参数会向下传递。 6. 在`url`和`path`函数甚至是`include`函数中都可以用字典向视图传入额外的参数,如果参数与捕获的参数同名,则使用字典中的参数。 @@ -488,7 +479,9 @@ python manage.py inspectdb > /models.py - 向浏览器传输二进制数据。 ```Python - buffer = ByteIO() + from io import BytesIO + + buffer = BytesIO() resp = HttpResponse(content_type='...') resp['Content-Disposition'] = 'attachment; filename="..."' @@ -496,13 +489,16 @@ python manage.py inspectdb > /models.py ``` ```Python + from io import BytesIO + + import xlwt + + def get_style(name, color=0, bold=False, italic=False): - style = xlwt.XFStyle() font = xlwt.Font() - font.name = name - font.colour_index = color - font.bold = bold - font.italic = italic + font.name, font.colour_index, font.bold, font.italic = \ + name, color, bold, italic + style = xlwt.XFStyle() style.font = font return style @@ -535,7 +531,6 @@ python manage.py inspectdb > /models.py # 如果文件名有中文需要处理成百分号编码 resp['content-disposition'] = 'attachment; filename="detail.xls"' return resp - ``` - 大文件的流式处理:`StreamingHttpResponse`。 @@ -680,7 +675,7 @@ class MyMiddleware(object): ``` ```Python -class MyMiddleware(object): +class MyMiddleware: def __init__(self): pass @@ -858,7 +853,7 @@ CACHES = { 'CONNECTION_POOL_KWARGS': { 'max_connections': 1000, }, - 'PASSWORD': '1qaz2wsx', + 'PASSWORD': 'yourpass', } }, # 页面缓存 @@ -873,7 +868,7 @@ CACHES = { 'CONNECTION_POOL_KWARGS': { 'max_connections': 500, }, - 'PASSWORD': '1qaz2wsx', + 'PASSWORD': 'yourpass', } }, # 会话缓存 @@ -889,7 +884,7 @@ CACHES = { 'CONNECTION_POOL_KWARGS': { 'max_connections': 2000, }, - 'PASSWORD': '1qaz2wsx', + 'PASSWORD': 'yourpass', } }, # 接口数据缓存 @@ -904,7 +899,7 @@ CACHES = { 'CONNECTION_POOL_KWARGS': { 'max_connections': 500, }, - 'PASSWORD': '1qaz2wsx', + 'PASSWORD': 'yourpass', } }, } @@ -1089,13 +1084,13 @@ LOGGING = { #### 使用djangorestframework -安装djangorestfrmework(为了描述方便,以下统一简称为drf)。 +安装djangorestfrmework(为了描述方便,以下统一简称为DRF)。 ```Shell pip install djangorestframework ``` -配置drf。 +配置DRF。 ```Python INSTALLED_APPS = [ @@ -1412,7 +1407,7 @@ class HouseInfoViewSet(CacheResponseMixin, ReadOnlyModelViewSet): #### 身份认证 -查看drf中APIView类的代码可以看出,drf默认的认证方案是 `DEFAULT_AUTHENTICATION_CLASSES`,如果修改authentication_classes就可以自行定制身份认证的方案。 +查看DRF中APIView类的代码可以看出,DRF默认的认证方案是 `DEFAULT_AUTHENTICATION_CLASSES`,如果修改authentication_classes就可以自行定制身份认证的方案。 ```Python class APIView(View): @@ -1499,7 +1494,7 @@ class EstateViewSet(CacheResponseMixin, ModelViewSet): #### 授予权限 -权限检查总是在视图的最开始处运行,在任何其他代码被允许进行之前。最简单的权限是允许通过身份验证的用户访问,并拒绝未经身份验证的用户访问,这对应于dfr中的`IsAuthenticated`类,可以用它来取代默认的`AllowAny`类。权限策略可以在Django的drf配置中用`DEFAULT_PERMISSION_CLASSES`全局设置。 +权限检查总是在视图的最开始处运行,在任何其他代码被允许进行之前。最简单的权限是允许通过身份验证的用户访问,并拒绝未经身份验证的用户访问,这对应于dfr中的`IsAuthenticated`类,可以用它来取代默认的`AllowAny`类。权限策略可以在Django的DRF配置中用`DEFAULT_PERMISSION_CLASSES`全局设置。 ```Python REST_FRAMEWORK = { @@ -1579,7 +1574,7 @@ class BlacklistPermission(permissions.BasePermission): ![](./res/rbac-full.png) -2. ACL - 访问控制列表(每个用户绑定自己的访问白名单)。 +2. ACL - 访问控制列表(每个用户绑定自己的访问白名单或黑名单)。 #### 访问限流 @@ -1621,7 +1616,7 @@ def example_view(request, format=None): # 此处省略下面的代码 ``` -当然也可以通过继承`BaseThrottle`来自定义限流策略,通常需要重写`allow_request`和`wait`方法。 +当然也可以通过继承`SimpleRateThrottle`来自定义限流策略,通常需要重写`allow_request`和`wait`方法。 ### 异步任务和计划任务 @@ -1689,6 +1684,7 @@ Celery是一个本身不提供队列服务,官方推荐使用RabbitMQ或Redis pass + # 消息的生产者 send_email.delay('', [], [], '', '') ``` @@ -1704,9 +1700,9 @@ Celery是一个本身不提供队列服务,官方推荐使用RabbitMQ或Redis # 将来实际部署项目的时候生产者、消费者、消息队列可能都是不同节点 beat_schedule={ 'task1': { - 'task': 'common.tasks.show_msg', - 'schedule': crontab(), - 'args': ('刘强东,奶茶妹妹喊你回家喝奶啦', ) + 'task': 'common.tasks.scheduled_task', + 'schedule': crontab('*', '*', '*', '*', '*'), + 'args': ('...', ) }, }, ) @@ -1714,8 +1710,8 @@ Celery是一个本身不提供队列服务,官方推荐使用RabbitMQ或Redis ```Python @app.task - def show_msg(content): - print(content) + def scheduled_task(*args, **kwargs): + pass ``` 7. 启动Celery创建执行定时任务的beat(消息的生产者)。 @@ -1764,13 +1760,13 @@ CORS_ORIGIN_ALLOW_ALL = True ### 安全保护 -问题1:什么是跨站脚本攻击,如何防范?(对提交的内容进行消毒) +问题1:什么是跨站脚本攻击(XSS),如何防范?(对提交的内容进行消毒) -问题2:什么是跨站身份伪造,如何防范?(使用随机令牌) +问题2:什么是跨站身份伪造(CSRF),如何防范?(使用随机令牌) -问题3:什么是SQL注射攻击,如何防范?(不拼接SQL语句,避免使用单引号) +问题3:什么是SQL注射攻击(SQL Injection),如何防范?(不拼接SQL语句,避免使用单引号) -问题4:什么是点击劫持攻击,如何防范?(不允许`