Ver Fonte

更新了基础部分的部分内容

jackfrued há 7 anos atrás
pai
commit
76f656868a
53 ficheiros alterados com 1473 adições e 0 exclusões
  1. 29 0
      Day01-15/Appendix-A/code/excel1.py
  2. 22 0
      Day01-15/Appendix-A/code/excel2.py
  3. 11 0
      Day01-15/Appendix-A/code/pdf1.py
  4. 20 0
      Day01-15/Appendix-A/code/pdf2.py
  5. 26 0
      Day01-15/Appendix-A/code/pillow1.py
  6. BIN
      Day01-15/Appendix-A/code/res/Python课程大纲.pdf
  7. BIN
      Day01-15/Appendix-A/code/res/guido.jpg
  8. BIN
      Day01-15/Appendix-A/code/res/学生明细表.xlsx
  9. BIN
      Day01-15/Appendix-A/code/res/用函数还是用复杂的表达式.docx
  10. 10 0
      Day01-15/Appendix-A/code/word1.py
  11. 21 0
      Day01-15/Appendix-A/code/word2.py
  12. 44 0
      Day01-15/Appendix-A/图像和办公文档处理.md
  13. 34 0
      Day01-15/Appendix-B/chatclient.py
  14. 44 0
      Day01-15/Appendix-B/chatserver.py
  15. 29 0
      Day01-15/Appendix-B/fileclient.py
  16. 46 0
      Day01-15/Appendix-B/fileserver.py
  17. BIN
      Day01-15/Appendix-B/guido.jpg
  18. 33 0
      Day01-15/Appendix-B/mmdownloader.py
  19. BIN
      Day01-15/Appendix-B/res/TCP-IP-model.png
  20. BIN
      Day01-15/Appendix-B/res/after-browser.jpg
  21. BIN
      Day01-15/Appendix-B/res/arpanet.png
  22. BIN
      Day01-15/Appendix-B/res/before-browser.jpg
  23. BIN
      Day01-15/Appendix-B/res/browers.jpg
  24. BIN
      Day01-15/Appendix-B/res/browser-market-place.jpeg
  25. BIN
      Day01-15/Appendix-B/res/how-data-is-processed.jpg
  26. BIN
      Day01-15/Appendix-B/res/osi_rm.gif
  27. BIN
      Day01-15/Appendix-B/res/osimodel.png
  28. BIN
      Day01-15/Appendix-B/res/tcpipprotocols.png
  29. BIN
      Day01-15/Appendix-B/res/telnet.png
  30. 25 0
      Day01-15/Appendix-B/socket1.py
  31. 20 0
      Day01-15/Appendix-B/socket2.py
  32. 19 0
      Day01-15/Appendix-B/socket3.py
  33. 22 0
      Day01-15/Appendix-B/socket4.py
  34. 24 0
      Day01-15/Appendix-B/socket5.py
  35. 12 0
      Day01-15/Appendix-B/timeclient.py
  36. 33 0
      Day01-15/Appendix-B/timeserver.py
  37. 303 0
      Day01-15/Appendix-B/网络编程入门.md
  38. 78 0
      Day01-15/Day10/code/renju.py
  39. 334 0
      Day01-15/Day10/code/snake.py
  40. 234 0
      Day76-90/Python数据可视化.md
  41. BIN
      Day76-90/res/201205230001213115.png
  42. BIN
      Day76-90/res/201205230001238839.png
  43. BIN
      Day76-90/res/20120523000125800.png
  44. BIN
      Day76-90/res/result-in-jupyter.png
  45. BIN
      Day76-90/res/result1.png
  46. BIN
      Day76-90/res/result2.png
  47. BIN
      Day76-90/res/result3.png
  48. BIN
      Day76-90/res/result4.png
  49. BIN
      Day76-90/res/result5.png
  50. BIN
      Day76-90/res/result6.png
  51. BIN
      Day76-90/res/result7.png
  52. BIN
      Day76-90/res/result8.png
  53. BIN
      Day76-90/res/result9.png

+ 29 - 0
Day01-15/Appendix-A/code/excel1.py

@@ -0,0 +1,29 @@
+"""
+
+创建Excel文件
+
+Version: 0.1
+Author: 骆昊
+Date: 2018-03-26
+
+"""
+
+from openpyxl import Workbook
+from openpyxl.worksheet.table import Table, TableStyleInfo
+
+workbook = Workbook()
+sheet = workbook.active
+data = [
+    [1001, '白元芳', '男', '13123456789'],
+    [1002, '白洁', '女', '13233445566']
+]
+sheet.append(['学号', '姓名', '性别', '电话'])
+for row in data:
+    sheet.append(row)
+tab = Table(displayName="Table1", ref="A1:E5")
+
+tab.tableStyleInfo = TableStyleInfo(
+    name="TableStyleMedium9", showFirstColumn=False,
+    showLastColumn=False, showRowStripes=True, showColumnStripes=True)
+sheet.add_table(tab)
+workbook.save('./res/全班学生数据.xlsx')

+ 22 - 0
Day01-15/Appendix-A/code/excel2.py

@@ -0,0 +1,22 @@
+"""
+
+读取Excel文件
+
+Version: 0.1
+Author: 骆昊
+Date: 2018-03-26
+
+"""
+
+from openpyxl import load_workbook
+from openpyxl import Workbook
+
+workbook = load_workbook('./res/学生明细表.xlsx')
+print(workbook.sheetnames)
+sheet = workbook[workbook.sheetnames[0]]
+print(sheet.title)
+for row in range(2, 7):
+	for col in range(65, 70):
+		cell_index = chr(col) + str(row)
+		print(sheet[cell_index].value, end='\t')
+	print()

+ 11 - 0
Day01-15/Appendix-A/code/pdf1.py

@@ -0,0 +1,11 @@
+"""
+
+创建PDF文件
+
+Version: 0.1
+Author: 骆昊
+Date: 2018-03-26
+
+"""
+
+import PyPDF2

+ 20 - 0
Day01-15/Appendix-A/code/pdf2.py

@@ -0,0 +1,20 @@
+"""
+
+读取PDF文件
+
+Version: 0.1
+Author: 骆昊
+Date: 2018-03-26
+
+"""
+
+from PyPDF2 import PdfFileReader
+
+with open('./res/Python课程大纲.pdf', 'rb') as f:
+	reader = PdfFileReader(f, strict=False)
+	print(reader.numPages)
+	if reader.isEncrypted:
+		reader.decrypt('')
+	current_page = reader.getPage(5)
+	print(current_page)
+	print(current_page.extractText())

+ 26 - 0
Day01-15/Appendix-A/code/pillow1.py

@@ -0,0 +1,26 @@
+"""
+
+使用pillow操作图像
+
+Version: 0.1
+Author: 骆昊
+Date: 2018-03-26
+
+"""
+
+from PIL import Image
+
+img = Image.open('./res/guido.jpg')
+print(img.size)
+print(img.format)
+print(img.format_description)
+img.save('./res/guido.png')
+
+img2 = Image.open('./res/guido.png')
+img3 = img2.crop((335, 435, 430, 615))
+for x in range(4):
+	for y in range(5):
+		img2.paste(img3, (95 * y , 180 * x))
+img2.resize((img.size[0] // 2, img.size[1] // 2))
+img2.rotate(90)
+img2.save('./res/guido2.png')

BIN
Day01-15/Appendix-A/code/res/Python课程大纲.pdf


BIN
Day01-15/Appendix-A/code/res/guido.jpg


BIN
Day01-15/Appendix-A/code/res/学生明细表.xlsx


BIN
Day01-15/Appendix-A/code/res/用函数还是用复杂的表达式.docx


+ 10 - 0
Day01-15/Appendix-A/code/word1.py

@@ -0,0 +1,10 @@
+"""
+
+创建Word文件
+
+Version: 0.1
+Author: 骆昊
+Date: 2018-03-26
+
+"""
+

+ 21 - 0
Day01-15/Appendix-A/code/word2.py

@@ -0,0 +1,21 @@
+"""
+
+读取Word文件
+
+Version: 0.1
+Author: 骆昊
+Date: 2018-03-26
+
+"""
+
+from docx import Document
+
+doc = Document('./res/用函数还是用复杂的表达式.docx')
+print(len(doc.paragraphs))
+print(doc.paragraphs[0].text)
+# print(doc.paragraphs[1].runs[0].text)
+
+content = []
+for para in doc.paragraphs:
+	content.append(para.text)
+print(''.join(content))

+ 44 - 0
Day01-15/Appendix-A/图像和办公文档处理.md

@@ -0,0 +1,44 @@
+## 图像和办公文档处理
+
+用程序来处理图像以及办公文档在实际开发中非常常见,我们可以通过Python生态圈中的第三方模块来完成这些操作。
+
+### 操作图像
+
+#### 计算机图像相关知识
+
+1. 颜色。如果你有使用颜料画画的经历,那么一定知道混合红、黄、蓝三种颜料可以得到其他的颜色,事实上这三种颜色就是被我们称为美术三原色的东西,它们是不能再分解的基本颜色。在计算机中,我们可以将红、绿、蓝三种色光以不同的比例叠加来组合成其他的颜色,因此这三种颜色就是色光三原色,所以我们通常会将一个颜色表示为一个RGB值或RGBA值(其中的A表示Alpha通道,它决定了透过这个图像的像素,也就是透明度)。
+2. 像素。对于一个由数字序列表示的图像来说,最小的单位就是图像上单一颜色的小方格,这些小方块都有一个明确的位置和被分配的色彩数值,而这些一小方格的颜色和位置决定了该图像最终呈现出来的样子,它们是不可分割的单位,我们通常称之为像素(pixel)。每一个图像都包含了一定量的像素,这些像素决定图像在屏幕上所呈现的大小。
+
+#### 用Pillow操作图像
+
+- 剪裁图片
+- 调整图片大小
+- 旋转和翻转
+- 操作像素
+- 添加水印
+
+### 处理Excel电子表格
+
+Python的OpenPyXL模块让我们可以在Python程序中读取和修改Excel电子表格,LibreOffice Calc和OpenOffice Calc也都能处理Excel的电子表格文件,这就意味着OpenPyXL模块也能处理来自这些应用程序的电子表格。关于OpenPyXL的使用手册和使用文档可以查看它的[官方文档](https://openpyxl.readthedocs.io/en/stable/#)。
+
+### 处理Word文档
+
+| 属性          | 描述       |
+| ------------- | ---------- |
+| bold          | 粗体       |
+| italic        | 斜体       |
+| underline     | 下划线     |
+| strike        | 删除线     |
+| double_strike | 双删除线   |
+| all_caps      | 大写首字母 |
+| small_caps    | 大写首字母 |
+| shadow        | 带阴影     |
+| outline       | 轮廓显示   |
+| rtl           | 从右向左   |
+| imprint       | 凹嵌页面   |
+| emboss        | 凸出页面   |
+
+
+### 处理PDF文档
+
+PDF是Portable Document Format的缩写,使用.pdf作为文件扩展名。

+ 34 - 0
Day01-15/Appendix-B/chatclient.py

@@ -0,0 +1,34 @@
+from socket import socket
+from threading import Thread
+
+
+def main():
+
+    class RefreshScreenThread(Thread):
+
+        def __init__(self, client):
+            super().__init__()
+            self._client = client
+
+        def run(self):
+            while running:
+                data = self._client.recv(1024)
+                print(data.decode('utf-8'))
+
+    nickname = input('请输入你的昵称: ')
+    myclient = socket()
+    myclient.connect(('10.7.189.118', 12345))
+    running = True
+    RefreshScreenThread(myclient).start()
+    while running:
+        content = input('请发言: ')
+        if content == 'byebye':
+            myclient.send(content.encode('utf-8'))
+            running = False
+        else:
+            msg = nickname + ': ' + content
+            myclient.send(msg.encode('utf-8'))
+
+
+if __name__ == '__main__':
+    main()

+ 44 - 0
Day01-15/Appendix-B/chatserver.py

@@ -0,0 +1,44 @@
+from socket import socket
+from threading import Thread
+
+
+def main():
+
+    class ClientHandler(Thread):
+
+        def __init__(self, client):
+            super().__init__()
+            self._client = client
+
+        def run(self):
+            try:
+                while True:
+                    try:
+                        data = self._client.recv(1024)
+                        if data.decode('utf-8') == 'byebye':
+                            clients.remove(self._client)
+                            self._client.close()
+                            break
+                        else:
+                            for client in clients:
+                                client.send(data)
+                    except Exception as e:
+                        print(e)
+                        clients.remove(self._client)
+                        break
+            except Exception as e:
+                print(e)
+
+    server = socket()
+    server.bind(('10.7.189.118', 12345))
+    server.listen(512)
+    clients = []
+    while True:
+        curr_client, addr = server.accept()
+        print(addr[0], '连接到服务器.')
+        clients.append(curr_client)
+        ClientHandler(curr_client).start()
+
+
+if __name__ == '__main__':
+    main()

+ 29 - 0
Day01-15/Appendix-B/fileclient.py

@@ -0,0 +1,29 @@
+from socket import socket
+from json import loads
+from base64 import b64decode
+
+
+def main():
+    client = socket()
+    client.connect(('192.168.1.2', 5566))
+    # 定义一个保存二进制数据的对象
+    in_data = bytes()
+    # 由于不知道服务器发送的数据有多大每次接收1024字节
+    data = client.recv(1024)
+    while data:
+        # 将收到的数据拼接起来
+        in_data += data
+        data = client.recv(1024)
+    # 将收到的二进制数据解码成JSON字符串并转换成字典
+    # loads函数的作用就是将JSON字符串转成字典对象
+    my_dict = loads(in_data.decode('utf-8'))
+    filename = my_dict['filename']
+    filedata = my_dict['filedata'].encode('utf-8')
+    with open('/Users/Hao/' + filename, 'wb') as f:
+        # 将base64格式的数据解码成二进制数据并写入文件
+        f.write(b64decode(filedata))
+    print('图片已保存.')
+
+
+if __name__ == '__main__':
+    main()

+ 46 - 0
Day01-15/Appendix-B/fileserver.py

@@ -0,0 +1,46 @@
+from socket import socket, SOCK_STREAM, AF_INET
+from base64 import b64encode
+from json import dumps
+from threading import Thread
+
+
+def main():
+
+    # 自定义线程类
+    class FileTransferHandler(Thread):
+
+        def __init__(self, cclient):
+            super().__init__()
+            self.cclient = cclient
+
+        def run(self):
+            my_dict = {}
+            my_dict['filename'] = 'guido.jpg'
+            # JSON是纯文本不能携带二进制数据
+            # 所以图片的二进制数据要处理成base64编码
+            my_dict['filedata'] = data
+            # 通过dumps函数将字典处理成JSON字符串
+            json_str = dumps(my_dict)
+            # 发送JSON字符串
+            self.cclient.send(json_str.encode('utf-8'))
+            self.cclient.close()
+
+    # 1.创建套接字对象并指定使用哪种传输服务
+    server = socket()
+    # 2.绑定IP地址和端口(区分不同的服务)
+    server.bind(('192.168.1.2', 5566))
+    # 3.开启监听 - 监听客户端连接到服务器
+    server.listen(512)
+    print('服务器启动开始监听...')
+    with open('guido.jpg', 'rb') as f:
+        # 将二进制数据处理成base64再解码成字符串
+        data = b64encode(f.read()).decode('utf-8')
+    while True:
+        client, addr = server.accept()
+        # 用一个字典(键值对)来保存要发送的各种数据
+        # 待会可以将字典处理成JSON格式在网络上传递
+        FileTransferHandler(client).start()
+
+
+if __name__ == '__main__':
+    main()

BIN
Day01-15/Appendix-B/guido.jpg


+ 33 - 0
Day01-15/Appendix-B/mmdownloader.py

@@ -0,0 +1,33 @@
+from time import time
+from threading import Thread
+
+import requests
+
+
+class DownloadHanlder(Thread):
+
+    def __init__(self, url):
+        super().__init__()
+        self.url = url
+
+    def run(self):
+        filename = self.url[self.url.rfind('/') + 1:]
+        resp = requests.get(self.url)
+        with open('/Users/Hao/Downloads/' + filename, 'wb') as f:
+            f.write(resp.content)
+
+
+def main():
+    # 通过requests模块的get函数获取网络资源
+    resp = requests.get(
+        'http://api.tianapi.com/meinv/?key=772a81a51ae5c780251b1f98ea431b84&num=10')
+    # 将服务器返回的JSON格式的数据解析为字典
+    data_model = resp.json()
+    for mm_dict in data_model['newslist']:
+        url = mm_dict['picUrl']
+        # 通过多线程的方式实现图片下载
+        DownloadHanlder(url).start()
+
+
+if __name__ == '__main__':
+    main()

BIN
Day01-15/Appendix-B/res/TCP-IP-model.png


BIN
Day01-15/Appendix-B/res/after-browser.jpg


BIN
Day01-15/Appendix-B/res/arpanet.png


BIN
Day01-15/Appendix-B/res/before-browser.jpg


BIN
Day01-15/Appendix-B/res/browers.jpg


BIN
Day01-15/Appendix-B/res/browser-market-place.jpeg


BIN
Day01-15/Appendix-B/res/how-data-is-processed.jpg


BIN
Day01-15/Appendix-B/res/osi_rm.gif


BIN
Day01-15/Appendix-B/res/osimodel.png


BIN
Day01-15/Appendix-B/res/tcpipprotocols.png


BIN
Day01-15/Appendix-B/res/telnet.png


+ 25 - 0
Day01-15/Appendix-B/socket1.py

@@ -0,0 +1,25 @@
+"""
+
+套接字 - 基于TCP协议创建时间服务器
+
+Version: 0.1
+Author: 骆昊
+Date: 2018-03-22
+
+"""
+
+from socket import *
+from time import *
+
+server = socket(AF_INET, SOCK_STREAM)
+server.bind(('localhost', 6789))
+server.listen()
+print('服务器已经启动正在监听客户端连接.')
+while True:
+	client, addr = server.accept()
+	print('客户端%s:%d连接成功.' % (addr[0], addr[1]))
+	currtime = localtime(time())
+	timestr = strftime('%Y-%m-%d %H:%M:%S', currtime)
+	client.send(timestr.encode('utf-8'))
+	client.close()
+server.close()

+ 20 - 0
Day01-15/Appendix-B/socket2.py

@@ -0,0 +1,20 @@
+"""
+
+套接字 - 基于TCP协议创建时间客户端
+
+Version: 0.1
+Author: 骆昊
+Date: 2018-03-22
+
+"""
+
+from socket import *
+
+client = socket(AF_INET, SOCK_STREAM)
+client.connect(('localhost', 6789))
+while True:
+	data = client.recv(1024)
+	if not data:
+		break
+	print(data.decode('utf-8'))
+client.close()

+ 19 - 0
Day01-15/Appendix-B/socket3.py

@@ -0,0 +1,19 @@
+"""
+
+套接字 - 基于UDP协议Echo服务器
+
+Version: 0.1
+Author: 骆昊
+Date: 2018-03-22
+
+"""
+
+from socket import *
+from time import *
+
+server = socket(AF_INET, SOCK_DGRAM)
+server.bind(('localhost', 6789))
+while True:
+	data, addr = server.recvfrom(1024)
+	server.sendto(data, addr)
+server.close()

+ 22 - 0
Day01-15/Appendix-B/socket4.py

@@ -0,0 +1,22 @@
+"""
+
+套接字 - 基于UDP协议创建Echo客户端
+
+Version: 0.1
+Author: 骆昊
+Date: 2018-03-22
+
+"""
+
+from socket import *
+
+client = socket(AF_INET, SOCK_DGRAM)
+while True:
+	data_str = input('请输入: ')
+	client.sendto(data_str.encode('utf-8'), ('localhost', 6789))
+	data, addr = client.recvfrom(1024)
+	data_str = data.decode('utf-8')
+	print('服务器回应:', data_str)
+	if data_str == 'bye':
+		break
+client.close()

+ 24 - 0
Day01-15/Appendix-B/socket5.py

@@ -0,0 +1,24 @@
+"""
+
+使用socketserver模块创建时间服务器
+
+Version: 0.1
+Author: 骆昊
+Date: 2018-03-22
+
+"""
+
+from socketserver import TCPServer, StreamRequestHandler
+from time import *
+
+
+class EchoRequestHandler(StreamRequestHandler):
+
+	def handle(self):
+		currtime = localtime(time())
+		timestr = strftime('%Y-%m-%d %H:%M:%S', currtime)
+		self.wfile.write(timestr.encode('utf-8'))
+
+
+server = TCPServer(('localhost', 6789), EchoRequestHandler)
+server.serve_forever()

+ 12 - 0
Day01-15/Appendix-B/timeclient.py

@@ -0,0 +1,12 @@
+from socket import socket
+
+
+def main():
+    client = socket()
+    client.connect(('10.7.152.69', 6789))
+    print(client.recv(1024).decode('utf-8'))
+    client.close()
+
+
+if __name__ == '__main__':
+    main()

+ 33 - 0
Day01-15/Appendix-B/timeserver.py

@@ -0,0 +1,33 @@
+from socket import socket, SOCK_STREAM, AF_INET
+from datetime import datetime
+
+
+def main():
+    # 1.创建套接字对象并指定使用哪种传输服务
+    # family=AF_INET - IPv4地址
+    # family=AF_INET6 - IPv6地址
+    # type=SOCK_STREAM - TCP套接字
+    # type=SOCK_DGRAM - UDP套接字
+    # type=SOCK_RAW - 原始套接字
+    server = socket(family=AF_INET, type=SOCK_STREAM)
+    # 2.绑定IP地址和端口(区分不同的服务)
+    server.bind(('192.168.1.2', 6789))
+    # 3.开启监听 - 监听客户端连接到服务器
+    server.listen(512)
+    print('服务器启动开始监听...')
+    # 4.通过循环接收客户端的连接并作出相应的处理(提供服务)
+    while True:
+        # accept方法是一个阻塞方法如果没有客户端连接到服务器
+        # 这个方法就会阻塞代码不会向下执行
+        # accept方法返回元组其中的第一个元素是客户端对象
+        # 第二个元素是客户端的地址(由IP和端口两部分构成)
+        client, addr = server.accept()
+        print(str(addr) + '连接到了服务器.')
+        # 5.发送数据
+        client.send(str(datetime.now()).encode('utf-8'))
+        # 6.断开连接
+        client.close()
+
+
+if __name__ == '__main__':
+    main()

+ 303 - 0
Day01-15/Appendix-B/网络编程入门.md

@@ -0,0 +1,303 @@
+## 网络编程入门
+
+### 计算机网络基础
+
+计算机网络是独立自主的计算机互联而成的系统的总称,组建计算机网络最主要的目的是实现多台计算机之间的通信和资源共享。今天计算机网络中的设备和计算机网络的用户已经多得不可计数,而计算机网络也可以称得上是一个“复杂巨系统”,对于这样的系统,我们不可能用一两篇文章把它讲清楚,有兴趣的读者可以自行阅读Andrew S.Tanenbaum老师的经典之作《计算机网络》或Kurose和Ross老师合著的《计算机网络:自顶向下方法》来了解计算机网络的相关知识。
+
+#### 计算机网络发展史
+
+1. 1960s - 美国国防部ARPANET项目问世,奠定了分组交换网络的基础。
+
+   ![](./res/arpanet.png)
+
+2. 1980s - 国际标准化组织(ISO)发布OSI/RM,奠定了网络技术标准化的基础。
+
+   ![](./res/osimodel.png)
+
+3. 1990s - 英国人[蒂姆·伯纳斯-李](https://zh.wikipedia.org/wiki/%E6%8F%90%E5%A7%86%C2%B7%E6%9F%8F%E5%85%A7%E8%8C%B2-%E6%9D%8E)发明了图形化的浏览器,浏览器的简单易用性使得计算机网络迅速被普及。
+
+   在没有浏览器的年代,上网是这样的。
+
+   ![](./res/before-browser.jpg)
+
+   有了浏览器以后,上网是这样的。
+
+   ![](./res/after-browser.jpg)
+
+#### TCP/IP模型
+
+实现网络通信的基础是网络通信协议,这些协议通常是由[互联网工程任务组](https://zh.wikipedia.org/wiki/%E4%BA%92%E8%81%94%E7%BD%91%E5%B7%A5%E7%A8%8B%E4%BB%BB%E5%8A%A1%E7%BB%84) (IETF)制定的。所谓“协议”就是通信计算机双方必须共同遵从的一组约定,例如怎样建立连接、怎样互相识别等,网络协议的三要素是:语法、语义和时序。构成我们今天使用的Internet的基础的是TCP/IP协议族,所谓协议族就是一系列的协议及其构成的通信模型,我们通常也把这套东西称为TCP/IP模型。与国际标准化组织发布的OSI/RM这个七层模型不同,TCP/IP是一个四层模型,也就是说,该模型将我们使用的网络从逻辑上分解为四个层次,自底向上依次是:网络接口层、网络层、传输层和应用层,如下图所示。
+
+![](./res/TCP-IP-model.png)
+
+IP通常被翻译为网际协议,它服务于网络层,主要实现了寻址和路由的功能。接入网络的每一台主机都需要有自己的IP地址,IP地址就是主机在计算机网络上的身份标识。当然由于IPv4地址的匮乏,我们平常在家里、办公室以及其他可以接入网络的公共区域上网时获得的IP地址并不是全球唯一的IP地址,而是一个[局域网(LAN)](https://zh.wikipedia.org/zh-hans/%E5%B1%80%E5%9F%9F%E7%BD%91)中的内部IP地址,通过[网络地址转换(NAT)服务](https://zh.wikipedia.org/wiki/%E7%BD%91%E7%BB%9C%E5%9C%B0%E5%9D%80%E8%BD%AC%E6%8D%A2)我们也可以实现对网络的访问。计算机网络上有大量的被我们称为“[路由器](https://zh.wikipedia.org/wiki/%E8%B7%AF%E7%94%B1%E5%99%A8)”的网络中继设备,它们会存储转发我们发送到网络上的数据分组,让从源头发出的数据最终能够找到传送到目的地通路,这项功能就是所谓的路由。
+
+TCP全称传输控制协议,它是基于IP提供的寻址和路由服务而建立起来的负责实现端到端可靠传输的协议,之所以将TCP称为可靠的传输协议是因为TCP向调用者承诺了三件事情:
+
+1. 数据不传丢不传错(利用握手、校验和重传机制可以实现)。
+2. 流量控制(通过滑动窗口匹配数据发送者和接收者之间的传输速度)。
+3. 拥塞控制(通过RTT时间以及对滑动窗口的控制缓解网络拥堵)。
+
+
+
+#### 网络应用模式
+
+1. C/S模式和B/S模式。这里的C指的是Client(客户端),通常是一个需要安装到某个宿主操作系统上的应用程序;而B指的是Browser(浏览器),它几乎是所有图形化操作系统都默认安装了的一个应用软件;通过C或B都可以实现对S(服务器)的访问。关于二者的比较和讨论在网络上有一大堆的文章,在此我们就不再浪费笔墨了。
+2. 去中心化的网络应用模式。不管是B/S还是C/S都需要服务器的存在,服务器就是整个应用模式的中心,而去中心化的网络应用通常没有固定的服务器或者固定的客户端,所有应用的使用者既可以作为资源的提供者也可以作为资源的访问者。
+
+### 基于HTTP协议的网络资源访问
+
+#### HTTP(超文本传输协议)
+
+HTTP是超文本传输协议(Hyper-Text Transfer Proctol)的简称,维基百科上对HTTP的解释是:超文本传输协议是一种用于分布式、协作式和超媒体信息系统的应用层协议,它是[万维网](https://zh.wikipedia.org/wiki/%E5%85%A8%E7%90%83%E8%B3%87%E8%A8%8A%E7%B6%B2)数据通信的基础,设计HTTP最初的目的是为了提供一种发布和接收[HTML](https://zh.wikipedia.org/wiki/HTML)页面的方法,通过HTTP或者[HTTPS](https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8%E5%8D%8F%E8%AE%AE)(超文本传输安全协议)请求的资源由URI([统一资源标识符](https://zh.wikipedia.org/wiki/%E7%B5%B1%E4%B8%80%E8%B3%87%E6%BA%90%E6%A8%99%E8%AD%98%E7%AC%A6))来标识。关于HTTP的更多内容,我们推荐阅读阮一峰老师的[《HTTP 协议入门》](http://www.ruanyifeng.com/blog/2016/08/http.html),简单的说,通过HTTP我们可以获取网络上的(基于字符的)资源,开发中经常会用到的网络API(有的地方也称之为网络数据接口)就是基于HTTP来实现数据传输的。
+
+#### JSON格式
+
+**JSON**(**J**ava**S**cript **O**bject **N**otation)是一种轻量级的数据交换语言,该语言以易于让人阅读的文字(纯文本)为基础,用来传输由属性值或者序列性的值组成的数据对象。尽管JSON是最初只是Javascript中一种创建对象的字面量语法,但它在当下更是一种独立于语言的数据格式,很多编程语言都支持JSON格式数据的生成和解析,Python内置的json模块也提供了这方面的功能。由于JSON是纯文本,它和[XML](https://zh.wikipedia.org/wiki/XML)一样都适用于异构系统之间的数据交换,而相较于XML,JSON显得更加的轻便和优雅。下面是表达同样信息的XML和JSON,而JSON的优势是相当直观的。
+
+XML的例子:
+
+```XML
+<?xml version="1.0" encoding="UTF-8"?>
+<message>
+	<from>Alice</from>
+	<to>Bob</to>
+	<content>Will you marry me?</content>
+</message>
+```
+
+JSON的例子:
+
+```JSON
+{
+    'from': 'Alice',
+    'to': 'Bob',
+    'content': 'Will you marry me?'
+}
+```
+
+#### requests库
+
+requests是一个基于HTTP协议来使用网络的第三库,其[官方网站](http://cn.python-requests.org/zh_CN/latest/)有这样的一句介绍它的话:“Requests是唯一的一个**非转基因**的Python HTTP库,人类可以安全享用。”简单的说,使用requests库可以非常方便的使用HTTP,避免安全缺陷、冗余代码以及“重复发明轮子”(行业黑话,通常用在软件工程领域表示重新创造一个已有的或是早已被优化過的基本方法)。前面的文章中我们已经使用过这个库,下面我们还是通过requests来实现一个访问网络数据接口并从中获取美女图片下载链接然后下载美女图片到本地的例子程序,程序中使用了[天行数据](https://www.tianapi.com/)提供的网络API。
+
+我们可以先通过pip安装requests及其依赖库。
+
+```Shell
+pip install requests
+```
+
+如果使用PyCharm作为开发工具,可以直接在代码中书写`import requests`,然后通过代码修复功能来自动下载安装requests。
+
+```Python
+from time import time
+from threading import Thread
+
+import requests
+
+
+# 继承Thread类创建自定义的线程类
+class DownloadHanlder(Thread):
+
+    def __init__(self, url):
+        super().__init__()
+        self.url = url
+
+    def run(self):
+        filename = self.url[self.url.rfind('/') + 1:]
+        resp = requests.get(self.url)
+        with open('/Users/Hao/' + filename, 'wb') as f:
+            f.write(resp.content)
+
+
+def main():
+    # 通过requests模块的get函数获取网络资源
+    # 下面的代码中使用了天行数据接口提供的网络API
+    # 要使用该数据接口需要在天行数据的网站上注册
+    # 然后用自己的Key替换掉下面代码的中APIKey即可
+    resp = requests.get(
+        'http://api.tianapi.com/meinv/?key=APIKey&num=10')
+    # 将服务器返回的JSON格式的数据解析为字典
+    data_model = resp.json()
+    for mm_dict in data_model['newslist']:
+        url = mm_dict['picUrl']
+        # 通过多线程的方式实现图片下载
+        DownloadHanlder(url).start()
+
+
+if __name__ == '__main__':
+    main()
+
+```
+
+### 基于传输层协议的套接字编程
+
+套接字这个词对很多不了解网络编程的人来说显得非常晦涩和陌生,其实说得通俗点,套接字就是一套用[C语言](https://zh.wikipedia.org/wiki/C%E8%AF%AD%E8%A8%80)写成的应用程序开发库,主要用于实现进程间通信和网络编程,在网络应用开发中被广泛使用。在Python中也可以基于套接字来使用传输层提供的传输服务,并基于此开发自己的网络应用。实际开发中使用的套接字可以分为三类:流套接字(TCP套接字)、数据报套接字和原始套接字。
+
+#### TCP套接字
+
+所谓TCP套接字就是使用TCP协议提供的传输服务来实现网络通信的编程接口。在Python中可以通过创建socket对象并指定type属性为SOCK_STREAM来使用TCP套接字。由于一台主机可能拥有多个IP地址,而且很有可能会配置多个不同的服务,所以作为服务器端的程序,需要在创建套接字对象后将其绑定到指定的IP地址和端口上。这里的端口并不是物理设备而是对IP地址的扩展,用于区分不同的服务,例如我们通常将HTTP服务跟80端口绑定,而MySQL数据库服务默认绑定在3306端口,这样当服务器收到用户请求时就可以根据端口号来确定到底用户请求的是HTTP服务器还是数据库服务器提供的服务。端口的取值范围是0~65535,而1024以下的端口我们通常称之为“著名端口”(留给像FTP、HTTP、SMTP等“著名服务”使用的端口,有的地方也称之为“周知端口”),自定义的服务通常不使用这些端口,除非自定义的是HTTP或FTP这样的著名服务。
+
+下面的代码实现了一个提供时间日期的服务器。
+
+```Python
+from socket import socket, SOCK_STREAM, AF_INET
+from datetime import datetime
+
+
+def main():
+    # 1.创建套接字对象并指定使用哪种传输服务
+    # family=AF_INET - IPv4地址
+    # family=AF_INET6 - IPv6地址
+    # type=SOCK_STREAM - TCP套接字
+    # type=SOCK_DGRAM - UDP套接字
+    # type=SOCK_RAW - 原始套接字
+    server = socket(family=AF_INET, type=SOCK_STREAM)
+    # 2.绑定IP地址和端口(端口用于区分不同的服务)
+    # 同一时间在同一个端口上只能绑定一个服务否则报错
+    server.bind(('192.168.1.2', 6789))
+    # 3.开启监听 - 监听客户端连接到服务器
+    # 参数512可以理解为连接队列的大小
+    server.listen(512)
+    print('服务器启动开始监听...')
+    while True:
+        # 4.通过循环接收客户端的连接并作出相应的处理(提供服务)
+        # accept方法是一个阻塞方法如果没有客户端连接到服务器代码不会向下执行
+        # accept方法返回一个元组其中的第一个元素是客户端对象
+        # 第二个元素是连接到服务器的客户端的地址(由IP和端口两部分构成)
+        client, addr = server.accept()
+        print(str(addr) + '连接到了服务器.')
+        # 5.发送数据
+        client.send(str(datetime.now()).encode('utf-8'))
+        # 6.断开连接
+        client.close()
+
+
+if __name__ == '__main__':
+    main()
+
+```
+
+运行服务器程序后我们可以通过Windows系统的telnet来访问该服务器,结果如下图所示。
+
+```Shell
+telnet 192.168.1.2 6789
+```
+
+![](./res/telnet.png)
+
+当然我们也可以通过Python的程序来实现TCP客户端的功能,相较于实现服务器程序,实现客户端程序就简单多了,代码如下所示。
+
+```Python
+from socket import socket
+
+
+def main():
+    # 1.创建套接字对象默认使用IPv4和TCP协议
+    client = socket()
+    # 2.连接到服务器(需要指定IP地址和端口)
+    client.connect(('192.168.1.2', 6789))
+    # 3.从服务器接收数据
+    print(client.recv(1024).decode('utf-8'))
+    client.close()
+
+
+if __name__ == '__main__':
+    main()
+
+```
+
+需要注意的是,上面的服务器并没有使用多线程或者异步I/O的处理方式,这也就意味着当服务器与一个客户端处于通信状态时,其他的客户端只能排队等待。很显然,这样的服务器并不能满足我们的需求,我们需要的服务器是能够同时接纳和处理多个用户请求的。下面我们来设计一个使用多线程技术处理多个用户请求的服务器,该服务器会向连接到服务器的客户端发送一张图片。
+
+服务器端代码:
+
+```Python
+from socket import socket, SOCK_STREAM, AF_INET
+from base64 import b64encode
+from json import dumps
+from threading import Thread
+
+
+def main():
+    
+    # 自定义线程类
+    class FileTransferHandler(Thread):
+
+        def __init__(self, cclient):
+            super().__init__()
+            self.cclient = cclient
+
+        def run(self):
+            my_dict = {}
+            my_dict['filename'] = 'guido.jpg'
+            # JSON是纯文本不能携带二进制数据
+            # 所以图片的二进制数据要处理成base64编码
+            my_dict['filedata'] = data
+            # 通过dumps函数将字典处理成JSON字符串
+            json_str = dumps(my_dict)
+            # 发送JSON字符串
+            self.cclient.send(json_str.encode('utf-8'))
+            self.cclient.close()
+
+    # 1.创建套接字对象并指定使用哪种传输服务
+    server = socket()
+    # 2.绑定IP地址和端口(区分不同的服务)
+    server.bind(('192.168.1.2', 5566))
+    # 3.开启监听 - 监听客户端连接到服务器
+    server.listen(512)
+    print('服务器启动开始监听...')
+    with open('guido.jpg', 'rb') as f:
+        # 将二进制数据处理成base64再解码成字符串
+        data = b64encode(f.read()).decode('utf-8')
+    while True:
+        client, addr = server.accept()
+        # 启动一个线程来处理客户端的请求
+        FileTransferHandler(client).start()
+
+
+if __name__ == '__main__':
+    main()
+
+```
+
+客户端代码:
+
+```Python
+from socket import socket
+from json import loads
+from base64 import b64decode
+
+
+def main():
+    client = socket()
+    client.connect(('192.168.1.2', 5566))
+    # 定义一个保存二进制数据的对象
+    in_data = bytes()
+    # 由于不知道服务器发送的数据有多大每次接收1024字节
+    data = client.recv(1024)
+    while data:
+        # 将收到的数据拼接起来
+        in_data += data
+        data = client.recv(1024)
+    # 将收到的二进制数据解码成JSON字符串并转换成字典
+    # loads函数的作用就是将JSON字符串转成字典对象
+    my_dict = loads(in_data.decode('utf-8'))
+    filename = my_dict['filename']
+    filedata = my_dict['filedata'].encode('utf-8')
+    with open('/Users/Hao/' + filename, 'wb') as f:
+        # 将base64格式的数据解码成二进制数据并写入文件
+        f.write(b64decode(filedata))
+    print('图片已保存.')
+
+
+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的读者可以自行阅读。
+
+> **说明**:上面的代码主要为了讲解网络编程的相关内容因此并没有对异常状况进行处理,请读者自行添加异常处理代码来增强程序的健壮性。
+
+#### UDP套接字
+
+传输层除了有可靠的传输协议TCP之外,还有一种非常轻便的传输协议叫做用户数据报协议,简称UDP。TCP和UDP都是提供端到端传输服务的协议,二者的差别就如同打电话和发短信的区别,后者不对传输的可靠性和可达性做出任何承诺从而避免了TCP中握手和重传的开销,所以在强调性能和而不是数据完整性的场景中(例如传输网络音视频数据),UDP可能是更好的选择。可能大家会注意到一个现象,就是在观看网络视频时,有时会出现卡顿,有时会出现花屏,这无非就是部分数据传丢或传错造成的。在Python中也可以使用UDP套接字来创建网络应用,对此我们不进行赘述,有兴趣的读者可以自行研究。

+ 78 - 0
Day01-15/Day10/code/renju.py

@@ -0,0 +1,78 @@
+import pygame
+
+EMPTY = 0
+BLACK = 1
+WHITE = 2
+
+black_color = [0, 0, 0]
+white_color = [255, 255, 255]
+
+
+class RenjuBoard(object):
+
+    def __init__(self):
+        self._board = [[]] * 15
+        self.reset()
+
+    def reset(self):
+        for row in range(len(self._board)):
+            self._board[row] = [EMPTY] * 15
+
+    def move(self, row, col, is_black):
+        if self._board[row][col] == EMPTY:
+            self._board[row][col] = BLACK if is_black else WHITE
+            return True
+        return False
+
+    def draw(self, screen):
+        for index in range(1, 16):
+            pygame.draw.line(screen, black_color,
+                             [40, 40 * index], [600, 40 * index], 1)
+            pygame.draw.line(screen, black_color,
+                             [40 * index, 40], [40 * index, 600], 1)
+        pygame.draw.rect(screen, black_color, [36, 36, 568, 568], 4)
+        pygame.draw.circle(screen, black_color, [320, 320], 5, 0)
+        pygame.draw.circle(screen, black_color, [160, 160], 5, 0)
+        pygame.draw.circle(screen, black_color, [480, 480], 5, 0)
+        pygame.draw.circle(screen, black_color, [480, 160], 5, 0)
+        pygame.draw.circle(screen, black_color, [160, 480], 5, 0)
+        for row in range(len(self._board)):
+            for col in range(len(self._board[row])):
+                if self._board[row][col] != EMPTY:
+                    ccolor = black_color \
+                        if self._board[row][col] == BLACK else white_color
+                    pos = [40 * (col + 1), 40 * (row + 1)]
+                    pygame.draw.circle(screen, ccolor, pos, 20, 0)
+
+
+def main():
+    board = RenjuBoard()
+    is_black = True
+    pygame.init()
+    pygame.display.set_caption('五子棋')
+    screen = pygame.display.set_mode([640, 640])
+    screen.fill([255, 255, 0])
+    board.draw(screen)
+    pygame.display.flip()
+    running = True
+    while running:
+        for event in pygame.event.get():
+            if event.type == pygame.QUIT:
+                running = False
+            elif event.type == pygame.KEYUP:
+                pass
+            elif event.type == pygame.MOUSEBUTTONDOWN\
+                    and event.button == 1:
+                x, y = event.pos
+                row = round((y - 40) / 40)
+                col = round((x - 40) / 40)
+                if board.move(row, col, is_black):
+                    is_black = not is_black
+                    screen.fill([255, 255, 0])
+                    board.draw(screen)
+                    pygame.display.flip()
+    pygame.quit()
+
+
+if __name__ == '__main__':
+    main()

+ 334 - 0
Day01-15/Day10/code/snake.py

@@ -0,0 +1,334 @@
+from abc import ABCMeta, abstractmethod
+from enum import Enum, unique
+from random import randrange
+from threading import Thread
+
+import pygame
+
+
+class Color(object):
+    """颜色"""
+
+    GRAY = (242, 242, 242)
+    BLACK = (0, 0, 0)
+    GREEN = (0, 255, 0)
+    PINK = (255, 20, 147)
+
+
+@unique
+class Direction(Enum):
+    """方向"""
+
+    UP = 0
+    RIGHT = 1
+    DOWN = 2
+    LEFT = 3
+
+
+class GameObject(object, metaclass=ABCMeta):
+    """游戏中的对象"""
+
+    def __init__(self, x=0, y=0, color=Color.BLACK):
+        """
+        初始化方法
+
+        :param x: 横坐标
+        :param y: 纵坐标
+        :param color: 颜色
+        """
+        self._x = x
+        self._y = y
+        self._color = color
+
+    @property
+    def x(self):
+        return self._x
+
+    @property
+    def y(self):
+        return self._y
+
+    @abstractmethod
+    def draw(self, screen):
+        """
+        绘制
+
+        :param screen: 屏幕
+        """
+        pass
+
+
+class Wall(GameObject):
+    """围墙"""
+
+    def __init__(self, x, y, width, height, color=Color.BLACK):
+        """
+        初始化方法
+
+        :param x: 横坐标
+        :param y: 纵坐标
+        :param width: 宽度
+        :param height: 高度
+        :param color: 颜色
+        """
+        super().__init__(x, y, color)
+        self._width = width
+        self._height = height
+
+    @property
+    def width(self):
+        return self._width
+
+    @property
+    def height(self):
+        return self._height
+
+    def draw(self, screen):
+        pygame.draw.rect(screen, self._color,
+                         (self._x, self._y, self._width, self._height), 4)
+
+
+class Food(GameObject):
+    """食物"""
+
+    def __init__(self, x, y, size, color=Color.PINK):
+        """
+        初始化方法
+
+        :param x: 横坐标
+        :param y: 纵坐标
+        :param size: 大小
+        :param color: 颜色
+        """
+        super().__init__(x, y, color)
+        self._size = size
+        self._hidden = False
+
+    def draw(self, screen):
+        if not self._hidden:
+            pygame.draw.circle(screen, self._color,
+                               (self._x + self._size // 2, self._y + self._size // 2),
+                               self._size // 2, 0)
+        self._hidden = not self._hidden
+
+
+class SnakeNode(GameObject):
+    """蛇身上的节点"""
+
+    def __init__(self, x, y, size, color=Color.GREEN):
+        """
+        初始化方法
+
+        :param x: 横坐标
+        :param y: 纵坐标
+        :param size: 大小
+        :param color: 颜色
+        """
+        super().__init__(x, y, color)
+        self._size = size
+
+    @property
+    def size(self):
+        return self._size
+
+    def draw(self, screen):
+        pygame.draw.rect(screen, self._color,
+                         (self._x, self._y, self._size, self._size), 0)
+        pygame.draw.rect(screen, Color.BLACK,
+                         (self._x, self._y, self._size, self._size), 1)
+
+
+class Snake(GameObject):
+    """蛇"""
+
+    def __init__(self, x, y, size=20, length=5):
+        """
+        初始化方法
+
+        :param x: 横坐标
+        :param y: 纵坐标
+        :param size: 大小
+        :param length: 初始长度
+        """
+        super().__init__()
+        self._dir = Direction.LEFT
+        self._nodes = []
+        self._alive = True
+        self._new_dir = None
+        for index in range(length):
+            node = SnakeNode(x + index * size, y, size)
+            self._nodes.append(node)
+
+    @property
+    def dir(self):
+        return self._dir
+
+    @property
+    def alive(self):
+        return self._alive
+
+    @property
+    def head(self):
+        return self._nodes[0]
+
+    def change_dir(self, new_dir):
+        """
+        改变方向
+
+        :param new_dir: 新方向
+        """
+        if new_dir != self._dir and \
+                (self._dir.value + new_dir.value) % 2 != 0:
+            self._new_dir = new_dir
+
+    def move(self):
+        """移动"""
+        if self._new_dir:
+            self._dir, self._new_dir = self._new_dir, None
+        snake_dir = self._dir
+        x, y, size = self.head.x, self.head.y, self.head.size
+        if snake_dir == Direction.UP:
+            y -= size
+        elif snake_dir == Direction.RIGHT:
+            x += size
+        elif snake_dir == Direction.DOWN:
+            y += size
+        else:
+            x -= size
+        new_head = SnakeNode(x, y, size)
+        self._nodes.insert(0, new_head)
+        self._nodes.pop()
+
+    def collide(self, wall):
+        """
+        撞墙
+
+        :param wall: 围墙
+        """
+        head = self.head
+        if head.x < wall.x or head.x + head.size > wall.x + wall.width \
+                or head.y < wall.y or head.y + head.size > wall.y + wall.height:
+            self._alive = False
+
+    def eat_food(self, food):
+        """
+        吃食物
+
+        :param food: 食物
+
+        :return: 吃到食物返回True否则返回False
+        """
+        if self.head.x == food.x and self.head.y == food.y:
+            tail = self._nodes[-1]
+            self._nodes.append(tail)
+            return True
+        return False
+
+    def eat_self(self):
+        """咬自己"""
+        for index in range(4, len(self._nodes)):
+            node = self._nodes[index]
+            if node.x == self.head.x and node.y == self.head.y:
+                self._alive = False
+
+    def draw(self, screen):
+        for node in self._nodes:
+            node.draw(screen)
+
+
+def main():
+
+    def refresh():
+        """刷新游戏窗口"""
+        screen.fill(Color.GRAY)
+        wall.draw(screen)
+        food.draw(screen)
+        snake.draw(screen)
+        pygame.display.flip()
+
+    def handle_key_event(key_event):
+        """处理按键事件"""
+        key = key_event.key
+        if key == pygame.K_F2:
+            reset_game()
+        elif key in (pygame.K_a, pygame.K_w, pygame.K_d, pygame.K_s):
+            if snake.alive:
+                if key == pygame.K_w:
+                    new_dir = Direction.UP
+                elif key == pygame.K_d:
+                    new_dir = Direction.RIGHT
+                elif key == pygame.K_s:
+                    new_dir = Direction.DOWN
+                else:
+                    new_dir = Direction.LEFT
+                snake.change_dir(new_dir)
+
+    def create_food():
+        """创建食物"""
+        unit_size = snake.head.size
+        max_row = wall.height // unit_size
+        max_col = wall.width // unit_size
+        row = randrange(0, max_row)
+        col = randrange(0, max_col)
+        return Food(wall.x + unit_size * col, wall.y + unit_size * row, unit_size)
+
+    def reset_game():
+        """重置游戏"""
+        nonlocal food, snake
+        food = create_food()
+        snake = Snake(250, 290)
+
+    def background_task():
+        nonlocal running, food
+        while running:
+            if snake.alive:
+                refresh()
+            clock.tick(10)
+            if snake.alive:
+                snake.move()
+                snake.collide(wall)
+                if snake.eat_food(food):
+                    food = create_food()
+                snake.eat_self()
+
+    """
+    class BackgroundTask(Thread):
+
+        def run(self):
+            nonlocal running, food
+            while running:
+                if snake.alive:
+                    refresh()
+                clock.tick(10)
+                if snake.alive:
+                    snake.move()
+                    snake.collide(wall)
+                    if snake.eat_food(food):
+                        food = create_food()
+                    snake.eat_self()
+    """
+
+    wall = Wall(10, 10, 600, 600)
+    snake = Snake(250, 290)
+    food = create_food()
+    pygame.init()
+    screen = pygame.display.set_mode((620, 620))
+    pygame.display.set_caption('贪吃蛇')
+    # 创建控制游戏每秒帧数的时钟
+    clock = pygame.time.Clock()
+    running = True
+    # 启动后台线程负责刷新窗口和让蛇移动
+    # BackgroundTask().start()
+    Thread(target=background_task).start()
+    # 处理事件的消息循环
+    while running:
+        for event in pygame.event.get():
+            if event.type == pygame.QUIT:
+                running = False
+            elif event.type == pygame.KEYDOWN:
+                handle_key_event(event)
+    pygame.quit()
+
+
+if __name__ == '__main__':
+    main()

+ 234 - 0
Day76-90/Python数据可视化.md

@@ -0,0 +1,234 @@
+## Python数据可视化
+
+数据的处理、分析和可视化已经成为Python近年来最为重要的应用领域之一,其中数据的可视化指的是将数据呈现为漂亮的统计图表,然后进一步发现数据中包含的规律以及隐藏的信息。数据可视化又跟数据挖掘和大数据分析紧密相关,而这些领域以及当下被热议的“深度学习”其最终的目标都是为了实现从过去的数据去对未来的状况进行预测。Python在实现数据可视化方面是非常棒的,即便是使用个人电脑也能够实现对百万级甚至更大体量的数据进行探索的工作,而这些工作都可以在现有的第三方库的基础上来完成(无需“重复的发明轮子”)。[Matplotlib](https://matplotlib.org/)就是Python绘图库中的佼佼者,它包含了大量的工具,你可以使用这些工具创建各种图形(包括散点图、折线图、直方图、饼图、雷达图等),Python科学计算社区也经常使用它来完成数据可视化的工作。
+
+### 安装matplotlib
+
+可以使用pip来安装matplotlib,命令如下所示。
+
+```Shell
+pip install matplotlib
+```
+
+### 绘制折线图
+
+```Python
+# coding: utf-8
+import matplotlib.pyplot as plt
+
+
+def main():
+    # 保存x轴数据的列表
+    x_values = [x for x in range(1, 11)]
+    # 保存y轴数据的列表
+    y_values = [x ** 2 for x in range(1, 11)]
+    # 设置图表的标题以及x和y轴的说明
+    plt.title('Square Numbers')
+    plt.xlabel('Value', fontsize=18)
+    plt.ylabel('Square', fontsize=18)
+    # 设置刻度标记的文字大小
+    plt.tick_params(axis='both', labelsize=16)
+    # 绘制折线图
+    plt.plot(x_values, y_values)
+    plt.show()
+
+
+if __name__ == '__main__':
+    main()
+
+```
+
+运行程序,效果如下图所示。
+
+![](./res/result1.png)
+
+如果使用jupyter的notebook,需要使用魔法指令`%matplotlib inresline`来设置在页面中显示图表,效果如下所示。
+
+![](./res/result-in-jupyter.png)
+
+### 绘制散点图
+
+可以将上面代码中的的`plot`函数换成`scatter`函数来绘制散点图,效果如下图所示。
+
+![](./res/result2.png)
+
+当然,也可以直接通过`plot`函数设置绘图的颜色和线条的形状将折线图改造为散点图,对应的代码如下所示,其中参数'xr'表示每个点的记号是‘x’图形,颜色是红色(<u>r</u>ed)。
+
+```Python
+plt.plot(x_values, y_values, 'xr')
+```
+
+重新运行程序,效果如下图所示。
+
+![](./res/result3.png)
+
+可能大家已经注意到了,1和10对应的‘x’记号在图形边角的位置不太明显,要解决这个问题可以通过添加下面的代码调整x轴和y轴的坐标范围。
+
+```Python
+plt.axis([0, 12, 0, 120])
+```
+
+调整后的效果如下图所示。
+
+![](./res/result4.png)
+
+### 绘制正弦曲线
+
+在下面的程序中,我们使用了名为[NumPy](http://www.numpy.org/)的第三方库来产生样本并计算正弦值。NumPy是一个运行速度非常快的数学库,主要用于数组计算。它可以让你在Python中使用向量和数学矩阵,以及许多用C语言实现的底层函数。如果想通过Python学习数据科学或者机器学习相关的内容,那么就得先学会使用NumPy。
+
+```Python
+# coding: utf-8
+import matplotlib.pyplot as plt
+import numpy as np
+
+
+def main():
+    # 指定采样的范围以及样本的数量
+    x_values = np.linspace(0, 2 * np.pi, 1000)
+    # 计算每个样本对应的正弦值
+    y_values = np.sin(x_values)
+    # 绘制折线图(线条形状为--, 颜色为蓝色)
+    plt.plot(x_values, y_values, '--b')
+    plt.show()
+
+
+if __name__ == '__main__':
+    main()
+
+```
+
+运行程序,效果如下图所示。
+
+![](./res/result5.png)
+
+如果要在一个坐标系上绘制多个图像,可以按照如下的方式修改代码。
+
+```Python
+# coding: utf-8
+import matplotlib.pyplot as plt
+import numpy as np
+
+
+def main():
+    x_values = np.linspace(0, 2 * np.pi, 1000)
+    plt.plot(x_values, np.sin(x_values), '--b')
+    plt.plot(x_values, np.sin(2 * x_values), '--r')
+    plt.show()
+
+
+if __name__ == '__main__':
+    main()
+
+```
+
+修改后的代码运行效果如下图所示。
+
+![](./res/result6.png)
+
+如果需要分别在两个坐标系上绘制出两条曲线,可以按照如下的方式操作。
+
+```Python
+# coding: utf-8
+import matplotlib.pyplot as plt
+import numpy as np
+
+
+def main():
+    # 将样本数量减少为50个
+    x_values = np.linspace(0, 2 * np.pi, 50)
+    # 设置绘图为2行1列活跃区为1区(第一个图)
+    plt.subplot(2, 1, 1)
+    plt.plot(x_values, np.sin(x_values), 'o-b')
+    # 设置绘图为2行1列活跃区为2区(第二个图)
+    plt.subplot(2, 1, 2)
+    plt.plot(x_values, np.sin(2 * x_values), '.-r')
+    plt.show()
+
+
+if __name__ == '__main__':
+    main()
+
+```
+
+效果如下图所示。
+
+![](./res/result7.png)
+
+### 绘制直方图
+
+我们可以通过NumPy的random模块的normal函数来生成[正态分布](https://zh.wikipedia.org/wiki/%E6%AD%A3%E6%80%81%E5%88%86%E5%B8%83)的采样数据,其中的三个参数分别表示期望、标准差和样本数量,然后绘制成直方图,代码如下所示。
+
+```Python
+# coding: utf-8
+import matplotlib.pyplot as plt
+import numpy as np
+
+
+def main():
+    # 通过random模块的normal函数产生1000个正态分布的样本
+    data = np.random.normal(10.0, 5.0, 1000)
+    # 绘制直方图(直方的数量为10个)
+    plt.hist(data, 10)
+    plt.show()
+
+
+if __name__ == '__main__':
+    main()
+
+```
+
+运行效果如下图所示。
+
+![](./res/result8.png)
+
+### 使用Pygal绘制矢量图
+
+矢量图(SVG)是[计算机图形学](https://zh.wikipedia.org/wiki/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9B%BE%E5%BD%A2%E5%AD%A6)中用点、直线或者多边形等基于数学方程的几何图元表示的图像,也是目前应用得非常多的一种图像文件格式,全称是“Scalable Vector Graphics”。和使用像素表示图像的位图不同,SVG基于XML存储图像数据,它是W3C定义的一种开放标准的矢量图形语言,可以用来设计更为清晰的Web图像,因为SVG与分辨率无关,在任意放大时不会丢失细节或影响清晰度。SVG可以直接用代码来描绘图像,也可以用任何文字处理工具来打开它,通过改变SVG的代码我们可以让图像具备交互功能。
+
+Python中可以使用Pygal来生成SVG,可以通过pip来安装它。
+
+```Python
+from random import randint
+import pygal
+
+
+def roll_dice(n=1):
+	total = 0
+	for _ in range(n):
+		total += randint(1, 6)
+	return total
+
+
+def main():
+    results = []
+    # 将两颗色子摇10000次记录点数
+    for _ in range(10000):
+        face = roll_dice(2)
+        results.append(face)
+    freqs = []
+    # 统计2~12点各出现了多少次
+    for value in range(2, 13):
+        freq = results.count(value)
+        freqs.append(freq)
+    # 绘制柱状图
+    hist = pygal.Bar()
+    hist.title = 'Result of rolling two dice'
+    hist.x_labels = [x for x in range(2, 13)]
+    hist.add('Frequency', freqs)
+    # 保存矢量图
+    hist.render_to_file('result.svg')
+
+
+if __name__ == '__main__':
+    main()
+    
+
+```
+
+运行上面的程序,效果如下图所示。
+
+![](./res/result9.png)
+
+### 后记
+
+Matplotlib和NumPy的强大我们在这里也只是窥视了其冰山一角,我们在后续的内容里面还会使用到这两个第三方库,到时候我们再续点为大家介绍其他的功能。

BIN
Day76-90/res/201205230001213115.png


BIN
Day76-90/res/201205230001238839.png


BIN
Day76-90/res/20120523000125800.png


BIN
Day76-90/res/result-in-jupyter.png


BIN
Day76-90/res/result1.png


BIN
Day76-90/res/result2.png


BIN
Day76-90/res/result3.png


BIN
Day76-90/res/result4.png


BIN
Day76-90/res/result5.png


BIN
Day76-90/res/result6.png


BIN
Day76-90/res/result7.png


BIN
Day76-90/res/result8.png


BIN
Day76-90/res/result9.png