Commit 32127829 authored by jackfrued's avatar jackfrued

更新了部分文档和代码

parent 2d08d42f
from turtle import* """
绘制小猪佩奇
"""
from turtle import *
def nose(x,y):#鼻子 def nose(x,y):
penup()#提起笔 """画鼻子"""
goto(x,y)#定位 penup()
pendown()#落笔,开始画 # 将海龟移动到指定的坐标
setheading(-30)#将乌龟的方向设置为to_angle/为数字(0-东、90-北、180-西、270-南) goto(x,y)
begin_fill()#准备开始填充图形 pendown()
a=0.4 # 设置海龟的方向(0-东、90-北、180-西、270-南)
setheading(-30)
begin_fill()
a = 0.4
for i in range(120): for i in range(120):
if 0<=i<30 or 60<=i<90: if 0 <= i < 30 or 60 <= i <90:
a=a+0.08 a = a + 0.08
left(3) #向左转3度 # 向左转3度
forward(a) #向前走a的步长 left(3)
# 向前走
forward(a)
else: else:
a=a-0.08 a = a - 0.08
left(3) left(3)
forward(a) forward(a)
end_fill()#填充完成 end_fill()
penup() penup()
setheading(90) setheading(90)
forward(25) forward(25)
setheading(0) setheading(0)
forward(10) forward(10)
pendown() pendown()
pencolor(255,155,192)#画笔颜色 # 设置画笔的颜色(红, 绿, 蓝)
pencolor(255, 155, 192)
setheading(10) setheading(10)
begin_fill() begin_fill()
circle(5) circle(5)
color(160,82,45)#返回或设置pencolor和fillcolor color(160, 82, 45)
end_fill() end_fill()
penup() penup()
setheading(0) setheading(0)
forward(20) forward(20)
pendown() pendown()
pencolor(255,155,192) pencolor(255, 155, 192)
setheading(10) setheading(10)
begin_fill() begin_fill()
circle(5) circle(5)
color(160,82,45) color(160, 82, 45)
end_fill() end_fill()
def head(x,y):#头 def head(x, y):
color((255,155,192),"pink") """画头"""
color((255, 155, 192), "pink")
penup() penup()
goto(x,y) goto(x,y)
setheading(0) setheading(0)
pendown() pendown()
begin_fill() begin_fill()
setheading(180) setheading(180)
circle(300,-30) circle(300, -30)
circle(100,-60) circle(100, -60)
circle(80,-100) circle(80, -100)
circle(150,-20) circle(150, -20)
circle(60,-95) circle(60, -95)
setheading(161) setheading(161)
circle(-300,15) circle(-300, 15)
penup() penup()
goto(-100,100) goto(-100, 100)
pendown() pendown()
setheading(-30) setheading(-30)
a=0.4 a = 0.4
for i in range(60): for i in range(60):
if 0<=i<30 or 60<=i<90: if 0<= i < 30 or 60 <= i < 90:
a=a+0.08 a = a + 0.08
lt(3) #向左转3度 lt(3) #向左转3度
fd(a) #向前走a的步长 fd(a) #向前走a的步长
else: else:
a=a-0.08 a = a - 0.08
lt(3) lt(3)
fd(a) fd(a)
end_fill() end_fill()
def ears(x,y): #耳朵 def ears(x,y):
color((255,155,192),"pink") """画耳朵"""
color((255, 155, 192), "pink")
penup() penup()
goto(x,y) goto(x, y)
pendown() pendown()
begin_fill() begin_fill()
setheading(100) setheading(100)
circle(-50,50) circle(-50, 50)
circle(-10,120) circle(-10, 120)
circle(-50,54) circle(-50, 54)
end_fill() end_fill()
penup() penup()
setheading(90) setheading(90)
forward(-12) forward(-12)
...@@ -96,14 +104,15 @@ def ears(x,y): #耳朵 ...@@ -96,14 +104,15 @@ def ears(x,y): #耳朵
pendown() pendown()
begin_fill() begin_fill()
setheading(100) setheading(100)
circle(-50,50) circle(-50, 50)
circle(-10,120) circle(-10, 120)
circle(-50,56) circle(-50, 56)
end_fill() end_fill()
def eyes(x,y):#眼睛 def eyes(x,y):
color((255,155,192),"white") """画眼睛"""
color((255, 155, 192), "white")
penup() penup()
setheading(90) setheading(90)
forward(-20) forward(-20)
...@@ -113,7 +122,6 @@ def eyes(x,y):#眼睛 ...@@ -113,7 +122,6 @@ def eyes(x,y):#眼睛
begin_fill() begin_fill()
circle(15) circle(15)
end_fill() end_fill()
color("black") color("black")
penup() penup()
setheading(90) setheading(90)
...@@ -124,8 +132,7 @@ def eyes(x,y):#眼睛 ...@@ -124,8 +132,7 @@ def eyes(x,y):#眼睛
begin_fill() begin_fill()
circle(3) circle(3)
end_fill() end_fill()
color((255, 155, 192), "white")
color((255,155,192),"white")
penup() penup()
seth(90) seth(90)
forward(-25) forward(-25)
...@@ -135,7 +142,6 @@ def eyes(x,y):#眼睛 ...@@ -135,7 +142,6 @@ def eyes(x,y):#眼睛
begin_fill() begin_fill()
circle(15) circle(15)
end_fill() end_fill()
color("black") color("black")
penup() penup()
setheading(90) setheading(90)
...@@ -148,8 +154,9 @@ def eyes(x,y):#眼睛 ...@@ -148,8 +154,9 @@ def eyes(x,y):#眼睛
end_fill() end_fill()
def cheek(x,y):#腮 def cheek(x,y):
color((255,155,192)) """画脸颊"""
color((255, 155, 192))
penup() penup()
goto(x,y) goto(x,y)
pendown() pendown()
...@@ -159,35 +166,39 @@ def cheek(x,y):#腮 ...@@ -159,35 +166,39 @@ def cheek(x,y):#腮
end_fill() end_fill()
def mouth(x,y): #嘴 def mouth(x,y):
color(239,69,19) """画嘴巴"""
color(239, 69, 19)
penup() penup()
goto(x,y) goto(x, y)
pendown() pendown()
setheading(-80) setheading(-80)
circle(30,40) circle(30, 40)
circle(40,80) circle(40, 80)
def setting(): #参数设置 def setting():
"""设置参数"""
pensize(4) pensize(4)
hideturtle() #使乌龟无形(隐藏) # 隐藏海龟
colormode(255) #将其设置为1.0或255.随后 颜色三元组的r,g,b值必须在0 .. cmode范围内 hideturtle()
color((255,155,192),"pink") colormode(255)
setup(840,500) color((255, 155, 192), "pink")
setup(840, 500)
speed(10) speed(10)
def main(): def main():
setting() #画布、画笔设置 """主函数"""
nose(-100,100) #鼻子 setting()
head(-69,167) #头 nose(-100, 100)
ears(0,160) #耳朵 head(-69, 167)
eyes(0,140) #眼睛 ears(0, 160)
cheek(80,10) #腮 eyes(0, 140)
mouth(-20,30) #嘴 cheek(80, 10)
mouth(-20, 30)
done() done()
if __name__ == '__main__': if __name__ == '__main__':
main() main()
...@@ -7,38 +7,38 @@ ...@@ -7,38 +7,38 @@
1. 1989年圣诞节:Guido von Rossum开始写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语言实现的(后面又出现了Java和C#实现的版本Jython和IronPython,以及PyPy、Brython、Pyston等其他实现),可以调用C语言的库函数。在最早的版本中,Python已经提供了对“类”,“函数”,“异常处理”等构造块的支持,同时提供了“列表”和“字典”等核心数据类型,同时支持以模块为基础的拓展系统。
3. 1994年1月:Python 1.0正式发布。 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的整个开发过程更加透明,社区对开发进度的影响逐渐扩大,生态圈开始慢慢形成。 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.6/2.7版本,因为目前还有公司在项目和运维中使用Python 2.x版本的代码 5. 2008年12月3日:Python 3.0发布,它并不完全兼容之前的Python代码,不过因为目前还有不少公司在项目和运维中使用Python 2.x版本,所以Python 3.x的很多新特性后来也被移植到Python 2.6/2.7版本中
目前我们使用的Python 3.6.x的版本是在2016年的12月23日发布的,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的优缺点
Python的优点很多,简单的可以总结为以下几点。 Python的优点很多,简单的可以总结为以下几点。
1. 简单和明确,做一件事只有一种方法。 1. 简单和明确,做一件事只有一种方法。
2. 学习曲线低,与其他很多语言比上手更容易 2. 学习曲线低,跟其他很多语言相比,Python更容易上手
3. 开放源代码,拥有强大的社区和生态圈。 3. 开放源代码,拥有强大的社区和生态圈。
4. 解释型语言,完美的平台可移植性。 4. 解释型语言,天生具有平台可移植性。
5. 支持两种主流的编程范式,可以使用面向对象和函数式编程 5. 支持两种主流的编程范式(面向对象编程和函数式编程)都提供了支持
6. 可扩展性和可嵌入性,可以调用C/C++代码也可以在C/C++中调用 6. 可扩展性和可嵌入性,可以调用C/C++代码,也可以在C/C++中调用Python
7. 代码规范程度高,可读性强,适合有代码洁癖和强迫症的人群。 7. 代码规范程度高,可读性强,适合有代码洁癖和强迫症的人群。
Python的缺点主要集中在以下几点。 Python的缺点主要集中在以下几点。
1. 执行效率低下,因此计算密集型任务可以由C/C++编写。 1. 执行效率稍低,因此计算密集型任务可以由C/C++编写。
2. 代码无法加密,但是现在的公司很多都不是卖软件而是卖服务,这个问题慢慢会淡化。 2. 代码无法加密,但是现在的公司很多都不是卖软件而是卖服务,这个问题会被淡化。
3. 在开发时可以选择的框架太多,有选择的地方就有错误。 3. 在开发时可以选择的框架太多(如Web框架就有100多个),有选择的地方就有错误。
#### Python的应用领域 #### Python的应用领域
目前Python在云基础设施、DevOps、网络爬虫开发、数据分析挖掘、机器学习等领域都有着广泛的应用,因此也产生了服务器开发、数据接口开发、自动化运维、科学计算和数据可视化、聊天机器人开发、图像识别和处理等一系列的职位。 目前Python在云基础设施、DevOps、网络爬虫开发、数据分析挖掘、机器学习等领域都有着广泛的应用,因此也产生了Web后端开发、数据接口开发、自动化运维、自动化测试、科学计算和可视化、数据分析、量化交易、机器人开发、图像识别和处理等一系列的职位。
### 搭建编程环境 ### 搭建编程环境
#### Windows环境 #### Windows环境
可以在[Python官方网站](https://www.python.org)下载到Python的Windows安装程序(exe文件),需要注意的是如果在Windows 7环境下安装需要先安装Service Pack 1补丁包(可以通过一些工具软件自动安装系统补丁的功能来安装),安装过程建议勾选“Add Python 3.6 to PATH”(将Python 3.6添加到PATH环境变量)并选择自定义安装,在设置“Optional Features”界面最好将“pip”、“tcl/tk”、“Python test suite”等项全部勾选上。强烈建议使用自定义的安装路径并保证路径中没有中文。安装完成会看到“Setup was successful”的提示,但是在启动Python环境时可能会因为缺失一些动态链接库文件而导致Python解释器无法运行,常见的问题主要是api-ms-win-crt\*.dll缺失以及更新DirectX之后导致某些动态链接库文件缺失,前者可以参照[《api-ms-win-crt\*.dll缺失原因分析和解决方法》]()一文讲解的方法进行处理或者直接在[微软官网](https://www.microsoft.com/zh-cn/download/details.aspx?id=48145)下载Visual C++ Redistributable for Visual Studio 2015文件进行修复,后者可以下载一个DirectX修复工具进行修复。 可以在[Python官方网站](https://www.python.org)下载到Python的Windows安装程序(exe文件),需要注意的是如果在Windows 7环境下安装需要先安装Service Pack 1补丁包(可以通过一些工具软件自动安装系统补丁的功能来安装),安装过程建议勾选“Add Python 3.6 to PATH”(将Python 3.6添加到PATH环境变量)并选择自定义安装,在设置“Optional Features”界面最好将“pip”、“tcl/tk”、“Python test suite”等项全部勾选上。强烈建议使用自定义的安装路径并保证路径中没有中文。安装完成会看到“Setup was successful”的提示,但是在启动Python环境时可能会因为缺失一些动态链接库文件而导致Python解释器无法运行,常见的问题主要是api-ms-win-crt\*.dll缺失以及更新DirectX之后导致某些动态链接库文件缺失,前者可以参照[《api-ms-win-crt\*.dll缺失原因分析和解决方法》]()一文讲解的方法进行处理或者直接在[微软官网](https://www.microsoft.com/zh-cn/download/details.aspx?id=48145)下载Visual C++ Redistributable for Visual Studio 2015文件进行修复,后者可以下载一个DirectX修复工具进行修复。
#### Linux环境 #### Linux环境
...@@ -53,15 +53,15 @@ yum -y install wget gcc zlib-devel bzip2-devel openssl-devel ncurses-devel sqlit ...@@ -53,15 +53,15 @@ yum -y install wget gcc zlib-devel bzip2-devel openssl-devel ncurses-devel sqlit
下载Python源代码并解压缩到指定目录。 下载Python源代码并解压缩到指定目录。
```Shell ```Shell
wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tar.xz wget https://www.python.org/ftp/python/3.7.0/Python-3.7.1.tar.xz
xz -d Python-3.7.0.tar.xz xz -d Python-3.7.1.tar.xz
tar -xvf Python-3.7.0.tar tar -xvf Python-3.7.1.tar
``` ```
切换至Python源代码目录并执行下面的命令进行配置和安装。 切换至Python源代码目录并执行下面的命令进行配置和安装。
```Shell ```Shell
cd Python-3.7.0 cd Python-3.7.1
./configure --prefix=/usr/local/python37 --enable-optimizations ./configure --prefix=/usr/local/python37 --enable-optimizations
make && make install make && make install
``` ```
...@@ -87,7 +87,7 @@ source .bash_profile ...@@ -87,7 +87,7 @@ source .bash_profile
#### MacOS环境 #### MacOS环境
MacOS也是自带了Python 2.x版本的,可以通过[Python的官方网站](https://www.python.org)提供的安装文件(pkg文件)安装3.x的版本。默认安装完成后,可以通过在终端执行python命令来启动2.x版本的Python解释器,可以通过执行python3命令来启动3.x版本的Python解释器,当然也可以通过重新设置软链接来修改启动Python解释器的命令 MacOS也是自带了Python 2.x版本的,可以通过[Python的官方网站](https://www.python.org)提供的安装文件(pkg文件)安装3.x的版本。默认安装完成后,可以通过在终端执行python命令来启动2.x版本的Python解释器,可以通过执行python3命令来启动3.x版本的Python解释器。
### 从终端运行Python程序 ### 从终端运行Python程序
...@@ -137,7 +137,6 @@ python hello.py ...@@ -137,7 +137,6 @@ python hello.py
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-02-26
""" """
print('hello, world!') print('hello, world!')
...@@ -179,8 +178,6 @@ python -m pip install ipython jupyter ...@@ -179,8 +178,6 @@ python -m pip install ipython jupyter
jupyter notebook jupyter notebook
``` ```
![](./res/python-jupyter-1.png)
![](./res/python-jupyter-2.png) ![](./res/python-jupyter-2.png)
#### Sublime - 文本编辑神器 #### Sublime - 文本编辑神器
...@@ -203,13 +200,13 @@ jupyter notebook ...@@ -203,13 +200,13 @@ jupyter notebook
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') 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')
``` ```
- 安装插件。通过Preference菜单的Package Control或快捷键Ctrl+Shift+P打开命令面板,在面板中输入Install Package就可以找到安装插件的工具,然后再查找需要的插件。我们推荐大家安装以下几个插件 - 安装插件。通过Preference菜单的Package Control或快捷键Ctrl+Shift+P打开命令面板,在面板中输入Install Package就可以找到安装插件的工具,然后再查找需要的插件。我们推荐大家安装以下几个插件
- SublimeCodeIntel - 代码自动补全工具插件 - SublimeCodeIntel - 代码自动补全工具插件
- Emmet - 前端开发代码模板插件 - Emmet - 前端开发代码模板插件
- Git - 版本控制工具插件 - Git - 版本控制工具插件
- Python PEP8 Autoformat - PEP8规范自动格式化插件 - Python PEP8 Autoformat - PEP8规范自动格式化插件
- ConvertToUTF8 - 将本地编码转换为UTF-8 - ConvertToUTF8 - 将本地编码转换为UTF-8
#### PyCharm - Python开发神器 #### PyCharm - Python开发神器
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
#### 指令和程序 #### 指令和程序
计算机的硬件系统通常由五大部件构成,包括:运算器、控制器、存储器、输入设备和输出设备。其中,运算器和控制器放在一起就是我们通常所说的中央处理器,它的功能是执行各种运算和控制指令以及处理计算机软件中的数据。我们通常所说的程序实际上就是指令的集合,我们程序就是将一系列的指令按照某种方式组织到一起,然后通过这些指令去控制计算机做我们想让它做的事情。今天我们使用的计算机虽然器件做工越来越精密,处理能力越来越强大,但究其本质来说仍然属于[“冯·诺依曼结构”](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)科普一下。 计算机的硬件系统通常由五大部件构成,包括:运算器、控制器、存储器、输入设备和输出设备。其中,运算器和控制器放在一起就是我们通常所说的中央处理器,它的功能是执行各种运算和控制指令以及处理计算机软件中的数据。我们通常所说的程序实际上就是指令的集合,我们程序就是将一系列的指令按照某种方式组织到一起,然后通过这些指令去控制计算机做我们想让它做的事情。今天我们使用的计算机虽然器件做工越来越精密,处理能力越来越强大,但究其本质来说仍然属于[“冯·诺依曼结构”](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)科普一下。
### 变量和类型 ### 变量和类型
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
- 浮点型:浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的,浮点数除了数学写法(如`123.456`)之外还支持科学计数法(如`1.23456e2`)。 - 浮点型:浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的,浮点数除了数学写法(如`123.456`)之外还支持科学计数法(如`1.23456e2`)。
- 字符串型:字符串是以单引号或双引号括起来的任意文本,比如`'hello'``"hello"`,字符串还有原始字符串表示法、字节字符串表示法、Unicode字符串表示法,而且可以书写成多行的形式(用三个单引号或三个双引号开头,三个单引号或三个双引号结尾)。 - 字符串型:字符串是以单引号或双引号括起来的任意文本,比如`'hello'``"hello"`,字符串还有原始字符串表示法、字节字符串表示法、Unicode字符串表示法,而且可以书写成多行的形式(用三个单引号或三个双引号开头,三个单引号或三个双引号结尾)。
- 布尔型:布尔值只有`True``False`两种值,要么是`True`,要么是`False`,在Python中,可以直接用`True``False`表示布尔值(请注意大小写),也可以通过布尔运算计算出来(例如`3 < 5`会产生布尔值`True`,而`2 == 1`会产生布尔值`False`)。 - 布尔型:布尔值只有`True``False`两种值,要么是`True`,要么是`False`,在Python中,可以直接用`True``False`表示布尔值(请注意大小写),也可以通过布尔运算计算出来(例如`3 < 5`会产生布尔值`True`,而`2 == 1`会产生布尔值`False`)。
- 复数型:形如`3+5j`,跟数学上的复数表示一样,唯一不同的是虚部的i换成了j - 复数型:形如`3+5j`,跟数学上的复数表示一样,唯一不同的是虚部的`i`换成了`j`
#### 变量命名 #### 变量命名
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
- 受保护的实例属性用单个下划线开头(后面会讲到)。 - 受保护的实例属性用单个下划线开头(后面会讲到)。
- 私有的实例属性用两个下划线开头(后面会讲到)。 - 私有的实例属性用两个下划线开头(后面会讲到)。
当然,作为一个专业的程序员,给变量(事实上应该是所有的标识符)命名做到见名知意也是非常重要的。 当然,作为一个专业的程序员,给变量(事实上应该是所有的标识符)命名做到见名知意也是非常重要的。
#### 变量的使用 #### 变量的使用
...@@ -39,7 +39,6 @@ ...@@ -39,7 +39,6 @@
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-02-27
""" """
a = 321 a = 321
...@@ -62,7 +61,6 @@ print(a ** b) ...@@ -62,7 +61,6 @@ print(a ** b)
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-02-27
""" """
a = int(input('a = ')) a = int(input('a = '))
...@@ -138,7 +136,6 @@ Python支持多种运算符,下表大致按照优先级从高到低的顺序 ...@@ -138,7 +136,6 @@ Python支持多种运算符,下表大致按照优先级从高到低的顺序
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-02-27
""" """
a = 5 a = 5
...@@ -178,7 +175,6 @@ F = 1.8C + 32 ...@@ -178,7 +175,6 @@ F = 1.8C + 32
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-02-27
""" """
f = float(input('请输入华氏温度: ')) f = float(input('请输入华氏温度: '))
...@@ -195,7 +191,6 @@ print('%.1f华氏度 = %.1f摄氏度' % (f, c)) ...@@ -195,7 +191,6 @@ print('%.1f华氏度 = %.1f摄氏度' % (f, c))
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-02-27
""" """
import math import math
...@@ -216,7 +211,6 @@ print('面积: %.2f' % area) ...@@ -216,7 +211,6 @@ print('面积: %.2f' % area)
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-02-27
""" """
year = int(input('请输入年份: ')) year = int(input('请输入年份: '))
......
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-02-28
""" """
username = input('请输入用户名: ') username = input('请输入用户名: ')
...@@ -44,7 +43,6 @@ f(x) = x + 2 (-1 <= x <= 1) ...@@ -44,7 +43,6 @@ f(x) = x + 2 (-1 <= x <= 1)
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-02-28
""" """
x = float(input('x = ')) x = float(input('x = '))
...@@ -68,7 +66,6 @@ f(x) = x + 2 (-1 <= x <= 1) ...@@ -68,7 +66,6 @@ f(x) = x + 2 (-1 <= x <= 1)
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-02-28
""" """
x = float(input('x = ')) x = float(input('x = '))
...@@ -94,7 +91,6 @@ print('f(%.2f) = %.2f' % (x, y)) ...@@ -94,7 +91,6 @@ print('f(%.2f) = %.2f' % (x, y))
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-02-28
""" """
value = float(input('请输入长度: ')) value = float(input('请输入长度: '))
...@@ -115,7 +111,6 @@ else: ...@@ -115,7 +111,6 @@ else:
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-02-28
""" """
from random import randint from random import randint
...@@ -150,7 +145,6 @@ print(result) ...@@ -150,7 +145,6 @@ print(result)
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-02-28
""" """
score = float(input('请输入成绩: ')) score = float(input('请输入成绩: '))
...@@ -175,7 +169,6 @@ print('对应的等级是:', grade) ...@@ -175,7 +169,6 @@ print('对应的等级是:', grade)
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-02-28
""" """
import math import math
...@@ -193,7 +186,7 @@ else: ...@@ -193,7 +186,7 @@ else:
``` ```
> **说明:**上面的代码中使用了`math`模块的`sqrt`函数来计算平方根。用边长计算三角形面积的公式叫做[海伦公式](https://zh.wikipedia.org/zh-hans/海伦公式)。 > **说明:**上面的代码中使用了`math`模块的`sqrt`函数来计算平方根。用边长计算三角形面积的公式叫做[海伦公式](https://zh.wikipedia.org/zh-hans/海伦公式)。
#### 练习5:实现一个个人所得税计算器。 #### 练习5:个人所得税计算器。
```Python ```Python
""" """
...@@ -201,7 +194,6 @@ else: ...@@ -201,7 +194,6 @@ else:
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-02-28
""" """
salary = float(input('本月收入: ')) salary = float(input('本月收入: '))
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
### 循环结构的应用场景 ### 循环结构的应用场景
如果在程序中我们需要重复的执行某条或某些指令,例如用程序控制机器人踢足球,如果机器人持球而且还没有进入射门范围,那么我们就要一直发出让机器人向球门方向奔跑的指令。当然你可能已经注意到了,刚才的描述中其实不仅仅有需要重复的动作,还有我们上一个章节讲到的分支结构。再举一个简单的例子,比如在我们的程序中要实现每隔1秒中在屏幕上打印一个&quot;hello, world&quot;这样的字符串并持续一个小时,我们肯定不能够将`print('hello, world')`这句代码写上3600遍,如果真的需要这样做那么我们的工作就太无聊了。因此,我们需要循环结构,使用循环结构我们就可以轻松的控制某件事或者某些事重复、重复、再重复的发生。在Python中构造循环结构有两种做法,一种是`for-in`循环,一种是`while`循环。 如果在程序中我们需要重复的执行某条或某些指令,例如用程序控制机器人踢足球,如果机器人持球而且还没有进入射门范围,那么我们就要一直发出让机器人向球门方向奔跑的指令。当然你可能已经注意到了,刚才的描述中其实不仅仅有需要重复的动作,还有我们上一个章节讲到的分支结构。再举一个简单的例子,比如在我们的程序中要实现每隔1秒中在屏幕上打印一个&quot;hello, world&quot;这样的字符串并持续一个小时,我们肯定不能够将`print('hello, world')`这句代码写上3600遍,如果真的需要这样做那么编程的工作就太无聊了。因此,我们需要了解一下循环结构,有了循环结构我们就可以轻松的控制某件事或者某些事重复、重复、再重复的发生。在Python中构造循环结构有两种做法,一种是`for-in`循环,一种是`while`循环。
### for-in循环 ### for-in循环
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-03-01
""" """
sum = 0 sum = 0
...@@ -37,7 +36,6 @@ print(sum) ...@@ -37,7 +36,6 @@ print(sum)
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-03-01
""" """
sum = 0 sum = 0
...@@ -54,7 +52,6 @@ print(sum) ...@@ -54,7 +52,6 @@ print(sum)
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-03-01
""" """
sum = 0 sum = 0
...@@ -77,7 +74,6 @@ print(sum) ...@@ -77,7 +74,6 @@ print(sum)
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-03-01
""" """
import random import random
...@@ -109,8 +105,6 @@ if counter > 7: ...@@ -109,8 +105,6 @@ if counter > 7:
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-03-01
""" """
for i in range(1, 10): for i in range(1, 10):
...@@ -196,7 +190,6 @@ for factor in range(x, 0, -1): ...@@ -196,7 +190,6 @@ for factor in range(x, 0, -1):
Version: 0.1 Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-03-01
""" """
row = int(input('请输入行数: ')) row = int(input('请输入行数: '))
......
...@@ -27,7 +27,6 @@ fmn = 1 ...@@ -27,7 +27,6 @@ fmn = 1
for num in range(1, m - n + 1): for num in range(1, m - n + 1):
fmn *= num fmn *= num
print(fm // fn // fmn) print(fm // fn // fmn)
``` ```
### 函数的作用 ### 函数的作用
...@@ -134,7 +133,8 @@ def foo(): ...@@ -134,7 +133,8 @@ def foo():
print('goodbye, world!') print('goodbye, world!')
foo() # 输出goodbye, world! # 下面的代码会输出什么呢?
foo()
``` ```
当然上面的这种情况我们很容易就能避免,但是如果项目是由多人协作进行团队开发的时候,团队中可能有多个程序员都定义了名为`foo`的函数,那么怎么解决这种命名冲突呢?答案其实很简单,Python中每个文件就代表了一个模块(module),我们在不同的模块中可以有同名的函数,在使用函数的时候我们通过`import`关键字导入指定的模块就可以区分到底要使用的是哪个模块中的`foo`函数,代码如下所示。 当然上面的这种情况我们很容易就能避免,但是如果项目是由多人协作进行团队开发的时候,团队中可能有多个程序员都定义了名为`foo`的函数,那么怎么解决这种命名冲突呢?答案其实很简单,Python中每个文件就代表了一个模块(module),我们在不同的模块中可以有同名的函数,在使用函数的时候我们通过`import`关键字导入指定的模块就可以区分到底要使用的是哪个模块中的`foo`函数,代码如下所示。
...@@ -158,11 +158,13 @@ test.py ...@@ -158,11 +158,13 @@ test.py
```Python ```Python
from module1 import foo from module1 import foo
foo() # 输出hello, world! # 输出hello, world!
foo()
from module2 import foo from module2 import foo
foo() # 输出goodbye, world! # 输出goodbye, world!
foo()
``` ```
也可以按照如下所示的方式来区分到底要使用哪一个`foo`函数。 也可以按照如下所示的方式来区分到底要使用哪一个`foo`函数。
...@@ -185,7 +187,8 @@ test.py ...@@ -185,7 +187,8 @@ test.py
from module1 import foo from module1 import foo
from module2 import foo from module2 import foo
foo() # 输出goodbye, world! # 输出goodbye, world!
foo()
``` ```
test.py test.py
...@@ -194,7 +197,8 @@ test.py ...@@ -194,7 +197,8 @@ test.py
from module2 import foo from module2 import foo
from module1 import foo from module1 import foo
foo() # 输出hello, world! # 输出hello, world!
foo()
``` ```
需要说明的是,如果我们导入的模块除了定义函数之外还中有可以执行代码,那么Python解释器在导入这个模块时就会执行这些代码,事实上我们可能并不希望如此,因此如果我们在模块中编写了执行代码,最好是将这些执行代码放入如下所示的条件中,这样的话除非直接运行该模块,if条件下的这些代码是不会执行的,因为只有直接执行的模块的名字才是“\_\_main\_\_”。 需要说明的是,如果我们导入的模块除了定义函数之外还中有可以执行代码,那么Python解释器在导入这个模块时就会执行这些代码,事实上我们可能并不希望如此,因此如果我们在模块中编写了执行代码,最好是将这些执行代码放入如下所示的条件中,这样的话除非直接运行该模块,if条件下的这些代码是不会执行的,因为只有直接执行的模块的名字才是“\_\_main\_\_”。
......
...@@ -42,7 +42,6 @@ def main(): ...@@ -42,7 +42,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
下面是运行程序得到的一次运行结果。 下面是运行程序得到的一次运行结果。
...@@ -86,7 +85,6 @@ def main(): ...@@ -86,7 +85,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
在上面的代码中,我们通过`Process`类创建了进程对象,通过`target`参数我们传入一个函数来表示进程启动后要执行的代码,后面的`args`是一个元组,它代表了传递给函数的参数。`Process`对象的`start`方法用来启动进程,而`join`方法表示等待进程执行结束。运行上面的代码可以明显发现两个下载任务“同时”启动了,而且程序的执行时间将大大缩短,不再是两个任务的时间总和。下面是程序的一次执行结果。 在上面的代码中,我们通过`Process`类创建了进程对象,通过`target`参数我们传入一个函数来表示进程启动后要执行的代码,后面的`args`是一个元组,它代表了传递给函数的参数。`Process`对象的`start`方法用来启动进程,而`join`方法表示等待进程执行结束。运行上面的代码可以明显发现两个下载任务“同时”启动了,而且程序的执行时间将大大缩短,不再是两个任务的时间总和。下面是程序的一次执行结果。
...@@ -375,7 +373,6 @@ def main(): ...@@ -375,7 +373,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
如果使用多线程将耗时间的任务放到一个独立的线程中执行,这样就不会因为执行耗时间的任务而阻塞了主线程,修改后的代码如下所示。 如果使用多线程将耗时间的任务放到一个独立的线程中执行,这样就不会因为执行耗时间的任务而阻塞了主线程,修改后的代码如下所示。
...@@ -424,7 +421,6 @@ def main(): ...@@ -424,7 +421,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
#### 例子2:使用多进程对复杂任务进行“分而治之”。 #### 例子2:使用多进程对复杂任务进行“分而治之”。
...@@ -448,7 +444,6 @@ def main(): ...@@ -448,7 +444,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
在上面的代码中,我故意先去创建了一个列表容器然后填入了100000000个数,这一步其实是比较耗时间的,所以为了公平起见,当我们将这个任务分解到8个进程中去执行的时候,我们暂时也不考虑列表切片操作花费的时间,只是把做运算和合并运算结果的时间统计出来,代码如下所示。 在上面的代码中,我故意先去创建了一个列表容器然后填入了100000000个数,这一步其实是比较耗时间的,所以为了公平起见,当我们将这个任务分解到8个进程中去执行的时候,我们暂时也不考虑列表切片操作花费的时间,只是把做运算和合并运算结果的时间统计出来,代码如下所示。
...@@ -493,7 +488,6 @@ def main(): ...@@ -493,7 +488,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
比较两段代码的执行结果(在我目前使用的MacBook上,上面的代码需要大概6秒左右的时间,而下面的代码只需要不到1秒的时间,再强调一次我们只是比较了运算的时间,不考虑列表创建及切片操作花费的时间),使用多进程后由于获得了更多的CPU执行时间以及更好的利用了CPU的多核特性,明显的减少了程序的执行时间,而且计算量越大效果越明显。当然,如果愿意还可以将多个进程部署在不同的计算机上,做成分布式进程,具体的做法就是通过multiprocessing.managers模块中提供的管理器将`Queue`对象通过网络共享出来(注册到网络上让其他计算机可以访问),这部分内容也留到爬虫的专题再进行讲解。 比较两段代码的执行结果(在我目前使用的MacBook上,上面的代码需要大概6秒左右的时间,而下面的代码只需要不到1秒的时间,再强调一次我们只是比较了运算的时间,不考虑列表创建及切片操作花费的时间),使用多进程后由于获得了更多的CPU执行时间以及更好的利用了CPU的多核特性,明显的减少了程序的执行时间,而且计算量越大效果越明显。当然,如果愿意还可以将多个进程部署在不同的计算机上,做成分布式进程,具体的做法就是通过multiprocessing.managers模块中提供的管理器将`Queue`对象通过网络共享出来(注册到网络上让其他计算机可以访问),这部分内容也留到爬虫的专题再进行讲解。
\ No newline at end of file
...@@ -126,7 +126,6 @@ def main(): ...@@ -126,7 +126,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
### 基于传输层协议的套接字编程 ### 基于传输层协议的套接字编程
...@@ -174,7 +173,6 @@ def main(): ...@@ -174,7 +173,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
运行服务器程序后我们可以通过Windows系统的telnet来访问该服务器,结果如下图所示。 运行服务器程序后我们可以通过Windows系统的telnet来访问该服务器,结果如下图所示。
...@@ -203,7 +201,6 @@ def main(): ...@@ -203,7 +201,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
需要注意的是,上面的服务器并没有使用多线程或者异步I/O的处理方式,这也就意味着当服务器与一个客户端处于通信状态时,其他的客户端只能排队等待。很显然,这样的服务器并不能满足我们的需求,我们需要的服务器是能够同时接纳和处理多个用户请求的。下面我们来设计一个使用多线程技术处理多个用户请求的服务器,该服务器会向连接到服务器的客户端发送一张图片。 需要注意的是,上面的服务器并没有使用多线程或者异步I/O的处理方式,这也就意味着当服务器与一个客户端处于通信状态时,其他的客户端只能排队等待。很显然,这样的服务器并不能满足我们的需求,我们需要的服务器是能够同时接纳和处理多个用户请求的。下面我们来设计一个使用多线程技术处理多个用户请求的服务器,该服务器会向连接到服务器的客户端发送一张图片。
...@@ -256,7 +253,6 @@ def main(): ...@@ -256,7 +253,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
客户端代码: 客户端代码:
...@@ -291,7 +287,6 @@ def main(): ...@@ -291,7 +287,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
在这个案例中,我们使用了JSON作为数据传输的格式(通过JSON格式对传输的数据进行了序列化和反序列化的操作),但是JSON并不能携带二进制数据,因此对图片的二进制数据进行了Base64编码的处理。Base64是一种用64个字符表示所有二进制数据的编码方式,通过将二进制数据每6位一组的方式重新组织,刚好可以使用0~9的数字、大小写字母以及“+”和“/”总共64个字符表示从`000000``111111`的64种状态。[维基百科](https://zh.wikipedia.org/wiki/Base64)上有关于Base64编码的详细讲解,不熟悉Base64的读者可以自行阅读。 在这个案例中,我们使用了JSON作为数据传输的格式(通过JSON格式对传输的数据进行了序列化和反序列化的操作),但是JSON并不能携带二进制数据,因此对图片的二进制数据进行了Base64编码的处理。Base64是一种用64个字符表示所有二进制数据的编码方式,通过将二进制数据每6位一组的方式重新组织,刚好可以使用0~9的数字、大小写字母以及“+”和“/”总共64个字符表示从`000000``111111`的64种状态。[维基百科](https://zh.wikipedia.org/wiki/Base64)上有关于Base64编码的详细讲解,不熟悉Base64的读者可以自行阅读。
......
...@@ -31,7 +31,6 @@ def main(): ...@@ -31,7 +31,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
如果要发送带有附件的邮件,那么可以按照下面的方式进行操作。 如果要发送带有附件的邮件,那么可以按照下面的方式进行操作。
...@@ -88,7 +87,6 @@ def main(): ...@@ -88,7 +87,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
### 发送短信 ### 发送短信
...@@ -119,7 +117,5 @@ def main(): ...@@ -119,7 +117,5 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
## Python语言进阶 ## Python语言进程
### 数据结构和算法 1. 数据结构和算法
- 排序算法(冒泡和归并)和查找算法(顺序和折半)
#### 排序算法(冒泡和归并)
```Python
```Python def bubble_sort(origin_items, comp=lambda x, y: x > y):
def bubble_sort(items, comp=lambda x, y: x > y): """高质量冒泡排序(搅拌排序)"""
"""高质量冒泡排序(搅拌排序)""" items = origin_items[:]
for i in range(len(items) - 1): for i in range(len(items) - 1):
swapped = False swapped = False
for j in range(len(items) - 1 - i): for j in range(len(items) - 1 - i):
if comp(items[j], items[j + 1]): if comp(items[j], items[j + 1]):
items[j], items[j + 1] = items[j + 1], items[j] items[j], items[j + 1] = items[j + 1], items[j]
swapped = True swapped = True
if swapped: if swapped:
swapped = False swapped = False
for j in range(len(items) - 2 - i, i, -1): for j in range(len(items) - 2 - i, i, -1):
if comp(items[j - 1], items[j]): if comp(items[j - 1], items[j]):
items[j], items[j - 1] = items[j - 1], items[j] items[j], items[j - 1] = items[j - 1], items[j]
swapped = True swapped = True
if not swapped: if not swapped:
break break
``` return items
```
```Python
def merge_sort(items, comp=lambda x, y: x <= y): ```Python
"""归并排序(分治法)""" def merge_sort(items, comp=lambda x, y: x <= y):
if len(items) < 2: """归并排序(分治法)"""
return items[:] if len(items) < 2:
mid = len(items) // 2 return items[:]
left = merge_sort(items[:mid], comp) mid = len(items) // 2
right = merge_sort(items[mid:], comp) left = merge_sort(items[:mid], comp)
return merge(left, right, comp) right = merge_sort(items[mid:], comp)
return merge(left, right, comp)
def merge(items1, items2, comp=lambda x, y: x <= y):
"""合并(将两个有序的列表合并成一个有序的列表)""" def merge(items1, items2, comp):
items = [] """合并(将两个有序的列表合并成一个有序的列表)"""
idx1, idx2 = 0, 0 items = []
while idx1 < len(items1) and idx2 < len(items2): idx1, idx2 = 0, 0
if comp(items1[idx1], items2[idx2]): while idx1 < len(items1) and idx2 < len(items2):
items.append(items1[idx1]) if comp(items1[idx1], items2[idx2]):
idx1 += 1 items.append(items1[idx1])
else: idx1 += 1
items.append(items2[idx2]) else:
idx2 += 1 items.append(items2[idx2])
items += items1[idx1:] idx2 += 1
items += items2[idx2:] items += items1[idx1:]
return items items += items2[idx2:]
``` return items
```
```Python
def seq_search(items, key): ```Python
"""顺序查找""" def seq_search(items, key):
for index, item in enumerate(items): """顺序查找"""
if item == key: for index, item in enumerate(items):
return index if item == key:
return -1 return index
``` return -1
#### 查找算法(顺序和折半) ```
```Python ```Python
def bin_search(items, key): def bin_search(items, key):
"""折半查找(循环实现)""" """折半查找"""
start, end = 0, len(items) - 1 start, end = 0, len(items) - 1
while start <= end: while start <= end:
mid = (start + end) // 2 mid = (start + end) // 2
if key > items[mid]: if key > items[mid]:
start = mid + 1 start = mid + 1
elif key < items[mid]: elif key < items[mid]:
end = mid - 1 end = mid - 1
else: else:
return mid return mid
return -1 return -1
``` ```
#### 使用生成式(推导式) - 使用生成式(推导式)语法
```Python ```Python
prices = { prices = {
'AAPL': 191.88, 'AAPL': 191.88,
'GOOG': 1186.96, 'GOOG': 1186.96,
'IBM': 149.24, 'IBM': 149.24,
'ORCL': 48.44, 'ORCL': 48.44,
'ACN': 166.89, 'ACN': 166.89,
'FB': 208.09, 'FB': 208.09,
'SYMC': 21.29 'SYMC': 21.29
} }
# 用股票价格大于100元的股票构造一个新的字典 # 用股票价格大于100元的股票构造一个新的字典
prices2 = {key: value for key, value in prices.items() if value > 100} prices2 = {key: value for key, value in prices.items() if value > 100}
print(prices2) print(prices2)
``` ```
#### 嵌套的列表 - 嵌套的列表
```Python ```Python
def main(): names = ['关羽', '张飞', '赵云', '马超', '黄忠']
names = ['关羽', '张飞', '赵云', '马超', '黄忠'] courses = ['语文', '数学', '英语']
courses = ['语文', '数学', '英语'] # 录入五个学生三门课程的成绩
# 录入五个学生三门课程的成绩 # 错误 - 参考http://pythontutor.com/visualize.html#mode=edit
# 错误 - 参考http://pythontutor.com/visualize.html#mode=edit # scores = [[None] * len(courses)] * len(names)
# scores = [[None] * len(courses)] * len(names) scores = [[None] * len(courses) for _ in range(len(names))]
scores = [[None] * len(courses) for _ in range(len(names))] for row, name in enumerate(names):
for row, name in enumerate(names): for col, course in enumerate(courses):
for col, course in enumerate(courses): scores[row][col] = float(input(f'请输入{name}的{course}成绩: '))
scores[row][col] = float(input(f'请输入{name}的{course}成绩: ')) print(scores)
print(scores) ```
[Python Tutor](http://pythontutor.com/) - VISUALIZE CODE AND GET LIVE HELP
if __name__ == '__main__':
main() - heapq、itertools等的用法
``` ```Python
"""
[Python Tutor](http://pythontutor.com/) - VISUALIZE CODE AND GET LIVE HELP 从列表中找出最大的或最小的N个元素
"""
#### heapq、itertools等的用法 import heapq
```Python list1 = [34, 25, 12, 99, 87, 63, 58, 78, 88, 92]
""" list2 = [
从列表中找出最大的或最小的N个元素 {'name': 'IBM', 'shares': 100, 'price': 91.1},
""" {'name': 'AAPL', 'shares': 50, 'price': 543.22},
import heapq {'name': 'FB', 'shares': 200, 'price': 21.09},
{'name': 'HPQ', 'shares': 35, 'price': 31.75},
{'name': 'YHOO', 'shares': 45, 'price': 16.35},
def main(): {'name': 'ACME', 'shares': 75, 'price': 115.65}
list1 = [34, 25, 12, 99, 87, 63, 58, 78, 88, 92] ]
list2 = [ print(heapq.nlargest(3, list1))
{'name': 'IBM', 'shares': 100, 'price': 91.1}, print(heapq.nsmallest(3, list1))
{'name': 'AAPL', 'shares': 50, 'price': 543.22}, print(heapq.nlargest(2, list2, key=lambda x: x['price']))
{'name': 'FB', 'shares': 200, 'price': 21.09}, print(heapq.nlargest(2, list2, key=lambda x: x['shares']))
{'name': 'HPQ', 'shares': 35, 'price': 31.75}, ```
{'name': 'YHOO', 'shares': 45, 'price': 16.35},
{'name': 'ACME', 'shares': 75, 'price': 115.65} ```Python
] """
print(heapq.nlargest(3, list1)) 排列 / 组合 / 笛卡尔积
print(heapq.nsmallest(3, list1)) """
print(heapq.nlargest(2, list2, key=lambda x: x['price'])) import itertools
print(heapq.nlargest(2, list2, key=lambda x: x['shares']))
for val in itertools.permutations('ABCD'):
print(val)
if __name__ == '__main__':
main() for val in itertools.combinations('ABCDE', 3):
``` print(val)
```Python for val in itertools.product('ABCD', '123'):
""" print(val)
排列 / 组合 / 笛卡尔积 ```
"""
import itertools - collections模块下的工具类
```Python
def main(): """
for val in itertools.permutations('ABCD'): 找出序列中出现次数最多的元素
print(val) """
print('-' * 50) from collections import Counter
for val in itertools.combinations('ABCDE', 3):
print(val) words = [
print('-' * 50) 'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
for val in itertools.product('ABCD', '123'): 'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around',
print(val) 'the', 'eyes', "don't", 'look', 'around', 'the', 'eyes',
'look', 'into', 'my', 'eyes', "you're", 'under'
]
if __name__ == '__main__': counter = Counter(words)
main() print(counter.most_common(3))
``` ```
#### collections模块下的工具类 - 穷举法、贪婪法、分治法、回溯法、动态规划
```Python 例子:百钱百鸡和五人分鱼。
"""
找出序列中出现次数最多的元素 ```Python
""" """
from collections import Counter 穷举法 - 穷尽所有可能直到找到正确答案
"""
def main(): # 公鸡5元一只 母鸡3元一只 小鸡1元三只
words = [ # 用100元买100只鸡 问公鸡/母鸡/小鸡各多少只
'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes', for x in range(20):
'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', for y in range(33):
'the', 'eyes', "don't", 'look', 'around', 'the', 'eyes', z = 100 - x - y
'look', 'into', 'my', 'eyes', "you're", 'under' if 5 * x + 3 * y + z // 3 == 100 and z % 3 == 0:
] print(x, y, z)
counter = Counter(words)
print(counter.most_common(3)) # A、B、C、D、E五人在某天夜里合伙捕鱼 最后疲惫不堪各自睡觉
# 第二天A第一个醒来 他将鱼分为5份 扔掉多余的1条 拿走自己的一份
# B第二个醒来 也将鱼分为5份 扔掉多余的1条 拿走自己的一份
if __name__ == '__main__': # 然后C、D、E依次醒来也按同样的方式分鱼 问他们至少捕了多少条鱼
main() fish = 1
``` while True:
total = fish
#### 穷举法、贪婪法、分治法、动态规划 enough = True
for _ in range(5):
```Python if (total - 1) % 5 == 0:
""" total = (total - 1) // 5 * 4
穷举法 - 穷尽所有可能直到找到正确答案 else:
""" enough = False
break
if enough:
def main(): print(fish)
# 公鸡5元一只 母鸡3元一只 小鸡1元三只 break
# 用100元买100只鸡 问公鸡/母鸡/小鸡各多少只 fish += 1
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) ```Python
# A、B、C、D、E五人在某天夜里合伙捕鱼 最后疲惫不堪各自睡觉 """
# 第二天A第一个醒来 他将鱼分为5份 扔掉多余的1条 拿走自己的一份 动态规划 - 适用于有重叠子问题和最优子结构性质的问题
# B第二个醒来 也将鱼分为5份 扔掉多余的1条 拿走自己的一份 使用动态规划方法所耗时间往往远少于朴素解法(用空间换取时间)
# 然后C、D、E依次醒来也按同样的方式分鱼 问他们至少捕了多少条鱼 """
fish = 1
while True:
total = fish def fib(num, temp={}):
enough = True """用递归计算Fibonacci数"""
for _ in range(5): if num in (1, 2):
if (total - 1) % 5 == 0: return 1
total = (total - 1) // 5 * 4 try:
else: return temp[num]
enough = False except KeyError:
break temp[num] = fib(num - 1) + fib(num - 2)
if enough: return temp[num]
print(fish) ```
break
fish += 1 2. 函数的使用方式
- 将函数视为“一等公民”
if __name__ == '__main__':
main() - 函数可以赋值给变量
``` - 函数可以作为函数的参数
- 函数可以作为函数的返回值
```Python
""" - 高阶函数的用法(`filter``map`以及它们的替代品)
动态规划 - 适用于有重叠子问题和最优子结构性质的问题
使用动态规划方法所耗时间往往远少于朴素解法(用空间换取时间) ```Python
""" items1 = list(map(lambda x: x ** 2, filter(lambda x: x % 2, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
items2 = [x ** 2 for x in range(1, 10) if x % 2]
```
def fib(num, temp={}):
"""用递归计算Fibonacci数""" - 位置参数、可变参数、关键字参数、命名关键字参数
if num in (1, 2):
return 1 - 参数的元信息(代码可读性问题)
try:
return temp[num] - 匿名函数和内联函数的用法(`lambda`函数)
except KeyError:
temp[num] = fib(num - 1) + fib(num - 2) - 闭包和作用域问题
return temp[num]
``` - Python搜索变量的LEGB顺序(Local --> Embedded --> Global --> Built-in)
### 函数的使用方式 - `global`和`nonlocal`关键字的作用
- 将函数视为“一等公民” `global`:声明使用全局变量,如果不存在就把局部变量放到全局作用域。
- 高阶函数的用法(filter、map以及它们的替代品) `nonlocal`:声明使用嵌套作用域的变量(嵌套作用域必须存在该变量)。
- 位置参数、可变参数、关键字参数、命名关键字参数 - 装饰器函数(使用装饰器和取消装饰器)
- 参数的元信息(代码可读性问题) 例子:输出函数执行时间的装饰器。
- 匿名函数和内联函数的用法(lambda函数) ```Python
def record_time(func):
- 闭包和作用域问题(LEGB) """自定义装饰函数的装饰器"""
- 装饰器函数(使用装饰器和取消装饰器) @wraps(func)
def wrapper(*args, **kwargs):
输出函数执行时间的装饰器。 start = time()
result = func(*args, **kwargs)
```Python print(f'{func.__name__}: {time() - start}秒')
from functools import wraps return result
from time import time
return wrapper
```
def record(output):
如果装饰器不希望跟`print`函数耦合,可以编写带参数的装饰器。
def decorate(func):
```Python
@wraps(func) from functools import wraps
def wrapper(*args, **kwargs): from time import time
start = time()
result = func(*args, **kwargs)
output(func.__name__, time() - start) def record(output):
return result """自定义带参数的装饰器"""
return wrapper def decorate(func):
return decorate @wraps(func)
``` def wrapper(*args, **kwargs):
start = time()
```Python result = func(*args, **kwargs)
from functools import wraps output(func.__name__, time() - start)
from time import time return result
return wrapper
class Record(object):
return decorate
def __init__(self, output): ```
self.output = output
```Python
def __call__(self, func): from functools import wraps
from time import time
@wraps(func)
def wrapper(*args, **kwargs):
start = time() class Record(object):
result = func(*args, **kwargs) """自定义装饰器类(通过__call__魔术方法使得对象可以当成函数调用)"""
self.output(func.__name__, time() - start)
return result def __init__(self, output):
self.output = output
return wrapper
``` def __call__(self, func):
用装饰器来实现单例模式。 @wraps(func)
def wrapper(*args, **kwargs):
```Python start = time()
from functools import wraps result = func(*args, **kwargs)
self.output(func.__name__, time() - start)
return result
def singleton(cls):
instances = {} return wrapper
```
@wraps(cls)
def wrapper(*args, **kwargs): > 说明:由于对带装饰功能的函数添加了@wraps装饰器,可以通过`func.__wrapped__`方式获得被装饰之前的函数或类来取消装饰器的作用。
if cls not in instances:
instances[cls] = cls(*args, **kwargs) 例子:用装饰器来实现单例模式。
return instances[cls]
```Python
return wrapper from functools import wraps
@singleton def singleton(cls):
class Singleton(object): """装饰类的装饰器"""
pass instances = {}
```
@wraps(cls)
### 面向对象相关知识 def wrapper(*args, **kwargs):
if cls not in instances:
- 三大支柱:封装、继承、多态 instances[cls] = cls(*args, **kwargs)
return instances[cls]
```Python
""" return wrapper
月薪结算系统
部门经理每月15000 程序员每小时200 销售员1800底薪+销售额5%提成
""" @singleton
from abc import ABCMeta, abstractmethod class President(object):
"""总统(单例类)"""
pass
class Employee(metaclass=ABCMeta): ```
"""员工(抽象类)"""
> 说明:上面的代码中用到了闭包(closure),不知道你是否已经意识到了。还没有一个小问题就是,上面的代码并没有实现线程安全的单例,如果要实现线程安全的单例应该怎么做呢?
def __init__(self, name):
self._name = name ```Python
from functools import wraps
@property
def name(self):
"""姓名""" def singleton(cls):
return self._name """线程安全的单例装饰器"""
instances = {}
@abstractmethod locker = Lock()
def get_salary(self):
"""结算月薪(抽象方法)""" @wraps(cls)
pass def wrapper(*args, **kwargs):
if cls not in instances:
with locker:
class Manager(Employee): if cls not in instances:
"""部门经理""" instances[cls] = cls(*args, **kwargs)
return instances[cls]
def get_salary(self):
return 15000.0 return wrapper
```
class Programmer(Employee): 3. 面向对象相关知识
"""程序员"""
- 三大支柱:封装、继承、多态
def __init__(self, name):
self._working_hour = 0 例子:工资结算系统。
super().__init__(name)
```Python
@property """
def working_hour(self): 月薪结算系统 - 部门经理每月15000 程序员每小时200 销售员1800底薪加销售额5%提成
"""工作时间""" """
return self._working_hour from abc import ABCMeta, abstractmethod
@working_hour.setter
def working_hour(self, hour): class Employee(metaclass=ABCMeta):
self._working_hour = hour if hour > 0 else 0 """员工(抽象类)"""
def get_salary(self): def __init__(self, name):
return 200.0 * self.working_hour self._name = name
@property
class Salesman(Employee): def name(self):
"""销售员""" """姓名"""
return self._name
def __init__(self, name):
self._sales = 0.0 @abstractmethod
super().__init__(name) def get_salary(self):
"""结算月薪(抽象方法)"""
@property pass
def sales(self):
return self._sales
class Manager(Employee):
@sales.setter """部门经理"""
def sales(self, sales):
self._sales = sales if sales > 0 else 0 def get_salary(self):
return 15000.0
def get_salary(self):
return 1800.0 + self.sales * 0.05
class Programmer(Employee):
"""程序员"""
def main():
emps = [ def __init__(self, name):
Manager('刘备'), Manager('曹操'), Programmer('许褚'), self._working_hour = 0
Salesman('貂蝉'), Salesman('赵云'), Programmer('张辽'), super().__init__(name)
Programmer('关羽'), Programmer('周瑜')
] @property
for emp in emps: def working_hour(self):
if isinstance(emp, Programmer): """工作时间"""
emp.working_hour = int(input('本月工作时间: ')) return self._working_hour
elif isinstance(emp, Salesman):
emp.sales = float(input('本月销售额: ')) @working_hour.setter
print('%s: %.2f元' % (emp.name, emp.get_salary())) def working_hour(self, hour):
self._working_hour = hour if hour > 0 else 0
if __name__ == '__main__': def get_salary(self):
main() return 200.0 * self.working_hour
```
- 对象的复制(深复制/深拷贝/深度克隆和浅复制/浅拷贝/影子克隆) class Salesman(Employee):
"""销售员"""
- 垃圾回收、循环引用和弱引用
def __init__(self, name):
Python使用了自动化内存管理,这种管理机制以**引用计数**为基础,同时也引入了**标记-清除**和**分代收集**两种机制为辅的策略。 self._sales = 0.0
super().__init__(name)
```C
typedef struct_object { @property
/* 引用计数 */ def sales(self):
int ob_refcnt; return self._sales
/* 对象指针 */
struct_typeobject *ob_type; @sales.setter
} PyObject; def sales(self, sales):
``` self._sales = sales if sales > 0 else 0
```C def get_salary(self):
/* 增加引用计数的宏定义 */ return 1800.0 + self.sales * 0.05
#define Py_INCREF(op) ((op)->ob_refcnt++)
/* 减少引用计数的宏定义 */
#define Py_DECREF(op) \ //减少计数 class EmployeeFactory():
if (--(op)->ob_refcnt != 0) \ """创建员工的工厂(工厂模式 - 通过工厂实现对象使用者和对象之间的解耦合)"""
; \
else \ @staticmethod
__Py_Dealloc((PyObject *)(op)) def create(emp_type, *args):
``` """创建员工"""
emp_type = emp_type.upper()
导致引用计数+1的情况: emp = None
if emp_type == 'M':
- 对象被创建,例如`a = 23` emp = Manager(*args)
- 对象被引用,例如`b = a` elif emp_type == 'P':
- 对象被作为参数,传入到一个函数中,例如`f(a)` emp = Programmer(*args)
- 对象作为一个元素,存储在容器中,例如`list1 = [a, a]` elif emp_type == 'S':
emp = Salesman(*args)
导致引用计数-1的情况: return emp
- 对象的别名被显式销毁,例如`del a`
- 对象的别名被赋予新的对象,例如`a = 24` def main():
- 一个对象离开它的作用域,例如f函数执行完毕时,f函数中的局部变量(全局变量不会) """主函数"""
- 对象所在的容器被销毁,或从容器中删除对象 emps = [
EmployeeFactory.create('M', '曹操'), EmployeeFactory.create('P', '荀彧'),
引用计数可能会导致循环引用问题,而循环引用会导致内存泄露,如下面的代码所示。为了解决这个问题,Python中引入了“标记-清除”和“分代收集”。在创建一个对象的时候,对象被放在第一代中,如果在第一代的垃圾检查中对象存活了下来,该对象就会被放到第二代中,同理在第二代的垃圾检查中对象存活下来,该对象就会被放到第三代中。 EmployeeFactory.create('P', '郭嘉'), EmployeeFactory.create('S', '典韦')
]
```Python for emp in emps:
list1 = [] # 用isinstance函数识别对象引用所引用对象的类型
list2 = [] if isinstance(emp, Programmer):
list1.append(list2) emp.working_hour = int(input('本月工作时间: '))
list2.append(list1) elif isinstance(emp, Salesman):
``` emp.sales = float(input('本月销售额: '))
print('%s: %.2f元' % (emp.name, emp.get_salary()))
以下情况会导致垃圾回收:
- 调用`gc.collect()` if __name__ == '__main__':
- gc模块的计数器达到阀值 main()
- 程序退出 ```
如果循环引用中两个对象都定义了`__del__`方法,gc模块不会销毁这些不可达对象,因为gc模块不知道应该先调用哪个对象的`__del__`方法,这个问题在Python 3.6中得到了解决。 - 类与类之间的关系
也可以通过`weakref`模块构造弱引用的方式来解决循环引用的问题。 - is-a关系:继承
- has-a关系:关联 / 聚合 / 合成
- 魔法属性和方法(请参考《Python魔法方法指南》) - use-a关系:依赖
有几个小问题请大家思考: 例子:扑克游戏。
- 自定义的对象能不能使用运算符做运算? ```Python
- 自定义的对象能不能放到set中?能去重吗? """
- 自定义的对象能不能作为dict的键? 经验:符号常量总是优于字面常量,枚举类型是定义符号常量的最佳选择
- 自定义的对象能不能使用上下文语法? """
from enum import Enum, unique
- 混入(Mixin)
import random
```Python
"""
限制字典只有在指定的key不存在时才能设置键值对 @unique
MRO - Method Resolution Order - 多重继承时的方法解析顺序 class Suite(Enum):
""" """花色"""
SPADE, HEART, CLUB, DIAMOND = range(4)
class SetOnceMappingMixin:
__slots__ = () def __lt__(self, other):
return self.value < other.value
def __setitem__(self, key, value):
if key in self:
raise KeyError(str(key) + ' already set') class Card(object):
return super().__setitem__(key, value) """牌"""
def __init__(self, suite, face):
class SetOnceDict(SetOnceMappingMixin, dict): """初始化方法"""
pass self.suite = suite
self.face = face
def main(): def show(self):
dict1 = SetOnceDict() """显示牌面"""
try: suites = ['♠️', '♥️', '♣️', '♦️']
dict1['username'] = 'jackfrued' faces = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
dict1['username'] = 'hellokitty' return f'{suites[self.suite.value]} {faces[self.face]}'
dict1['username'] = 'wangdachui'
except KeyError: def __str__(self):
pass return self.show()
print(dict1)
def __repr__(self):
return self.show()
if __name__ == '__main__':
main()
``` class Poker(object):
"""扑克"""
- 元编程和元类
def __init__(self):
用元类实现单例模式。 self.index = 0
self.cards = [Card(suite, face)
```Python for suite in Suite
""" for face in range(1, 14)]
通过元类实现单例模式
""" def shuffle(self):
"""洗牌(随机乱序)"""
random.shuffle(self.cards)
class SingletonMeta(type): self.index = 0
"""单例的元类"""
def deal(self):
def __init__(cls, *args, **kwargs): """发牌"""
cls.__instance = None card = self.cards[self.index]
super().__init__(*args, **kwargs) self.index += 1
return card
def __call__(cls, *args, **kwargs):
if cls.__instance is None: @property
cls.__instance = super().__call__(*args, **kwargs) def has_more(self):
return cls.__instance return self.index < len(self.cards)
class Singleton(metaclass=SingletonMeta): class Player(object):
"""单例类""" """玩家"""
def __init__(self, name): def __init__(self, name):
self._name = name self.name = name
from random import randrange self.cards = []
self._value = randrange(100000)
def get_one(self, card):
@property """摸一张牌"""
def name(self): self.cards.append(card)
return self._name
def sort(self, comp=lambda card: (card.suite, card.face)):
@property """整理手上的牌"""
def value(self): self.cards.sort(key=comp)
return self._value
def main():
def main(): """主函数"""
sin1 = Singleton('Lee') poker = Poker()
sin2 = Singleton('Wang') poker.shuffle()
print(sin1 == sin2) players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
print(sin1.value, sin2.value) while poker.has_more:
print(sin1.name, sin2.name) for player in players:
player.get_one(poker.deal())
for player in players:
if __name__ == '__main__': player.sort()
main() print(player.name, end=': ')
``` print(player.cards)
### 迭代器和生成器
if __name__ == '__main__':
```Python main()
""" ```
生成器和迭代器
""" - 对象的复制(深复制/深拷贝/深度克隆和浅复制/浅拷贝/影子克隆)
- 垃圾回收、循环引用和弱引用
def fib1(num):
"""普通函数""" Python使用了自动化内存管理,这种管理机制以**引用计数**为基础,同时也引入了**标记-清除**和**分代收集**两种机制为辅的策略。
a, b = 0, 1
for _ in range(num): ```C
a, b = b, a + b typedef struct_object {
return a /* 引用计数 */
int ob_refcnt;
/* 对象指针 */
def fib2(num): struct_typeobject *ob_type;
"""生成器""" } PyObject;
a, b = 0, 1 ```
for _ in range(num):
a, b = b, a + b ```C
yield a /* 增加引用计数的宏定义 */
#define Py_INCREF(op) ((op)->ob_refcnt++)
/* 减少引用计数的宏定义 */
class Fib3: #define Py_DECREF(op) \ //减少计数
"""迭代器""" if (--(op)->ob_refcnt != 0) \
; \
def __init__(self, num): else \
self.num = num __Py_Dealloc((PyObject *)(op))
self.a, self.b = 0, 1 ```
self.idx = 0
导致引用计数+1的情况:
def __iter__(self):
return self - 对象被创建,例如`a = 23`
- 对象被引用,例如`b = a`
def __next__(self): - 对象被作为参数,传入到一个函数中,例如`f(a)`
if self.idx < self.num: - 对象作为一个元素,存储在容器中,例如`list1 = [a, a]`
self.a, self.b = self.b, self.a + self.b
self.idx += 1 导致引用计数-1的情况:
return self.a
raise StopIteration() - 对象的别名被显式销毁,例如`del a`
- 对象的别名被赋予新的对象,例如`a = 24`
- 一个对象离开它的作用域,例如f函数执行完毕时,f函数中的局部变量(全局变量不会)
def main(): - 对象所在的容器被销毁,或从容器中删除对象
for val in fib2(20):
print(val) 引用计数可能会导致循环引用问题,而循环引用会导致内存泄露,如下面的代码所示。为了解决这个问题,Python中引入了“标记-清除”和“分代收集”。在创建一个对象的时候,对象被放在第一代中,如果在第一代的垃圾检查中对象存活了下来,该对象就会被放到第二代中,同理在第二代的垃圾检查中对象存活下来,该对象就会被放到第三代中。
print('-' * 50)
for val in Fib3(20): ```Python
print(val) # 循环引用会导致内存泄露 - Python除了引用技术还引入了标记清理和分代回收
# 在Python 3.6以前如果重写__del__魔术方法会导致循环引用处理失效
# 如果不想造成循环引用可以使用弱引用
if __name__ == '__main__': list1 = []
main() list2 = []
``` list1.append(list2)
list2.append(list1)
### 并发编程 ```
- 多线程和多进程 以下情况会导致垃圾回收:
- 协程和异步I/O
- concurrent.futures - 调用`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
class SingletonMeta(type):
"""自定义元类"""
def __init__(cls, *args, **kwargs):
cls.__instance = None
super().__init__(*args, **kwargs)
def __call__(cls, *args, **kwargs):
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
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
def add_money(account, money):
"""向指定账户打钱"""
account.deposit(money)
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=add_money, args=(account, 1)
# ).start()
# 创建线程的第2种方式
# AddMoneyThread(account, 1).start()
# 创建线程的第3种方式
# 调用线程池中的线程来执行特定的任务
future = pool.submit(add_money, account, 1)
futures.append(future)
# 关闭线程池
pool.shutdown()
for future in futures:
future.result()
print(account.balance)
if __name__ == '__main__':
main()
```
- 多进程:多进程可以有效的解决GIL的问题,实现多进程主要的类是Process,其他辅助的类跟threading模块中的类似,进程间共享数据可以使用管道、套接字等,在multiprocessing模块中有一个Queue类,它基于管道和锁机制提供了多个进程共享的队列。下面是官方文档上关于多进程和进程池的一个示例。
```Python
"""
多进程和进程池的使用
"""
import concurrent.futures
import math
PRIMES = [
112272535095293,
112582705942171,
112272535095293,
115280095190773,
115797848077099,
1099726899285419
]
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`对象获得协程执行结果。
\ No newline at end of file
...@@ -1249,7 +1249,3 @@ build environment: ...@@ -1249,7 +1249,3 @@ build environment:
3. HISTSIZE 3. HISTSIZE
4. RANDOM 4. RANDOM
5. PATH 5. PATH
...@@ -115,7 +115,6 @@ HTTP响应(响应行+响应头+空行+消息体): ...@@ -115,7 +115,6 @@ HTTP响应(响应行+响应头+空行+消息体):
3. HTTPie:命令行HTTP客户端。 3. HTTPie:命令行HTTP客户端。
```Shell ```Shell
$ http --header http://www.scu.edu.cn $ http --header http://www.scu.edu.cn
HTTP/1.1 200 OK HTTP/1.1 200 OK
Accept-Ranges: bytes Accept-Ranges: bytes
...@@ -138,8 +137,6 @@ HTTP响应(响应行+响应头+空行+消息体): ...@@ -138,8 +137,6 @@ HTTP响应(响应行+响应头+空行+消息体):
4. BuiltWith:识别网站所用技术的工具。 4. BuiltWith:识别网站所用技术的工具。
```Python ```Python
>>>
>>> import builtwith >>> import builtwith
>>> builtwith.parse('http://www.bootcss.com/') >>> builtwith.parse('http://www.bootcss.com/')
{'web-servers': ['Nginx'], 'font-scripts': ['Font Awesome'], 'javascript-frameworks': ['Lo-dash', 'Underscore.js', 'Vue.js', 'Zepto', 'jQuery'], 'web-frameworks': ['Twitter Bootstrap']} {'web-servers': ['Nginx'], 'font-scripts': ['Font Awesome'], 'javascript-frameworks': ['Lo-dash', 'Underscore.js', 'Vue.js', 'Zepto', 'jQuery'], 'web-frameworks': ['Twitter Bootstrap']}
...@@ -153,8 +150,6 @@ HTTP响应(响应行+响应头+空行+消息体): ...@@ -153,8 +150,6 @@ HTTP响应(响应行+响应头+空行+消息体):
5. python-whois:查询网站所有者的工具。 5. python-whois:查询网站所有者的工具。
```Python ```Python
>>>
>>> import whois >>> import whois
>>> whois.whois('baidu.com') >>> whois.whois('baidu.com')
{'domain_name': ['BAIDU.COM', 'baidu.com'], 'registrar': 'MarkMonitor, Inc.', 'whois_server': 'whois.markmonitor.com', 'referral_url': None, 'updated_date': [datetime.datetime(2017, 7, 28, 2, 36, 28), datetime.datetime(2017, 7, 27, 19, 36, 28)], 'creation_date': [datetime.datetime(1999, 10, 11, 11, 5, 17), datetime.datetime(1999, 10, 11, 4, 5, 17)], 'expiration_date': [datetime.datetime(2026, 10, 11, 11, 5, 17), datetime.datetime(2026, 10, 11, 0, 0)], 'name_servers': ['DNS.BAIDU.COM', 'NS2.BAIDU.COM', 'NS3.BAIDU.COM', 'NS4.BAIDU.COM', 'NS7.BAIDU.COM', 'dns.baidu.com', 'ns4.baidu.com', 'ns3.baidu.com', 'ns7.baidu.com', 'ns2.baidu.com'], 'status': ['clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited', 'clientTransferProhibited https://icann.org/epp#clientTransferProhibited', 'clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited', 'serverDeleteProhibited https://icann.org/epp#serverDeleteProhibited', 'serverTransferProhibited https://icann.org/epp#serverTransferProhibited', 'serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited', 'clientUpdateProhibited (https://www.icann.org/epp#clientUpdateProhibited)', 'clientTransferProhibited (https://www.icann.org/epp#clientTransferProhibited)', 'clientDeleteProhibited (https://www.icann.org/epp#clientDeleteProhibited)', 'serverUpdateProhibited (https://www.icann.org/epp#serverUpdateProhibited)', 'serverTransferProhibited (https://www.icann.org/epp#serverTransferProhibited)', 'serverDeleteProhibited (https://www.icann.org/epp#serverDeleteProhibited)'], 'emails': ['abusecomplaints@markmonitor.com', 'whoisrelay@markmonitor.com'], 'dnssec': 'unsigned', 'name': None, 'org': 'Beijing Baidu Netcom Science Technology Co., Ltd.', 'address': None, 'city': None, 'state': 'Beijing', 'zipcode': None, 'country': 'CN'} {'domain_name': ['BAIDU.COM', 'baidu.com'], 'registrar': 'MarkMonitor, Inc.', 'whois_server': 'whois.markmonitor.com', 'referral_url': None, 'updated_date': [datetime.datetime(2017, 7, 28, 2, 36, 28), datetime.datetime(2017, 7, 27, 19, 36, 28)], 'creation_date': [datetime.datetime(1999, 10, 11, 11, 5, 17), datetime.datetime(1999, 10, 11, 4, 5, 17)], 'expiration_date': [datetime.datetime(2026, 10, 11, 11, 5, 17), datetime.datetime(2026, 10, 11, 0, 0)], 'name_servers': ['DNS.BAIDU.COM', 'NS2.BAIDU.COM', 'NS3.BAIDU.COM', 'NS4.BAIDU.COM', 'NS7.BAIDU.COM', 'dns.baidu.com', 'ns4.baidu.com', 'ns3.baidu.com', 'ns7.baidu.com', 'ns2.baidu.com'], 'status': ['clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited', 'clientTransferProhibited https://icann.org/epp#clientTransferProhibited', 'clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited', 'serverDeleteProhibited https://icann.org/epp#serverDeleteProhibited', 'serverTransferProhibited https://icann.org/epp#serverTransferProhibited', 'serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited', 'clientUpdateProhibited (https://www.icann.org/epp#clientUpdateProhibited)', 'clientTransferProhibited (https://www.icann.org/epp#clientTransferProhibited)', 'clientDeleteProhibited (https://www.icann.org/epp#clientDeleteProhibited)', 'serverUpdateProhibited (https://www.icann.org/epp#serverUpdateProhibited)', 'serverTransferProhibited (https://www.icann.org/epp#serverTransferProhibited)', 'serverDeleteProhibited (https://www.icann.org/epp#serverDeleteProhibited)'], 'emails': ['abusecomplaints@markmonitor.com', 'whoisrelay@markmonitor.com'], 'dnssec': 'unsigned', 'name': None, 'org': 'Beijing Baidu Netcom Science Technology Co., Ltd.', 'address': None, 'city': None, 'state': 'Beijing', 'zipcode': None, 'country': 'CN'}
...@@ -195,7 +190,6 @@ HTTP响应(响应行+响应头+空行+消息体): ...@@ -195,7 +190,6 @@ HTTP响应(响应行+响应头+空行+消息体):
下面的例子给出了一个从“搜狐体育”上获取NBA新闻标题和链接的爬虫。 下面的例子给出了一个从“搜狐体育”上获取NBA新闻标题和链接的爬虫。
```Python ```Python
from urllib.error import URLError from urllib.error import URLError
from urllib.request import urlopen from urllib.request import urlopen
...@@ -304,7 +298,6 @@ if __name__ == '__main__': ...@@ -304,7 +298,6 @@ if __name__ == '__main__':
- 使用未经验证的上下文 - 使用未经验证的上下文
```Python ```Python
import ssl import ssl
request = urllib.request.Request(url='...', headers={...}) request = urllib.request.Request(url='...', headers={...})
...@@ -315,10 +308,8 @@ if __name__ == '__main__': ...@@ -315,10 +308,8 @@ if __name__ == '__main__':
- 设置全局的取消证书验证 - 设置全局的取消证书验证
```Python ```Python
import ssl import ssl
ssl._create_default_https_context = ssl._create_unverified_context ssl._create_default_https_context = ssl._create_unverified_context
``` ```
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
### HTML页面分析 ### HTML页面分析
```HTML ```HTML
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
...@@ -136,7 +135,6 @@ pyquery相当于jQuery的Python实现,可以用于解析HTML网页。 ...@@ -136,7 +135,6 @@ pyquery相当于jQuery的Python实现,可以用于解析HTML网页。
### 实例 - 获取知乎发现上的问题链接 ### 实例 - 获取知乎发现上的问题链接
```Python ```Python
from urllib.parse import urljoin from urllib.parse import urljoin
import re import re
...@@ -168,6 +166,5 @@ def main(): ...@@ -168,6 +166,5 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
...@@ -20,21 +20,19 @@ Redis是REmote DIctionary Server的缩写,它是一个用ANSI C编写的高性 ...@@ -20,21 +20,19 @@ Redis是REmote DIctionary Server的缩写,它是一个用ANSI C编写的高性
可以使用Linux系统的包管理工具(如yum)来安装Redis,也可以通过在Redis的[官方网站](https://redis.io/)下载Redis的[源代码](http://download.redis.io/releases/redis-3.2.11.tar.gz)解压缩解归档之后进行构件安装。 可以使用Linux系统的包管理工具(如yum)来安装Redis,也可以通过在Redis的[官方网站](https://redis.io/)下载Redis的[源代码](http://download.redis.io/releases/redis-3.2.11.tar.gz)解压缩解归档之后进行构件安装。
```Shell ```Shell
wget http://download.redis.io/releases/redis-3.2.11.tar.gz
# wget http://download.redis.io/releases/redis-3.2.11.tar.gz gunzip redis-3.2.11.tar.gz
# gunzip redis-3.2.11.tar.gz tar -xvf redis-3.2.11.tar
# tar -xvf redis-3.2.11.tar cd redis-3.2.11
# cd redis-3.2.11 make && make install
# make && make install
``` ```
接下来我们将redis-3.2.11目录下的redis.conf配置文件复制到用户主目录下并修改配置文件(如果你对配置文件不是很有把握就不要直接修改而是先复制一份再修改这个副本)。 接下来我们将redis-3.2.11目录下的redis.conf配置文件复制到用户主目录下并修改配置文件(如果你对配置文件不是很有把握就不要直接修改而是先复制一份再修改这个副本)。
```Shell ```Shell
cd ..
# cd .. cp redis-3.2.11/redis.conf redis.conf
# cp redis-3.2.11/redis.conf redis.conf vim redis.conf
# vim redis.conf
``` ```
配置将Redis服务绑定到指定的IP地址和端口。 配置将Redis服务绑定到指定的IP地址和端口。
...@@ -76,8 +74,7 @@ Redis是REmote DIctionary Server的缩写,它是一个用ANSI C编写的高性 ...@@ -76,8 +74,7 @@ Redis是REmote DIctionary Server的缩写,它是一个用ANSI C编写的高性
接下来启动Redis服务器,可以将服务器放在后台去运行。 接下来启动Redis服务器,可以将服务器放在后台去运行。
```Shell ```Shell
redis-server redis.conf &
# redis-server redis.conf &
_.-``__ ''-._ _.-``__ ''-._
_.-`` `. `_. ''-._ Redis 3.2.11 (00000000/0) 64 bit _.-`` `. `_. ''-._ Redis 3.2.11 (00000000/0) 64 bit
...@@ -100,8 +97,7 @@ Redis是REmote DIctionary Server的缩写,它是一个用ANSI C编写的高性 ...@@ -100,8 +97,7 @@ Redis是REmote DIctionary Server的缩写,它是一个用ANSI C编写的高性
接下来,我们尝试用Redis客户端去连接服务器。 接下来,我们尝试用Redis客户端去连接服务器。
```Shell ```Shell
redis-cli -h 172.18.61.250 -p 6379
# redis-cli -h 172.18.61.250 -p 6379
172.18.61.250:6379> auth 1qaz2wsx 172.18.61.250:6379> auth 1qaz2wsx
OK OK
172.18.61.250:6379> ping 172.18.61.250:6379> ping
...@@ -112,7 +108,6 @@ PONG ...@@ -112,7 +108,6 @@ PONG
Redis有着非常丰富的数据类型,也有很多的命令来操作这些数据,具体的内容可以查看[Redis命令参考](http://redisdoc.com/),在这个网站上,除了Redis的命令参考,还有Redis的详细文档,其中包括了通知、事务、主从复制、持久化、哨兵、集群等内容。 Redis有着非常丰富的数据类型,也有很多的命令来操作这些数据,具体的内容可以查看[Redis命令参考](http://redisdoc.com/),在这个网站上,除了Redis的命令参考,还有Redis的详细文档,其中包括了通知、事务、主从复制、持久化、哨兵、集群等内容。
```Shell ```Shell
172.18.61.250:6379> set username admin 172.18.61.250:6379> set username admin
OK OK
172.18.61.250:6379> get username 172.18.61.250:6379> get username
...@@ -166,13 +161,11 @@ OK ...@@ -166,13 +161,11 @@ OK
可以使用pip安装redis模块。redis模块的核心是名为Redis的类,该类的对象代表一个Redis客户端,通过该客户端可以向Redis服务器发送命令并获取执行的结果。上面我们在Redis客户端中使用的命令基本上就是Redis对象可以接收的消息,所以如果了解了Redis的命令就可以在Python中玩转Redis。 可以使用pip安装redis模块。redis模块的核心是名为Redis的类,该类的对象代表一个Redis客户端,通过该客户端可以向Redis服务器发送命令并获取执行的结果。上面我们在Redis客户端中使用的命令基本上就是Redis对象可以接收的消息,所以如果了解了Redis的命令就可以在Python中玩转Redis。
```Shell ```Shell
pip3 install redis
$ pip3 install redis python3
$ python3
``` ```
```Python ```Python
>>> import redis >>> 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='1qaz2wsx')
>>> client.set('username', 'admin') >>> client.set('username', 'admin')
...@@ -202,14 +195,14 @@ MongoDB将数据存储为一个文档,一个文档由一系列的“键值对 ...@@ -202,14 +195,14 @@ MongoDB将数据存储为一个文档,一个文档由一系列的“键值对
可以从MongoDB的[官方下载链接](https://www.mongodb.com/download-center#community)下载MongoDB,官方为Windows系统提供了一个Installer程序,而Linux和MacOS则提供了压缩文件。下面简单说一下Linux系统如何安装和配置MongoDB。 可以从MongoDB的[官方下载链接](https://www.mongodb.com/download-center#community)下载MongoDB,官方为Windows系统提供了一个Installer程序,而Linux和MacOS则提供了压缩文件。下面简单说一下Linux系统如何安装和配置MongoDB。
```Shell ```Shell
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-amazon-3.6.5.tgz
gunzip mongodb-linux-x86_64-amazon-3.6.5.tgz
mkdir mongodb-3.6.5
tar -xvf mongodb-linux-x86_64-amazon-3.6.5.tar --strip-components 1 -C mongodb-3.6.5/
export PATH=$PATH:~/mongodb-3.6.5/bin
mkdir -p /data/db
mongod --bind_ip 172.18.61.250
# wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-amazon-3.6.5.tgz
# gunzip mongodb-linux-x86_64-amazon-3.6.5.tgz
# mkdir mongodb-3.6.5
# tar -xvf mongodb-linux-x86_64-amazon-3.6.5.tar --strip-components 1 -C mongodb-3.6.5/
# export PATH=$PATH:~/mongodb-3.6.5/bin
# mkdir -p /data/db
# mongod --bind_ip 172.18.61.250
2018-06-03T18:03:28.232+0800 I CONTROL [initandlisten] MongoDB starting : pid=1163 port=27017 dbpath=/data/db 64-bit host=iZwz97tbgo9lkabnat2lo8Z 2018-06-03T18:03:28.232+0800 I CONTROL [initandlisten] MongoDB starting : pid=1163 port=27017 dbpath=/data/db 64-bit host=iZwz97tbgo9lkabnat2lo8Z
2018-06-03T18:03:28.232+0800 I CONTROL [initandlisten] db version v3.6.5 2018-06-03T18:03:28.232+0800 I CONTROL [initandlisten] db version v3.6.5
2018-06-03T18:03:28.232+0800 I CONTROL [initandlisten] git version: a20ecd3e3a174162052ff99913bc2ca9a839d618 2018-06-03T18:03:28.232+0800 I CONTROL [initandlisten] git version: a20ecd3e3a174162052ff99913bc2ca9a839d618
...@@ -239,7 +232,8 @@ MongoDB将数据存储为一个文档,一个文档由一系列的“键值对 ...@@ -239,7 +232,8 @@ MongoDB将数据存储为一个文档,一个文档由一系列的“键值对
启动服务器后可以使用交互式环境跟服务器通信,如下所示。 启动服务器后可以使用交互式环境跟服务器通信,如下所示。
```shell ```shell
# mongo --host 172.18.61.250 mongo --host 172.18.61.250
MongoDB shell version v3.6.5 MongoDB shell version v3.6.5
connecting to: mongodb://172.18.61.250:27017/ connecting to: mongodb://172.18.61.250:27017/
... ...
...@@ -249,7 +243,6 @@ connecting to: mongodb://172.18.61.250:27017/ ...@@ -249,7 +243,6 @@ connecting to: mongodb://172.18.61.250:27017/
1. 查看、创建和删除数据库。 1. 查看、创建和删除数据库。
```JavaScript ```JavaScript
> // 显示所有数据库 > // 显示所有数据库
> show dbs > show dbs
admin 0.000GB admin 0.000GB
...@@ -267,7 +260,6 @@ connecting to: mongodb://172.18.61.250:27017/ ...@@ -267,7 +260,6 @@ connecting to: mongodb://172.18.61.250:27017/
2. 创建、删除和查看集合。 2. 创建、删除和查看集合。
```JavaScript ```JavaScript
> // 创建并切换到school数据库 > // 创建并切换到school数据库
> use school > use school
switched to db school switched to db school
...@@ -292,7 +284,6 @@ connecting to: mongodb://172.18.61.250:27017/ ...@@ -292,7 +284,6 @@ connecting to: mongodb://172.18.61.250:27017/
3. 文档的CRUD操作。 3. 文档的CRUD操作。
```JavaScript ```JavaScript
> // 向students集合插入文档 > // 向students集合插入文档
> db.students.insert({stuid: 1001, name: '骆昊', age: 38}) > db.students.insert({stuid: 1001, name: '骆昊', age: 38})
WriteResult({ "nInserted" : 1 }) WriteResult({ "nInserted" : 1 })
...@@ -394,13 +385,11 @@ connecting to: mongodb://172.18.61.250:27017/ ...@@ -394,13 +385,11 @@ connecting to: mongodb://172.18.61.250:27017/
可以通过pip安装pymongo来实现对MongoDB的操作。 可以通过pip安装pymongo来实现对MongoDB的操作。
```Shell ```Shell
pip3 install pymongo
$ pip3 install pymongo python3
$ python3
``` ```
```Python ```Python
>>> from pymongo import MongoClient >>> from pymongo import MongoClient
>>> client = MongoClient('mongodb://120.77.222.217:27017') >>> client = MongoClient('mongodb://120.77.222.217:27017')
>>> db = client.school >>> db = client.school
...@@ -451,7 +440,6 @@ $ python3 ...@@ -451,7 +440,6 @@ $ python3
### 实例 - 缓存知乎发现上的链接和页面代码 ### 实例 - 缓存知乎发现上的链接和页面代码
```Python ```Python
from hashlib import sha1 from hashlib import sha1
from urllib.parse import urljoin from urllib.parse import urljoin
...@@ -501,7 +489,6 @@ def main(): ...@@ -501,7 +489,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
......
...@@ -44,7 +44,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池 ...@@ -44,7 +44,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
1. 生成器 - 数据的生产者。 1. 生成器 - 数据的生产者。
```Python ```Python
from time import sleep from time import sleep
...@@ -70,7 +69,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池 ...@@ -70,7 +69,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
生成器还可以叠加来组成生成器管道,代码如下所示。 生成器还可以叠加来组成生成器管道,代码如下所示。
```Python ```Python
# Fibonacci数生成器 # Fibonacci数生成器
def fib(): def fib():
a, b = 0, 1 a, b = 0, 1
...@@ -94,13 +92,11 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池 ...@@ -94,13 +92,11 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
2. 协程 - 数据的消费者。 2. 协程 - 数据的消费者。
```Python ```Python
from time import sleep from time import sleep
...@@ -130,13 +126,11 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池 ...@@ -130,13 +126,11 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
> 说明:上面代码中countdown_gen函数中的第1行consumer.send(None)是为了激活生成器,通俗的说就是让生成器执行到有yield关键字的地方挂起,当然也可以通过next(consumer)来达到同样的效果。如果不愿意每次都用这样的代码来“预激”生成器,可以写一个包装器来完成该操作,代码如下所示。 > 说明:上面代码中countdown_gen函数中的第1行consumer.send(None)是为了激活生成器,通俗的说就是让生成器执行到有yield关键字的地方挂起,当然也可以通过next(consumer)来达到同样的效果。如果不愿意每次都用这样的代码来“预激”生成器,可以写一个包装器来完成该操作,代码如下所示。
```Python ```Python
from functools import wraps from functools import wraps
...@@ -156,7 +150,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池 ...@@ -156,7 +150,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
3. 异步I/O - 非阻塞式I/O操作。 3. 异步I/O - 非阻塞式I/O操作。
```Python ```Python
import asyncio import asyncio
...@@ -179,13 +172,11 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池 ...@@ -179,13 +172,11 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
4. `async`和`await`。 4. `async`和`await`。
```Python ```Python
import asyncio import asyncio
import aiohttp import aiohttp
...@@ -215,7 +206,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池 ...@@ -215,7 +206,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
上面的代码使用了[AIOHTTP](https://github.com/aio-libs/aiohttp)这个非常著名的第三方库,它实现了HTTP客户端和HTTP服务器的功能,对异步操作提供了非常好的支持,有兴趣可以阅读它的[官方文档](https://aiohttp.readthedocs.io/en/stable/)。 上面的代码使用了[AIOHTTP](https://github.com/aio-libs/aiohttp)这个非常著名的第三方库,它实现了HTTP客户端和HTTP服务器的功能,对异步操作提供了非常好的支持,有兴趣可以阅读它的[官方文档](https://aiohttp.readthedocs.io/en/stable/)。
...@@ -225,7 +215,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池 ...@@ -225,7 +215,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
下面我们把之间讲的所有知识结合起来,用面向对象的方式实现一个爬取“手机搜狐网”的多线程爬虫。 下面我们把之间讲的所有知识结合起来,用面向对象的方式实现一个爬取“手机搜狐网”的多线程爬虫。
```Python ```Python
import pickle import pickle
import zlib import zlib
from enum import Enum, unique from enum import Enum, unique
...@@ -382,6 +371,5 @@ def main(): ...@@ -382,6 +371,5 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
...@@ -25,14 +25,12 @@ WebKit的代码始于1998年的KHTML项目,当时它是Konqueror浏览器的 ...@@ -25,14 +25,12 @@ WebKit的代码始于1998年的KHTML项目,当时它是Konqueror浏览器的
如果没有打算用上面所说的方式来渲染页面并获得动态内容,其实还有一种替代方案就是使用自动化测试工具Selenium,它提供了浏览器自动化的API接口,这样就可以通过操控浏览器来获取动态内容。首先可以使用pip来安装Selenium。 如果没有打算用上面所说的方式来渲染页面并获得动态内容,其实还有一种替代方案就是使用自动化测试工具Selenium,它提供了浏览器自动化的API接口,这样就可以通过操控浏览器来获取动态内容。首先可以使用pip来安装Selenium。
```Shell ```Shell
pip3 install selenium
$ pip3 install selenium
``` ```
下面以“阿里V任务”的“直播服务”为例,来演示如何使用Selenium获取到动态内容并抓取主播图片。 下面以“阿里V任务”的“直播服务”为例,来演示如何使用Selenium获取到动态内容并抓取主播图片。
```Python ```Python
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
...@@ -47,13 +45,11 @@ def main(): ...@@ -47,13 +45,11 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
运行上面的程序会发现没有任何的输出,因为页面的HTML代码上根本找不到`<img>`标签。接下来我们使用Selenium来获取到页面上的动态内容,再提取主播图片。 运行上面的程序会发现没有任何的输出,因为页面的HTML代码上根本找不到`<img>`标签。接下来我们使用Selenium来获取到页面上的动态内容,再提取主播图片。
```Python ```Python
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
...@@ -69,21 +65,18 @@ def main(): ...@@ -69,21 +65,18 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
在上面的程序中,我们通过Selenium实现对Chrome浏览器的操控,如果要操控其他的浏览器,可以创对应的浏览器对象,例如Firefox、IE等。运行上面的程序,如果看到如下所示的错误提示,那是说明我们还没有将Chrome浏览器的驱动添加到PATH环境变量中,也没有在程序中指定Chrome浏览器驱动所在的位置。 在上面的程序中,我们通过Selenium实现对Chrome浏览器的操控,如果要操控其他的浏览器,可以创对应的浏览器对象,例如Firefox、IE等。运行上面的程序,如果看到如下所示的错误提示,那是说明我们还没有将Chrome浏览器的驱动添加到PATH环境变量中,也没有在程序中指定Chrome浏览器驱动所在的位置。
```Shell ```Shell
selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH. Please see https://sites.google.com/a/chromium.org/chromedriver/home selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH. Please see https://sites.google.com/a/chromium.org/chromedriver/home
``` ```
为了解决上面的问题,可以到Selenium的[官方网站]()找到浏览器驱动的下载链接并下载需要的驱动,在Linux或macOS系统下可以通过下面的命令来设置PATH环境变量,Windows下配置环境变量也非常简单,不清楚的可以自行了解。 为了解决上面的问题,可以到Selenium的[官方网站]()找到浏览器驱动的下载链接并下载需要的驱动,在Linux或macOS系统下可以通过下面的命令来设置PATH环境变量,Windows下配置环境变量也非常简单,不清楚的可以自行了解。
```Shell ```Shell
export PATH=$PATH:/Users/Hao/Downloads/Tools/chromedriver/
$ export PATH=$PATH:/Users/Hao/Downloads/Tools/chromedriver/
``` ```
其中`/Users/Hao/Downloads/Tools/chromedriver/ `就是chromedriver所在的路径。 其中`/Users/Hao/Downloads/Tools/chromedriver/ `就是chromedriver所在的路径。
\ No newline at end of file
...@@ -43,13 +43,11 @@ Scrapy的整个数据处理流程由Scrapy引擎进行控制,通常的运转 ...@@ -43,13 +43,11 @@ Scrapy的整个数据处理流程由Scrapy引擎进行控制,通常的运转
```Shell ```Shell
$
``` ```
项目的目录结构如下图所示。 项目的目录结构如下图所示。
```Shell ```Shell
(venv) $ tree (venv) $ tree
. .
|____ scrapy.cfg |____ scrapy.cfg
...@@ -78,7 +76,6 @@ $ ...@@ -78,7 +76,6 @@ $
1. 在items.py文件中定义字段,这些字段用来保存数据,方便后续的操作。 1. 在items.py文件中定义字段,这些字段用来保存数据,方便后续的操作。
```Python ```Python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Define here the models for your scraped items # Define here the models for your scraped items
...@@ -102,12 +99,10 @@ $ ...@@ -102,12 +99,10 @@ $
2. 在spiders文件夹中编写自己的爬虫。 2. 在spiders文件夹中编写自己的爬虫。
```Shell ```Shell
(venv) $ scrapy genspider movie movie.douban.com --template=crawl (venv) $ scrapy genspider movie movie.douban.com --template=crawl
``` ```
```Python ```Python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import scrapy import scrapy
from scrapy.selector import Selector from scrapy.selector import Selector
...@@ -136,28 +131,24 @@ $ ...@@ -136,28 +131,24 @@ $
item['classification']= sel.xpath('//span[@property="v:genre"]/text()').extract() item['classification']= sel.xpath('//span[@property="v:genre"]/text()').extract()
item['actor']= sel.xpath('//*[@id="info"]/span[3]/a[1]/text()').extract() item['actor']= sel.xpath('//*[@id="info"]/span[3]/a[1]/text()').extract()
return item return item
``` ```
> 说明:上面我们通过Scrapy提供的爬虫模板创建了Spider,其中的rules中的LinkExtractor对象会自动完成对新的链接的解析,该对象中有一个名为extract_link的回调方法。Scrapy支持用XPath语法和CSS选择器进行数据解析,对应的方法分别是xpath和css,上面我们使用了XPath语法对页面进行解析,如果不熟悉XPath语法可以看看后面的补充说明。 > 说明:上面我们通过Scrapy提供的爬虫模板创建了Spider,其中的rules中的LinkExtractor对象会自动完成对新的链接的解析,该对象中有一个名为extract_link的回调方法。Scrapy支持用XPath语法和CSS选择器进行数据解析,对应的方法分别是xpath和css,上面我们使用了XPath语法对页面进行解析,如果不熟悉XPath语法可以看看后面的补充说明。
到这里,我们已经可以通过下面的命令让爬虫运转起来。 到这里,我们已经可以通过下面的命令让爬虫运转起来。
```Shell ```Shell
(venv)$ scrapy crawl movie (venv)$ scrapy crawl movie
``` ```
可以在控制台看到爬取到的数据,如果想将这些数据保存到文件中,可以通过`-o`参数来指定文件名,Scrapy支持我们将爬取到的数据导出成JSON、CSV、XML、pickle、marshal等格式。 可以在控制台看到爬取到的数据,如果想将这些数据保存到文件中,可以通过`-o`参数来指定文件名,Scrapy支持我们将爬取到的数据导出成JSON、CSV、XML、pickle、marshal等格式。
```Shell ```Shell
(venv)$ scrapy crawl moive -o result.json (venv)$ scrapy crawl moive -o result.json
``` ```
3. 在pipelines.py中完成对数据进行持久化的操作。 3. 在pipelines.py中完成对数据进行持久化的操作。
```Python ```Python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Define your item pipelines here # Define your item pipelines here
...@@ -211,7 +202,6 @@ $ ...@@ -211,7 +202,6 @@ $
4. 修改settings.py文件对项目进行配置。 4. 修改settings.py文件对项目进行配置。
```Python ```Python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Scrapy settings for douban project # Scrapy settings for douban project
...@@ -325,7 +315,6 @@ $ ...@@ -325,7 +315,6 @@ $
XML文件。 XML文件。
```XML ```XML
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<bookstore> <bookstore>
......
## 项目部署上线指南
### 准备上线
1. 上线前的检查工作。
```Shell
python manage.py check --deploy
```
2. 将DEBUG设置为False并配置ALLOWED_HOSTS。
```Python
DEBUG = False
ALLOWED_HOSTS = ['*']
```
3. 安全相关的配置。
```Python
# 保持HTTPS连接的时间
SECURE_HSTS_SECONDS = 3600
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# 自动重定向到安全连接
SECURE_SSL_REDIRECT = True
# 避免浏览器自作聪明推断内容类型
SECURE_CONTENT_TYPE_NOSNIFF = True
# 避免跨站脚本攻击
SECURE_BROWSER_XSS_FILTER = True
# COOKIE只能通过HTTPS进行传输
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# 防止点击劫持攻击手段 - 修改HTTP协议响应头
# 当前网站是不允许使用<iframe>标签进行加载的
X_FRAME_OPTIONS = 'DENY'
```
4. 敏感信息放到环境变量或文件中。
```Python
SECRET_KEY = os.environ['SECRET_KEY']
DB_USER = os.environ['DB_USER']
DB_PASS = os.environ['DB_PASS']
REDIS_AUTH = os.environ['REDIS_AUTH']
```
### 更新服务器Python环境到3.x
> 说明:如果需要清除之前的安装,就删除对应的文件和文件夹即可
1. 安装底层依赖库。
```Shell
yum -y install wget gcc zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel libffi-devel
```
2. 下载Python源代码。
```Shell
wget https://www.python.org/ftp/python/3.7.1/Python-3.7.1.tar.xz
```
3. 解压缩和解归档。
```Shell
xz -d Python-3.7.1.tar.xz
tar -xvf Python-3.7.1.tar
```
4. 执行配置生成Makefile(构建文件)。
```Shell
cd Python-3.7.1
./configure --prefix=/usr/local/python37 --enable-optimizations
```
5. 构建和安装。
```Shell
make && make install
```
6. 配置PATH环境变量并激活。
```Shell
cd ~
vim .bash_profile
```
```INI
... 此处省略上面的代码...
export PATH=$PATH:/usr/local/python37/bin
... 此处省略下面的代码...
```
```Shell
source .bash_profile
```
7. 注册软链接(符号链接)- 这一步不是必须的。
```Shell
ln -s /usr/local/python37/bin/python3 /usr/bin/python3
ln -s /usr/local/python37/bin/pip3 /usr/bin/pip3
```
8. 测试Python环境是否更新成功。
```Shell
python3 --version
python --version
```
### 项目目录结构
假设项目文件夹为`project`,下面的四个子目录分别是:`conf`、`logs`、`src`和`venv`分别用来保存项目的配置文件、日志文件、源代码和虚拟环境。其中,`conf`目录下的子目录`cert`中保存了配置HTTPS需要使用的证书和密钥;`src`目录下的项目代码可以通过版本控制工具从代码仓库中检出;虚拟环境可以通过venv或其他工具进行创建。
```
project
├── conf
│   ├── cert
│   │   ├── 214915882850706.key
│   │   └── 214915882850706.pem
│   ├── nginx.conf
│   └── uwsgi.ini
├── logs
│   ├── access.log
│   ├── error.log
│   └── uwsgi.log
├── code
│   └── fangall
│   ├── api
│   ├── common
│   ├── fang
│   ├── rent
│   ├── user
│   ├── manage.py
│   ├── README.md
│   ├── static
│   └── templates
└── venv
├── bin
│   ├── activate
│   ├── activate.csh
│   ├── activate.fish
│   ├── celery
│   ├── celerybeat
│   ├── celeryd
│   ├── celeryd-multi
│   ├── coverage
│   ├── coverage3
│   ├── coverage-3.7
│   ├── django-admin
│   ├── django-admin.py
│   ├── easy_install
│   ├── easy_install-3.7
│   ├── pip
│   ├── pip3
│   ├── pip3.7
│   ├── __pycache__
│   ├── pyrsa-decrypt
│   ├── pyrsa-decrypt-bigfile
│   ├── pyrsa-encrypt
│   ├── pyrsa-encrypt-bigfile
│   ├── pyrsa-keygen
│   ├── pyrsa-priv2pub
│   ├── pyrsa-sign
│   ├── pyrsa-verify
│   ├── python -> python3
│   ├── python3 -> /usr/bin/python3
│   └── uwsgi
├── include
├── lib
│   └── python3.7
├── lib64 -> lib
├── pip-selfcheck.json
└── pyvenv.cfg
```
下面以阿里云为例,简单说明如何为项目注册域名、解析域名以及购买权威机构颁发的证书。
1. [注册域名](https://wanwang.aliyun.com/domain/)。
![](./res/aliyun-domain.png)
2. [域名备案](https://beian.aliyun.com/)。
![](./res/aliyun-keeprecord.png)
3. [域名解析](https://dns.console.aliyun.com/#/dns/domainList)。
![](./res/aliyun-dnslist.png)
![](./res/aliyun-resolve-settings.png)
4. [购买证书](https://www.aliyun.com/product/cas)。
![](./res/aliyun-certificate.png)
### uWSGI的配置
1. 在`project`目录下创建并激活虚拟环境。
```Shell
python3 -m venv venv
source venv/bin/activate
```
2. 安装项目依赖项。
```Shell
pip install -r requirements.txt
```
3. 通过pip安装uWSGI。
```Shell
pip install uwsgi
```
4. 修改uWSGI的配置文件(`/root/project/conf/uwsgi.ini`)。
```INI
[uwsgi]
# 配置前导路径
base=/root/project
# 配置项目名称
name=fangtx
# 守护进程
master=true
# 进程个数
processes=4
# 虚拟环境
pythonhome=%(base)/venv
# 项目地址
chdir=%(base)/code/%(name)
# 指定python解释器
pythonpath=%(pythonhome)/bin/python
# 指定uwsgi文件
module=%(name).wsgi
# 通信的地址和端口(自己服务器的IP地址和端口)
socket=172.18.61.250:8000
# 日志文件地址
logto=%(base)/logs/uwsgi.log
```
> 说明:可以先将“通信的地址和端口”项等号前面改为http来进行测试,如果没有问题再改回成socket,然后通过Nginx来实现项目的“动静分离”(静态资源交给Nginx处理,动态内容交给 uWSGI处理)。按照下面的方式可以启动uWSGI服务器。
5. 启动服务器。
```Shell
uwsgi --ini conf/uwsgi.ini
```
### Nginx的配置
1. 安装Nginx。
```Shell
yum -y install nginx
```
2. 修改全局配置文件(`/etc/nginx/nginx.conf`)。
```Nginx
# 配置用户
user root;
# 工作进程数(建议跟CPU的核数量一致)
worker_processes auto;
# 错误日志
error_log /var/log/nginx/error.log;
# 进程文件
pid /run/nginx.pid;
# 包含其他的配置
include /usr/share/nginx/modules/*.conf;
# 工作模式(多路IO复用方式)和连接上限
events {
use epoll;
worker_connections 1024;
}
# HTTP服务器相关配置
http {
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# 访问日志
access_log /var/log/nginx/access.log main;
# 开启高效文件传输模式
sendfile on;
# 用sendfile传输文件时有利于改善性能
tcp_nopush on;
# 禁用Nagle来解决交互性问题
tcp_nodelay on;
# 客户端保持连接时间
keepalive_timeout 30;
types_hash_max_size 2048;
# 包含MIME类型的配置
include /etc/nginx/mime.types;
# 默认使用二进制流格式
default_type application/octet-stream;
# 包含其他配置文件
include /etc/nginx/conf.d/*.conf;
# 包含项目的Nginx配置文件
include /root/project/conf/*.conf;
}
```
3. 编辑局部配置文件(`/root/project/conf/nginx.conf`)。
```Nginx
server {
listen 80;
server_name _;
access_log /root/project/logs/access.log;
error_log /root/project/logs/error.log;
location / {
include uwsgi_params;
uwsgi_pass 172.18.61.250:8000;
}
location /static/ {
alias /root/project/static/;
expires 30d;
}
}
server {
listen 443;
server_name _;
ssl on;
access_log /root/project/logs/access.log;
error_log /root/project/logs/error.log;
ssl_certificate /root/project/conf/cert/214915882850706.pem;
ssl_certificate_key /root/project/conf/cert/214915882850706.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
include uwsgi_params;
uwsgi_pass 172.18.61.250:8000;
}
location /static/ {
alias /root/project/static/;
expires 30d;
}
}
```
到此为止,我们可以启动Nginx来访问我们的应用程序,HTTP和HTTPS都是没有问题的,如果Nginx已经运行,在修改配置文件后,我们可以用下面的命令重新启动Nginx。
4. 重启Nginx服务器。
```Shell
nginx -s reload
```
```Shell
systemctl restart nginx
```
> 说明:可以对Django项目使用`python manage.py collectstatic`命令将静态资源收集到指定目录下,要做到这点只需要在项目的配置文件`settings.py`中添加`STATIC_ROOT`配置即可。
#### 负载均衡配置
下面的配置中我们使用Nginx实现负载均衡,为另外的三个Nginx服务器(通过Docker创建)提供反向代理服务。
```Shell
docker run -d -p 801:80 --name nginx1 nginx:latest
docker run -d -p 802:80 --name nginx2 nginx:latest
docker run -d -p 803:80 --name nginx3 nginx:latest
```
```Nginx
user root;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
# 为HTTP服务配置负载均衡
http {
upstream fangtx {
server 172.18.61.250:801 weight=4;
server 172.18.61.250:802 weight=2;
server 172.18.61.250:803 weight=2;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl;
listen [::]:443 ssl;
ssl on;
access_log /root/project/logs/access.log;
error_log /root/project/logs/error.log;
ssl_certificate /root/project/conf/cert/214915882850706.pem;
ssl_certificate_key /root/project/conf/cert/214915882850706.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_buffering off;
proxy_pass http://fangtx;
}
}
}
```
> 说明:Nginx在配置负载均衡时,默认使用WRR(加权轮询算法),除此之外还支持ip_hash、fair(需要安装upstream_fair模块)和url_hash算法。此外,在配置upstream模块时可以指定服务器的状态值,包括:backup(备份机器,其他服务器不可用时才将请求分配到该机器)、down、fail_timeout(请求失败达到max_fails后的暂停服务时间)、max_fails(允许请求失败的次数)和weight(轮询的权重)。
### Keepalived
当使用Nginx进行负载均衡配置时,要考虑负载均衡服务器宕机的情况。为此可以使用Keepalived来实现负载均衡主机和备机的热切换,从而保证系统的高可用性。Keepalived的配置还是比较复杂,通常由专门做运维的人进行配置,一个基本的配置可以参照[《Keepalived的配置和使用》](https://www.jianshu.com/p/dd93bc6d45f5)。
### MySQL主从复制
下面还是基于Docker来演示如何配置MySQL主从复制。我们事先准备好MySQL的配置文件以及保存MySQL数据和运行日志的目录,然后通过Docker的数据卷映射来指定容器的配置、数据和日志文件的位置。
```Shell
root
└── mysql
├── conf
│   ├── master
│   │   └── mysqld.cnf
│   ├── slave1
│   │   └── mysqld.cnf
│   ├── slave2
│   │   └── mysqld.cnf
│   └── slave3
│   └── mysqld.cnf
└── data
├── master
├── slave1
├── slave2
└── slave3
```
1. MySQL的配置文件(master和slave的配置文件需要不同的server-id)。
```
[mysqld]
pid-file=/var/run/mysqld/mysqld.pid
socket=/var/run/mysqld/mysqld.sock
datadir=/var/lib/mysql
log-error=/var/log/mysql/error.log
server-id=1
log_bin=/var/log/mysql/mysql-bin.log
expire_logs_days=30
max_binlog_size=256M
symbolic-links=0
```
2. 创建和配置master。
```Shell
docker run -d -p 3306:3306 --name mysql57 \
-v /root/mysql/conf/master:/etc/mysql/mysql.conf.d \
-v /root/mysql/data/master:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
docker exec -it mysql57 /bin/bash
```
```Shell
mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.7.23-log MySQL Community Server (GPL)
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> grant replication slave on *.* to 'slave'@'%' identified by 'iamslave';
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 590 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
mysql> quit
Bye
exit
```
上面创建Docker容器时使用的`-v`参数(`--volume`)表示映射数据卷,冒号前是宿主机的目录,冒号后是容器中的目录,这样相当于将宿主机中的目录挂载到了容器中。
3. 创建和配置slave。
```Shell
docker run -d -p 3307:3306 --name mysql57-slave-1 \
-v /root/mysql/conf/slave1:/etc/mysql/mysql.conf.d \
-v /root/mysql/data/slave1:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--link mysql57:mysql57 mysql:5.7
docker exec -it mysql57-slave-1 /bin/bash
```
```Shell
mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.23-log MySQL Community Server (GPL)
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> reset slave;
Query OK, 0 rows affected (0.02 sec)
mysql> change master to master_host='mysql57', master_user='slave', master_password='iamslave', master_log_file='mysql-bin.000003', master_log_pos=590;
Query OK, 0 rows affected, 2 warnings (0.03 sec)
mysql> start slave;
Query OK, 0 rows affected (0.01 sec)
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: mysql57
Master_User: slave
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000001
Read_Master_Log_Pos: 590
Relay_Log_File: f352f05eb9d0-relay-bin.000002
Relay_Log_Pos: 320
Relay_Master_Log_File: mysql-bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 590
Relay_Log_Space: 534
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 1
Master_UUID: 30c38043-ada1-11e8-8fa1-0242ac110002
Master_Info_File: /var/lib/mysql/master.info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.00 sec)
mysql> quit
Bye
exit
```
接下来可以如法炮制配置出slave2和slave3,这样就可以搭建起一个“一主带三从”的主从复制环境。上面创建创建容器时使用的`--link`参数用来配置容器在网络上的主机名(网络地址别名),下一节有这个知识点的介绍。
### Docker
事实上,项目上线中最为麻烦的事情就是配置软件运行环境,环境的差异会给软件的安装和部署带来诸多的麻烦,而Docker正好可以解决这个问题。关于Docker在之前的文档中我们已经介绍过了,接下来我们对Docker的知识做一些必要的补充。
1. 创建镜像文件。
将容器保存成镜像:
```Shell
docker commit -m "..." -a "..." <container-name> jackfrued/<image-name>
```
使用Dockerfile构建镜像:
```Dockerfile
# 指定基础镜像文件
FROM centos:latest
# 指定维护者信息
MAINTAINER jackfrued
# 执行命令
RUN yum -y install gcc
RUN cd ~
RUN mkdir -p project/code
RUN mkdir -p project/logs
# 拷贝文件
COPY ...
# 暴露端口
EXPOSE ...
# 在容器启动时执行命令
CMD ~/init.sh
```
```Shell
docker build -t jackfrued/<image-name> .
```
2. 镜像的导入和导出。
```Shell
docker save -o <file-name>.tar <image-name>:<version>
docker load -i <file-name>.tar
```
3. 推送到DockerHub服务器。
```Shell
docker tag <image-name>:<version> jackfrued/<name>
docker login
docker push jackfrued/<name>
```
4. 容器之间的通信。
```Shell
docker run --link <container-name>:<alias-name>
```
如果我们能够在Docker中完成项目的部署,并且将整个部署好的容器打包成镜像文件进行分发和安装,这样就可以解决项目在多个节点上进行部署时可能遇到的麻烦,而且整个部署可以在很短的时间内完成。
### Supervisor
[Supervisor](https://github.com/Supervisor/supervisor)是一个用Python写的进程管理工具,可以很方便的用来在类Unix系统下启动、重启(自动重启程序)和关闭进程。
1. 安装Supervisor。
```Shell
yum -y install supervisor
```
2. 查看Supervisor的配置文件。
```Shell
vim /etc/supervisord.conf
```
```INI
; 此处省略上面的代码
; The [include] section can just contain the "files" setting. This
; setting can list multiple files (separated by whitespace or
; newlines). It can also contain wildcards. The filenames are
; interpreted as relative to this file. Included files *cannot*
; include files themselves.
[include]
files = supervisord.d/*.ini
```
可以看出自定义的管理配置代码可以放在`/etc/supervisord.d`目录中,并且文件名以`ini`作为后缀即可。
3. 编写管理配置代码。
```Shell
cd /etc/supervisord.d
vim fangtx.ini
```
```INI
```
4. 启动Supervisor服务和查看状态。
```Shell
systemctl start supervisord
supervisorctl status
```
### 其他服务
1. 常用开源软件。
| 功能 | 开源方案 |
| ------------------- | ------------------------- |
| 版本控制工具 | Git、Mercurial、SVN |
| 缺陷管理 | Redmine、Mantis |
| 负载均衡 | Nginx、LVS、HAProxy |
| 邮件服务 | Postfix、Sendmail |
| HTTP服务 | Nginx、Apache |
| 消息队列 | RabbitMQ、ZeroMQ、Redis |
| 文件系统 | FastDFS |
| 基于位置服务(LBS) | MongoDB、Redis |
| 监控服务 | Nagios、Zabbix |
| 关系型数据库 | MySQL、PostgreSQL |
| 非关系型数据库 | MongoDB、Redis、Cassandra |
| 搜索引擎 | ElasticSearch、Solr |
| 缓存服务 | Mamcached、Redis |
2. 常用云服务。
| 功能 | 可用的云服务 |
| -------------- | --------------------------------------- |
| 团队协作工具 | Teambition、钉钉 |
| 代码托管平台 | Github、Gitee、CODING |
| 邮件服务 | SendCloud |
| 云存储(CDN) | 七牛、OSS、LeanCloud、Bmob、又拍云、AWS |
| 移动端推送 | 极光、友盟、百度 |
| 即时通信 | 环信、融云 |
| 短信服务 | 云片、极光、Luosimao、又拍云 |
| 第三方登录 | 友盟、ShareSDK |
| 网站监控和统计 | 阿里云监控、监控宝、百度云观测、小鸟云 |
...@@ -145,19 +145,26 @@ ...@@ -145,19 +145,26 @@
- 使用进程 - fork函数 / multiprocessing模块 / 进程池 / 进程间通信 - 使用进程 - fork函数 / multiprocessing模块 / 进程池 / 进程间通信
- 使用线程 - thread模块 / threading模块 / Thread类 / Lock类 - 使用线程 - thread模块 / threading模块 / Thread类 / Lock类
#### Day14 - [网络编程入门](./Day01-15/Day14/网络编程入门.md) #### Day14-A - [网络编程入门](./Day01-15/Day14/网络编程入门.md)
- 计算机网络基础 - 计算机网络发展史 / “TCP-IP”模型 / IP地址 / 端口 / 协议 / 其他相关概念 - 计算机网络基础 - 计算机网络发展史 / “TCP-IP”模型 / IP地址 / 端口 / 协议 / 其他相关概念
- 网络应用架构 - “客户端-服务器”架构 / “浏览器-服务器”架构 - 网络应用架构 - “客户端-服务器”架构 / “浏览器-服务器”架构
- Python网络编程 - 套接字的概念 / socket模块 / socket函数 / 创建TCP服务器 / 创建TCP客户端 / 创建UDP服务器 / 创建UDP客户端 / SocketServer模块 - Python网络编程 - 套接字的概念 / socket模块 / socket函数 / 创建TCP服务器 / 创建TCP客户端 / 创建UDP服务器 / 创建UDP客户端 / SocketServer模块
#### Day15 - [网络应用开发](./Day01-15/Day15/网络应用开发.md) #### Day14-B - [网络应用开发](./Day01-15/Day15/网络应用开发.md)
- 访问网络API - 网络API概述 / 访问URL / requests模块 / 解析JSON格式数据 - 访问网络API - 网络API概述 / 访问URL / requests模块 / 解析JSON格式数据
- 文件传输 - FTP协议 / ftplib模块 / 交互式FTP应用 - 文件传输 - FTP协议 / ftplib模块 / 交互式FTP应用
- 电子邮件 - SMTP协议 / POP3协议 / IMAP协议 / smtplib模块 / poplib模块 / imaplib模块 - 电子邮件 - SMTP协议 / POP3协议 / IMAP协议 / smtplib模块 / poplib模块 / imaplib模块
- 短信服务 - twilio模块 / 国内的短信服务 - 短信服务 - twilio模块 / 国内的短信服务
#### Day15 - 图像和文档处理
- 用Pillow处理图片
- 读写Word文档
- 读写Excel文件
- 生成PDF文件
### Day16~Day20 - [Python语言进阶 ](./Day16-20/Python语言进阶.md) ### Day16~Day20 - [Python语言进阶 ](./Day16-20/Python语言进阶.md)
- 常用数据结构 - 常用数据结构
......
## 玩转PyCharm(上) ## 玩转PyCharm
PyCharm是由JetBrains公司开发的提供给Python专业的开发者的一个集成开发环境,它最大的优点是能够大大提升Python开发者的工作效率,为开发者集成了很多用起来非常顺手的功能,包括代码调试、高亮语法、代码跳转、智能提示、自动补全、单元测试、版本控制等等。此外,PyCharm还提供了对一些高级功能的支持,包括支持基于Django框架的Web开发、。 PyCharm是由JetBrains公司开发的提供给Python专业的开发者的一个集成开发环境,它最大的优点是能够大大提升Python开发者的工作效率,为开发者集成了很多用起来非常顺手的功能,包括代码调试、高亮语法、代码跳转、智能提示、自动补全、单元测试、版本控制等等。此外,PyCharm还提供了对一些高级功能的支持,包括支持基于Django框架的Web开发、。
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment