Jelajahi Sumber

更新了部分文档和代码

jackfrued 7 tahun lalu
induk
melakukan
321278297f
63 mengubah file dengan 1953 tambahan dan 865 penghapusan
  1. 80 69
      Day01-15/Day01/code/peppa_pig.py
  2. 23 26
      Day01-15/Day01/初识Python.md
  3. 0 0
      Day01-15/Day02/语言元素.md
  4. 1 9
      Day01-15/Day03/分支结构.md
  5. 1 8
      Day01-15/Day04/循环结构.md
  6. 10 6
      Day01-15/Day06/函数和模块的使用.md
  7. 0 6
      Day01-15/Day13/进程和线程.md
  8. 0 0
      Day01-15/Day14-A/code/chatclient.py
  9. 0 0
      Day01-15/Day14-A/code/chatserver.py
  10. 0 0
      Day01-15/Day14-A/code/fileclient.py
  11. 0 0
      Day01-15/Day14-A/code/fileserver.py
  12. 0 0
      Day01-15/Day14-A/code/guido.jpg
  13. 0 0
      Day01-15/Day14-A/code/mmdownloader.py
  14. 0 0
      Day01-15/Day14-A/code/socket1.py
  15. 0 0
      Day01-15/Day14-A/code/socket2.py
  16. 0 0
      Day01-15/Day14-A/code/socket3.py
  17. 0 0
      Day01-15/Day14-A/code/socket4.py
  18. 0 0
      Day01-15/Day14-A/code/socket5.py
  19. 0 0
      Day01-15/Day14-A/code/timeclient.py
  20. 0 0
      Day01-15/Day14-A/code/timeserver.py
  21. 0 0
      Day01-15/Day14-A/res/TCP-IP-model.png
  22. 0 0
      Day01-15/Day14-A/res/after-browser.jpg
  23. 0 0
      Day01-15/Day14-A/res/arpanet.png
  24. 0 0
      Day01-15/Day14-A/res/before-browser.jpg
  25. 0 0
      Day01-15/Day14-A/res/browers.jpg
  26. 0 0
      Day01-15/Day14-A/res/browser-market-place.jpeg
  27. 0 0
      Day01-15/Day14-A/res/how-data-is-processed.jpg
  28. 0 0
      Day01-15/Day14-A/res/osi_rm.gif
  29. 0 0
      Day01-15/Day14-A/res/osimodel.png
  30. 0 0
      Day01-15/Day14-A/res/tcpipprotocols.png
  31. 0 0
      Day01-15/Day14-A/res/telnet.png
  32. 0 5
      Day01-15/Day14-A/网络编程入门.md
  33. 0 4
      Day01-15/Day14-B/网络应用开发.md
  34. 0 0
      Day01-15/Day15/code/excel1.py
  35. 0 0
      Day01-15/Day15/code/excel2.py
  36. 0 0
      Day01-15/Day15/code/pdf1.py
  37. 0 0
      Day01-15/Day15/code/pdf2.py
  38. 0 0
      Day01-15/Day15/code/pillow1.py
  39. 0 0
      Day01-15/Day15/code/res/Python课程大纲.pdf
  40. 0 0
      Day01-15/Day15/code/res/guido.jpg
  41. 0 0
      Day01-15/Day15/code/res/学生明细表.xlsx
  42. 0 0
      Day01-15/Day15/code/res/用函数还是用复杂的表达式.docx
  43. 0 0
      Day01-15/Day15/code/word1.py
  44. 0 0
      Day01-15/Day15/code/word2.py
  45. 0 0
      Day01-15/Day15/图像和办公文档处理.md
  46. 1038 644
      Day16-20/Python语言进阶.md
  47. 0 4
      Day31-35/玩转Linux操作系统.md
  48. 0 9
      Day66-75/01.网络爬虫和相关工具.md
  49. 0 3
      Day66-75/02.数据采集和解析.md
  50. 23 36
      Day66-75/03.存储数据.md
  51. 1 13
      Day66-75/04.并发下载.md
  52. 2 9
      Day66-75/05.解析动态内容.md
  53. 0 11
      Day66-75/07.Scrapy入门.md
  54. 764 0
      Day91-100/项目部署上线指南.md
  55. 9 2
      README.md
  56. TEMPAT SAMPAH
      res/01.django_single_server.png
  57. TEMPAT SAMPAH
      res/02.django_dedicated_db_server.png
  58. TEMPAT SAMPAH
      res/03.django_dedicated_static_server.png
  59. TEMPAT SAMPAH
      res/04.django_load_balance.png
  60. TEMPAT SAMPAH
      res/05.django_massive_cluster.png
  61. TEMPAT SAMPAH
      res/django_request_response_cycle.png
  62. TEMPAT SAMPAH
      res/hadoop_ecosystem.png
  63. 1 1
      玩转PyCharm.md

+ 80 - 69
Day01-15/Day01/code/peppa_pig.py

@@ -1,93 +1,101 @@
-from turtle import*
+"""
+绘制小猪佩奇
+"""
+from turtle import *
 
 
-def nose(x,y):#鼻子
-    penup()#提起笔
-    goto(x,y)#定位
-    pendown()#落笔,开始画
-    setheading(-30)#将乌龟的方向设置为to_angle/为数字(0-东、90-北、180-西、270-南)
-    begin_fill()#准备开始填充图形
-    a=0.4
+def nose(x,y):
+    """画鼻子"""
+    penup()
+    # 将海龟移动到指定的坐标
+    goto(x,y)
+    pendown()
+    # 设置海龟的方向(0-东、90-北、180-西、270-南)
+    setheading(-30)
+    begin_fill()
+    a = 0.4
     for i in range(120):
-        if 0<=i<30 or 60<=i<90:
-            a=a+0.08
-            left(3) #向左转3度
-            forward(a) #向前走a的步长
+        if 0 <= i < 30 or 60 <= i <90:
+            a = a + 0.08
+            # 向左转3度
+            left(3)
+            # 向前走
+            forward(a)
         else:
-            a=a-0.08
+            a = a - 0.08
             left(3)
             forward(a)
-    end_fill()#填充完成
-
+    end_fill()
     penup()
     setheading(90)
     forward(25)
     setheading(0)
     forward(10)
     pendown()
-    pencolor(255,155,192)#画笔颜色
+    # 设置画笔的颜色(红, 绿, 蓝)
+    pencolor(255, 155, 192)
     setheading(10)
     begin_fill()
     circle(5)
-    color(160,82,45)#返回或设置pencolor和fillcolor
+    color(160, 82, 45)
     end_fill()
-
     penup()
     setheading(0)
     forward(20)
     pendown()
-    pencolor(255,155,192)
+    pencolor(255, 155, 192)
     setheading(10)
     begin_fill()
     circle(5)
-    color(160,82,45)
+    color(160, 82, 45)
     end_fill()
 
 
-def head(x,y):#头
-    color((255,155,192),"pink")
+def head(x, y):
+    """画头"""
+    color((255, 155, 192), "pink")
     penup()
     goto(x,y)
     setheading(0)
     pendown()
     begin_fill()
     setheading(180)
-    circle(300,-30)
-    circle(100,-60)
-    circle(80,-100)
-    circle(150,-20)
-    circle(60,-95)
+    circle(300, -30)
+    circle(100, -60)
+    circle(80, -100)
+    circle(150, -20)
+    circle(60, -95)
     setheading(161)
-    circle(-300,15)
+    circle(-300, 15)
     penup()
-    goto(-100,100)
+    goto(-100, 100)
     pendown()
     setheading(-30)
-    a=0.4
+    a = 0.4
     for i in range(60):
-        if 0<=i<30 or 60<=i<90:
-            a=a+0.08
+        if 0<= i < 30 or 60 <= i < 90:
+            a = a + 0.08
             lt(3) #向左转3度
             fd(a) #向前走a的步长
         else:
-            a=a-0.08
+            a = a - 0.08
             lt(3)
             fd(a)
     end_fill()
 
 
-def ears(x,y): #耳朵
-    color((255,155,192),"pink")
+def ears(x,y):
+    """画耳朵"""
+    color((255, 155, 192), "pink")
     penup()
-    goto(x,y)
+    goto(x, y)
     pendown()
     begin_fill()
     setheading(100)
-    circle(-50,50)
-    circle(-10,120)
-    circle(-50,54)
+    circle(-50, 50)
+    circle(-10, 120)
+    circle(-50, 54)
     end_fill()
-
     penup()
     setheading(90)
     forward(-12)
@@ -96,14 +104,15 @@ def ears(x,y): #耳朵
     pendown()
     begin_fill()
     setheading(100)
-    circle(-50,50)
-    circle(-10,120)
-    circle(-50,56)
+    circle(-50, 50)
+    circle(-10, 120)
+    circle(-50, 56)
     end_fill()
 
 
-def eyes(x,y):#眼睛
-    color((255,155,192),"white")
+def eyes(x,y):
+    """画眼睛"""
+    color((255, 155, 192), "white")
     penup()
     setheading(90)
     forward(-20)
@@ -113,7 +122,6 @@ def eyes(x,y):#眼睛
     begin_fill()
     circle(15)
     end_fill()
-
     color("black")
     penup()
     setheading(90)
@@ -124,8 +132,7 @@ def eyes(x,y):#眼睛
     begin_fill()
     circle(3)
     end_fill()
-
-    color((255,155,192),"white")
+    color((255, 155, 192), "white")
     penup()
     seth(90)
     forward(-25)
@@ -135,7 +142,6 @@ def eyes(x,y):#眼睛
     begin_fill()
     circle(15)
     end_fill()
-
     color("black")
     penup()
     setheading(90)
@@ -148,8 +154,9 @@ def eyes(x,y):#眼睛
     end_fill()
 
 
-def cheek(x,y):#腮
-    color((255,155,192))
+def cheek(x,y):
+    """画脸颊"""
+    color((255, 155, 192))
     penup()
     goto(x,y)
     pendown()
@@ -159,35 +166,39 @@ def cheek(x,y):#腮
     end_fill()
 
 
-def mouth(x,y): #嘴
-    color(239,69,19)
+def mouth(x,y):
+    """画嘴巴"""
+    color(239, 69, 19)
     penup()
-    goto(x,y)
+    goto(x, y)
     pendown()
     setheading(-80)
-    circle(30,40)
-    circle(40,80)
+    circle(30, 40)
+    circle(40, 80)
 
 
-def setting():          #参数设置
+def setting():
+    """设置参数"""
     pensize(4)
-    hideturtle()        #使乌龟无形(隐藏)
-    colormode(255)      #将其设置为1.0或255.随后 颜色三元组的r,g,b值必须在0 .. cmode范围内
-    color((255,155,192),"pink")
-    setup(840,500)
+    # 隐藏海龟
+    hideturtle()
+    colormode(255)
+    color((255, 155, 192), "pink")
+    setup(840, 500)
     speed(10)
 
 
 def main():
-    setting()           #画布、画笔设置
-    nose(-100,100)      #鼻子
-    head(-69,167)       #头
-    ears(0,160)         #耳朵
-    eyes(0,140)         #眼睛
-    cheek(80,10)        #腮
-    mouth(-20,30)       #嘴
+    """主函数"""
+    setting() 
+    nose(-100, 100)
+    head(-69, 167)
+    ears(0, 160)
+    eyes(0, 140)
+    cheek(80, 10)
+    mouth(-20, 30)
     done()
 
 
 if __name__ == '__main__':
-	main()
+    main()

+ 23 - 26
Day01-15/Day01/初识Python.md

@@ -7,38 +7,38 @@
 1. 1989年圣诞节:Guido von Rossum开始写Python语言的编译器。
 2. 1991年2月:第一个Python编译器(同时也是解释器)诞生,它是用C语言实现的(后面又出现了Java和C#实现的版本Jython和IronPython,以及PyPy、Brython、Pyston等其他实现),可以调用C语言的库函数。在最早的版本中,Python已经提供了对“类”,“函数”,“异常处理”等构造块的支持,同时提供了“列表”和“字典”等核心数据类型,同时支持以模块为基础的拓展系统。
 3. 1994年1月:Python 1.0正式发布。
-4. 2000年10月16日:Python 2.0发布,增加了实现完整的[垃圾回收](https://zh.wikipedia.org/wiki/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%B8)),并且支持[Unicode](https://zh.wikipedia.org/wiki/Unicode)。与此同时,Python的整个开发过程更加透明,社区对开发进度的影响逐渐扩大,生态圈开始慢慢形成。
-5. 2008年12月3日:Python 3.0发布,此版不完全兼容之前的Python代码,不过很多新特性后来也被移植到旧的Python 2.6/2.7版本,因为目前还有公司在项目和运维中使用Python 2.x版本的代码
+4. 2000年10月16日:Python 2.0发布,增加了实现完整的[垃圾回收](https://zh.wikipedia.org/wiki/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%B8)),提供了对[Unicode](https://zh.wikipedia.org/wiki/Unicode)的支持。与此同时,Python的整个开发过程更加透明,社区对开发进度的影响逐渐扩大,生态圈开始慢慢形成。
+5. 2008年12月3日:Python 3.0发布,它并不完全兼容之前的Python代码,不过因为目前还有不少公司在项目和运维中使用Python 2.x版本,所以Python 3.x的很多新特性后来也被移植到Python 2.6/2.7版本中
 
-目前我们使用的Python 3.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的优点很多,简单的可以总结为以下几点。
 
 1. 简单和明确,做一件事只有一种方法。
-2. 学习曲线低,与其他很多语言比上手更容易
+2. 学习曲线低,跟其他很多语言相比,Python更容易上手
 3. 开放源代码,拥有强大的社区和生态圈。
-4. 解释型语言,完美的平台可移植性。
-5. 支持两种主流的编程范式,可以使用面向对象和函数式编程
-6. 可扩展性和可嵌入性,可以调用C/C++代码也可以在C/C++中调用。
+4. 解释型语言,天生具有平台可移植性。
+5. 支持两种主流的编程范式(面向对象编程和函数式编程)都提供了支持
+6. 可扩展性和可嵌入性,可以调用C/C++代码也可以在C/C++中调用Python
 7. 代码规范程度高,可读性强,适合有代码洁癖和强迫症的人群。
 
 Python的缺点主要集中在以下几点。
 
-1. 执行效率低,因此计算密集型任务可以由C/C++编写。
-2. 代码无法加密,但是现在的公司很多都不是卖软件而是卖服务,这个问题慢慢会淡化。
-3. 在开发时可以选择的框架太多,有选择的地方就有错误。
+1. 执行效率低,因此计算密集型任务可以由C/C++编写。
+2. 代码无法加密,但是现在的公司很多都不是卖软件而是卖服务,这个问题会淡化。
+3. 在开发时可以选择的框架太多(如Web框架就有100多个),有选择的地方就有错误。
 
 #### Python的应用领域
 
-目前Python在云基础设施、DevOps、网络爬虫开发、数据分析挖掘、机器学习等领域都有着广泛的应用,因此也产生了服务器开发、数据接口开发、自动化运维、科学计算和数据可视化、聊天机器人开发、图像识别和处理等一系列的职位。
+目前Python在云基础设施、DevOps、网络爬虫开发、数据分析挖掘、机器学习等领域都有着广泛的应用,因此也产生了Web后端开发、数据接口开发、自动化运维、自动化测试、科学计算和可视化、数据分析、量化交易、机器人开发、图像识别和处理等一系列的职位。
 
 ### 搭建编程环境
 
 #### 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环境
 
@@ -53,15 +53,15 @@ yum -y install wget gcc zlib-devel bzip2-devel openssl-devel ncurses-devel sqlit
 下载Python源代码并解压缩到指定目录。
 
 ```Shell
-wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tar.xz
-xz -d Python-3.7.0.tar.xz
-tar -xvf Python-3.7.0.tar
+wget https://www.python.org/ftp/python/3.7.0/Python-3.7.1.tar.xz
+xz -d Python-3.7.1.tar.xz
+tar -xvf Python-3.7.1.tar
 ```
 
 切换至Python源代码目录并执行下面的命令进行配置和安装。
 
 ```Shell
-cd Python-3.7.0
+cd Python-3.7.1
 ./configure --prefix=/usr/local/python37 --enable-optimizations
 make && make install
 ```
@@ -87,7 +87,7 @@ source .bash_profile
 
 #### 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程序
 
@@ -137,7 +137,6 @@ python hello.py
 
 Version: 0.1
 Author: 骆昊
-Date: 2018-02-26
 """
 
 print('hello, world!')
@@ -179,8 +178,6 @@ python -m pip install ipython jupyter
 jupyter notebook
 ```
 
-![](./res/python-jupyter-1.png)
-
 ![](./res/python-jupyter-2.png)
 
 #### Sublime - 文本编辑神器
@@ -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')
   ```
 
-- 安装插件。通过Preference菜单的Package Control或快捷键Ctrl+Shift+P打开命令面板,在面板中输入Install Package就可以找到安装插件的工具,然后再查找需要的插件。我们推荐大家安装以下几个插件
+- 安装插件。通过Preference菜单的Package Control或快捷键Ctrl+Shift+P打开命令面板,在面板中输入Install Package就可以找到安装插件的工具,然后再查找需要的插件。我们推荐大家安装以下几个插件
 
-  - SublimeCodeIntel - 代码自动补全工具插件
-  - Emmet - 前端开发代码模板插件
-  - Git - 版本控制工具插件
-  - Python PEP8 Autoformat - PEP8规范自动格式化插件
-  - ConvertToUTF8 - 将本地编码转换为UTF-8
+  - SublimeCodeIntel - 代码自动补全工具插件
+  - Emmet - 前端开发代码模板插件
+  - Git - 版本控制工具插件
+  - Python PEP8 Autoformat - PEP8规范自动格式化插件
+  - ConvertToUTF8 - 将本地编码转换为UTF-8
 
 #### PyCharm - Python开发神器
 

File diff ditekan karena terlalu besar
+ 0 - 0
Day01-15/Day02/语言元素.md


+ 1 - 9
Day01-15/Day03/分支结构.md

@@ -14,7 +14,6 @@
 
 Version: 0.1
 Author: 骆昊
-Date: 2018-02-28
 """
 
 username = input('请输入用户名: ')
@@ -44,7 +43,6 @@ f(x) =  x + 2   (-1 <= x <= 1)
 
 Version: 0.1
 Author: 骆昊
-Date: 2018-02-28
 """
 
 x = float(input('x = '))
@@ -68,7 +66,6 @@ f(x) =	x + 2	(-1 <= x <= 1)
 
 Version: 0.1
 Author: 骆昊
-Date: 2018-02-28
 """
 
 x = float(input('x = '))
@@ -94,7 +91,6 @@ print('f(%.2f) = %.2f' % (x, y))
 
 Version: 0.1
 Author: 骆昊
-Date: 2018-02-28
 """
 
 value = float(input('请输入长度: '))
@@ -115,7 +111,6 @@ else:
 
 Version: 0.1
 Author: 骆昊
-Date: 2018-02-28
 """
 
 from random import randint
@@ -150,7 +145,6 @@ print(result)
 
 Version: 0.1
 Author: 骆昊
-Date: 2018-02-28
 """
 
 score = float(input('请输入成绩: '))
@@ -175,7 +169,6 @@ print('对应的等级是:', grade)
 
 Version: 0.1
 Author: 骆昊
-Date: 2018-02-28
 """
 
 import math
@@ -193,7 +186,7 @@ else:
 ```
 > **说明:**上面的代码中使用了`math`模块的`sqrt`函数来计算平方根。用边长计算三角形面积的公式叫做[海伦公式](https://zh.wikipedia.org/zh-hans/海伦公式)。
 
-#### 练习5:实现一个个人所得税计算器。
+#### 练习5:个人所得税计算器。
 
 ```Python
 """
@@ -201,7 +194,6 @@ else:
 
 Version: 0.1
 Author: 骆昊
-Date: 2018-02-28
 """
 
 salary = float(input('本月收入: '))

+ 1 - 8
Day01-15/Day04/循环结构.md

@@ -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循环
 
@@ -14,7 +14,6 @@
 
 Version: 0.1
 Author: 骆昊
-Date: 2018-03-01
 """
 
 sum = 0
@@ -37,7 +36,6 @@ print(sum)
 
 Version: 0.1
 Author: 骆昊
-Date: 2018-03-01
 """
 
 sum = 0
@@ -54,7 +52,6 @@ print(sum)
 
 Version: 0.1
 Author: 骆昊
-Date: 2018-03-01
 """
 
 sum = 0
@@ -77,7 +74,6 @@ print(sum)
 
 Version: 0.1
 Author: 骆昊
-Date: 2018-03-01
 """
 
 import random
@@ -109,8 +105,6 @@ if counter > 7:
 
 Version: 0.1
 Author: 骆昊
-Date: 2018-03-01
-
 """
 
 for i in range(1, 10):
@@ -196,7 +190,6 @@ for factor in range(x, 0, -1):
 
 Version: 0.1
 Author: 骆昊
-Date: 2018-03-01
 """
 
 row = int(input('请输入行数: '))

+ 10 - 6
Day01-15/Day06/函数和模块的使用.md

@@ -27,7 +27,6 @@ fmn = 1
 for num in range(1, m - n + 1):
     fmn *= num
 print(fm // fn // fmn)
-
 ```
 
 ### 函数的作用
@@ -134,7 +133,8 @@ def foo():
 	print('goodbye, world!')
 
 
-foo()	# 输出goodbye, world!
+# 下面的代码会输出什么呢?
+foo()
 ```
 
 当然上面的这种情况我们很容易就能避免,但是如果项目是由多人协作进行团队开发的时候,团队中可能有多个程序员都定义了名为`foo`的函数,那么怎么解决这种命名冲突呢?答案其实很简单,Python中每个文件就代表了一个模块(module),我们在不同的模块中可以有同名的函数,在使用函数的时候我们通过`import`关键字导入指定的模块就可以区分到底要使用的是哪个模块中的`foo`函数,代码如下所示。
@@ -158,11 +158,13 @@ test.py
 ```Python
 from module1 import foo
 
-foo()	# 输出hello, world!
+# 输出hello, world!
+foo()
 
 from module2 import foo
 
-foo()	# 输出goodbye, world!
+# 输出goodbye, world!
+foo()
 ```
 
 也可以按照如下所示的方式来区分到底要使用哪一个`foo`函数。
@@ -185,7 +187,8 @@ test.py
 from module1 import foo
 from module2 import foo
 
-foo()	# 输出goodbye, world!
+# 输出goodbye, world!
+foo()
 ```
 
 test.py
@@ -194,7 +197,8 @@ test.py
 from module2 import foo
 from module1 import foo
 
-foo()	# 输出hello, world!
+# 输出hello, world!
+foo()
 ```
 
 需要说明的是,如果我们导入的模块除了定义函数之外还中有可以执行代码,那么Python解释器在导入这个模块时就会执行这些代码,事实上我们可能并不希望如此,因此如果我们在模块中编写了执行代码,最好是将这些执行代码放入如下所示的条件中,这样的话除非直接运行该模块,if条件下的这些代码是不会执行的,因为只有直接执行的模块的名字才是“\_\_main\_\_”。

+ 0 - 6
Day01-15/Day13/进程和线程.md

@@ -42,7 +42,6 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 
 下面是运行程序得到的一次运行结果。
@@ -86,7 +85,6 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 
 在上面的代码中,我们通过`Process`类创建了进程对象,通过`target`参数我们传入一个函数来表示进程启动后要执行的代码,后面的`args`是一个元组,它代表了传递给函数的参数。`Process`对象的`start`方法用来启动进程,而`join`方法表示等待进程执行结束。运行上面的代码可以明显发现两个下载任务“同时”启动了,而且程序的执行时间将大大缩短,不再是两个任务的时间总和。下面是程序的一次执行结果。
@@ -375,7 +373,6 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 
 如果使用多线程将耗时间的任务放到一个独立的线程中执行,这样就不会因为执行耗时间的任务而阻塞了主线程,修改后的代码如下所示。
@@ -424,7 +421,6 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 
 #### 例子2:使用多进程对复杂任务进行“分而治之”。
@@ -448,7 +444,6 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 
 在上面的代码中,我故意先去创建了一个列表容器然后填入了100000000个数,这一步其实是比较耗时间的,所以为了公平起见,当我们将这个任务分解到8个进程中去执行的时候,我们暂时也不考虑列表切片操作花费的时间,只是把做运算和合并运算结果的时间统计出来,代码如下所示。
@@ -493,7 +488,6 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 
 比较两段代码的执行结果(在我目前使用的MacBook上,上面的代码需要大概6秒左右的时间,而下面的代码只需要不到1秒的时间,再强调一次我们只是比较了运算的时间,不考虑列表创建及切片操作花费的时间),使用多进程后由于获得了更多的CPU执行时间以及更好的利用了CPU的多核特性,明显的减少了程序的执行时间,而且计算量越大效果越明显。当然,如果愿意还可以将多个进程部署在不同的计算机上,做成分布式进程,具体的做法就是通过multiprocessing.managers模块中提供的管理器将`Queue`对象通过网络共享出来(注册到网络上让其他计算机可以访问),这部分内容也留到爬虫的专题再进行讲解。

+ 0 - 0
Day01-15/Day14/code/chatclient.py → Day01-15/Day14-A/code/chatclient.py


+ 0 - 0
Day01-15/Day14/code/chatserver.py → Day01-15/Day14-A/code/chatserver.py


+ 0 - 0
Day01-15/Day14/code/fileclient.py → Day01-15/Day14-A/code/fileclient.py


+ 0 - 0
Day01-15/Day14/code/fileserver.py → Day01-15/Day14-A/code/fileserver.py


+ 0 - 0
Day01-15/Day14/code/guido.jpg → Day01-15/Day14-A/code/guido.jpg


+ 0 - 0
Day01-15/Day14/code/mmdownloader.py → Day01-15/Day14-A/code/mmdownloader.py


+ 0 - 0
Day01-15/Day14/code/socket1.py → Day01-15/Day14-A/code/socket1.py


+ 0 - 0
Day01-15/Day14/code/socket2.py → Day01-15/Day14-A/code/socket2.py


+ 0 - 0
Day01-15/Day14/code/socket3.py → Day01-15/Day14-A/code/socket3.py


+ 0 - 0
Day01-15/Day14/code/socket4.py → Day01-15/Day14-A/code/socket4.py


+ 0 - 0
Day01-15/Day14/code/socket5.py → Day01-15/Day14-A/code/socket5.py


+ 0 - 0
Day01-15/Day14/code/timeclient.py → Day01-15/Day14-A/code/timeclient.py


+ 0 - 0
Day01-15/Day14/code/timeserver.py → Day01-15/Day14-A/code/timeserver.py


+ 0 - 0
Day01-15/Day14/res/TCP-IP-model.png → Day01-15/Day14-A/res/TCP-IP-model.png


+ 0 - 0
Day01-15/Day14/res/after-browser.jpg → Day01-15/Day14-A/res/after-browser.jpg


+ 0 - 0
Day01-15/Day14/res/arpanet.png → Day01-15/Day14-A/res/arpanet.png


+ 0 - 0
Day01-15/Day14/res/before-browser.jpg → Day01-15/Day14-A/res/before-browser.jpg


+ 0 - 0
Day01-15/Day14/res/browers.jpg → Day01-15/Day14-A/res/browers.jpg


+ 0 - 0
Day01-15/Day14/res/browser-market-place.jpeg → Day01-15/Day14-A/res/browser-market-place.jpeg


+ 0 - 0
Day01-15/Day14/res/how-data-is-processed.jpg → Day01-15/Day14-A/res/how-data-is-processed.jpg


+ 0 - 0
Day01-15/Day14/res/osi_rm.gif → Day01-15/Day14-A/res/osi_rm.gif


+ 0 - 0
Day01-15/Day14/res/osimodel.png → Day01-15/Day14-A/res/osimodel.png


+ 0 - 0
Day01-15/Day14/res/tcpipprotocols.png → Day01-15/Day14-A/res/tcpipprotocols.png


+ 0 - 0
Day01-15/Day14/res/telnet.png → Day01-15/Day14-A/res/telnet.png


+ 0 - 5
Day01-15/Day14/网络编程入门.md → Day01-15/Day14-A/网络编程入门.md

@@ -126,7 +126,6 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 
 ### 基于传输层协议的套接字编程
@@ -174,7 +173,6 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 
 运行服务器程序后我们可以通过Windows系统的telnet来访问该服务器,结果如下图所示。
@@ -203,7 +201,6 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 
 需要注意的是,上面的服务器并没有使用多线程或者异步I/O的处理方式,这也就意味着当服务器与一个客户端处于通信状态时,其他的客户端只能排队等待。很显然,这样的服务器并不能满足我们的需求,我们需要的服务器是能够同时接纳和处理多个用户请求的。下面我们来设计一个使用多线程技术处理多个用户请求的服务器,该服务器会向连接到服务器的客户端发送一张图片。
@@ -256,7 +253,6 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 
 客户端代码:
@@ -291,7 +287,6 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 
 在这个案例中,我们使用了JSON作为数据传输的格式(通过JSON格式对传输的数据进行了序列化和反序列化的操作),但是JSON并不能携带二进制数据,因此对图片的二进制数据进行了Base64编码的处理。Base64是一种用64个字符表示所有二进制数据的编码方式,通过将二进制数据每6位一组的方式重新组织,刚好可以使用0~9的数字、大小写字母以及“+”和“/”总共64个字符表示从`000000`到`111111`的64种状态。[维基百科](https://zh.wikipedia.org/wiki/Base64)上有关于Base64编码的详细讲解,不熟悉Base64的读者可以自行阅读。

+ 0 - 4
Day01-15/Day15/网络应用开发.md → Day01-15/Day14-B/网络应用开发.md

@@ -31,7 +31,6 @@ def main():
 
 if __name__ == '__main__':
 	main()
-
 ```
 
 如果要发送带有附件的邮件,那么可以按照下面的方式进行操作。
@@ -88,7 +87,6 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 
 ### 发送短信
@@ -119,7 +117,5 @@ def main():
 
 if __name__ == '__main__':
     main()
-
-
 ```
 

+ 0 - 0
Day01-15/图像和办公文档/code/excel1.py → Day01-15/Day15/code/excel1.py


+ 0 - 0
Day01-15/图像和办公文档/code/excel2.py → Day01-15/Day15/code/excel2.py


+ 0 - 0
Day01-15/图像和办公文档/code/pdf1.py → Day01-15/Day15/code/pdf1.py


+ 0 - 0
Day01-15/图像和办公文档/code/pdf2.py → Day01-15/Day15/code/pdf2.py


+ 0 - 0
Day01-15/图像和办公文档/code/pillow1.py → Day01-15/Day15/code/pillow1.py


+ 0 - 0
Day01-15/图像和办公文档/code/res/Python课程大纲.pdf → Day01-15/Day15/code/res/Python课程大纲.pdf


+ 0 - 0
Day01-15/图像和办公文档/code/res/guido.jpg → Day01-15/Day15/code/res/guido.jpg


+ 0 - 0
Day01-15/图像和办公文档/code/res/学生明细表.xlsx → Day01-15/Day15/code/res/学生明细表.xlsx


+ 0 - 0
Day01-15/图像和办公文档/code/res/用函数还是用复杂的表达式.docx → Day01-15/Day15/code/res/用函数还是用复杂的表达式.docx


+ 0 - 0
Day01-15/图像和办公文档/code/word1.py → Day01-15/Day15/code/word1.py


+ 0 - 0
Day01-15/图像和办公文档/code/word2.py → Day01-15/Day15/code/word2.py


+ 0 - 0
Day01-15/图像和办公文档/图像和办公文档处理.md → Day01-15/Day15/图像和办公文档处理.md


+ 1038 - 644
Day16-20/Python语言进阶.md

@@ -1,644 +1,1038 @@
-## Python语言进阶
-
-### 数据结构和算法
-
-#### 排序算法(冒泡和归并)
-
-```Python
-def bubble_sort(items, comp=lambda x, y: x > y):
-    """高质量冒泡排序(搅拌排序)"""
-    for i in range(len(items) - 1):
-        swapped = False
-        for j in range(len(items) - 1 - i):
-            if comp(items[j], items[j + 1]):
-                items[j], items[j + 1] = items[j + 1], items[j]
-                swapped = True
-        if swapped:
-            swapped = False
-            for j in range(len(items) - 2 - i, i, -1):
-                if comp(items[j - 1], items[j]):
-                    items[j], items[j - 1] = items[j - 1], items[j]
-                    swapped = True
-        if not swapped:
-            break
-```
-
-```Python
-def merge_sort(items, comp=lambda x, y: x <= y):
-    """归并排序(分治法)"""
-    if len(items) < 2:
-        return items[:]
-    mid = len(items) // 2
-    left = merge_sort(items[:mid], comp)
-    right = merge_sort(items[mid:], comp)
-    return merge(left, right, comp)
-
-
-def merge(items1, items2, comp=lambda x, y: x <= y):
-    """合并(将两个有序的列表合并成一个有序的列表)"""
-    items = []
-    idx1, idx2 = 0, 0
-    while idx1 < len(items1) and idx2 < len(items2):
-        if comp(items1[idx1], items2[idx2]):
-            items.append(items1[idx1])
-            idx1 += 1
-        else:
-            items.append(items2[idx2])
-            idx2 += 1
-    items += items1[idx1:]
-    items += items2[idx2:]
-    return items
-```
-
-```Python
-def seq_search(items, key):
-    """顺序查找"""
-    for index, item in enumerate(items):
-        if item == key:
-            return index
-    return -1
-```
-#### 查找算法(顺序和折半)
-
-```Python
-def bin_search(items, key):
-    """折半查找(循环实现)"""
-    start, end = 0, len(items) - 1
-    while start <= end:
-        mid = (start + end) // 2
-        if key > items[mid]:
-            start = mid + 1
-        elif key < items[mid]:
-            end = mid - 1
-        else:
-            return mid
-    return -1
-```
-
-#### 使用生成式(推导式)
-
-```Python
-prices = {
-    'AAPL': 191.88,
-    'GOOG': 1186.96,
-    'IBM': 149.24,
-    'ORCL': 48.44,
-    'ACN': 166.89,
-    'FB': 208.09,
-    'SYMC': 21.29
-}
-# 用股票价格大于100元的股票构造一个新的字典
-prices2 = {key: value for key, value in prices.items() if value > 100}
-print(prices2)
-```
-
-#### 嵌套的列表
-
-```Python
-def main():
-    names = ['关羽', '张飞', '赵云', '马超', '黄忠']
-    courses = ['语文', '数学', '英语']
-    # 录入五个学生三门课程的成绩
-    # 错误 - 参考http://pythontutor.com/visualize.html#mode=edit
-    # scores = [[None] * len(courses)] * len(names)
-    scores = [[None] * len(courses) for _ in range(len(names))]
-    for row, name in enumerate(names):
-        for col, course in enumerate(courses):
-            scores[row][col] = float(input(f'请输入{name}的{course}成绩: '))
-    print(scores)
-
-
-if __name__ == '__main__':
-    main()
-```
-
-[Python Tutor](http://pythontutor.com/) - VISUALIZE CODE AND GET LIVE HELP
-
-#### heapq、itertools等的用法
-
-```Python
-"""
-从列表中找出最大的或最小的N个元素
-"""
-import heapq
-
-
-def main():
-    list1 = [34, 25, 12, 99, 87, 63, 58, 78, 88, 92]
-    list2 = [
-        {'name': 'IBM', 'shares': 100, 'price': 91.1},
-        {'name': 'AAPL', 'shares': 50, 'price': 543.22},
-        {'name': 'FB', 'shares': 200, 'price': 21.09},
-        {'name': 'HPQ', 'shares': 35, 'price': 31.75},
-        {'name': 'YHOO', 'shares': 45, 'price': 16.35},
-        {'name': 'ACME', 'shares': 75, 'price': 115.65}
-    ]
-    print(heapq.nlargest(3, list1))
-    print(heapq.nsmallest(3, list1))
-    print(heapq.nlargest(2, list2, key=lambda x: x['price']))
-    print(heapq.nlargest(2, list2, key=lambda x: x['shares']))
-
-
-if __name__ == '__main__':
-    main()
-```
-
-```Python
-"""
-排列 / 组合 / 笛卡尔积
-"""
-import itertools
-
-
-def main():
-    for val in itertools.permutations('ABCD'):
-        print(val)
-    print('-' * 50)
-    for val in itertools.combinations('ABCDE', 3):
-        print(val)
-    print('-' * 50)
-    for val in itertools.product('ABCD', '123'):
-        print(val)
-
-
-if __name__ == '__main__':
-    main()
-```
-
-#### collections模块下的工具类
-
-```Python
-"""
-找出序列中出现次数最多的元素
-"""
-from collections import Counter
-
-
-def main():
-    words = [
-        'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
-        'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around',
-        'the', 'eyes', "don't", 'look', 'around', 'the', 'eyes',
-        'look', 'into', 'my', 'eyes', "you're", 'under'
-    ]
-    counter = Counter(words)
-    print(counter.most_common(3))
-
-
-if __name__ == '__main__':
-    main()
-```
-
-#### 穷举法、贪婪法、分治法、动态规划
-
-```Python
-"""
-穷举法 - 穷尽所有可能直到找到正确答案
-"""
-
-
-def main():
-    # 公鸡5元一只 母鸡3元一只 小鸡1元三只
-    # 用100元买100只鸡 问公鸡/母鸡/小鸡各多少只
-    for x in range(20):
-        for y in range(33):
-            z = 100 - x - y
-            if 5 * x + 3 * y + z // 3 == 100 and z % 3 == 0:
-                print(x, y, z)
-    # A、B、C、D、E五人在某天夜里合伙捕鱼 最后疲惫不堪各自睡觉
-    # 第二天A第一个醒来 他将鱼分为5份 扔掉多余的1条 拿走自己的一份
-    # B第二个醒来 也将鱼分为5份 扔掉多余的1条 拿走自己的一份
-    # 然后C、D、E依次醒来也按同样的方式分鱼 问他们至少捕了多少条鱼
-    fish = 1
-    while True:
-        total = fish
-        enough = True
-        for _ in range(5):
-            if (total - 1) % 5 == 0:
-                total = (total - 1) // 5 * 4
-            else:
-                enough = False
-                break
-        if enough:
-            print(fish)
-            break
-        fish += 1
-
-
-if __name__ == '__main__':
-    main()
-```
-
-```Python
-"""
-动态规划 - 适用于有重叠子问题和最优子结构性质的问题
-使用动态规划方法所耗时间往往远少于朴素解法(用空间换取时间)
-"""
-
-
-def fib(num, temp={}):
-    """用递归计算Fibonacci数"""
-    if num in (1, 2):
-        return 1
-    try:
-        return temp[num]
-    except KeyError:
-        temp[num] = fib(num - 1) + fib(num - 2)
-        return temp[num]
-```
-
-### 函数的使用方式
-
-- 将函数视为“一等公民”
-
-- 高阶函数的用法(filter、map以及它们的替代品)
-
-- 位置参数、可变参数、关键字参数、命名关键字参数
-
-- 参数的元信息(代码可读性问题)
-
-- 匿名函数和内联函数的用法(lambda函数)
-
-- 闭包和作用域问题(LEGB)
-
-- 装饰器函数(使用装饰器和取消装饰器)
-
-  输出函数执行时间的装饰器。
-
-  ```Python
-  from functools import wraps
-  from time import time
-  
-  
-  def record(output):
-  	
-  	def decorate(func):
-  		
-  		@wraps(func)
-  		def wrapper(*args, **kwargs):
-  			start = time()
-  			result = func(*args, **kwargs)
-  			output(func.__name__, time() - start)
-  			return result
-              
-  		return wrapper
-  	
-  	return decorate
-  ```
-
-  ```Python
-  from functools import wraps
-  from time import time
-  
-  
-  class Record(object):
-  
-      def __init__(self, output):
-          self.output = output
-  
-      def __call__(self, func):
-  
-          @wraps(func)
-          def wrapper(*args, **kwargs):
-              start = time()
-              result = func(*args, **kwargs)
-              self.output(func.__name__, time() - start)
-              return result
-  
-          return wrapper
-  ```
-
-  用装饰器来实现单例模式。
-
-  ```Python
-  from functools import wraps
-  
-  
-  def singleton(cls):
-      instances = {}
-  
-      @wraps(cls)
-      def wrapper(*args, **kwargs):
-          if cls not in instances:
-              instances[cls] = cls(*args, **kwargs)
-          return instances[cls]
-  
-      return wrapper
-  
-  
-  @singleton
-  class Singleton(object):
-      pass
-  ```
-
-### 面向对象相关知识
-
-- 三大支柱:封装、继承、多态
-
-  ```Python
-  """
-  月薪结算系统
-  部门经理每月15000 程序员每小时200 销售员1800底薪+销售额5%提成
-  """
-  from abc import ABCMeta, abstractmethod
-  
-  
-  class Employee(metaclass=ABCMeta):
-      """员工(抽象类)"""
-  
-      def __init__(self, name):
-          self._name = name
-  
-      @property
-      def name(self):
-          """姓名"""
-          return self._name
-  
-      @abstractmethod
-      def get_salary(self):
-          """结算月薪(抽象方法)"""
-          pass
-  
-  
-  class Manager(Employee):
-      """部门经理"""
-  
-      def get_salary(self):
-          return 15000.0
-  
-  
-  class Programmer(Employee):
-      """程序员"""
-  
-      def __init__(self, name):
-          self._working_hour = 0
-          super().__init__(name)
-  
-      @property
-      def working_hour(self):
-          """工作时间"""
-          return self._working_hour
-  
-      @working_hour.setter
-      def working_hour(self, hour):
-          self._working_hour = hour if hour > 0 else 0
-  
-      def get_salary(self):
-          return 200.0 * self.working_hour
-  
-  
-  class Salesman(Employee):
-      """销售员"""
-  
-      def __init__(self, name):
-          self._sales = 0.0
-          super().__init__(name)
-  
-      @property
-      def sales(self):
-          return self._sales
-  
-      @sales.setter
-      def sales(self, sales):
-          self._sales = sales if sales > 0 else 0
-  
-      def get_salary(self):
-          return 1800.0 + self.sales * 0.05
-  
-  
-  def main():
-      emps = [
-          Manager('刘备'), Manager('曹操'), Programmer('许褚'),
-          Salesman('貂蝉'), Salesman('赵云'), Programmer('张辽'),
-          Programmer('关羽'), Programmer('周瑜')
-      ]
-      for emp in emps:
-          if isinstance(emp, Programmer):
-              emp.working_hour = int(input('本月工作时间: '))
-          elif isinstance(emp, Salesman):
-              emp.sales = float(input('本月销售额: '))
-          print('%s: %.2f元' % (emp.name, emp.get_salary()))
-  
-  
-  if __name__ == '__main__':
-      main()
-  ```
-
-- 对象的复制(深复制/深拷贝/深度克隆和浅复制/浅拷贝/影子克隆)
-
-- 垃圾回收、循环引用和弱引用
-
-  Python使用了自动化内存管理,这种管理机制以**引用计数**为基础,同时也引入了**标记-清除**和**分代收集**两种机制为辅的策略。
-
-  ```C
-  typedef struct_object {
-      /* 引用计数 */
-      int ob_refcnt;
-      /* 对象指针 */
-      struct_typeobject *ob_type;
-  } PyObject;
-  ```
-
-  ```C
-  /* 增加引用计数的宏定义 */
-  #define Py_INCREF(op)   ((op)->ob_refcnt++)
-  /* 减少引用计数的宏定义 */
-  #define Py_DECREF(op) \ //减少计数
-      if (--(op)->ob_refcnt != 0) \
-          ; \
-      else \
-          __Py_Dealloc((PyObject *)(op))
-  ```
-
-  导致引用计数+1的情况:
-
-  - 对象被创建,例如`a = 23`
-  - 对象被引用,例如`b = a`
-  - 对象被作为参数,传入到一个函数中,例如`f(a)`
-  - 对象作为一个元素,存储在容器中,例如`list1 = [a, a]`
-
-  导致引用计数-1的情况:
-
-  - 对象的别名被显式销毁,例如`del a`
-  - 对象的别名被赋予新的对象,例如`a = 24`
-  - 一个对象离开它的作用域,例如f函数执行完毕时,f函数中的局部变量(全局变量不会)
-  - 对象所在的容器被销毁,或从容器中删除对象
-
-  引用计数可能会导致循环引用问题,而循环引用会导致内存泄露,如下面的代码所示。为了解决这个问题,Python中引入了“标记-清除”和“分代收集”。在创建一个对象的时候,对象被放在第一代中,如果在第一代的垃圾检查中对象存活了下来,该对象就会被放到第二代中,同理在第二代的垃圾检查中对象存活下来,该对象就会被放到第三代中。
-
-  ```Python
-  list1 = []
-  list2 = []
-  list1.append(list2)
-  list2.append(list1)
-  ```
-
-  以下情况会导致垃圾回收:
-
-  - 调用`gc.collect()`
-  - gc模块的计数器达到阀值
-  - 程序退出
-
-  如果循环引用中两个对象都定义了`__del__`方法,gc模块不会销毁这些不可达对象,因为gc模块不知道应该先调用哪个对象的`__del__`方法,这个问题在Python 3.6中得到了解决。
-
-  也可以通过`weakref`模块构造弱引用的方式来解决循环引用的问题。
-
-- 魔法属性和方法(请参考《Python魔法方法指南》)
-
-  有几个小问题请大家思考:
-
-  - 自定义的对象能不能使用运算符做运算?
-  - 自定义的对象能不能放到set中?能去重吗?
-  - 自定义的对象能不能作为dict的键?
-  - 自定义的对象能不能使用上下文语法?
-
-- 混入(Mixin)
-
-  ```Python
-  """
-  限制字典只有在指定的key不存在时才能设置键值对
-  MRO - Method Resolution Order - 多重继承时的方法解析顺序
-  """
-  
-  
-  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
-  
-  
-  def main():
-      dict1 = SetOnceDict()
-      try:
-          dict1['username'] = 'jackfrued'
-          dict1['username'] = 'hellokitty'
-          dict1['username'] = 'wangdachui'
-      except KeyError:
-          pass
-      print(dict1)
-  
-  
-  if __name__ == '__main__':
-      main()
-  ```
-
-- 元编程和元类
-
-  用元类实现单例模式。
-
-  ```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 Singleton(metaclass=SingletonMeta):
-      """单例类"""
-  
-      def __init__(self, name):
-          self._name = name
-          from random import randrange
-          self._value = randrange(100000)
-  
-      @property
-      def name(self):
-          return self._name
-  
-      @property
-      def value(self):
-          return self._value
-  
-  
-  def main():
-      sin1 = Singleton('Lee')
-      sin2 = Singleton('Wang')
-      print(sin1 == sin2)
-      print(sin1.value, sin2.value)
-      print(sin1.name, sin2.name)
-  
-  
-  if __name__ == '__main__':
-      main()
-  ```
-
-### 迭代器和生成器
-
-```Python
-"""
-生成器和迭代器
-"""
-
-
-def fib1(num):
-    """普通函数"""
-    a, b = 0, 1
-    for _ in range(num):
-        a, b = b, a + b
-    return a
-
-
-def fib2(num):
-    """生成器"""
-    a, b = 0, 1
-    for _ in range(num):
-        a, b = b, a + b
-        yield a
-
-
-class Fib3:
-    """迭代器"""
-
-    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()
-
-
-def main():
-    for val in fib2(20):
-        print(val)
-    print('-' * 50)
-    for val in Fib3(20):
-        print(val)
-
-
-if __name__ == '__main__':
-    main()
-```
-
-### 并发编程
-
-- 多线程和多进程
-- 协程和异步I/O
-- concurrent.futures
-
+## Python语言进程
+
+1. 数据结构和算法
+   - 排序算法(冒泡和归并)和查找算法(顺序和折半)
+
+     ```Python
+     def bubble_sort(origin_items, comp=lambda x, y: x > y):
+         """高质量冒泡排序(搅拌排序)"""
+         items = origin_items[:]
+         for i in range(len(items) - 1):
+             swapped = False
+             for j in range(len(items) - 1 - i):
+                 if comp(items[j], items[j + 1]):
+                     items[j], items[j + 1] = items[j + 1], items[j]
+                     swapped = True
+             if swapped:
+                 swapped = False
+                 for j in range(len(items) - 2 - i, i, -1):
+                     if comp(items[j - 1], items[j]):
+                         items[j], items[j - 1] = items[j - 1], items[j]
+                         swapped = True
+             if not swapped:
+                 break
+         return items
+     ```
+
+     ```Python
+     def merge_sort(items, comp=lambda x, y: x <= y):
+         """归并排序(分治法)"""
+         if len(items) < 2:
+             return items[:]
+         mid = len(items) // 2
+         left = merge_sort(items[:mid], comp)
+         right = merge_sort(items[mid:], comp)
+         return merge(left, right, comp)
+     
+     
+     def merge(items1, items2, comp):
+         """合并(将两个有序的列表合并成一个有序的列表)"""
+         items = []
+         idx1, idx2 = 0, 0
+         while idx1 < len(items1) and idx2 < len(items2):
+             if comp(items1[idx1], items2[idx2]):
+                 items.append(items1[idx1])
+                 idx1 += 1
+             else:
+                 items.append(items2[idx2])
+                 idx2 += 1
+         items += items1[idx1:]
+         items += items2[idx2:]
+         return items
+     ```
+
+     ```Python
+     def seq_search(items, key):
+         """顺序查找"""
+         for index, item in enumerate(items):
+             if item == key:
+                 return index
+         return -1
+     ```
+
+     ```Python
+     def bin_search(items, key):
+         """折半查找"""
+         start, end = 0, len(items) - 1
+         while start <= end:
+             mid = (start + end) // 2
+             if key > items[mid]:
+                 start = mid + 1
+             elif key < items[mid]:
+                 end = mid - 1
+             else:
+                 return mid
+         return -1
+     ```
+
+   - 使用生成式(推导式)语法
+
+     ```Python
+     prices = {
+         'AAPL': 191.88,
+         'GOOG': 1186.96,
+         'IBM': 149.24,
+         'ORCL': 48.44,
+         'ACN': 166.89,
+         'FB': 208.09,
+         'SYMC': 21.29
+     }
+     # 用股票价格大于100元的股票构造一个新的字典
+     prices2 = {key: value for key, value in prices.items() if value > 100}
+     print(prices2)
+     ```
+
+   - 嵌套的列表
+
+     ```Python
+     names = ['关羽', '张飞', '赵云', '马超', '黄忠']
+     courses = ['语文', '数学', '英语']
+     # 录入五个学生三门课程的成绩
+     # 错误 - 参考http://pythontutor.com/visualize.html#mode=edit
+     # scores = [[None] * len(courses)] * len(names)
+     scores = [[None] * len(courses) for _ in range(len(names))]
+     for row, name in enumerate(names):
+         for col, course in enumerate(courses):
+             scores[row][col] = float(input(f'请输入{name}的{course}成绩: '))
+             print(scores)
+     ```
+
+     [Python Tutor](http://pythontutor.com/) - VISUALIZE CODE AND GET LIVE HELP
+
+   - heapq、itertools等的用法
+     ```Python
+     """
+     从列表中找出最大的或最小的N个元素
+     """
+     import heapq
+     
+     list1 = [34, 25, 12, 99, 87, 63, 58, 78, 88, 92]
+     list2 = [
+         {'name': 'IBM', 'shares': 100, 'price': 91.1},
+         {'name': 'AAPL', 'shares': 50, 'price': 543.22},
+         {'name': 'FB', 'shares': 200, 'price': 21.09},
+         {'name': 'HPQ', 'shares': 35, 'price': 31.75},
+         {'name': 'YHOO', 'shares': 45, 'price': 16.35},
+         {'name': 'ACME', 'shares': 75, 'price': 115.65}
+     ]
+     print(heapq.nlargest(3, list1))
+     print(heapq.nsmallest(3, list1))
+     print(heapq.nlargest(2, list2, key=lambda x: x['price']))
+     print(heapq.nlargest(2, list2, key=lambda x: x['shares']))
+     ```
+
+     ```Python
+     """
+     排列 / 组合 / 笛卡尔积
+     """
+     import itertools
+     
+     for val in itertools.permutations('ABCD'):
+         print(val)
+     
+     for val in itertools.combinations('ABCDE', 3):
+         print(val)
+     
+     for val in itertools.product('ABCD', '123'):
+         print(val)
+     ```
+
+   - collections模块下的工具类
+
+     ```Python
+     """
+     找出序列中出现次数最多的元素
+     """
+     from collections import Counter
+     
+     words = [
+         'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
+         'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around',
+         'the', 'eyes', "don't", 'look', 'around', 'the', 'eyes',
+         'look', 'into', 'my', 'eyes', "you're", 'under'
+     ]
+     counter = Counter(words)
+     print(counter.most_common(3))
+     ```
+
+   - 穷举法、贪婪法、分治法、回溯法、动态规划
+
+     例子:百钱百鸡和五人分鱼。
+
+     ```Python
+     """
+     穷举法 - 穷尽所有可能直到找到正确答案
+     """
+     
+     # 公鸡5元一只 母鸡3元一只 小鸡1元三只
+     # 用100元买100只鸡 问公鸡/母鸡/小鸡各多少只
+     for x in range(20):
+         for y in range(33):
+             z = 100 - x - y
+             if 5 * x + 3 * y + z // 3 == 100 and z % 3 == 0:
+                 print(x, y, z)
+     
+     # A、B、C、D、E五人在某天夜里合伙捕鱼 最后疲惫不堪各自睡觉
+     # 第二天A第一个醒来 他将鱼分为5份 扔掉多余的1条 拿走自己的一份
+     # B第二个醒来 也将鱼分为5份 扔掉多余的1条 拿走自己的一份
+     # 然后C、D、E依次醒来也按同样的方式分鱼 问他们至少捕了多少条鱼
+     fish = 1
+     while True:
+         total = fish
+         enough = True
+         for _ in range(5):
+             if (total - 1) % 5 == 0:
+                 total = (total - 1) // 5 * 4
+             else:
+                 enough = False
+                 break
+         if enough:
+             print(fish)
+             break
+         fish += 1
+     ```
+
+     例子:斐波拉切数列。
+
+     ```Python
+     """
+     动态规划 - 适用于有重叠子问题和最优子结构性质的问题
+     使用动态规划方法所耗时间往往远少于朴素解法(用空间换取时间)
+     """
+     
+     
+     def fib(num, temp={}):
+         """用递归计算Fibonacci数"""
+         if num in (1, 2):
+             return 1
+         try:
+             return temp[num]
+         except KeyError:
+             temp[num] = fib(num - 1) + fib(num - 2)
+             return temp[num]
+     ```
+
+2. 函数的使用方式
+
+   - 将函数视为“一等公民”
+
+     - 函数可以赋值给变量
+     - 函数可以作为函数的参数
+     - 函数可以作为函数的返回值
+
+   - 高阶函数的用法(`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]
+     ```
+
+   - 位置参数、可变参数、关键字参数、命名关键字参数
+
+   - 参数的元信息(代码可读性问题)
+
+   - 匿名函数和内联函数的用法(`lambda`函数)
+
+   - 闭包和作用域问题
+
+     - Python搜索变量的LEGB顺序(Local --> Embedded --> Global --> Built-in)
+
+     - `global`和`nonlocal`关键字的作用
+
+       `global`:声明使用全局变量,如果不存在就把局部变量放到全局作用域。
+
+       `nonlocal`:声明使用嵌套作用域的变量(嵌套作用域必须存在该变量)。
+
+   - 装饰器函数(使用装饰器和取消装饰器)
+
+     例子:输出函数执行时间的装饰器。
+
+     ```Python
+     def record_time(func):
+         """自定义装饰函数的装饰器"""
+         
+         @wraps(func)
+         def wrapper(*args, **kwargs):
+             start = time()
+             result = func(*args, **kwargs)
+             print(f'{func.__name__}: {time() - start}秒')
+             return result
+             
+         return wrapper
+     ```
+
+     如果装饰器不希望跟`print`函数耦合,可以编写带参数的装饰器。
+
+     ```Python
+     from functools import wraps
+     from time import time
+     
+     
+     def record(output):
+         """自定义带参数的装饰器"""
+     	
+     	def decorate(func):
+     		
+     		@wraps(func)
+     		def wrapper(*args, **kwargs):
+     			start = time()
+     			result = func(*args, **kwargs)
+     			output(func.__name__, time() - start)
+     			return result
+                 
+     		return wrapper
+     	
+     	return decorate
+     ```
+
+     ```Python
+     from functools import wraps
+     from time import time
+     
+     
+     class Record(object):
+         """自定义装饰器类(通过__call__魔术方法使得对象可以当成函数调用)"""
+     
+         def __init__(self, output):
+             self.output = output
+     
+         def __call__(self, func):
+     
+             @wraps(func)
+             def wrapper(*args, **kwargs):
+                 start = time()
+                 result = func(*args, **kwargs)
+                 self.output(func.__name__, time() - start)
+                 return result
+     
+             return wrapper
+     ```
+
+     > 说明:由于对带装饰功能的函数添加了@wraps装饰器,可以通过`func.__wrapped__`方式获得被装饰之前的函数或类来取消装饰器的作用。
+
+     例子:用装饰器来实现单例模式。
+
+     ```Python
+     from functools import wraps
+     
+     
+     def singleton(cls):
+         """装饰类的装饰器"""
+         instances = {}
+     
+         @wraps(cls)
+         def wrapper(*args, **kwargs):
+             if cls not in instances:
+                 instances[cls] = cls(*args, **kwargs)
+             return instances[cls]
+     
+         return wrapper
+     
+     
+     @singleton
+     class President(object):
+         """总统(单例类)"""
+         pass
+     ```
+
+     > 说明:上面的代码中用到了闭包(closure),不知道你是否已经意识到了。还没有一个小问题就是,上面的代码并没有实现线程安全的单例,如果要实现线程安全的单例应该怎么做呢?
+
+     ```Python
+     from functools import wraps
+     
+     
+     def singleton(cls):
+         """线程安全的单例装饰器"""
+         instances = {}
+         locker = Lock()
+     
+         @wraps(cls)
+         def wrapper(*args, **kwargs):
+             if cls not in instances:
+                 with locker:
+                     if cls not in instances:
+                         instances[cls] = cls(*args, **kwargs)
+             return instances[cls]
+     
+         return wrapper
+     ```
+
+3. 面向对象相关知识
+
+   - 三大支柱:封装、继承、多态
+
+     例子:工资结算系统。
+
+     ```Python
+     """
+     月薪结算系统 - 部门经理每月15000 程序员每小时200 销售员1800底薪加销售额5%提成
+     """
+     from abc import ABCMeta, abstractmethod
+     
+     
+     class Employee(metaclass=ABCMeta):
+         """员工(抽象类)"""
+     
+         def __init__(self, name):
+             self._name = name
+     
+         @property
+         def name(self):
+             """姓名"""
+             return self._name
+     
+         @abstractmethod
+         def get_salary(self):
+             """结算月薪(抽象方法)"""
+             pass
+     
+     
+     class Manager(Employee):
+         """部门经理"""
+     
+         def get_salary(self):
+             return 15000.0
+     
+     
+     class Programmer(Employee):
+         """程序员"""
+     
+         def __init__(self, name):
+             self._working_hour = 0
+             super().__init__(name)
+     
+         @property
+         def working_hour(self):
+             """工作时间"""
+             return self._working_hour
+     
+         @working_hour.setter
+         def working_hour(self, hour):
+             self._working_hour = hour if hour > 0 else 0
+     
+         def get_salary(self):
+             return 200.0 * self.working_hour
+     
+     
+     class Salesman(Employee):
+         """销售员"""
+     
+         def __init__(self, name):
+             self._sales = 0.0
+             super().__init__(name)
+     
+         @property
+         def sales(self):
+             return self._sales
+     
+         @sales.setter
+         def sales(self, sales):
+             self._sales = sales if sales > 0 else 0
+     
+         def get_salary(self):
+             return 1800.0 + self.sales * 0.05
+     
+     
+     class EmployeeFactory():
+         """创建员工的工厂(工厂模式 - 通过工厂实现对象使用者和对象之间的解耦合)"""
+     
+         @staticmethod
+         def create(emp_type, *args):
+             """创建员工"""
+             emp_type = emp_type.upper()
+             emp = None
+             if emp_type == 'M':
+                 emp = Manager(*args)
+             elif emp_type == 'P':
+                 emp = Programmer(*args)
+             elif emp_type == 'S':
+                 emp = Salesman(*args)
+             return emp
+     
+     
+     def main():
+         """主函数"""
+         emps = [
+             EmployeeFactory.create('M', '曹操'), EmployeeFactory.create('P', '荀彧'),
+             EmployeeFactory.create('P', '郭嘉'), EmployeeFactory.create('S', '典韦')
+         ]
+         for emp in emps:
+             # 用isinstance函数识别对象引用所引用对象的类型
+             if isinstance(emp, Programmer):
+                 emp.working_hour = int(input('本月工作时间: '))
+             elif isinstance(emp, Salesman):
+                 emp.sales = float(input('本月销售额: '))
+             print('%s: %.2f元' % (emp.name, emp.get_salary()))
+     
+     
+     if __name__ == '__main__':
+         main()
+     ```
+
+   - 类与类之间的关系
+
+     - is-a关系:继承
+     - has-a关系:关联 / 聚合 / 合成
+     - use-a关系:依赖
+
+     例子:扑克游戏。
+
+     ```Python
+     """
+     经验:符号常量总是优于字面常量,枚举类型是定义符号常量的最佳选择
+     """
+     from enum import Enum, unique
+     
+     import random
+     
+     
+     @unique
+     class Suite(Enum):
+         """花色"""
+     
+         SPADE, HEART, CLUB, DIAMOND = range(4)
+     
+         def __lt__(self, other):
+             return self.value < other.value
+     
+     
+     class Card(object):
+         """牌"""
+     
+         def __init__(self, suite, face):
+             """初始化方法"""
+             self.suite = suite
+             self.face = face
+     
+         def show(self):
+             """显示牌面"""
+             suites = ['♠️', '♥️', '♣️', '♦️']
+             faces = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
+             return f'{suites[self.suite.value]} {faces[self.face]}'
+     
+         def __str__(self):
+             return self.show()
+     
+         def __repr__(self):
+             return self.show()
+     
+     
+     class Poker(object):
+         """扑克"""
+     
+         def __init__(self):
+             self.index = 0
+             self.cards = [Card(suite, face)
+                           for suite in Suite
+                           for face in range(1, 14)]
+     
+         def shuffle(self):
+             """洗牌(随机乱序)"""
+             random.shuffle(self.cards)
+             self.index = 0
+     
+         def deal(self):
+             """发牌"""
+             card = self.cards[self.index]
+             self.index += 1
+             return card
+     
+         @property
+         def has_more(self):
+             return self.index < len(self.cards)
+     
+     
+     class Player(object):
+         """玩家"""
+     
+         def __init__(self, name):
+             self.name = name
+             self.cards = []
+     
+         def get_one(self, card):
+             """摸一张牌"""
+             self.cards.append(card)
+     
+         def sort(self, comp=lambda card: (card.suite, card.face)):
+             """整理手上的牌"""
+             self.cards.sort(key=comp)
+     
+     
+     def main():
+         """主函数"""
+         poker = Poker()
+         poker.shuffle()
+         players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
+         while poker.has_more:
+             for player in players:
+                     player.get_one(poker.deal())
+         for player in players:
+             player.sort()
+             print(player.name, end=': ')
+             print(player.cards)
+     
+     
+     if __name__ == '__main__':
+         main()
+     ```
+
+   - 对象的复制(深复制/深拷贝/深度克隆和浅复制/浅拷贝/影子克隆)
+
+   - 垃圾回收、循环引用和弱引用
+
+     Python使用了自动化内存管理,这种管理机制以**引用计数**为基础,同时也引入了**标记-清除**和**分代收集**两种机制为辅的策略。
+
+     ```C
+     typedef struct_object {
+         /* 引用计数 */
+         int ob_refcnt;
+         /* 对象指针 */
+         struct_typeobject *ob_type;
+     } PyObject;
+     ```
+
+     ```C
+     /* 增加引用计数的宏定义 */
+     #define Py_INCREF(op)   ((op)->ob_refcnt++)
+     /* 减少引用计数的宏定义 */
+     #define Py_DECREF(op) \ //减少计数
+         if (--(op)->ob_refcnt != 0) \
+             ; \
+         else \
+             __Py_Dealloc((PyObject *)(op))
+     ```
+
+     导致引用计数+1的情况:
+
+     - 对象被创建,例如`a = 23`
+     - 对象被引用,例如`b = a`
+     - 对象被作为参数,传入到一个函数中,例如`f(a)`
+     - 对象作为一个元素,存储在容器中,例如`list1 = [a, a]`
+
+     导致引用计数-1的情况:
+
+     - 对象的别名被显式销毁,例如`del a`
+     - 对象的别名被赋予新的对象,例如`a = 24`
+     - 一个对象离开它的作用域,例如f函数执行完毕时,f函数中的局部变量(全局变量不会)
+     - 对象所在的容器被销毁,或从容器中删除对象
+
+     引用计数可能会导致循环引用问题,而循环引用会导致内存泄露,如下面的代码所示。为了解决这个问题,Python中引入了“标记-清除”和“分代收集”。在创建一个对象的时候,对象被放在第一代中,如果在第一代的垃圾检查中对象存活了下来,该对象就会被放到第二代中,同理在第二代的垃圾检查中对象存活下来,该对象就会被放到第三代中。
+
+     ```Python
+     # 循环引用会导致内存泄露 - Python除了引用技术还引入了标记清理和分代回收
+     # 在Python 3.6以前如果重写__del__魔术方法会导致循环引用处理失效
+     # 如果不想造成循环引用可以使用弱引用
+     list1 = []
+     list2 = [] 
+     list1.append(list2)
+     list2.append(list1)
+     ```
+
+     以下情况会导致垃圾回收:
+
+     - 调用`gc.collect()`
+     - gc模块的计数器达到阀值
+     - 程序退出
+
+     如果循环引用中两个对象都定义了`__del__`方法,gc模块不会销毁这些不可达对象,因为gc模块不知道应该先调用哪个对象的`__del__`方法,这个问题在Python 3.6中得到了解决。
+
+     也可以通过`weakref`模块构造弱引用的方式来解决循环引用的问题。
+
+   - 魔法属性和方法(请参考《Python魔法方法指南》)
+
+     有几个小问题请大家思考:
+
+     - 自定义的对象能不能使用运算符做运算?
+     - 自定义的对象能不能放到set中?能去重吗?
+     - 自定义的对象能不能作为dict的键?
+     - 自定义的对象能不能使用上下文语法?
+
+   - 混入(Mixin)
+
+     例子:自定义字典限制只有在指定的key不存在时才能在字典中设置键值对。
+
+     ```Python
+     class SetOnceMappingMixin:
+         """自定义混入类"""
+         __slots__ = ()
+     
+         def __setitem__(self, key, value):
+             if key in self:
+                 raise KeyError(str(key) + ' already set')
+             return super().__setitem__(key, value)
+     
+     
+     class SetOnceDict(SetOnceMappingMixin, dict):
+         """自定义字典"""
+         pass
+     
+     
+     my_dict= SetOnceDict()
+     try:
+         my_dict['username'] = 'jackfrued'
+         my_dict['username'] = 'hellokitty'
+     except KeyError:
+         pass
+     print(my_dict)
+     ```
+
+   - 元编程和元类
+
+     例子:用元类实现单例模式。
+
+     ```Python
+     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`对象获得协程执行结果。

+ 0 - 4
Day31-35/玩转Linux操作系统.md

@@ -1249,7 +1249,3 @@ build environment:
 3. HISTSIZE
 4. RANDOM
 5. PATH
-
-
-
-

+ 0 - 9
Day66-75/01.网络爬虫和相关工具.md

@@ -115,7 +115,6 @@ HTTP响应(响应行+响应头+空行+消息体):
 3. HTTPie:命令行HTTP客户端。
 
    ```Shell
-   
    $ http --header http://www.scu.edu.cn
    HTTP/1.1 200 OK
    Accept-Ranges: bytes
@@ -138,8 +137,6 @@ HTTP响应(响应行+响应头+空行+消息体):
 4. BuiltWith:识别网站所用技术的工具。
 
    ```Python
-   
-   >>>
    >>> import builtwith
    >>> 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']}
@@ -153,8 +150,6 @@ HTTP响应(响应行+响应头+空行+消息体):
 5. python-whois:查询网站所有者的工具。
 
    ```Python
-   
-   >>>
    >>> import whois
    >>> 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'}
@@ -195,7 +190,6 @@ HTTP响应(响应行+响应头+空行+消息体):
 下面的例子给出了一个从“搜狐体育”上获取NBA新闻标题和链接的爬虫。
 
 ```Python
-
 from urllib.error import URLError
 from urllib.request import urlopen
 
@@ -304,7 +298,6 @@ if __name__ == '__main__':
    - 使用未经验证的上下文
 
      ```Python
-     
      import ssl
      
      request = urllib.request.Request(url='...', headers={...}) 
@@ -315,10 +308,8 @@ if __name__ == '__main__':
    - 设置全局的取消证书验证
 
      ```Python
-     
      import ssl
      
      ssl._create_default_https_context = ssl._create_unverified_context
      ```
 
-     

+ 0 - 3
Day66-75/02.数据采集和解析.md

@@ -12,7 +12,6 @@
 ### HTML页面分析
 
 ```HTML
-
 <!DOCTYPE html>
 <html lang="en">
     <head>
@@ -136,7 +135,6 @@ pyquery相当于jQuery的Python实现,可以用于解析HTML网页。
 ### 实例 - 获取知乎发现上的问题链接
 
 ```Python
-
 from urllib.parse import urljoin
 
 import re
@@ -168,6 +166,5 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 

+ 23 - 36
Day66-75/03.存储数据.md

@@ -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)解压缩解归档之后进行构件安装。
 
 ```Shell
-
-# wget http://download.redis.io/releases/redis-3.2.11.tar.gz
-# gunzip redis-3.2.11.tar.gz
-# tar -xvf redis-3.2.11.tar
-# cd redis-3.2.11
-# make && make install
+wget http://download.redis.io/releases/redis-3.2.11.tar.gz
+gunzip redis-3.2.11.tar.gz
+tar -xvf redis-3.2.11.tar
+cd redis-3.2.11
+make && make install
 ```
 
 接下来我们将redis-3.2.11目录下的redis.conf配置文件复制到用户主目录下并修改配置文件(如果你对配置文件不是很有把握就不要直接修改而是先复制一份再修改这个副本)。
 
 ```Shell
-
-# cd ..
-# cp redis-3.2.11/redis.conf redis.conf
-# vim redis.conf
+cd ..
+cp redis-3.2.11/redis.conf redis.conf
+vim redis.conf
 ```
 
 配置将Redis服务绑定到指定的IP地址和端口。
@@ -76,8 +74,7 @@ Redis是REmote DIctionary Server的缩写,它是一个用ANSI C编写的高性
 接下来启动Redis服务器,可以将服务器放在后台去运行。
 
 ```Shell
-
-# redis-server redis.conf &
+redis-server redis.conf &
 
            _.-``__ ''-._
       _.-``    `.  `_.  ''-._           Redis 3.2.11 (00000000/0) 64 bit
@@ -100,8 +97,7 @@ Redis是REmote DIctionary Server的缩写,它是一个用ANSI C编写的高性
 接下来,我们尝试用Redis客户端去连接服务器。
 
 ```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
 OK
 172.18.61.250:6379> ping
@@ -112,7 +108,6 @@ PONG
 Redis有着非常丰富的数据类型,也有很多的命令来操作这些数据,具体的内容可以查看[Redis命令参考](http://redisdoc.com/),在这个网站上,除了Redis的命令参考,还有Redis的详细文档,其中包括了通知、事务、主从复制、持久化、哨兵、集群等内容。
 
 ```Shell
-
 172.18.61.250:6379> set username admin
 OK
 172.18.61.250:6379> get username
@@ -166,13 +161,11 @@ OK
 可以使用pip安装redis模块。redis模块的核心是名为Redis的类,该类的对象代表一个Redis客户端,通过该客户端可以向Redis服务器发送命令并获取执行的结果。上面我们在Redis客户端中使用的命令基本上就是Redis对象可以接收的消息,所以如果了解了Redis的命令就可以在Python中玩转Redis。
 
 ```Shell
-
-$ pip3 install redis
-$ python3
+pip3 install redis
+python3
 ```
 
 ```Python
-
 >>> import redis
 >>> client = redis.Redis(host='1.2.3.4', port=6379, password='1qaz2wsx')
 >>> client.set('username', 'admin')
@@ -202,14 +195,14 @@ MongoDB将数据存储为一个文档,一个文档由一系列的“键值对
 可以从MongoDB的[官方下载链接](https://www.mongodb.com/download-center#community)下载MongoDB,官方为Windows系统提供了一个Installer程序,而Linux和MacOS则提供了压缩文件。下面简单说一下Linux系统如何安装和配置MongoDB。
 
 ```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] db version v3.6.5
 2018-06-03T18:03:28.232+0800 I CONTROL  [initandlisten] git version: a20ecd3e3a174162052ff99913bc2ca9a839d618
@@ -239,7 +232,8 @@ MongoDB将数据存储为一个文档,一个文档由一系列的“键值对
 启动服务器后可以使用交互式环境跟服务器通信,如下所示。
 
 ```shell
-# mongo --host 172.18.61.250
+mongo --host 172.18.61.250
+
 MongoDB shell version v3.6.5
 connecting to: mongodb://172.18.61.250:27017/
 ...
@@ -249,7 +243,6 @@ connecting to: mongodb://172.18.61.250:27017/
 1. 查看、创建和删除数据库。
 
    ```JavaScript
-   
    > // 显示所有数据库
    > show dbs
    admin   0.000GB
@@ -267,7 +260,6 @@ connecting to: mongodb://172.18.61.250:27017/
 2. 创建、删除和查看集合。
 
    ```JavaScript
-   
    > // 创建并切换到school数据库
    > use school
    switched to db school
@@ -292,7 +284,6 @@ connecting to: mongodb://172.18.61.250:27017/
 3. 文档的CRUD操作。
 
    ```JavaScript
-   
    > // 向students集合插入文档
    > db.students.insert({stuid: 1001, name: '骆昊', age: 38})
    WriteResult({ "nInserted" : 1 })
@@ -394,13 +385,11 @@ connecting to: mongodb://172.18.61.250:27017/
 可以通过pip安装pymongo来实现对MongoDB的操作。
 
 ```Shell
-
-$ pip3 install pymongo
-$ python3
+pip3 install pymongo
+python3
 ```
 
 ```Python
-
 >>> from pymongo import MongoClient
 >>> client = MongoClient('mongodb://120.77.222.217:27017') 
 >>> db = client.school
@@ -451,7 +440,6 @@ $ python3
 ### 实例 - 缓存知乎发现上的链接和页面代码
 
 ```Python
-
 from hashlib import sha1
 from urllib.parse import urljoin
 
@@ -501,7 +489,6 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 
 

+ 1 - 13
Day66-75/04.并发下载.md

@@ -44,7 +44,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
 1. 生成器 - 数据的生产者。
 
    ```Python
-   
    from time import sleep
    
    
@@ -70,7 +69,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
    生成器还可以叠加来组成生成器管道,代码如下所示。
 
    ```Python
-   
    # Fibonacci数生成器
    def fib():
        a, b = 0, 1
@@ -94,13 +92,11 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
    
    if __name__ == '__main__':
        main()
-   
    ```
 
 2. 协程 - 数据的消费者。
 
    ```Python
-   
    from time import sleep
    
    
@@ -130,13 +126,11 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
    
    if __name__ == '__main__':
        main()
-   
    ```
 
    > 说明:上面代码中countdown_gen函数中的第1行consumer.send(None)是为了激活生成器,通俗的说就是让生成器执行到有yield关键字的地方挂起,当然也可以通过next(consumer)来达到同样的效果。如果不愿意每次都用这样的代码来“预激”生成器,可以写一个包装器来完成该操作,代码如下所示。
 
    ```Python
-   
    from functools import wraps
    
    
@@ -156,7 +150,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
 3. 异步I/O - 非阻塞式I/O操作。
 
    ```Python
-   
    import asyncio
    
    
@@ -179,13 +172,11 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
    
    if __name__ == '__main__':
        main()
-   
    ```
 
-4.  `async`和`await`。
+4. `async`和`await`。
 
    ```Python
-   
    import asyncio
    import aiohttp
    
@@ -215,7 +206,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
    
    if __name__ == '__main__':
        main()
-   
    ```
 
    上面的代码使用了[AIOHTTP](https://github.com/aio-libs/aiohttp)这个非常著名的第三方库,它实现了HTTP客户端和HTTP服务器的功能,对异步操作提供了非常好的支持,有兴趣可以阅读它的[官方文档](https://aiohttp.readthedocs.io/en/stable/)。
@@ -225,7 +215,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
 下面我们把之间讲的所有知识结合起来,用面向对象的方式实现一个爬取“手机搜狐网”的多线程爬虫。
 
 ```Python
-
 import pickle
 import zlib
 from enum import Enum, unique
@@ -382,6 +371,5 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 

+ 2 - 9
Day66-75/05.解析动态内容.md

@@ -25,14 +25,12 @@ WebKit的代码始于1998年的KHTML项目,当时它是Konqueror浏览器的
 如果没有打算用上面所说的方式来渲染页面并获得动态内容,其实还有一种替代方案就是使用自动化测试工具Selenium,它提供了浏览器自动化的API接口,这样就可以通过操控浏览器来获取动态内容。首先可以使用pip来安装Selenium。
 
 ```Shell
-
-$ pip3 install selenium
+pip3 install selenium
 ```
 
 下面以“阿里V任务”的“直播服务”为例,来演示如何使用Selenium获取到动态内容并抓取主播图片。
 
 ```Python
-
 import requests
 
 from bs4 import BeautifulSoup
@@ -47,13 +45,11 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 
 运行上面的程序会发现没有任何的输出,因为页面的HTML代码上根本找不到`<img>`标签。接下来我们使用Selenium来获取到页面上的动态内容,再提取主播图片。
 
 ```Python
-
 from bs4 import BeautifulSoup
 from selenium import webdriver
 from selenium.webdriver.common.keys import Keys
@@ -69,21 +65,18 @@ def main():
 
 if __name__ == '__main__':
     main()
-
 ```
 
 在上面的程序中,我们通过Selenium实现对Chrome浏览器的操控,如果要操控其他的浏览器,可以创对应的浏览器对象,例如Firefox、IE等。运行上面的程序,如果看到如下所示的错误提示,那是说明我们还没有将Chrome浏览器的驱动添加到PATH环境变量中,也没有在程序中指定Chrome浏览器驱动所在的位置。
 
 ```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的[官方网站]()找到浏览器驱动的下载链接并下载需要的驱动,在Linux或macOS系统下可以通过下面的命令来设置PATH环境变量,Windows下配置环境变量也非常简单,不清楚的可以自行了解。
 
 ```Shell
-
-$ export PATH=$PATH:/Users/Hao/Downloads/Tools/chromedriver/
+export PATH=$PATH:/Users/Hao/Downloads/Tools/chromedriver/
 ```
 
 其中`/Users/Hao/Downloads/Tools/chromedriver/ `就是chromedriver所在的路径。

+ 0 - 11
Day66-75/07.Scrapy入门.md

@@ -43,13 +43,11 @@ Scrapy的整个数据处理流程由Scrapy引擎进行控制,通常的运转
 
 ```Shell
 
-$ 
 ```
 
 项目的目录结构如下图所示。
 
 ```Shell
-
 (venv) $ tree
 .
 |____ scrapy.cfg
@@ -78,7 +76,6 @@ $
 1. 在items.py文件中定义字段,这些字段用来保存数据,方便后续的操作。
 
    ```Python
-   
    # -*- coding: utf-8 -*-
    
    # Define here the models for your scraped items
@@ -102,12 +99,10 @@ $
 2. 在spiders文件夹中编写自己的爬虫。
 
    ```Shell
-   
    (venv) $ scrapy genspider movie movie.douban.com --template=crawl
    ```
 
    ```Python
-   
    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.selector import Selector
@@ -136,28 +131,24 @@ $
            item['classification']= sel.xpath('//span[@property="v:genre"]/text()').extract()
            item['actor']= sel.xpath('//*[@id="info"]/span[3]/a[1]/text()').extract()
            return item
-   
    ```
    > 说明:上面我们通过Scrapy提供的爬虫模板创建了Spider,其中的rules中的LinkExtractor对象会自动完成对新的链接的解析,该对象中有一个名为extract_link的回调方法。Scrapy支持用XPath语法和CSS选择器进行数据解析,对应的方法分别是xpath和css,上面我们使用了XPath语法对页面进行解析,如果不熟悉XPath语法可以看看后面的补充说明。
 
    到这里,我们已经可以通过下面的命令让爬虫运转起来。
 
    ```Shell
-   
    (venv)$ scrapy crawl movie
    ```
 
    可以在控制台看到爬取到的数据,如果想将这些数据保存到文件中,可以通过`-o`参数来指定文件名,Scrapy支持我们将爬取到的数据导出成JSON、CSV、XML、pickle、marshal等格式。
 
    ```Shell
-   
    (venv)$ scrapy crawl moive -o result.json
    ```
 
 3. 在pipelines.py中完成对数据进行持久化的操作。
 
    ```Python
-   
    # -*- coding: utf-8 -*-
    
    # Define your item pipelines here
@@ -211,7 +202,6 @@ $
 4. 修改settings.py文件对项目进行配置。
 
    ```Python
-   
    # -*- coding: utf-8 -*-
    
    # Scrapy settings for douban project
@@ -325,7 +315,6 @@ $
    XML文件。
 
    ```XML
-   
    <?xml version="1.0" encoding="UTF-8"?>
    
    <bookstore>

+ 764 - 0
Day91-100/项目部署上线指南.md

@@ -0,0 +1,764 @@
+## 项目部署上线指南
+
+### 准备上线
+
+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                          |
+   | 网站监控和统计 | 阿里云监控、监控宝、百度云观测、小鸟云  |
+

+ 9 - 2
README.md

@@ -145,19 +145,26 @@
 - 使用进程 - fork函数 / multiprocessing模块 / 进程池 / 进程间通信
 - 使用线程 - thread模块 / threading模块 / Thread类 / Lock类
 
-#### Day14 - [网络编程入门](./Day01-15/Day14/网络编程入门.md)
+#### Day14-A - [网络编程入门](./Day01-15/Day14/网络编程入门.md)
 
 - 计算机网络基础 - 计算机网络发展史 / “TCP-IP”模型 / IP地址 / 端口 / 协议 / 其他相关概念
 - 网络应用架构 - “客户端-服务器”架构 / “浏览器-服务器”架构
 - Python网络编程 - 套接字的概念 / socket模块 /  socket函数 / 创建TCP服务器 / 创建TCP客户端 / 创建UDP服务器 / 创建UDP客户端 / SocketServer模块
 
-#### Day15 - [网络应用开发](./Day01-15/Day15/网络应用开发.md)
+#### Day14-B - [网络应用开发](./Day01-15/Day15/网络应用开发.md)
 
 - 访问网络API - 网络API概述 / 访问URL / requests模块 / 解析JSON格式数据
 - 文件传输 - FTP协议 / ftplib模块 / 交互式FTP应用
 - 电子邮件 - SMTP协议 / POP3协议 / IMAP协议 / smtplib模块 / poplib模块 / imaplib模块
 - 短信服务 - twilio模块 / 国内的短信服务
 
+#### Day15 - 图像和文档处理
+
+- 用Pillow处理图片
+- 读写Word文档
+- 读写Excel文件
+- 生成PDF文件
+
 ### Day16~Day20 - [Python语言进阶 ](./Day16-20/Python语言进阶.md)
 
 - 常用数据结构

TEMPAT SAMPAH
res/01.django_single_server.png


TEMPAT SAMPAH
res/02.django_dedicated_db_server.png


TEMPAT SAMPAH
res/03.django_dedicated_static_server.png


TEMPAT SAMPAH
res/04.django_load_balance.png


TEMPAT SAMPAH
res/05.django_massive_cluster.png


TEMPAT SAMPAH
res/django_request_response_cycle.png


TEMPAT SAMPAH
res/hadoop_ecosystem.png


+ 1 - 1
玩转PyCharm.md

@@ -1,4 +1,4 @@
-## 玩转PyCharm(上)
+## 玩转PyCharm
 
 PyCharm是由JetBrains公司开发的提供给Python专业的开发者的一个集成开发环境,它最大的优点是能够大大提升Python开发者的工作效率,为开发者集成了很多用起来非常顺手的功能,包括代码调试、高亮语法、代码跳转、智能提示、自动补全、单元测试、版本控制等等。此外,PyCharm还提供了对一些高级功能的支持,包括支持基于Django框架的Web开发、。
 

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini