Python 是一种极具可读性和通用性的编程语言,其名字来源于英国喜剧团体 Monty Python。开发团队致力于使语言有趣易用,对错误提供即时反馈,这使得 Python 成为初学者的理想选择。作为一种多范式语言,Python 支持多种编程风格,包括脚本和面向对象,使其广泛应用于各种场景。随着在工业领域的应用日益广泛,如联合太空联盟(NASA 的主要飞机支持承包商)和工业光魔(VFX 和卢卡斯影业的动画工作室),Python 为寻求额外编程语言的人提供了巨大潜力。
自1994年 Python 新闻组(comp.lang.python)成立以来,Python 的用户基础不断增长,使其成为开源开发中最受欢迎的编程语言之一。如今,Python 已经成为一种非常热门的编程语言,实战项目也备受关注。接下来,我们将介绍十个适合练手的 Python 实战项目。
1. 即时标记
这个项目源自《Python 基础教程》的实践部分,通过编写代码来熟悉 Python 的语法并练习基本及非基本语法。初始项目较为简单,重构后变得更具挑战性且更灵活。重构后的程序分为四个模块:处理程序模块、过滤器模块、规则模块(实际上是处理规则)和语法分析器。处理程序模块提供固定 HTML 标记的输出以及标签开始和结束的友好访问接口。示例代码如下:
```python
# handlers.py
def tag_start(name):
...
def tag_end(name):
...
def add_attr(attrs):
...
```
2. Web爬虫
这个项目可以帮助你学习如何使用 Python 进行网络爬虫开发。示例代码如下:
```python
import requests
from bs4 import BeautifulSoup
url = 'https://example.com'
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
```
3. 数据可视化
使用 Python 对数据进行可视化处理,可以学习如何使用诸如 Matplotlib、Seaborn 等库进行数据绘制。示例代码如下:
```python
import matplotlib.pyplot as plt
import seaborn as sns
data = ...
plt.figure(figsize=(10, 6))
sns.barplot(x='category', y='value', data=data)
plt.show()
```
4. 机器学习模型训练与预测
使用 Python 进行机器学习模型的训练和预测,可以学习如何使用诸如 scikit-learn、TensorFlow 等库进行模型构建和训练。示例代码如下:
```python
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
import numpy as np
X = ...
y = ...
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
model = LinearRegression()
model.fit(X_train, y_train)
predictions = model.predict(X_test)
```
再来看第三个模块“规则”,这个模块,抛开那祖父类不说,其他类应该有的两个方法是condition和action,前者是用来判断读进来的字符串是不是符合自家规则,后者是用来执行操作的,所谓的执行操作就是指调用“处理程序模块”,输出前标签、内容、后标签。来看下这个模块的代码,其实这个里面几个类的关系,画到类图里面看会比较清晰。
rules.py:
```python
class Rule:
def __init__(self, condition, action):
self.condition = condition
self.action = action
def is_match(self, text):
return self.condition(text)
def execute(self, text):
self.action(text)
```
补充utils.py:
```python
def process_text(text, rules):
for rule in rules:
if rule.is_match(text):
rule.execute(text)
break
```
最后隆重的来看下“语法分析器模块”,这个模块的作用其实就是协调读入的文本和其他模块的关系。在往重点说就是,提供了两个存放“规则”和“过滤器”的列表,这么做的好处就是使得整个程序的灵活性得到了极大的提高,使得规则和过滤器变成的热插拔的方式,当然这个也归功于前面在写规则和过滤器时每一种类型的规则(过滤器)都单独的写成了一个类,而不是用if..else来区分。看代码:
```python
class GrammarAnalyzer:
def __init__(self):
self.rules = []
self.filters = []
def add_rule(self, rule):
self.rules.append(rule)
def add_filter(self, filter):
self.filters.append(filter)
def analyze(self, text):
utils.process_text(text, self.rules + self.filters)
```
这个模块里面的处理思路是,遍历客户端(也就是程序执行的入口)给插进去的所有的规则和过滤器,来处理读进来的文本。有一个细节的地方也要说一下,其实是和前面写的呼应一下,就是在遍历规则的时候通过调用condition这个东西来判断是否符合当前规则。我觉得这个程序很像是命令行模式,有空可以复习一下该模式,以保持记忆网节点的牢固性。最后说一下我以为的这个程序的用途:1、用来做代码高亮分析,如果改写成js版的话,可以做一个在线代码编辑器。2、可以用来学习,供我写博文用。还有其他的思路,可以留下您的真知灼见。补充一个类图,很简陋,但是应该能说明之间的关系。另外我还是建议如果看代码捋不清关系最好自己画图,自己画图才能熟悉整个结构。
这个项目的名称与其叫做万能的XML不如叫做自动构建网站。根据一份XML文件,生成对应目录结构的网站。不过,仅仅生成HTML网站还是太过简单了。如果能够连带生成CSS,那就更加强大了。这个功能有待后续研发,现在先来研究一下如何生成HTML网站结构。既然是通过XML结构生成网站,那么所有的事情都应该由这个XML文件来控制。
首先,我们要解析这个XML文件。在Python中解析XML和在Java中一样,有两种方式:SAX和DOM。两种处理方式的不同点在于速度和范围。SAX讲究的是效率,每次只处理文档的一小部分,快速而有效地利用内存。DOM则是相反的处理方式,先把所有的文档载入到内存,然后再进行处理,速度比较慢,也比较消耗内存。唯一的好处就是可以操作整个文档。
在Python中使用SAX方式处理XML,需要先引入xml.sax中的parse函数,以及xml.sax.handler中的ContentHandler类。后面的这个类是要和parse函数配合使用的。使用方式如下:parse('xxx.xml', xxxHandler)。这里的xxxHandler需要继承上面的ContentHandler类,但只需要继承即可,不需要有所作为。然后这个parse函数在处理XML文件的时候,会调用xxxHandler中的startElement、endElement函数来表示一个XML标签的开始和结束,中间的过程使用一个名为characters的函数来处理标签内部的所有字符串。
有了上面的这些认识,我们已经知道如何处理XML文件了。接下来,我们来看那个罪恶的源头——website.xml文件。分析其结构,只有两个节点:page和directory。很明显,page表示一个页面,directory表示一个目录。
所以处理这个XML文件的思路就变得清晰了:读取XML文件的每一个节点,然后判断是page还是directory。如果是page,则创建HTML页面;然后把节点中的内容写到文件里。如果遇到directory,就创建一个文件夹;然后再处理其内部的page节点(如果存在的话)。
下面来看这部分代码。书中的实现比较复杂,但也非常灵活。先来看一下,然后再分析。
看起来这个程序分析得有些复杂,但正如伟人毛毛所说,任何复杂的程序都是纸老虎。现在我们来详细分析一下这个程序。
首先,我们可以看到这个程序有两个类,实际上它们可以被视为一个类,因为存在继承关系。接下来,我们来看一下这个程序增加了哪些功能。除了我们之前分析出的startElement、endElement和characters方法外,还增加了startPage、endPage;startDirectory、endDirectory;defaultStart、defaultEnd;ensureDirectory;writeHeader、writeFooter;以及dispatch方法。除了dispatch方法之外,前面的方法都很容易理解,每一对方法都是专门处理对应的HTML标签和XML节点。而dispatch方法相对复杂,它的复杂之处在于它可以动态拼合函数并执行。
dispatch的处理思路是这样的:首先根据传递的参数(操作名称和节点名称)判断是否存在对应的函数,如startPage。如果不存在,则执行default+操作名称,例如defaultStart。在了解了每个函数的作用之后,我们就可以知道整个处理流程是什么样的了。首先创建一个public_html文件,用于存放整个网站的内容。然后读取XML节点,通过startElement和endElement方法调用dispatch进行处理。最后,我们来了解一下dispatch是如何调用具体处理函数的。至此,这个项目的基本分析就完成了。
在这个项目中,我们需要掌握的主要知识点有两个:一是如何在Python中使用SAX处理XML;另一个是Python中函数的使用,例如getattr和传参时的星号(*)等。
接下来,我们来看一个新闻聚合项目的练习。书中的第四个练习是关于新闻聚合的应用,虽然这种应用现在比较少见,至少我还没有用过,它被称为Usenet。这个程序的主要功能是从指定的来源(这里是Usenet新闻组)收集信息,并将这些信息保存到指定的目标文件中(这里使用了两种形式:纯文本和HTML文件)。这个程序的用途与现在的博客订阅工具或RSS订阅器类似。
首先,我们来看一下代码实现,然后再逐一分析:
这个程序首先从整体上进行分析,重点部分在于NewsAgent。它的作用是存储新闻来源、目标地址,然后分别调用来源服务器(NNTPSource以及SimpleWebSource)以及写新闻的类(PlainDestination和HTMLDestination)。可以看出,NNTPSource专门用来获取新闻服务器上的信息,而SimpleWebSource则是获取一个url上的数据。PlainDestination和HTMLDestination的作用很明显,前者用于将获取到的内容输出到终端,后者则将数据写入html文件中。
有了这些分析,接下来我们来看主程序中的内容。主程序的主要任务是为NewsAgent添加信息源和输出目的地址。这个程序确实很简单,但它采用了分层的设计。
接下来,我们来看一个简单的Python项目练习:虚拟茶话会。在学习、使用任何一种编程语言时,关于socket的练习从来都不会少,尤其是会编写一些局域网通信的程序。因此,这个项目正好可以作为练习socket编程的机会。
整个练习的思路是创建一个聊天服务器,其功能主要是提供客户端socket的连接、存储每个客户端的连接session、处理每个连接发送的消息以及解析客户端发送的数据。至于客户端方面,不需要编写代码,只需使用系统的telnet工具即可。
有了上面的分析,剩下的程序就没有什么可说的了,当然,除了那两个封装socket的类之外。为了尝试编写一个简单的通信程序,我使用了Python中的socket类。然而,不知道为什么,通信过程中总是出现意外。以下是这段简单的代码:
server.py
client.py
由于没有找到出错的原因,我暂时无法详细解释。不过,Python中提供了两个封装好的类来完成socket通信过程:asynchat中的async_chat和asyncore中的dispatcher以及asyncore本身。前面的类用于处理客户端与服务器之间的每次会话,后面的类主要用于提供socket连接服务。同时,将每一个socket连接都托管给前者(async_chat)来处理。
让我们来看一下代码:
整个程序分为三个部分:
1. 提供客户端的socket连接:ChatServer类。
🔹 存储每个客户端的连接session,处理每个连接发送的消息:ChatSession类,这个类的作用很简单,接受数据,判断是否有终结符,如果有调用found_terminator这个方法。
🔹 解析客户端发送的数据:就是剩下的room相关的类,这些类分别用来处理客户端发送的字符串和命令,都是继承自CommandHandler。
最终截图:
python项目练习六:使用CGI进行远程编辑
记得一开始接触web开发的时候,看视频,视频里面的老师一般都会语重心长的说:想当年我们一开始学习编程那会儿,都是用cgi编程,复杂的很,现在你们学习web编程,直接有现成的框架来用,十分简单。记得当然听完这句话之后就会觉得这个老师好有经验,技术很高。
不过后来慢慢的接触web编程时间长了,觉得cgi编程并不是像传说中的那么难,只不过是比较麻烦,在后台使用html硬编码来完成(也就是在后台使用类似print的语句输出html)。通过浏览器直接访问cgi文件,由web服务器执行cgi脚本,输出内容到浏览器。
关于cgi的更多内容可以参考这里:http://www.jdon.com/idea/cgi.htm
再来看这个python中的cgi,确实很简单。但是有一点我不确定,就是如果我是初学web编程的话,会不会觉得这个简单,这个角度的思考确实不好操作。
下面直接上代码吧,和书上的不太一样,因为书上的代码在我的电脑上不能正常运行。首先是index.html:edit.cgi,用来接受index页面的名字,然后根据名字查找文件,并且输出。
最后一个文件save.cgi:代码理解上比较简单,唯一麻烦的地方是web服务器的配置。我这里使用的是tomcat来做web服务器。需要修改tomcat配置,首先是配置cgi,我引用一段从网上搜来的文字:
要为Tomcat配置CGI服务主要有下面几个步骤:
1、把servlets-cgi.renametojar (在%CATALINA_HOME%/server/lib/目录下)改名为servlets-cgi.jar。
2、在Tomcat的%CATALINA_BASE%/conf/web.xml 文件中,把关于<servlet-name> CGI的那段的注释去掉。内容样式如下:
在Tomcat中配置CGI脚本的步骤如下:
1. 首先,确保已经安装了Perl。如果没有安装,可以从这里下载并安装:C:/Perl/bin/。
2. 打开Tomcat的%CATALINA_BASE%/conf/web.xml文件,找到关于对CGI进行映射的部分,将其注释去掉。修改后的内容样式如下:
```xml
```
其中,`
3. 在Tomcat的webapp下的某一个app下的WEB-INF中的cgi目录下创建一个名为test.py的文件,内容如下:
```python
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import os
import sys
import cgi
import cgitb
cgitb.enable() # 开启CGI错误报告
print("Content-Type: text/html;charset=utf-8")
print()
form = cgi.FieldStorage()
file_path = form.getvalue("file")
if file_path:
os.system(f"echo 'Hello, CGI!' > {file_path}")
else:
print("Error: No file path provided.")
```
4. 将test.py文件移动到刚才创建的cgi目录下。
5. 在命令行中执行以下命令启动Tomcat服务器:
```bash
startup.bat
```
6. 在浏览器中访问`http://localhost:8080/your_app_name/cgi-bin/test`,如果看到输出"Hello, CGI!",则说明配置成功。
这是一个很好的练习,尝试使用Python开发P2P程序,我们或许可以自己创建一个类似于迅雷的P2P下载工具。在讨论迅雷的原理之前,我想了解一下大家是否了解这个概念。如果你还不了解,那么阅读这篇文章后,我相信你会对此有所了解。既然你已经了解了,那么请过来给我一些建议和指导。
在我之前的Java项目中,我也曾接触过类似的概念,包括RMI(Remote Method Invocation)和XML-RPC。那么什么是XML-RPC呢?它与P2P有什么关系?下面是我的一些个人理解。
XML-RPC是一个远程过程调用(remote procedure call,RPC)的分布式计算协议,通过XML将调用函数封装,并使用HTTP协议作为传送机制[摘自维基百科]。因此,XML-RPC可以帮助我们实现远程调用功能,即调用相邻计算机上的方法。当然,前提是这些计算机上已经运行了我们编写的供远程调用的程序(无论是前台还是后台,就像迅雷一样,始终默默运行)。
此外,我们需要引入一个概念:Node,即节点。每个计算机被当作一个节点。这里所说的节点仅针对每个计算机只运行一个我们通过XML-RPC编写的程序。如果一台计算机同时运行了多个程序,那么实际上每个程序都是一个节点。有了节点的概念之后,我相信大家都能想象出不同节点之间如何连接并形成各种复杂的网状结构。
在这种情况下,每个节点可以与其他多个节点建立连接,但我们并不需要让一个节点与所有其他节点都建立连接。过多的连接会显得混乱,就像人际关系一样。那么在什么时候建立哪些节点之间的连接呢?这时就需要涉及到P2P的概念。所谓P2P,即点对点(peer to peer)。这里所说的点对点不仅仅是从一个点到另一个点,而是从多个点到一个点,或者从一个点到多个点。没有固定的起点和终点,所有的点都可以相互连接。
因此,在下载文件方面,这样的协议比传统的仅从某个点下载数据要快得多,资源也会更加丰富。其运作流程如下:以一个小A在迅雷中下载B片为例,迅雷可能并没有这个资源,但它可以帮助我们在节点中寻找拥有该资源的节点。例如小C的电脑上正好有这个资源,并且位于迅雷共享目录下。然后迅雷会将资源通过自己的节点传输回小A的电脑上,更有可能的情况是直接让小A和小C建立连接。
在迅雷下载过程中,我们可能注意到一个资源标识为“x/xx”的格式。其中,“x”表示为你提供该资源的节点数量,而“xx”表示拥有该资源的所有节点数目。需要注意的是,这些节点可能并不在线。下面我们将通过Python来实现这个功能。
首先,我们需要创建一个简单的尝试。在命令行中输入以下代码:
```python
from xmlrpclib import ServerProxy
s = ServerProxy('http://localhost:4242')
s.twice(2) # 通过ServerProxy调用远程的方法,
```
然后启动一个新的命令行窗口,进入Python环境,继续输入以下代码:
```python
from xmlrpclib import ServerProxy
s = ServerProxy('http://localhost:4242')
s.twice(2) # 通过ServerProxy调用远程的方法,
```
此时,你将看到通过远程方法的计算已经完成。尽管这个实现非常简陋,但它足以让你理解Python的远程调用功能。接下来,我们将进一步完善这个实现。
首先是Server.py文件中的相关设置:
- SimpleXMLRPCServer.allow_reuse_address:表示所占用的端口可以重用,即即使强制关闭node server后重新启动,也不会出现端口被占用的问题。
- MAX_HISTORY_LENGTH = 6:设置最大的节点长度,以防止节点无休止地搜索下去。
- UNHANDLED = 100:未处理错误码。
- ACCESS_DENIED = 200:访问被拒绝错误码。
接下来,我们来看Node节点的具体实现过程。首先启动供远程调用的服务器,调用的接口就是Node类。Node类中有三个方法供远程调用:hello、fetch和query。hello方法用于向当前节点添加邻节点信息;fetch方法用于获取数据;query方法则是节点之间用来交互的工具。
在fetch方法中,首先判断密码是否正确,然后通过调用自己的query方法查找数据。接下来我们看一下query方法的实现过程:首先调用私有方法_handle进行本地查找,如果没有找到数据,那么再通过_broadcast接口在所有已知节点中发送广播。需要注意的是,每次广播都会传递history参数,它有两个作用:一是防止将数据发送到重复的节点;二是限制当前所有链接节点的长度。
理解Node.js服务器的基本功能后,我们将探讨如何使用控制类代码对其进行管理。以下是`client.py`的分析:
首先,我们有一个随机生成密码的函数,用于防止非法调用该控制所控制的Node.js服务器。由于我们有客户端的合法使用权,因此不需要记住这个密码。
这段代码的主要作用是为用户提供一个可视化的命令行界面。通过继承`cmd`类,解析用户输入的命令。例如,在程序运行后出现命令提示符时,用户输入`fetch`,则会调用`do_fetch`方法并传递参数。
`do_fetch`方法的作用是调用Node.js服务器中的`fetch`方法以获取资源。另一个`do_exit`方法很容易理解,它接受`exit`命令以退出程序。
在程序初始化时,需要注意的是,它会读取`urlfile`参数传递的文件中的数据,其中包含节点的URL地址。读取后,程序会将这些地址添加到相邻节点中,供以后访问。然而,这个程序还有一些不完善的地方:在程序运行时,如果修改了URL配置文件,它不会读取新添加的节点URL。要解决这个问题,只需将获取URL的代码放到`do_fetch`方法中即可。
在运行程序之前,还需要执行一些操作。首先需要创建两个文件夹A和C,然后在C文件夹中创建一个名为B.txt的文件。接下来,在A和C所在的文件夹中分别创建`urlsA.txt`和`urlsC.txt`文件。在`urlsA.txt`中写入`http://localhost:4243`,然后打开两个命令行窗口。第一个窗口输入:
```bash
python client.py urlsA.txt A http://localhost:4242
```
按回车键后,是否看到提示符?接着输入:
```bash
fetch B.txt
```
按回车键后,看到提示“Couldn't find the file B.txt”。
在第二个命令行中输入以下命令:
```
python client.py urlsC.txt C http://localhost:4243
```
回车。然后输入以下命令:
```
fetch B.txt
```
如果没有反应,说明文件存在。接下来,在第一个命令行中再次输入以下命令:
```
fetch B.txt
```
看是否还是提示没找到文件。如果你对代码根据我上面的建议进行了修改的话,就不会出现错误了。如果没有修改,此时你需要退出程序,再次重启,然后在fetch B.txt,然后到A文件夹下查看一下,看是不是把B.txt下载到你的文件夹中了。
PS:上面的程序只能传输文本文件,大文件或者其他格式的文件无法传输。刚才研究了一下,使用xmlrpclib这个库中的Binary函数即可。具体使用方法为:先引入xmlrpclib,然后在server类的_handle方法中最后返回的那句代码return open(name).read()修改为 return xmlrpclib.Binary(open(name,'rb').read())。再把fetch方法中的f.write(result)修改为f.write(result.data)。另外这句话前面的那个写文件的方式要改为wb。
关于扩展的思路,我目前的想法是这样的:现在的程序监听的端口是通过命令行参数决定的。我觉得可以直接写到窗口上输入,或者写死在程序里。毕竟,我们需要使用固定的端口进行通信,共享文件夹以及urls里面的节点也可以是固定的。
另外,可以添加局域网查询功能,类似于迅雷网邻的换一换。这意味着查找当前局域网内正在使用我们的软件的所有用户,并将他们的资源情况列到我们的列表中。
关于python项目练习十:DIY街机游戏,这是最后一个项目。在完成前面的练习后,我们已经熟悉了Python的基本操作和功能。尽管这个项目相对复杂一些,但只要程序能正常执行,花点时间总能理解它。
这个练习是一个小游戏程序,如果要给它起个名字,应该叫“快躲,香蕉”。游戏的主要内容是从屏幕上方随机掉落一些铁块,屏幕下方有一个小香蕉受玩家控制,需要不断左右移动来躲避铁块。在躲避一定数量的铁块之后,将进入下一关。下一关仍然是避免铁块,但铁块下降的速度会加快很多。游戏中可以按下任意键暂停,再次按则继续,按下ESC键退出。这就是游戏的所有功能。接下来我们来看游戏的实现。
在实现功能和分析代码时,分类归纳始终是一个好习惯。这里也不例外。首先对所有代码进行分类:
1. 整体上有一个配置模块,用于调整游戏速度、屏幕宽度、香蕉移动速度、字体大小以及各种物体图片等。
2. 其次是一个元素模块,包括游戏中掉下来的两个铁块和被砸中的香蕉,以及它们具有的行为。
3. 然后是游戏中的各种状态模块,其中状态类的继承关系稍多一些。位于家谱顶端的是State类,由它衍生出其他所有状态。它的直接子类有Level(关卡)和Pause(暂停),而Pause类又有Info(信息)、levelCleared(关卡清除)、GameOver(游戏结束)和StartUp(开始)等子类。4. 最后是游戏的主模块,用于协调其他模块的工作。
接下来让我们看一下整体结构图:
有了上面的整体认识后,下面我们将详细讨论每个部分。我个人查看代码的方法是先整体分析,然后从程序入口点开始分析。我相信大多数人也是这样做的。
首先是squish.py文件中的game类:
在run方法中,该管理类首先调用pygame进行初始化并启动游戏界面。接下来,它进入一个while True的死循环,不断地进行状态判断、事件处理、更新当前状态以及绘制界面。让我们关注这个死循环,因为它是整个程序流程的核心部分。
状态和事件之间的关系是:当发生某个事件时,状态会发生变化。这些事件包括用户操作(如点击)、过关事件和死亡事件。事件来源包括用户操作、系统判断和系统判断。要深入分析,需要进一步了解剩余的代码。
以下是squish.py文件中剩余的所有代码:
用户判断部分包括Paused类中的update方法和handle方法,而系统判断部分则是Level类中的update方法。需要注意的是,Level类中update方法的第一行代码:`self.sprites.update()`,这是使铁块不断下落的关键代码。用户判断部分的代码已经给出,下面将贴上系统判断时用到的代码。
objects.py中的代码:
Banana和Weight类中的update和touches方法用于进行系统判断。
现在我们已经分析了主要的部分,剩下的内容只需要稍加了解即可理解。最后还有一个配置模块的代码config.py。