引言
??我相信學(xué)習(xí)Python過(guò)的朋友,一定會(huì)喜歡上這門語(yǔ)言,簡(jiǎn)單,庫(kù)多,易上手,學(xué)習(xí)成本低,但是如果是學(xué)習(xí)之后,不經(jīng)常使用,或者工作中暫時(shí)用不到,那么不久之后又會(huì)忘記,久而久之,就浪費(fèi)了很多的時(shí)間再自己的“曾經(jīng)”會(huì)的東西上。所以最好的方法就是實(shí)戰(zhàn),通過(guò)真是的小型項(xiàng)目,去鞏固,理解,深入Python,同樣的久而久之就不會(huì)忘記。
??所以這里小編帶大家編寫10個(gè)小型項(xiàng)目,去真正的實(shí)操Python,這10個(gè)小型項(xiàng)目是來(lái)自《Python權(quán)威指南》中后面10個(gè)章節(jié)的項(xiàng)目,有興趣的朋友可以自行閱讀。希望這篇文章能成為給大家在Python的學(xué)習(xí)道路上的奠基石。
??建議大家是一邊看代碼,一邊學(xué)習(xí),文章中會(huì)對(duì)代碼進(jìn)行解釋:
這里是項(xiàng)目的gitlab地址(全代碼):

https://gitlab.com/ZZY478086819/actualcombatproject

1. 項(xiàng)目1:自動(dòng)添加標(biāo)簽

??這個(gè)項(xiàng)目主要介紹如何使用Python杰出的文本處理功能,包括使用正則表達(dá)式將純文本文件轉(zhuǎn)換為用 HTML或XML等語(yǔ)言標(biāo)記的文件。

(1) 問(wèn)題描述

??假設(shè)你要將一個(gè)文件用作網(wǎng)頁(yè),而給你文件的人嫌麻煩,沒(méi)有 以HTML格式編寫它。你不想手工添加需要的所有標(biāo)簽,想編寫一個(gè)程序來(lái)自動(dòng)完成這項(xiàng)工作。大致而言,你的任務(wù)是對(duì)各種文本元素(如標(biāo)題和突出的文本)進(jìn)行分類,再清晰地標(biāo)記它 們。就這里的問(wèn)題而言,你將給文本添加HTML標(biāo)記,得到可作為網(wǎng)頁(yè)的文檔,讓W(xué)eb瀏覽器能 夠顯示它。然而,創(chuàng)建基本引擎后,完全可以添加其他類型的標(biāo)記(如各種形式的XML和LATEX 編碼)。對(duì)文本文件進(jìn)行分析后,你甚至可以執(zhí)行其他的任務(wù),如提取所有的標(biāo)題以制作目錄。

(2) 代碼實(shí)現(xiàn)前準(zhǔn)備

實(shí)現(xiàn)思路:
?? - 輸入無(wú)需包含人工編碼或標(biāo)簽
?? - 程序需要能夠處理不同的文本塊(如標(biāo)題、段落和列表項(xiàng))以及內(nèi)嵌文本(如突出的文 本和URL)。
?? - 雖然這個(gè)實(shí)現(xiàn)添加的是HTML標(biāo)簽,但應(yīng)該很容易對(duì)其進(jìn)行擴(kuò)展,以支持其他標(biāo)記語(yǔ)言
有用的工具:
?? - 肯定需要讀寫文件,至少要從標(biāo)準(zhǔn)輸入
?? - 可能需要迭代輸入行
?? - 需要使用一些字符串方法
?? - 可能用到一兩個(gè)生成器
?? - 可能需要模塊re

(3) 簡(jiǎn)單實(shí)現(xiàn)

分為兩個(gè)步驟

  • 找出文本塊:要找出這些文本塊,一種簡(jiǎn)單的方法是,收集空行前的所有行并將它們返回,然后重復(fù)這樣 的操作。不需要收集空行,因此不需要返回空文本塊(即多個(gè)空行)。另外,必須確保文件的最 后一行為空行,否則無(wú)法確定最后一個(gè)文本塊到哪里結(jié)束。
            
              #!/usr/bin/env python
# -*- coding: utf-8 -*-
#生成器lines是個(gè)簡(jiǎn)單的工具,在文件末尾添加一個(gè)空行
def lines(file):
    for line in file:
        yield line
    yield '\n'

# 生成器blocks實(shí)現(xiàn)了剛才描述的方法。生成文本塊時(shí),將其包含的所有行合并,
#并將兩端多余的空白(如列表項(xiàng)縮進(jìn)和換行符)刪除,得到一個(gè)表示文本塊的字符串。
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)

            
          
  • 添加一些標(biāo)記:可按如下基本步驟進(jìn)行:打印一些起始標(biāo)記、對(duì)于每個(gè)文本塊,在段落標(biāo)簽內(nèi)打印它、打印一些結(jié)束標(biāo)記。假設(shè)要將第一個(gè)文本塊放在一級(jí)標(biāo)題標(biāo)簽(h1)內(nèi),而不是段 落標(biāo)簽內(nèi)。另外,還需將用星號(hào)括起的文本改成突出文本(使用標(biāo)簽em)。這樣程序?qū)⒏杏靡恍?由于已經(jīng)編寫好了函數(shù)blocks。
            
              import sys,re
#引用剛剛編寫的util模塊
from util import *

print('
              
                zzy-python
              
              ')
title = True
file='../../file_data/test_input.txt'
#for block in blocks(sys.stdin) 這里可以使用標(biāo)準(zhǔn)的輸入,小編為了方便運(yùn)行,就本地讀取
with open(file) as f:
    for block in blocks(f):
        re.sub(r'\*(.+?\*)',r'
              
                \1
              
              ',block)
        if title:
            print('
              

') print(block) print('

') title=False else: print('

') print(block) print('

') print('')

??到這簡(jiǎn)單的實(shí)現(xiàn)就完成了但是如果要擴(kuò)展這個(gè)原型,該如何辦呢?可在for循環(huán)中添加檢查,以確定文本塊是否是標(biāo)題、列表項(xiàng)等。為此,需要添加其他的正則表達(dá)式,代碼可能很快變得很亂。更重要的是,要讓程序輸出其他格式的代碼(而不是HTML)很難,但是這個(gè)項(xiàng)目的目標(biāo)之一就是能夠輕松地添加其他輸出格式。

(4) 完整實(shí)現(xiàn)

??為了提高可擴(kuò)展性,需提高程序的模塊化程度(將功能放在 獨(dú)立的組件中)。要提高模塊化程度,方法之一是采用面向?qū)ο笤O(shè)計(jì)。這里我們需要尋找一些抽象,讓程序在變得復(fù)雜時(shí)也易于管理。下面先來(lái)列出一些潛在的組件:
解析器 :添加一個(gè)讀取文本并管理其他類的對(duì)象。
規(guī)則 :對(duì)于每種文本塊,都制定一條相應(yīng)的規(guī)則。這些規(guī)則能夠檢測(cè)不同類型的文本塊 并相應(yīng)地設(shè)置其格式。
過(guò)濾器 :使用正則表達(dá)式來(lái)處理內(nèi)嵌元素。
處理程序 :供解析器用來(lái)生成輸出。每個(gè)處理程序都生成不同的標(biāo)記。
那么接下來(lái),小編就對(duì)這幾個(gè)組件,進(jìn)行詳細(xì)介紹:

① 處理程序
??對(duì)于每種文本塊,它都提供兩個(gè)處理方法:一個(gè)用于添加起始標(biāo)簽,另一個(gè)用于添加結(jié)束標(biāo)簽。例如它可能包含用于處理段落的方法start_paragraph和end_paragraph。生成HTML代碼時(shí),可像 下面這樣實(shí)現(xiàn)這些方法:

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

對(duì)于其他類型的文本塊,添加不同的開始和結(jié)束標(biāo)簽,對(duì)于形如連接,**包圍的內(nèi)容,需要特殊處理,例:

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

當(dāng)然對(duì)于簡(jiǎn)單的文本內(nèi)容,我們只需要:

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

最后,我們可以創(chuàng)建一個(gè)處理程序的父類,負(fù)責(zé)處理一些管 理性細(xì)節(jié)。例如:不通過(guò)全名調(diào)用方法(如start_paragraph---start(selef,name)---調(diào)用 ’start_’+ name方法)等等。
② 規(guī)則
??處理程序的可擴(kuò)展性和靈活性都非常高了,該將注意力轉(zhuǎn)向解析(對(duì)文本進(jìn)行解讀) 了。為此,我們將規(guī)則定義為獨(dú)立的對(duì)象,而不像初次實(shí)現(xiàn)中那樣使用一條包含各種條件和操作 的大型if語(yǔ)句。規(guī)則是供主程序(解析器)使用的。主程序必須根據(jù)給定的文本塊選擇合適的規(guī)則來(lái)對(duì)其進(jìn) 行必要的轉(zhuǎn)換。換而言之,規(guī)則必須具備如下功能。
?? - 知道自己適用于那種文本塊(條件)。
?? - 對(duì)文本塊進(jìn)行轉(zhuǎn)換(操作)。
??因此每個(gè)規(guī)則對(duì)象都必須包含兩個(gè)方法:condition和action:
方法condition只需要一個(gè)參數(shù):待處理的文本塊。它返回一個(gè)布爾值,指出當(dāng)前規(guī)則是否 適用于處理指定的文本塊。方法action也將當(dāng)前文本塊作為參數(shù),但為了影響輸出,它還必須能夠訪問(wèn)處理器對(duì)象。

            
              #我們以標(biāo)題規(guī)則為例:
def condition(self, block):
#如果文本塊符合標(biāo)題的定義,就返回True;否則返回False。
 def action(self, block, handler):
/**調(diào)用諸如handler.start('headline')、handler.feed(block)和handler.end('headline')等方法。
我們不想嘗試其他規(guī)則,因此返回True,以結(jié)束對(duì)當(dāng)前文本塊的處理。*/
            
          

??當(dāng)然這里還可以定義一個(gè)rule的父類,比如action,condition方法可以在不同的規(guī)則中有自己的實(shí)現(xiàn)。

③ 過(guò)濾器
??由于Handler類包含方法sub,每個(gè)過(guò)濾器都可用一個(gè)正則表達(dá) 式和一個(gè)名稱(如emphasis或url)來(lái)表示。
④ 解析器
??接下來(lái)就是應(yīng)用的核心,Parser類。它使用一個(gè)處理程序以及一系列規(guī)則和過(guò)濾器 將純文本文件轉(zhuǎn)換為帶標(biāo)記的文件(這里是HTML文件)。
其中包括了:完成準(zhǔn) 備工作的構(gòu)造函數(shù)、添加規(guī)則的方法、添加過(guò)濾器的方法以及對(duì)文件進(jìn)行解析的方法。
⑤ 創(chuàng)建規(guī)則和過(guò)濾器
??至此,萬(wàn)事俱備,只欠東風(fēng)——還沒(méi)有創(chuàng)建具體的規(guī)則和過(guò)濾器。目前絕大部分工作都是在讓規(guī)則和過(guò)濾器與處理程序一樣靈活。通過(guò)使用一組復(fù)雜的規(guī)則,可處理復(fù)雜的文檔,但我們將保持盡可能簡(jiǎn)單。只創(chuàng)建分別用于處理題目、其他標(biāo)題和列表項(xiàng)的規(guī)則。應(yīng)將相連的列表項(xiàng)視為一個(gè)列表,因此還將創(chuàng)建一個(gè)處理 整個(gè)列表的列表規(guī)則。最后,可創(chuàng)建一個(gè)默認(rèn)規(guī)則,用于處理段落,即其他規(guī)則未處理的所有文本塊。各個(gè)不同的復(fù)雜文檔的規(guī)則已經(jīng)在代碼塊中解釋。
??最后我們通過(guò)正則表達(dá)式,添加過(guò)濾器,分別找出:出要突出的內(nèi)容、URL和Email 地址。(https://gitlab.com/ZZY478086819/actualcombatproject)
至此我們將以上的內(nèi)容通過(guò)代碼實(shí)現(xiàn),具體代碼小編已經(jīng)上傳至github上,具體的編寫步驟為:
處理程序(handlers.py) → 規(guī)則(rules.py)→主程序(markup.py)

2. 項(xiàng)目2:繪制圖表

??這個(gè)項(xiàng)目主要介紹:用Python創(chuàng)建圖表。具體地說(shuō),你將創(chuàng)建一個(gè)PDF文件,其中包含的圖表對(duì) 從文本文件讀取的數(shù)據(jù)進(jìn)行了可視化。雖然常規(guī)的電子表格軟件都提供這樣的功能,但Python提 供了更強(qiáng)大的功能。
PDF介紹: 它指的 是可移植的文檔格式(portable document format)。PDF是Adobe開發(fā)的一種格式,可表示任何包 含圖形和文本的文檔。不同于Microsoft Word等文檔,PDF文件是不可編輯的,但有適用于大多 數(shù)平臺(tái)的免費(fèi)閱讀器軟件。另外,無(wú)論在哪種平臺(tái)上使用什么閱讀器來(lái)查看,顯示的PDF文件都 相同;而HTML格式則不是這樣的,它要求平臺(tái)安裝指定的字體,還必須將圖片作為獨(dú)立的文件 進(jìn)行傳輸。

(1) 問(wèn)題描述

??根據(jù)不同的文本內(nèi)容,生成相應(yīng)的建PDF格式(和其他格式)的圖形和文檔。這個(gè)項(xiàng)目主要將根據(jù)有關(guān)太陽(yáng)黑子的數(shù)據(jù) (來(lái)自美國(guó)國(guó)家海洋和大氣管理局的空間天氣預(yù)測(cè)中心)創(chuàng)建一個(gè)折線圖。創(chuàng)建的程序必須具備如下功能:
?? - 從網(wǎng)上下載數(shù)據(jù)文件
?? - 對(duì)數(shù)據(jù)文件進(jìn)行解析,并提取感興趣的內(nèi)容
?? - 根據(jù)這些數(shù)據(jù)創(chuàng)建PDF圖形

(2) 準(zhǔn)備工作

?? - 圖形生成包:ReportLab(import reportlab)
?? - 測(cè)試數(shù)據(jù):http://www.swpc.noaa.gov中下載

(3) 簡(jiǎn)單實(shí)現(xiàn)

??ReportLab由很多部分組成,讓你能夠以多種方式生成輸出。就生成PDF而言,最基本的模塊 是pdfgen,其中的Canvas類包含多個(gè)低級(jí)繪圖方法。例如,要在名為c的Canvas上繪制直線,可調(diào) 用方法c.line。
??這里展示一個(gè)實(shí)例:它在一個(gè)100點(diǎn)×100點(diǎn)的PDF圖形中央繪制字符串"Hello, world!"。

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

#創(chuàng)建一個(gè)指定尺寸的Drawing對(duì)象
d=Drawing(100,100)

#再創(chuàng)建具有指定屬性的圖形元素(這里是一個(gè)String對(duì)象)
s=String(50,50,'Hello World',textAnchor='middle')
#將圖形元素添加到Drawing對(duì)象中
d.add(s)
#以PDF格式渲染Drawing對(duì)象,并將結(jié)果保存到文件中
renderPDF.drawToFile(d,'hello.pdf','A simple PDF file')

            
          

Python權(quán)威指南的10個(gè)項(xiàng)目(1~5)_第1張圖片

(4) 繪制折折線

??為繪制太陽(yáng)黑子數(shù)據(jù)折線圖,需要繪制一些直線。實(shí)際上,你需要繪制多條相連的直線。ReportLab提供了一個(gè)專門用于完成這種工作的類——PolyLine。
要繪制折線圖,必須為數(shù)據(jù)集中的每列數(shù)據(jù)繪制一條折線。
①這里先創(chuàng)建出一個(gè)太陽(yáng)黑子圖形程序的第一個(gè)原型:

            
              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),
]
#創(chuàng)建一個(gè)指定尺寸的Drawing對(duì)象
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權(quán)威指南的10個(gè)項(xiàng)目(1~5)_第2張圖片
②最終版
這里為了方便我們直接讀取本地的文件,測(cè)試文件已經(jīng)放入項(xiàng)目中:Predict.txt
Python權(quán)威指南的10個(gè)項(xiàng)目(1~5)_第3張圖片
具體的項(xiàng)目代碼粘貼在小編的github中!

3. 項(xiàng)目3:萬(wàn)能的XML

??這個(gè)項(xiàng)目的目標(biāo)是,根據(jù)描述各種網(wǎng)頁(yè)和目錄的單個(gè)XML文件生成完整的網(wǎng)站。
實(shí)現(xiàn)目標(biāo):

  • 整個(gè)網(wǎng)站由單個(gè)XML文件描述,該文件包含有關(guān)各個(gè)網(wǎng)頁(yè)和目錄的信息
  • 程序應(yīng)根據(jù)需要?jiǎng)?chuàng)建目錄和網(wǎng)頁(yè)
  • 應(yīng)能夠輕松地修改整個(gè)網(wǎng)站的設(shè)計(jì)并根據(jù)新的設(shè)計(jì)重新生成所有網(wǎng)頁(yè)

    (1) 問(wèn)題描述

    ??在這個(gè)項(xiàng)目中,要解決的通用問(wèn)題是解析(讀取并處理)XML文件。小編之前接到的一個(gè)任務(wù)就是解析XML提取其中相應(yīng)的字段,不過(guò)使用的java的dome4j解析的XML,雖然過(guò)程不復(fù)雜,但是我們看看Python有什么獨(dú)到之處。

    (2) 準(zhǔn)備工作

    ??- 使用的SAX解析器去解析XML(from xml.sax import make_parser)
    ??- 要編寫處理XML文件的程序,必須先設(shè)計(jì)要使用的XML格式(包含哪些屬性?各個(gè)標(biāo)簽都用來(lái)做什么),相當(dāng)于XML文件的元數(shù)據(jù)信息
    ??這里有些朋友可能對(duì)XML格式不是很了解,這里小編做一個(gè)介紹:

                    
                      
                        
                          

    title

    ??這里的 website是一個(gè)根標(biāo)簽 ,整個(gè)XML報(bào)告中只有一個(gè)。
    director、h1、page、ul則屬于website中的標(biāo)簽,可能有多個(gè) ,也可能嵌套。
    name="index" 表示標(biāo)簽中的屬性的name 和value
    ??這里我們只有了解一個(gè)XML報(bào)告中的每個(gè)標(biāo)簽的含義,才能做對(duì)應(yīng)的解析,提取有用的信息。

    (3) 簡(jiǎn)單實(shí)現(xiàn)

    ??說(shuō)了這么多我們先簡(jiǎn)單實(shí)現(xiàn)一個(gè)解析XML,這里提供一個(gè)文件website.xml。
    (具體文件小編會(huì)粘貼到自己的項(xiàng)目中)
    Python權(quán)威指南的10個(gè)項(xiàng)目(1~5)_第4張圖片
    這里我們通過(guò)解析website.xml,創(chuàng)建一個(gè)HTML頁(yè)面,執(zhí)行如下任務(wù):
    ?? - 在每個(gè)page元素的開頭,打開一個(gè)給定名稱的新文件,并在其中寫入合適的HTML首部(包 括指定的標(biāo)題)。
    ?? - 在每個(gè)page元素的末尾,將合適的HTML尾部寫入文件,再將文件關(guān)閉。
    ?? - 在page元素內(nèi)部,遍歷所有的標(biāo)簽和字符而不修改它們(將其原樣寫入文件)。
    ?? - 在page元素外部,忽略所有的標(biāo)簽(如website和directory)。

                    
                      #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    from xml.sax.handler import ContentHandler
    from xml.sax import parse
    '''
    這個(gè)模塊主要完成:
    簡(jiǎn)單的解析這個(gè)XML,提取有用信息,重新格式化為HTML格式,
    最終根據(jù)不同page寫入不同的HTML文件中
    '''
    class PageMaker(ContentHandler):
    #跟蹤是否在標(biāo)簽內(nèi)部
    passthrough = False
    #標(biāo)簽的開始
    def startElement(self,name,attrs):
        if name=='page':
            self.passthrough=True
            self.out= open(attrs['name'] + '.html', 'w')  #創(chuàng)建輸出到的HTML文件的名稱
            self.out.write('\n')
            #name="index" title="Home Page"
            #attrs['title']提取標(biāo)簽中屬性的key-value
            self.out.write('
                      
                        {}
                      
                      \n'.format(attrs['title']))
            self.out.write('\n')
        elif self.passthrough:  #如果標(biāo)簽下有嵌套的子標(biāo)簽
            self.out.write('<' + name)
            for key,val in attrs.items(): #獲取所有屬性
                self.out.write(' {}="{}"'.format(key, val))
            self.out.write('>')
    
    #標(biāo)簽的結(jié)束
    def endElement(self, name):
        if name=='page':
            self.passthrough = False
            self.out.write('\n\n')
            self.out.close()
        elif self.passthrough:
            self.out.write(''.format(name))
    
    #標(biāo)簽中的內(nèi)容比如:
                      

    123

    --- > 123 def characters(self, content): if self.passthrough:self.out.write(content) file_path='../../../file_data/website.xml' #解析 parse(file_path,PageMaker())

    解析完成之后在當(dāng)前目錄下:
    Python權(quán)威指南的10個(gè)項(xiàng)目(1~5)_第5張圖片
    出現(xiàn)這幾個(gè)文件,就是解析出來(lái)的HTML。
    不知道大家有沒(méi)有發(fā)現(xiàn)以上代碼的不足之處:
    ?? - 這里我們?cè)趕tartElement和endElement使用了if判斷語(yǔ)句,這里我們只處理了一個(gè)page標(biāo)簽,如果要處理的標(biāo)簽很多,那么這個(gè)if將很長(zhǎng)很長(zhǎng)
    ?? - HTML代碼時(shí)硬編碼
    ?? - 我們查看標(biāo)簽的時(shí)候由一個(gè)director標(biāo)簽,這里是將不同的page放入不同的目錄中,而以上的代碼最終生成的HTML都在同一個(gè)目錄下,這里我們?cè)俅螌?shí)現(xiàn)時(shí)將會(huì)改進(jìn)

(4) 最終版

??這里由于小編將代碼的各個(gè)功能進(jìn)行了解耦,分不同的功能模塊進(jìn)行開發(fā),這里小編將詳細(xì)介紹每個(gè)步驟具體實(shí)現(xiàn)什么功能,當(dāng)然最終的代碼小編也會(huì)上傳到github中供大家參考。
??鑒于SAX機(jī)制低級(jí)而簡(jiǎn)單,編寫一個(gè)混合類來(lái)處理管理性細(xì)節(jié)通常很有幫助。這些管理性細(xì) 節(jié)包括收集字符數(shù)據(jù),管理布爾狀態(tài)變量(如passthrough),將事件分派給自定義事件處理程序, 等等。就這個(gè)項(xiàng)目而言,狀態(tài)和數(shù)據(jù)處理非常簡(jiǎn)單,因此這里將專注于事件分派。
① 分派器混合類
??與其在標(biāo)準(zhǔn)通用事件處理程序(如startElement)中編寫長(zhǎng)長(zhǎng)的if語(yǔ)句,不如只編寫自定義 的具體事件處理程序(如startPage)并讓它們自動(dòng)被調(diào)用。你可在一個(gè)混合類中實(shí)現(xiàn)這種功能, 再通過(guò)繼承這個(gè)混合類和ContentHandler來(lái)創(chuàng)建一個(gè)子類。
程序?qū)崿F(xiàn)的功能:
?? - startElement被調(diào)用時(shí),如果參數(shù)name為'foo',它應(yīng)嘗試查找事件處理程序startFoo,并 使用提供給它的屬性調(diào)用這個(gè)處理程序
?? - 同樣,endElement被調(diào)用時(shí),如果參數(shù)name為'foo',它應(yīng)嘗試調(diào)用endFoo
?? - 如果沒(méi)有找到相應(yīng)的處理程序,這些方法應(yīng)調(diào)用方法defaultStart或defaultEnd。如果沒(méi) 有這些默認(rèn)處理程序,就什么都不做
簡(jiǎn)單案例:

            
              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() #將字符串的第一個(gè)字母變成大寫,其他字母變小寫
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)
            
          

②將首部和尾部寫入文件的方法以及默認(rèn)處理程序
??我們將編寫專門用于將首部和尾部寫入文件的方法,而不在事件處 理程序中直接調(diào)用self.out.write。這樣就可通過(guò)繼承來(lái)輕松地重寫這些方法。
簡(jiǎn)單案例:

            
              def writeHeader(self, title):
 self.out.write("\n \n 
              
                ")
 self.out.write(title)
 self.out.write("
              
              \n \n \n")
def writeFooter(self):
 self.out.write("\n \n\n")
            
          

③ 支持目錄
??為創(chuàng)建必要的目錄,需要使用函數(shù)os.makedirs,它在指定的路徑中創(chuàng)建必要的目錄。例如, os.makedirs('foo/bar/baz')在當(dāng)前目錄下創(chuàng)建目錄foo,再在目錄foo下創(chuàng)建目錄bar,然后在目 錄bar下創(chuàng)建目錄baz。如果目錄foo已經(jīng)存在,將只創(chuàng)建目錄bar和baz。同樣,如果目錄bar也已經(jīng) 存在,將只創(chuàng)建目錄baz。然而,如果目錄baz也已經(jīng)存在,通常將引發(fā)異常。為避免出現(xiàn)這種情 況,我們將關(guān)鍵字參數(shù)exist_ok設(shè)置為True。另一個(gè)很有用的函數(shù)是os.path.join,它使用正確 的分隔符(例如,在UNIX中為/)將多條路徑合而為一。
例:

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

④ 事件的處理
??這里需要4個(gè)事件處理程序,其中2個(gè)用于處理目錄,另外2個(gè)用于 處理頁(yè)面。目錄處理程序只使用了列表directory和方法ensureDirectory。頁(yè)面處理程序使用了方法writeHeader和writeFooter。另外,它們還設(shè)置了變量passthrough (以便將XHTML代碼直接寫入文件),而且打開和關(guān)閉與頁(yè)面相關(guān)的文件。

(5) 結(jié)果展示

Python權(quán)威指南的10個(gè)項(xiàng)目(1~5)_第6張圖片
通過(guò)解析website.xml,得到以上的目錄已經(jīng)html文件。具體的代碼在項(xiàng)目中,可以自行下載查看!

4. 項(xiàng)目4:新聞匯總

??本項(xiàng)目要編寫的程序是一個(gè)信息收集代理,能夠替你收集信息(具體地說(shuō)是新聞)并生成新聞 匯總。在這個(gè)項(xiàng)目中,需要做的并 僅僅使用urllib下載文件,還將使用另一個(gè)網(wǎng)絡(luò)庫(kù),即nntplib,它使用起來(lái)要難些。另外,還需重構(gòu)程序以支持不同的新聞源和目的地,進(jìn)而在中間層使用主引擎將前端和后端分開。
??最終項(xiàng)目實(shí)現(xiàn)的目標(biāo):
??- 可輕松地添加新聞源(乃至不同類型的新聞源) 能夠從眾多不同的新聞源收集新聞
??- 能夠以眾多不同的格式將生成的新聞匯編分發(fā)到眾多不同的目的地
??- 能夠輕松地添加新的目的地(乃至不同類型的目的地)

(1) 知識(shí)點(diǎn)擴(kuò)展

??NNTP是一種標(biāo)準(zhǔn)網(wǎng)絡(luò)協(xié)議,用于管理在Usenet討論組中發(fā)布的消息。NNTP服務(wù)器組成了一 個(gè)統(tǒng)一管理新聞組的全局網(wǎng)絡(luò),通過(guò)NNTP客戶端(也稱為新聞閱讀器)可發(fā)布和閱讀消息。NNTP 服務(wù)器組成的主網(wǎng)絡(luò)稱為Usenet,創(chuàng)建于1980年(但NNTP協(xié)議到1985年才開始使用)。相比于最 新的Web潮流,這算是一種很古老的技術(shù)了,但從某種程度上說(shuō),互聯(lián)網(wǎng)的很大一部分都基于這 樣的古老技術(shù)。

(2) 工作準(zhǔn)備

  • Nntplib類庫(kù)(from nntplib import NNTP)

(3) 初次實(shí)現(xiàn)

??最先開發(fā)出來(lái)一個(gè)簡(jiǎn)單的版本:是從NNTP服務(wù)器上的新聞組下載 最新的消息,使用print直接將結(jié)果打印到標(biāo)準(zhǔn)輸出。

            
              '''
一個(gè)簡(jiǎn)單的新聞收集代理
'''

from nntplib import NNTP
#服務(wù)器域名
servername='news.gmane.org'
#指定新聞組設(shè)置為當(dāng)前新聞組,并返回一些有關(guān)該新聞組的信息
group='gmane.comp.python.committers'
#創(chuàng)建server客戶端對(duì)象
server=NNTP(servername)
#指定要獲取多少篇文章
howmany=10
#返回的值為通用的服務(wù)器響應(yīng)、新聞組包含的消息數(shù)、第一條和最后一條消息的編號(hào)以及新聞組的名稱
resp, count, first, last, name = server.group(group)
start = last-howmany+1

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

#從overview中提取主題,并使用ID從服務(wù)器獲取消息正文
for id,over in overviews:
    subject=over['subject']
    resp,info=server.body(id)
    print(subject)
    print('-'*len(subject))
    for line in info.lines:
        #消息正文行是以字節(jié)的方式返回的,但為簡(jiǎn)單起見(jiàn),我們直接使用編碼Latin-1
        print(line.decode('latin1'))
    print()

#關(guān)閉連接
server.quit()
            
          

(4) 最終版

??這次我們將對(duì)代碼稍作重構(gòu)以修復(fù)這種問(wèn)題。你將把各部分代碼放在類和方法中,以提高程序的結(jié)構(gòu)化程 度和抽象程度,這樣就可用其他類替換有些部分。
??統(tǒng)計(jì)一下我們大概需要哪些類::信息、 代理、新聞、匯總、網(wǎng)絡(luò)、新聞源、目的地、前端、后端和主引擎。這個(gè)名詞清單表明,需要下 面這些主要的類:NewsAgent、NewsItem、Source和Destination。
??各種新聞源構(gòu)成了前端,目的地構(gòu)成了后端,而新聞代理位于中間層。這里我們對(duì)每個(gè)類進(jìn)行詳細(xì)的說(shuō)明:
① NewsItem
它只表示一段數(shù)據(jù),其中包括標(biāo)題和正文。

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

② NewsAgent
??準(zhǔn)確地確定要從新聞源和新聞目的地獲取什么,先來(lái)編寫代理本身是個(gè)不錯(cuò)的主意。代理 必須維護(hù)兩個(gè)列表:源列表和目的地列表。添加源和目的地的工作可通過(guò)方法addSource和 addDestination來(lái)完成。然后就是將新聞從源分發(fā)到目的地的方法。
③ Destination
?? - 生成的文本為HTML。
?? - 將文本寫入文件而不是標(biāo)準(zhǔn)輸出中。
?? - 除新聞列表外,還創(chuàng)建了一個(gè)目錄。
④ Source
?? - 代碼封裝在方法getItems中。原來(lái)的變量servername和group現(xiàn)在是構(gòu)造函數(shù)的參數(shù)。另 外,變量howmany也變成了構(gòu)造函數(shù)的參數(shù)。
?? - 調(diào)用了decode_header,它負(fù)責(zé)處理報(bào)頭字段(如subject)使用的特殊編碼。
?? - 不是直接打印每條新聞,而是生成NewsItem對(duì)象(讓getItems變成了生成器)。
?? 總的來(lái)說(shuō)就是:通過(guò)NewsItem將從網(wǎng)頁(yè)上獲取的新聞的內(nèi)容和標(biāo)題存放起來(lái),這里我們?cè)O(shè)置兩個(gè)數(shù)據(jù)源:一個(gè)是NNTP中獲取的新聞,一個(gè)是從urlopen從web網(wǎng)站中獲取的新聞,然后設(shè)置了兩個(gè)數(shù)據(jù)的目的地:一個(gè)是控制臺(tái)輸出,一個(gè)是寫入HTML文件中。通過(guò)NewsAgent對(duì)象,將數(shù)據(jù)源和目的地加入到列表中,然后在其distribute方法中,把從數(shù)據(jù)源獲取的數(shù)據(jù)發(fā)送給目的地。最后通過(guò)一個(gè)run方法,將這些步驟串聯(lián)起來(lái),這樣就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的從不同的渠道中獲取新聞,轉(zhuǎn)發(fā)的不同的渠道去。

5. 項(xiàng)目5:虛擬茶話會(huì)

?? 在這個(gè)項(xiàng)目中,將做些正式的網(wǎng)絡(luò)編程工作:編寫一個(gè)聊天服務(wù)器,讓人們能夠通過(guò) 網(wǎng)絡(luò)實(shí)時(shí)地聊天。只使用標(biāo)準(zhǔn)庫(kù)中的異步網(wǎng)絡(luò) 編程模塊(asyncore和asynchat)。

(1) 問(wèn)題描述

大概的項(xiàng)目需求如下:

  • 服務(wù)器必須能夠接受不同用戶的多個(gè)連接。
  • 它必須允許用戶并行地操作。
  • 它必須能夠解讀命令,如say或logout。
  • 它必須易于擴(kuò)展。
    其中的網(wǎng)絡(luò)連接和程序的異步特征需要使用特殊工具來(lái)實(shí)現(xiàn)。

    (2) 工作準(zhǔn)備

    ?? - 需要用到的新工具:標(biāo)準(zhǔn)庫(kù)模塊asyncore及其相關(guān)的模塊asynchat
    ?? - 框架asyncore讓你能夠處理多個(gè)同時(shí)連接的用戶
    ?? - 計(jì)算機(jī)的IP和port:本項(xiàng)目中使用本機(jī)的IP和5005端口

(3) 初步實(shí)現(xiàn)

??我們來(lái)將程序稍做分解。需要?jiǎng)?chuàng)建兩個(gè)主要的類:一個(gè)表示聊天服務(wù)器,另一個(gè)表示聊天會(huì) 話(連接的用戶)。
① ChatServer 類

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

from asyncore import dispatcher
import socket,asyncore

'''
一個(gè)能夠接受連接的服務(wù)器
'''

PORT=5005
NAME = 'TestChat'

'''
為創(chuàng)建簡(jiǎn)單的ChatServer類,可繼承模塊asyncore中的dispatcher類。dispatcher類基本上是
一個(gè)套接字對(duì)象,但還提供了一些事件處理功能。
'''
class ChatServer(dispatcher):
    '''
    一個(gè)接受連接并創(chuàng)建會(huì)話的類。它還負(fù)責(zé)向這些會(huì)話廣播
    '''
    def __init__(self,port):
        dispatcher.__init__(self)
        #調(diào)用了create_socket,并通過(guò)傳入兩個(gè)參數(shù)指定了要?jiǎng)?chuàng)建的套接字類型,通常都使用這里使用的類型
        self.create_socket(socket.AF_INET,socket.SOCK_STREAM)
        '''
            調(diào)用了set_reuse_addr,讓你能夠重用原來(lái)的地址(具體地說(shuō)是端口號(hào)),
            即便未妥善關(guān)閉服務(wù)器亦如此。不會(huì)出現(xiàn)端口被占用情況
        '''
        self.set_reuse_addr()
        '''
            bind的調(diào)用將服務(wù)器關(guān)聯(lián)到特定的地址(主機(jī)名和端口)。 
            空字符串表示:localhost,或者說(shuō)當(dāng)前機(jī)器的所有接口
        '''
        self.bind('',port)
        #listen的調(diào)用讓服務(wù)器監(jiān)聽連接;它還將在隊(duì)列中等待的最大連接數(shù)指定為5。
        self.listen(5)
    def handle_accept(self):
        '''
        重寫事件處理方法handle_accept,讓它在服務(wù)器接受客戶端連接時(shí)做些事情
        '''
        #調(diào)用self.accept,以允許客戶端連接。
        #返回一個(gè)連接(客戶端對(duì)應(yīng)的套接字)和一個(gè)地址(有關(guān)發(fā)起連接的機(jī)器的信息)。
        conn,addr=self.accept()
        #addr[0]是客戶端的IP地址
        print('Connection attempt from',addr[0])
if __name__=='__main__':
    s=ChatServer(PORT)
    try:
        #啟動(dòng)服務(wù)器的監(jiān)聽循環(huán)
        asyncore.loop()
    except KeyboardInterrupt:
        pass

            
          

② ChatSession 類
??這是一個(gè)新的版本,這里我們使用asynchat,我們?cè)O(shè)置一個(gè)會(huì)話,每一次有一個(gè)連接對(duì)象時(shí),就將這個(gè)連接對(duì)象加入會(huì)話中,好處是:每個(gè)連接都會(huì)創(chuàng)建一個(gè)新的dispatcher對(duì)象。

            
              '''
包含ChatSession類的服務(wù)器程序
'''

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)
        #設(shè)置結(jié)束符,
        self.set_terminator("\r\n")
        self.data=[]

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

    #讀取到結(jié)束符時(shí)將調(diào)用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存儲(chǔ)了一個(gè)會(huì)話列表
        self.sessions=[]
    #接受一個(gè)新請(qǐng)求,就會(huì)創(chuàng)建一個(gè)新的ChatSession對(duì)象,并將其附加到會(huì)話列表末尾
    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()

            
          

③ 整合
??要讓原型成為簡(jiǎn)單而功能完整的聊天服務(wù)器,還需添加一項(xiàng)主要功能:將用戶所說(shuō)的內(nèi)容(他 們輸入的每一行)廣播給其他用戶。要實(shí)現(xiàn)這種功能,可在服務(wù)器中使用一個(gè)簡(jiǎn)單的for循環(huán)來(lái) 遍歷會(huì)話列表,并將內(nèi)容行寫入每個(gè)會(huì)話。要將數(shù)據(jù)寫入async_chat對(duì)象,可使用方法push。
??這種廣播行為也帶來(lái)了一個(gè)問(wèn)題:客戶端斷開連接后,你必須確保將其從會(huì)話列表中刪除。 為此,可重寫事件處理方法handle_close。

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

PORT = 5005
NAME = 'TestChat'

class ChatSession(async_chat):
    """
    一個(gè)負(fù)責(zé)處理服務(wù)器和單個(gè)用戶間連接的類
    """
    def __init__(self,server,sock):
        #標(biāo)準(zhǔn)的設(shè)置任務(wù)
        async_chat.__init__(self,sock)
        self.server=server
        self.set_terminator("\r\n")
        self.data=[]
        #問(wèn)候用戶:
        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):
        """
       如果遇到結(jié)束符,就意味著讀取了一整行,
       因此將這行內(nèi)容廣播給每個(gè)人
        """
        line=''.join(self.data)
        self.data=[]
        self.server.broadcast(line)
    #客戶端斷開之后,將會(huì)話從列表中刪除
    def handle_close(self):
        async_chat.handle_close(self)
        self.server.disconnect(self)

class ChatServer(dispatcher):
    """
     一個(gè)接受連接并創(chuàng)建會(huì)話的類。它還負(fù)責(zé)向這些會(huì)話廣播
    """
    def __init__(self,port,name):
        dispatcher.__init__(self) #這一行一定要加
        self.name = name
        #標(biāo)準(zhǔn)的設(shè)置任務(wù):
        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) 最終版本

??第一個(gè)版本雖然是個(gè)管用的聊天服務(wù)器,但其功能很有限,最明顯的缺陷是沒(méi)法知道每句話 都是誰(shuí)說(shuō)的。另外,它也不能解釋命令(如say或logout),而最初的規(guī)范要求提供這樣的功能。 有鑒于此,需要添加對(duì)身份(每個(gè)用戶都有唯一的名字)和命令解釋的支持,同時(shí)必須讓每個(gè)會(huì) 話的行為都依賴于其所處的狀態(tài)(剛連接、已登錄等)。添加這些功能時(shí),必須確保程序是易于擴(kuò)展的。
① 基本命令解釋功能
??這里我們可以定義一些簡(jiǎn)單的命令,比如say、login 等等,即如果發(fā)送:say Hello, world!
將調(diào)用do_say('Hello, world!'),這個(gè)功能如何實(shí)現(xiàn)呢,這里寫一段偽代碼:

            
              #基本的命令解釋功能,例如:say Hello, world!
class CommandHandler:
    '''
        類似于標(biāo)準(zhǔn)庫(kù)中cmd.Cmd的簡(jiǎn)單命令處理程序
    '''
    #參數(shù)不正確
    def unknown(self,session,cmd):
        session.push('Unknown command: {}s\r\n'.format(cmd).encode())
    #根據(jù)命令,匹配方法,調(diào)用
    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())

            
          

② 聊天室
??每個(gè)聊天室都是一個(gè)包含特定命令的CommandHandler。另外,它還應(yīng) 記錄聊天室內(nèi)當(dāng)前有哪些用戶(會(huì)話)。除基本方法add和remove外,它還包含方法broadcast,這個(gè)方法對(duì)聊天室內(nèi)的所有用戶(會(huì) 話)調(diào)用push。這個(gè)類還以方法do_logout的方式定義了一個(gè)命令——logout。這個(gè)方法引發(fā)異常 EndSession,而這種異常將在較高的層級(jí)(found_terminator中)處理。
偽代碼:

            
              class EndSession(Exception):pass
class Room(CommandHandler):
    """
    可包含一個(gè)或多個(gè)用戶(會(huì)話)的通用環(huán)境。
    它負(fù)責(zé)基本的命令處理和廣播
    """
    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
            
          

③ 登錄和退出聊天室
??除表示常規(guī)聊天室(這個(gè)項(xiàng)目中只有一個(gè)這樣的聊天室)之外,Room的子類還可表示其他狀 態(tài),這正是你創(chuàng)建Room類的意圖所在。例如,用戶剛連接到服務(wù)器時(shí),將進(jìn)入專用的LoginRoom (其中沒(méi)有其他用戶)。LoginRoom在用戶進(jìn)入時(shí)打印一條歡迎消息(這是在方法add中實(shí)現(xiàn)的)。 它還重寫了方法unknown,使其讓用戶登錄。這個(gè)類只支持一個(gè)命令,即命令login,這個(gè)命令檢 查用戶名是否是可接受的(不是空字符串,且未被其他用戶使用)。
??LogoutRoom要簡(jiǎn)單得多,它唯一的職責(zé)是將用戶的名字從服務(wù)器中刪除(服務(wù)器包含存儲(chǔ)會(huì) 話的字典users)。如果用戶名不存在(因?yàn)橛脩魪奈吹卿洠瑢⒑雎砸虼硕l(fā)的KeyError異常。
④ 主聊天室
??主聊天室也重寫了方法add和remove。在方法add中,它廣播一條消息,指出有用戶進(jìn)入,同 時(shí)將用戶的名字添加到服務(wù)器中的字典users中。方法remove廣播一條消息,指出有用戶離開。
除了這些方法以外,主聊天室還實(shí)現(xiàn)了:
??- 命令say(由方法do_say實(shí)現(xiàn))廣播一行內(nèi)容,并在開頭指出這行內(nèi)容是哪位用戶說(shuō)的。
??- 命令look(由方法do_look實(shí)現(xiàn))告訴用戶聊天室內(nèi)當(dāng)前有哪些用戶。
??- 命令who(由方法do_who實(shí)現(xiàn))告訴用戶當(dāng)前有哪些用戶登錄了。在這個(gè)簡(jiǎn)單的服務(wù)器中, 命令look和who的作用相同,但如果你對(duì)其進(jìn)行擴(kuò)展,使其包含多個(gè)聊天室,這兩個(gè)命令 的作用將有所區(qū)別。
最終實(shí)現(xiàn)
??- ChatSession新增了方法enter,用于進(jìn)入新的聊天室。
??- ChatSession的構(gòu)造函數(shù)使用了LoginRoom。
??-方法handle_close使用了LogoutRoom。
??- ChatServer的構(gòu)造函數(shù)新增了字典屬性u(píng)sers和ChatRoom屬性main_room。

(5) 結(jié)果展示

??好吧,小編也是根據(jù)指南一步一步的將代碼實(shí)現(xiàn)了,但是不知道為啥就是跑不成功,然后就從網(wǎng)上搜了搜如何解決,雖然也查到了相關(guān)的案例,神奇的事情發(fā)生,我copy多個(gè)某某大神的代碼,居然運(yùn)行不了,而且報(bào)出同樣的錯(cuò)誤,本來(lái)想解決一下,造福大家,但是小編能力有限,實(shí)在不知道如何下手,這里小編把錯(cuò)誤展示出來(lái),有牛X的大神看見(jiàn)了幫小編分析解決一下唄!
Python權(quán)威指南的10個(gè)項(xiàng)目(1~5)
??但是 但是,雖然程序沒(méi)運(yùn)行出來(lái),但是至少學(xué)到了一些東西,總不能只知道代碼錯(cuò)了,不知道代碼就行實(shí)現(xiàn)了啥,對(duì)不對(duì),那不是欺騙了各位讀友嘛,所以小編這里把上面代碼的整個(gè)實(shí)現(xiàn)過(guò)程畫了一個(gè)圖分享給大家:
Python權(quán)威指南的10個(gè)項(xiàng)目(1~5)_第7張圖片

這個(gè)是Python權(quán)威指南的前5個(gè)項(xiàng)目,雖然后面了沒(méi)有實(shí)現(xiàn)效果圖,但是代碼和解釋是相當(dāng)充分的,后續(xù)的5個(gè)項(xiàng)目均有呈現(xiàn)的效果和完整的代碼,大家放心小編在寫代碼時(shí)也踩了不少的坑,有些問(wèn)題小編會(huì)以小案例的形式在測(cè)試代碼中體現(xiàn):

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