Python权威指南的10个项目(1~5)

发布时间:2020-07-14 19:26:35 作者:原生zzy
来源:网络 阅读:791

引言
  我相信学习Python过的朋友,一定会喜欢上这门语言,简单,库多,易上手,学习成本低,但是如果是学习之后,不经常使用,或者工作中暂时用不到,那么不久之后又会忘记,久而久之,就浪费了很多的时间再自己的“曾经”会的东西上。所以最好的方法就是实战,通过真是的小型项目,去巩固,理解,深入Python,同样的久而久之就不会忘记。
  所以这里小编带大家编写10个小型项目,去真正的实操Python,这10个小型项目是来自《Python权威指南》中后面10个章节的项目,有兴趣的朋友可以自行阅读。希望这篇文章能成为给大家在Python的学习道路上的奠基石。
  建议大家是一边看代码,一边学习,文章中会对代码进行解释:
这里是项目的gitlab地址(全代码):

https://gitlab.com/ZZY478086819/actualcombatproject

1. 项目1:自动添加标签

  这个项目主要介绍如何使用Python杰出的文本处理功能,包括使用正则表达式将纯文本文件转换为用 HTML或XML等语言标记的文件。

(1) 问题描述

  假设你要将一个文件用作网页,而给你文件的人嫌麻烦,没有 以HTML格式编写它。你不想手工添加需要的所有标签,想编写一个程序来自动完成这项工作。大致而言,你的任务是对各种文本元素(如标题和突出的文本)进行分类,再清晰地标记它 们。就这里的问题而言,你将给文本添加HTML标记,得到可作为网页的文档,让Web浏览器能 够显示它。然而,创建基本引擎后,完全可以添加其他类型的标记(如各种形式的XML和LATEX 编码)。对文本文件进行分析后,你甚至可以执行其他的任务,如提取所有的标题以制作目录。

(2) 代码实现前准备

实现思路:
   - 输入无需包含人工编码或标签
   - 程序需要能够处理不同的文本块(如标题、段落和列表项)以及内嵌文本(如突出的文 本和URL)。
   - 虽然这个实现添加的是HTML标签,但应该很容易对其进行扩展,以支持其他标记语言
有用的工具:
   - 肯定需要读写文件,至少要从标准输入
   - 可能需要迭代输入行
   - 需要使用一些字符串方法
   - 可能用到一两个生成器
   - 可能需要模块re

(3) 简单实现

分为两个步骤

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#生成器lines是个简单的工具,在文件末尾添加一个空行
def lines(file):
    for line in file:
        yield line
    yield '\n'

# 生成器blocks实现了刚才描述的方法。生成文本块时,将其包含的所有行合并,
#并将两端多余的空白(如列表项缩进和换行符)删除,得到一个表示文本块的字符串。
def blocks(file):
    block=[]
    for line in lines(file):
        if line.strip():
            block.append(line)
        elif block:
            yield ''.join(block).strip()
            block=[]

if __name__=='__main__':
    file='../../file_data/test_input.txt'
    with open(file,'r+') as f :
        for line in blocks(f):
            print(line)
import sys,re
#引用刚刚编写的util模块
from util import *

print('<html><head><title>zzy-python</title><body>')
title = True
file='../../file_data/test_input.txt'
#for block in blocks(sys.stdin) 这里可以使用标准的输入,小编为了方便运行,就本地读取
with open(file) as f:
    for block in blocks(f):
        re.sub(r'\*(.+?\*)',r'<em>\1</em>',block)
        if title:
            print('<h2>')
            print(block)
            print('</h2>')
            title=False
        else:
            print('<p>')
            print(block)
            print('</p>')
print('</body></html>')

  到这简单的实现就完成了但是如果要扩展这个原型,该如何办呢?可在for循环中添加检查,以确定文本块是否是标题、列表项等。为此,需要添加其他的正则表达式,代码可能很快变得很乱。更重要的是,要让程序输出其他格式的代码(而不是HTML)很难,但是这个项目的目标之一就是能够轻松地添加其他输出格式。

(4) 完整实现

  为了提高可扩展性,需提高程序的模块化程度(将功能放在 独立的组件中)。要提高模块化程度,方法之一是采用面向对象设计。这里我们需要寻找一些抽象,让程序在变得复杂时也易于管理。下面先来列出一些潜在的组件:
   解析器:添加一个读取文本并管理其他类的对象。
   规则:对于每种文本块,都制定一条相应的规则。这些规则能够检测不同类型的文本块 并相应地设置其格式。
   过滤器:使用正则表达式来处理内嵌元素。
   处理程序:供解析器用来生成输出。每个处理程序都生成不同的标记。
那么接下来,小编就对这几个组件,进行详细介绍:

① 处理程序
  对于每种文本块,它都提供两个处理方法:一个用于添加起始标签,另一个用于添加结束标签。例如它可能包含用于处理段落的方法start_paragraph和end_paragraph。生成HTML代码时,可像 下面这样实现这些方法:

class HTMLRenderer: 
    def start_paragraph(self):
        print('') 
    def end_paragraph(self):
         print('')

对于其他类型的文本块,添加不同的开始和结束标签,对于形如连接,**包围的内容,需要特殊处理,例:

def sub_emphasis(self, match): 
    return '{}'.format(match.group(1))

当然对于简单的文本内容,我们只需要:

def feed(self, data): 
    print(data)

最后,我们可以创建一个处理程序的父类,负责处理一些管 理性细节。例如:不通过全名调用方法(如start_paragraph---start(selef,name)---调用 ’start_’+ name方法)等等。
② 规则
  处理程序的可扩展性和灵活性都非常高了,该将注意力转向解析(对文本进行解读) 了。为此,我们将规则定义为独立的对象,而不像初次实现中那样使用一条包含各种条件和操作 的大型if语句。规则是供主程序(解析器)使用的。主程序必须根据给定的文本块选择合适的规则来对其进 行必要的转换。换而言之,规则必须具备如下功能。
   - 知道自己适用于那种文本块(条件)。
   - 对文本块进行转换(操作)。
  因此每个规则对象都必须包含两个方法:condition和action:
方法condition只需要一个参数:待处理的文本块。它返回一个布尔值,指出当前规则是否 适用于处理指定的文本块。方法action也将当前文本块作为参数,但为了影响输出,它还必须能够访问处理器对象。

#我们以标题规则为例:
def condition(self, block):
#如果文本块符合标题的定义,就返回True;否则返回False。
 def action(self, block, handler):
/**调用诸如handler.start('headline')、handler.feed(block)和handler.end('headline')等方法。
我们不想尝试其他规则,因此返回True,以结束对当前文本块的处理。*/

  当然这里还可以定义一个rule的父类,比如action,condition方法可以在不同的规则中有自己的实现。

③ 过滤器
  由于Handler类包含方法sub,每个过滤器都可用一个正则表达 式和一个名称(如emphasis或url)来表示。
④ 解析器
  接下来就是应用的核心,Parser类。它使用一个处理程序以及一系列规则和过滤器 将纯文本文件转换为带标记的文件(这里是HTML文件)。
其中包括了:完成准 备工作的构造函数、添加规则的方法、添加过滤器的方法以及对文件进行解析的方法。
⑤ 创建规则和过滤器
  至此,万事俱备,只欠东风——还没有创建具体的规则和过滤器。目前绝大部分工作都是在让规则和过滤器与处理程序一样灵活。通过使用一组复杂的规则,可处理复杂的文档,但我们将保持尽可能简单。只创建分别用于处理题目、其他标题和列表项的规则。应将相连的列表项视为一个列表,因此还将创建一个处理 整个列表的列表规则。最后,可创建一个默认规则,用于处理段落,即其他规则未处理的所有文本块。各个不同的复杂文档的规则已经在代码块中解释。
  最后我们通过正则表达式,添加过滤器,分别找出:出要突出的内容、URL和Email 地址。(https://gitlab.com/ZZY478086819/actualcombatproject)
至此我们将以上的内容通过代码实现,具体代码小编已经上传至github上,具体的编写步骤为:
处理程序(handlers.py) → 规则(rules.py)→主程序(markup.py)

2. 项目2:绘制图表

  这个项目主要介绍:用Python创建图表。具体地说,你将创建一个PDF文件,其中包含的图表对 从文本文件读取的数据进行了可视化。虽然常规的电子表格软件都提供这样的功能,但Python提 供了更强大的功能。
  PDF介绍:它指的 是可移植的文档格式(portable document format)。PDF是Adobe开发的一种格式,可表示任何包 含图形和文本的文档。不同于Microsoft Word等文档,PDF文件是不可编辑的,但有适用于大多 数平台的免费阅读器软件。另外,无论在哪种平台上使用什么阅读器来查看,显示的PDF文件都 相同;而HTML格式则不是这样的,它要求平台安装指定的字体,还必须将图片作为独立的文件 进行传输。

(1) 问题描述

  根据不同的文本内容,生成相应的建PDF格式(和其他格式)的图形和文档。这个项目主要将根据有关太阳黑子的数据 (来自美国国家海洋和大气管理局的空间天气预测中心)创建一个折线图。创建的程序必须具备如下功能:
   - 从网上下载数据文件
   - 对数据文件进行解析,并提取感兴趣的内容
   - 根据这些数据创建PDF图形

(2) 准备工作

   - 图形生成包:ReportLab(import reportlab)
   - 测试数据:http://www.swpc.noaa.gov中下载

(3) 简单实现

  ReportLab由很多部分组成,让你能够以多种方式生成输出。就生成PDF而言,最基本的模块 是pdfgen,其中的Canvas类包含多个低级绘图方法。例如,要在名为c的Canvas上绘制直线,可调 用方法c.line。
  这里展示一个实例:它在一个100点×100点的PDF图形中央绘制字符串"Hello, world!"。

from reportlab.graphics.shapes import Drawing,String
from reportlab.graphics import renderPDF

#创建一个指定尺寸的Drawing对象
d=Drawing(100,100)

#再创建具有指定属性的图形元素(这里是一个String对象)
s=String(50,50,'Hello World',textAnchor='middle')
#将图形元素添加到Drawing对象中
d.add(s)
#以PDF格式渲染Drawing对象,并将结果保存到文件中
renderPDF.drawToFile(d,'hello.pdf','A simple PDF file')

Python权威指南的10个项目(1~5)

(4) 绘制折折线

  为绘制太阳黑子数据折线图,需要绘制一些直线。实际上,你需要绘制多条相连的直线。ReportLab提供了一个专门用于完成这种工作的类——PolyLine。
要绘制折线图,必须为数据集中的每列数据绘制一条折线。
①这里先创建出一个太阳黑子图形程序的第一个原型:

from reportlab.lib import colors
from reportlab.graphics.shapes import *
from reportlab.graphics import renderPDF

# Year Month Predicted High Low
data=[
    (2007, 8, 113.2, 114.2, 112.2),
    (2007, 9, 112.8, 115.8, 109.8),
    (2007, 10, 111.0, 116.0, 106.0),
    (2007, 11, 109.8, 116.8, 102.8),
    (2007, 12, 107.3, 115.3, 99.3),
    (2008, 1, 105.2, 114.2, 96.2),
    (2008, 2, 104.1, 114.1, 94.1),
    (2008, 3, 99.9, 110.9, 88.9),
    (2008, 4, 94.8, 106.8, 82.8),
    (2008, 5, 91.2, 104.2, 78.2),
]
#创建一个指定尺寸的Drawing对象
drawing=Drawing(200,150)

pred=[row[2]-40 for row in data]
high = [row[3]-40 for row in data]
low = [row[4]-40 for row in data]
times=[200*((row[0]+row[1]/12.0)-2007)-110 for row in data]

drawing.add(PolyLine(list(zip(times,pred)), strokeColor=colors.blue))
drawing.add(PolyLine(list(zip(times,high)), strokeColor=colors.blue))
drawing.add(PolyLine(list(zip(times,low)), strokeColor=colors.blue))
drawing.add(String(65,115,'Sunspots',fontSize=18,fillColor=colors.red))
renderPDF.drawToFile(drawing,'report1.pdf','Sunspots')

Python权威指南的10个项目(1~5)
②最终版
这里为了方便我们直接读取本地的文件,测试文件已经放入项目中:Predict.txt
Python权威指南的10个项目(1~5)
具体的项目代码粘贴在小编的github中!

3. 项目3:万能的XML

  这个项目的目标是,根据描述各种网页和目录的单个XML文件生成完整的网站。
实现目标:

(4) 最终版

  这里由于小编将代码的各个功能进行了解耦,分不同的功能模块进行开发,这里小编将详细介绍每个步骤具体实现什么功能,当然最终的代码小编也会上传到github中供大家参考。
  鉴于SAX机制低级而简单,编写一个混合类来处理管理性细节通常很有帮助。这些管理性细 节包括收集字符数据,管理布尔状态变量(如passthrough),将事件分派给自定义事件处理程序, 等等。就这个项目而言,状态和数据处理非常简单,因此这里将专注于事件分派。
① 分派器混合类
  与其在标准通用事件处理程序(如startElement)中编写长长的if语句,不如只编写自定义 的具体事件处理程序(如startPage)并让它们自动被调用。你可在一个混合类中实现这种功能, 再通过继承这个混合类和ContentHandler来创建一个子类。
程序实现的功能:
   - startElement被调用时,如果参数name为'foo',它应尝试查找事件处理程序startFoo,并 使用提供给它的属性调用这个处理程序
   - 同样,endElement被调用时,如果参数name为'foo',它应尝试调用endFoo
   - 如果没有找到相应的处理程序,这些方法应调用方法defaultStart或defaultEnd。如果没 有这些默认处理程序,就什么都不做
简单案例:

class Dispatcher:
  def startElement(self, name, attrs): 
self.dispatch('start', name, attrs) 
def endElement(self, name): 
self.dispatch('end', name)
def dispatch(self, prefix, name, attrs=None): 
mname = prefix + name.capitalize() #将字符串的第一个字母变成大写,其他字母变小写
dname = 'default' + prefix.capitalize() 
method = getattr(self, mname, None) 
if callable(method): args = () 
else: method = getattr(self, dname, None) 
args = name, 
if prefix == 'start': args += attrs,
  if callable(method): method(*args)

②将首部和尾部写入文件的方法以及默认处理程序
  我们将编写专门用于将首部和尾部写入文件的方法,而不在事件处 理程序中直接调用self.out.write。这样就可通过继承来轻松地重写这些方法。
简单案例:

def writeHeader(self, title):
 self.out.write("<html>\n <head>\n <title>")
 self.out.write(title)
 self.out.write("</title>\n </head>\n <body>\n")
def writeFooter(self):
 self.out.write("\n </body>\n</html>\n")

③ 支持目录
  为创建必要的目录,需要使用函数os.makedirs,它在指定的路径中创建必要的目录。例如, os.makedirs('foo/bar/baz')在当前目录下创建目录foo,再在目录foo下创建目录bar,然后在目 录bar下创建目录baz。如果目录foo已经存在,将只创建目录bar和baz。同样,如果目录bar也已经 存在,将只创建目录baz。然而,如果目录baz也已经存在,通常将引发异常。为避免出现这种情 况,我们将关键字参数exist_ok设置为True。另一个很有用的函数是os.path.join,它使用正确 的分隔符(例如,在UNIX中为/)将多条路径合而为一。
例:

def ensureDirectory(self):
 path = os.path.join(*self.directory)
 os.makedirs(path, exist_ok=True)

④ 事件的处理
  这里需要4个事件处理程序,其中2个用于处理目录,另外2个用于 处理页面。目录处理程序只使用了列表directory和方法ensureDirectory。页面处理程序使用了方法writeHeader和writeFooter。另外,它们还设置了变量passthrough (以便将XHTML代码直接写入文件),而且打开和关闭与页面相关的文件。

(5) 结果展示

Python权威指南的10个项目(1~5)
通过解析website.xml,得到以上的目录已经html文件。具体的代码在项目中,可以自行下载查看!

4. 项目4:新闻汇总

  本项目要编写的程序是一个信息收集代理,能够替你收集信息(具体地说是新闻)并生成新闻 汇总。在这个项目中,需要做的并 仅仅使用urllib下载文件,还将使用另一个网络库,即nntplib,它使用起来要难些。另外,还需重构程序以支持不同的新闻源和目的地,进而在中间层使用主引擎将前端和后端分开。
  最终项目实现的目标:
  - 可轻松地添加新闻源(乃至不同类型的新闻源) 能够从众多不同的新闻源收集新闻
  - 能够以众多不同的格式将生成的新闻汇编分发到众多不同的目的地
  - 能够轻松地添加新的目的地(乃至不同类型的目的地)

(1) 知识点扩展

  NNTP是一种标准网络协议,用于管理在Usenet讨论组中发布的消息。NNTP服务器组成了一 个统一管理新闻组的全局网络,通过NNTP客户端(也称为新闻阅读器)可发布和阅读消息。NNTP 服务器组成的主网络称为Usenet,创建于1980年(但NNTP协议到1985年才开始使用)。相比于最 新的Web潮流,这算是一种很古老的技术了,但从某种程度上说,互联网的很大一部分都基于这 样的古老技术。

(2) 工作准备

(3) 初次实现

  最先开发出来一个简单的版本:是从NNTP服务器上的新闻组下载 最新的消息,使用print直接将结果打印到标准输出。

'''
一个简单的新闻收集代理
'''

from nntplib import NNTP
#服务器域名
servername='news.gmane.org'
#指定新闻组设置为当前新闻组,并返回一些有关该新闻组的信息
group='gmane.comp.python.committers'
#创建server客户端对象
server=NNTP(servername)
#指定要获取多少篇文章
howmany=10
#返回的值为通用的服务器响应、新闻组包含的消息数、第一条和最后一条消息的编号以及新闻组的名称
resp, count, first, last, name = server.group(group)
start = last-howmany+1

resp,overviews=server.over((start,last))

#从overview中提取主题,并使用ID从服务器获取消息正文
for id,over in overviews:
    subject=over['subject']
    resp,info=server.body(id)
    print(subject)
    print('-'*len(subject))
    for line in info.lines:
        #消息正文行是以字节的方式返回的,但为简单起见,我们直接使用编码Latin-1
        print(line.decode('latin1'))
    print()

#关闭连接
server.quit()

(4) 最终版

  这次我们将对代码稍作重构以修复这种问题。你将把各部分代码放在类和方法中,以提高程序的结构化程 度和抽象程度,这样就可用其他类替换有些部分。
  统计一下我们大概需要哪些类::信息、 代理、新闻、汇总、网络、新闻源、目的地、前端、后端和主引擎。这个名词清单表明,需要下 面这些主要的类:NewsAgent、NewsItem、Source和Destination。
  各种新闻源构成了前端,目的地构成了后端,而新闻代理位于中间层。这里我们对每个类进行详细的说明:
① NewsItem
它只表示一段数据,其中包括标题和正文。

class NewsItem:
    def __init__(self, title, body):
        self.title = title
        self.body = body

② NewsAgent
  准确地确定要从新闻源和新闻目的地获取什么,先来编写代理本身是个不错的主意。代理 必须维护两个列表:源列表和目的地列表。添加源和目的地的工作可通过方法addSource和 addDestination来完成。然后就是将新闻从源分发到目的地的方法。
③ Destination
   - 生成的文本为HTML。
   - 将文本写入文件而不是标准输出中。
   - 除新闻列表外,还创建了一个目录。
④ Source
   - 代码封装在方法getItems中。原来的变量servername和group现在是构造函数的参数。另 外,变量howmany也变成了构造函数的参数。
   - 调用了decode_header,它负责处理报头字段(如subject)使用的特殊编码。
   - 不是直接打印每条新闻,而是生成NewsItem对象(让getItems变成了生成器)。
   总的来说就是:通过NewsItem将从网页上获取的新闻的内容和标题存放起来,这里我们设置两个数据源:一个是NNTP中获取的新闻,一个是从urlopen从web网站中获取的新闻,然后设置了两个数据的目的地:一个是控制台输出,一个是写入HTML文件中。通过NewsAgent对象,将数据源和目的地加入到列表中,然后在其distribute方法中,把从数据源获取的数据发送给目的地。最后通过一个run方法,将这些步骤串联起来,这样就实现了一个简单的从不同的渠道中获取新闻,转发的不同的渠道去。

5. 项目5:虚拟茶话会

   在这个项目中,将做些正式的网络编程工作:编写一个聊天服务器,让人们能够通过 网络实时地聊天。只使用标准库中的异步网络 编程模块(asyncore和asynchat)。

(1) 问题描述

大概的项目需求如下:

(3) 初步实现

  我们来将程序稍做分解。需要创建两个主要的类:一个表示聊天服务器,另一个表示聊天会 话(连接的用户)。
① ChatServer 类

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from asyncore import dispatcher
import socket,asyncore

'''
一个能够接受连接的服务器
'''

PORT=5005
NAME = 'TestChat'

'''
为创建简单的ChatServer类,可继承模块asyncore中的dispatcher类。dispatcher类基本上是
一个套接字对象,但还提供了一些事件处理功能。
'''
class ChatServer(dispatcher):
    '''
    一个接受连接并创建会话的类。它还负责向这些会话广播
    '''
    def __init__(self,port):
        dispatcher.__init__(self)
        #调用了create_socket,并通过传入两个参数指定了要创建的套接字类型,通常都使用这里使用的类型
        self.create_socket(socket.AF_INET,socket.SOCK_STREAM)
        '''
            调用了set_reuse_addr,让你能够重用原来的地址(具体地说是端口号),
            即便未妥善关闭服务器亦如此。不会出现端口被占用情况
        '''
        self.set_reuse_addr()
        '''
            bind的调用将服务器关联到特定的地址(主机名和端口)。 
            空字符串表示:localhost,或者说当前机器的所有接口
        '''
        self.bind('',port)
        #listen的调用让服务器监听连接;它还将在队列中等待的最大连接数指定为5。
        self.listen(5)
    def handle_accept(self):
        '''
        重写事件处理方法handle_accept,让它在服务器接受客户端连接时做些事情
        '''
        #调用self.accept,以允许客户端连接。
        #返回一个连接(客户端对应的套接字)和一个地址(有关发起连接的机器的信息)。
        conn,addr=self.accept()
        #addr[0]是客户端的IP地址
        print('Connection attempt from',addr[0])
if __name__=='__main__':
    s=ChatServer(PORT)
    try:
        #启动服务器的监听循环
        asyncore.loop()
    except KeyboardInterrupt:
        pass

② ChatSession 类
  这是一个新的版本,这里我们使用asynchat,我们设置一个会话,每一次有一个连接对象时,就将这个连接对象加入会话中,好处是:每个连接都会创建一个新的dispatcher对象。

'''
包含ChatSession类的服务器程序
'''

from asyncore import dispatcher
from asynchat import async_chat
import socket,asyncore

PORT=5005

class ChatSession(async_chat):
    def __init__(self,socket):
        async_chat.__init__(self,socket)
        #设置结束符,
        self.set_terminator("\r\n")
        self.data=[]

    #从套接字读取一些文本
    def collect_incoming_data(self, data):
        self.data.append(data)

    #读取到结束符时将调用found_terminator
    def found_terminator(self):
        line=''.join(self.data)
        self.data=[]
        #使用line做些事情……
        print(line)

class ChatServer(dispatcher):
    def __init__(self,port):
        dispatcher.__init__()
        self.create_socket(socket.AF_INET,socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind("",port)
        self.listen(5)
       #ChatServer存储了一个会话列表
        self.sessions=[]
    #接受一个新请求,就会创建一个新的ChatSession对象,并将其附加到会话列表末尾
    def handle_accept(self):
        conn,addr=self.accept()
        self.sessions.append(ChatSession(conn))

if __name__=='__main__':
    s=ChatServer(PORT)
    try:
        asyncore.loop()
    except KeyboardInterrupt:
        print()

③ 整合
  要让原型成为简单而功能完整的聊天服务器,还需添加一项主要功能:将用户所说的内容(他 们输入的每一行)广播给其他用户。要实现这种功能,可在服务器中使用一个简单的for循环来 遍历会话列表,并将内容行写入每个会话。要将数据写入async_chat对象,可使用方法push。
  这种广播行为也带来了一个问题:客户端断开连接后,你必须确保将其从会话列表中删除。 为此,可重写事件处理方法handle_close。

from asyncore import dispatcher
from asynchat import async_chat
import socket,asyncore

PORT = 5005
NAME = 'TestChat'

class ChatSession(async_chat):
    """
    一个负责处理服务器和单个用户间连接的类
    """
    def __init__(self,server,sock):
        #标准的设置任务
        async_chat.__init__(self,sock)
        self.server=server
        self.set_terminator("\r\n")
        self.data=[]
        #问候用户:
        self.push(("Welcome to %s \r\n" % self.server.name).encode())

    def collect_incoming_data(self, data):
        self.data.append(data.decode())

    def found_terminator(self):
        """
       如果遇到结束符,就意味着读取了一整行,
       因此将这行内容广播给每个人
        """
        line=''.join(self.data)
        self.data=[]
        self.server.broadcast(line)
    #客户端断开之后,将会话从列表中删除
    def handle_close(self):
        async_chat.handle_close(self)
        self.server.disconnect(self)

class ChatServer(dispatcher):
    """
     一个接受连接并创建会话的类。它还负责向这些会话广播
    """
    def __init__(self,port,name):
        dispatcher.__init__(self) #这一行一定要加
        self.name = name
        #标准的设置任务:
        self.create_socket(socket.AF_INET,socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind(('',port))
        self.listen(5)

        self.sessions=[]

    def disconnect(self,session):
        self.sessions.remove(session)

    def broadcast(self,line):
        for session in self.sessions:
            session.push((line+"\r\n").encode())

    def handle_accept(self):
        conn,addr=self.accept()
        self.sessions.append(ChatSession(self,conn))

if __name__ == '__main__':
    s=ChatServer(PORT,NAME)
    try:
        asyncore.loop()
    except KeyboardInterrupt:
        print

(4) 最终版本

  第一个版本虽然是个管用的聊天服务器,但其功能很有限,最明显的缺陷是没法知道每句话 都是谁说的。另外,它也不能解释命令(如say或logout),而最初的规范要求提供这样的功能。 有鉴于此,需要添加对身份(每个用户都有唯一的名字)和命令解释的支持,同时必须让每个会 话的行为都依赖于其所处的状态(刚连接、已登录等)。添加这些功能时,必须确保程序是易于扩展的。
① 基本命令解释功能
  这里我们可以定义一些简单的命令,比如say、login 等等,即如果发送:say Hello, world!
将调用do_say('Hello, world!'),这个功能如何实现呢,这里写一段伪代码:

#基本的命令解释功能,例如:say Hello, world!
class CommandHandler:
    '''
        类似于标准库中cmd.Cmd的简单命令处理程序
    '''
    #参数不正确
    def unknown(self,session,cmd):
        session.push('Unknown command: {}s\r\n'.format(cmd).encode())
    #根据命令,匹配方法,调用
    def handler(self,session,line):
        if not line.strip():return
        parts=line.split(' ',1)
        cmd=parts[0]
        try:
            line=parts[1].strip()
        except IndexError:
            line=''
        meth = getattr(self, 'do_' + cmd, None)
        try:
            meth(session,line)
        except TypeError:
            self.unknown(session,cmd)
    def do_say(self,session,line):
        session.push(line.encode())

② 聊天室
  每个聊天室都是一个包含特定命令的CommandHandler。另外,它还应 记录聊天室内当前有哪些用户(会话)。除基本方法add和remove外,它还包含方法broadcast,这个方法对聊天室内的所有用户(会 话)调用push。这个类还以方法do_logout的方式定义了一个命令——logout。这个方法引发异常 EndSession,而这种异常将在较高的层级(found_terminator中)处理。
伪代码:

class EndSession(Exception):pass
class Room(CommandHandler):
    """
    可包含一个或多个用户(会话)的通用环境。
    它负责基本的命令处理和广播
    """
    def __init__(self,server):
        self.server=server
        self.sessions=[]
    def add(self,session):
        self.sessions.append(session)
    def remove(self,session):
        self.sessions.remove(session)

    def broadcast(self,line):
        for session in self.sessions:
            session.push(line.encode())
    def do_logout(self,session,line):
        raise EndSession

③ 登录和退出聊天室
  除表示常规聊天室(这个项目中只有一个这样的聊天室)之外,Room的子类还可表示其他状 态,这正是你创建Room类的意图所在。例如,用户刚连接到服务器时,将进入专用的LoginRoom (其中没有其他用户)。LoginRoom在用户进入时打印一条欢迎消息(这是在方法add中实现的)。 它还重写了方法unknown,使其让用户登录。这个类只支持一个命令,即命令login,这个命令检 查用户名是否是可接受的(不是空字符串,且未被其他用户使用)。
  LogoutRoom要简单得多,它唯一的职责是将用户的名字从服务器中删除(服务器包含存储会 话的字典users)。如果用户名不存在(因为用户从未登录),将忽略因此而引发的KeyError异常。
④ 主聊天室
  主聊天室也重写了方法add和remove。在方法add中,它广播一条消息,指出有用户进入,同 时将用户的名字添加到服务器中的字典users中。方法remove广播一条消息,指出有用户离开。
除了这些方法以外,主聊天室还实现了:
  - 命令say(由方法do_say实现)广播一行内容,并在开头指出这行内容是哪位用户说的。
  - 命令look(由方法do_look实现)告诉用户聊天室内当前有哪些用户。
  - 命令who(由方法do_who实现)告诉用户当前有哪些用户登录了。在这个简单的服务器中, 命令look和who的作用相同,但如果你对其进行扩展,使其包含多个聊天室,这两个命令 的作用将有所区别。
最终实现
  - ChatSession新增了方法enter,用于进入新的聊天室。
  - ChatSession的构造函数使用了LoginRoom。
  -方法handle_close使用了LogoutRoom。
  - ChatServer的构造函数新增了字典属性users和ChatRoom属性main_room。

(5) 结果展示

  好吧,小编也是根据指南一步一步的将代码实现了,但是不知道为啥就是跑不成功,然后就从网上搜了搜如何解决,虽然也查到了相关的案例,神奇的事情发生,我copy多个某某大神的代码,居然运行不了,而且报出同样的错误,本来想解决一下,造福大家,但是小编能力有限,实在不知道如何下手,这里小编把错误展示出来,有牛X的大神看见了帮小编分析解决一下呗!
Python权威指南的10个项目(1~5)
  但是 但是,虽然程序没运行出来,但是至少学到了一些东西,总不能只知道代码错了,不知道代码就行实现了啥,对不对,那不是欺骗了各位读友嘛,所以小编这里把上面代码的整个实现过程画了一个图分享给大家:
Python权威指南的10个项目(1~5)

这个是Python权威指南的前5个项目,虽然后面了没有实现效果图,但是代码和解释是相当充分的,后续的5个项目均有呈现的效果和完整的代码,大家放心小编在写代码时也踩了不少的坑,有些问题小编会以小案例的形式在测试代码中体现:

代码地址:https://gitlab.com/ZZY478086819/actualcombatproject

推荐阅读:
  1. orchestrator+proxysql+mysql5.7GTID主从切换测试过程
  2. Python权威指南的10个项目(6~10)

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

python小项目 权威

上一篇:mysql数据库的索引

下一篇:Python入门基础知识实例,值得收藏!

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》