Python应用开发实战

978-7-115-47757-6
作者: [美] 尼纳德·萨斯叶(Ninad Sathaye)
译者: 包永帅齐兆慧
编辑: 陈冀康
分类: Python

图书目录:

详情

本书全面介绍了Python语言的方方面面,不仅为读者介绍了Python语言的基础概念也融入了Python的其他特性的介绍。本书面向任何想要学习Python语言,并使用Python进行应用开发的读者。通过阅读本书,读者将掌握如何构建健壮的应用程序、如何包装和发布软件、如何更好地测试并维护自己的代码及文档等内容。

图书摘要

版权信息

书名:Python应用开发实战

ISBN:978-7-115-47757-6

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

著    [美] 尼纳德•萨斯叶(Ninad Sathaye)

译    包永帅 齐兆慧

责任编辑 陈冀康

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315


Copyright ©2016 Packt Publishing. First published in the English language under the title Learning Python Application Development.

All rights reserved.

本书由英国Packt Publishing公司授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。

版权所有,侵权必究。


Python是使用最多的动态编程语言之一,应用广泛,学习者众多。

本书使用一个生动有趣的、基于文本的游戏主题作为讲解内容来介绍Python应用开发过程的各个方面。全书共10章,涉及简单应用开发、模块化、应用代码的打包和发布、文档规范、单元测试、重构、设计模式、性能监测、性能优化、GUI应用、MVC框架等方方面面的软件开发知识和技能。

本书适合有一定Python语言基础,想要快速掌握Python项目开发知识、提高Python开发实战技能的读者学习参考。


在Ninad Sathaye的职业生涯中,他有多年使用包括Python和C++在内的多种语言设计和开发性能关键应用的经验。他曾是半导体行业中的一个软件架构师,最近又转入物联网领域。他拥有机械工程硕士学位。

我要感谢我的妻子Arati,她为本书带来游戏主题的创意。如果没有她持续的支持,就不可能完成本书。我也要衷心地感谢Will Ware对本书的技术审校,他极富价值的反馈切实帮助我将本书提高到更高的层次。感谢Deepti、Kunal、Zainab以及Packt出版社整个团队的辛勤工作和支持。我要特别感谢Packt出版社的Abhinash Sahu为本书中所有的人物创造出超棒的形象。衷心感谢Steve Furkay、Neeshma和 Kevin在本书构思初期提供很有价值的反馈意见。最后,我要感谢我的家人,感谢他们的鼓励和支持!


Will Ware是波士顿地区的软件工程师。他曾从事嵌入式系统、手机和Web的开发。他获得了麻省理工学院的电气工程学位和数学学位。他的兴趣包括科学、技术、工程、数学教育和3D打印。


Python是使用最广泛的动态编程语言之一。它支持一组丰富的库和框架,从而能够用来进行快速开发。但是,如此快速的开发通常会带来一些问题,容易导致代码的整体质量、性能和扩展性的降低。本书将会教你如何构建和部署有趣的应用,从而帮助你提高应用Python的技术水平。

从简单的程序入手,本书将教会你设计并开发出健壮而且高效的应用。对一些非常重要的主题讲解,本书将以一种容易理解且十分有趣的方式呈现出来。

本书使用一个奇幻主题作为工具来解释各种不同的概念。在本书中,你将会遇到许多虚幻的游戏人物。当你学习不同的内容时,这些想象的人物会与你对话,问问题,并提出新的功能需求。

每一章都关注应用开发的一个不同方面。最开始的几章关注软件的健壮性、应用代码的打包和发布。接下来的几章是关于通过增加代码的扩展性、重用性和可读性来提高应用的生命周期。你将学习到关于代码重构、单元测试、设计模式、文档规范和最佳实践的知识。

在着重讨论性能的3章内容中,将介绍识别瓶颈和提高性能的技术。最后1章将介绍图形用户界面(Graphical User Interface,GUI)开发。

在这本书中你所看到的一些代码段实际上是图片文件。

这些图片的渲染效果将取决于你的电子书阅读器的页面显示分辨率和缩放比例。

如果你很难清楚地阅读这些代码的话,可以尝试对你的PDF和电子书阅读器做以下设置:

如果问题仍然存在,你可以尝试一个不同的分辨率。

如何设置分辨率呢?这将取决于你的电子书阅读器。例如,如果你使用的是Adobe Reader,依次单击“编辑”—“偏好”,然后从左边的控制板中选择“页面显示”选项,你会发现在右面的控制板中有“分辨率”选项。尝试选择“96像素/英寸”或者比较接近这个值的分辨率,看看是否有助于更好地渲染图像。

第1章“开发简单应用”,从安装的先决条件和本书的主题开始。第一个程序是一个用脚本写的基于文本的奇幻游戏,然后会使用函数来开发一个加入新功能的增强版本。随着功能的不断增加,代码管理变得越来越困难。为了解决这个问题,我们将采用面向对象编程的概念重新设计该游戏。从这里开始,这个应用将成为接下来几章的参照代码。

第2章“异常处理”,本章将教会你如何修正上一章所写代码中存在的明显问题。你将学会如何添加异常处理代码,从而使应用程序更具健壮性。你还要学习try... except...finally语句、异常的抛出和再抛出、创建和使用自定义异常类,等等。

第3章“模块化、打包和部署”,本章将教你如何对前面的章节中编写的代码进行模块化和封装。在打包完成之后,接着会告诉你如何部署代码,如何进行增量发布,如何建立一个私有的Python包仓库,以及如何将代码接入版本控制工具中。

第4章“代码文档和最佳实践”,深入了解编码标准,这些标准是开发代码过程中需要遵守的一系列准则。遵守这些标准可以对代码的可读性和生命周期产生重大影响。在本章中,你将了解软件开发中另一个重要的方面,就是代码文档和最佳实践。这部分从reStructuredText格式开始,并使用它来书写文档字符串。你可以使用Sphinx文档生成器为代码生成HTML格式的说明文档。本章还将讨论一些编写Python代码的重要编码标准,以及使用PyLint检查代码质量。

第5章“单元测试和重构”,从对Python中的单元测试框架进行介绍开始。你将为到目前为止所开发的游戏应用程序编写一些单元测试。它涵盖了许多其他的主题,例如在单元测试中使用Mock库,以及使用代码覆盖率来衡量单元测试的有效性。本章的后面部分讨论了许多代码重构的技术。从下一章开始,我们将不再需要使用前面章节中开发的代码。后面的章节中都将有各自的与高奇幻主题紧密相关的简化的示例代码。

第6章“设计模式”,告诉你在开发过程中会不断遇到重复性问题。很多时候,专门针对这个问题有一个通用的解决方案或者处理方法的,这通常称为设计模式。本章介绍了一些常用的设计模式。其中包含了策略模式、简单的工厂模式、抽象工厂模式,以及适配器模式。对于每一种模式,将用一个简单的游戏场景展示一个实际问题。你将学习如何借助设计模式解决这个问题。每一种设计模式都将使用Python的方法实现。

第7章“性能——识别瓶颈”,这是性能提升系列3章内容中的第1章。你会编写一个简单的叫作Gold Hunt的程序,起初它看起来是没有问题的,但是当你调整参数时却会遇到很大的麻烦,即参数调整引发的性能问题。在本章中,你将学会识别代码中的比较耗时的模块。其中包含了一些基本的记录应用运行时间的方法,分析代码来识别性能瓶颈,了解内存分析的基本方法,并利用大写O作为符号来表示计算复杂度。

第8章“性能优化1”,教你如何解决在上一章中确定的一些性能瓶颈。另外,你还将学习到一些用来提高应用程序性能的技术,例如算法变化、列表理解、生成器表达式、选择正确的数据结构,等等。

第9章“性能优化2——NumPy和并行化”,这是关于提高性能的最后一个章节,通过本章你将会极大地提高Gold Hunt程序的性能。本章将介绍NumPy包。它还将介绍如何使用Python来进行并行处理。

第10章“简单的图形应用程序”,这是本书的最后一章,将会介绍简单的GUI应用程序的开发。之前的章节中介绍了使用命令行进行应用程序开发的几个关键方面。然而,在本章中你将学到Tkinter模块、MVC架构,并开发与第1章中的应用相对应的GUI版本程序。

本书中的示例代码兼容Python 3.5版本,支持代码包也提供兼容Python 2.7.9的文件。然而,在本书中,Python 3.5是默认的版本。请查看1.2节来获取需要安装的基础软件的详细内容。此外,还需要安装一些Python包的依赖关系。大部分的包可以使用pip(Python软件包管理器)进行安装。这些依赖软件在使用到它们的章节中会有所提及。

你已经了解了Python语言的基础和面向对象编程吗?

你想更进一步钻研、学习技术来让你的Python应用更具健壮性、可扩展性并更加高效吗?

如果以上问题你得到的是肯定的答案,那么你就是本书的目标读者。本书适用于那些有不同语言(例如C++或者Java)背景,同时想要掌握Python程序开发的读者。

如果下面的描述都不适用于你的话,那么这本书可能不适合你:

在本书中,你会发现为了区分不同类型的信息使用了一些不同的字体。下面是一些不同字体的例子以及它们相应的解释说明。

文本形式的代码、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟URL、用户输入、Twitter用户定位等信息显示为:“GoldHunt.find_coins方法有一些变动。”

代码块则设置为下面的形式:

results = pool.starmap_async(self.find_coins,
                               zip(itertools.repeat(x_list),
                                   itertools.repeat(y_list),
                                   x_centers,

任何的命令行输入和输出显示成如下格式:

export PATH=$PATH:/usr/bin/

新术语和重要词语会用粗体进行标识。例如你在屏幕上看到的菜单或对话框,在文本中出现时候显示成这样:“正如前面提到的,在安装的时候,你需要选择Add Python 3.5 to PATH。

 

警告和重要的提示以这种形式显示在框中。

 

 

小贴士和小窍门以这种形式呈现。


本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。

本书提供如下资源:

要获得以上配套资源,请在异步社区本书页面中点击,跳转到下载界面,按提示进行操作即可。注意:为保证购书读者的权益,该操作会给出相关提示,要求输入提取码进行验证。

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,点击“提交勘误”,输入勘误信息,单击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线提交投稿(直接访问www.epubit.com/selfpublish/submission即可)。

如果您是学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。

“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com

“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社近30年的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。

异步社区

微信服务号


Python 是使用最广泛的动态编程语言之一。它支持一组丰富的包、图形用户界面(Graphical User Interface,GUI)库和Web框架,让你能够构建出高效的跨平台应用。它是一种理想的快速应用开发语言。如此快速的开发通常会带来一些问题,容易导致代码的整体质量、性能和扩展性的降低。本书将会告诉你处理此类情况的方法,并帮助你开发出更好的Python应用。核心概念的解释将借助于命令行应用,这些内容会在后面的章节中逐步展开。

本章是全书的导言部分,这是一个对Python编程的回顾。正因如此,希望你最好已掌握一些关于Python语言的知识,同时也了解面向对象编程(Object Oriented Programming,OOP)的概念。

下面是本章内容的组织结构:

代码解释会稍显冗余。有经验的读者可以跳过这些例子直接进入下一章,但要确保你已理解本书的主题,并复习一下ch01_ex03.py文件中的代码。在接下来的章节中,你将通过学到的技术来逐步完善该代码。

在阅读本章余下的部分之前,让我们先来了解一些事项。如果你尚未了解,你应该阅读前言,其中记录了以下大多数事项:

 

通常,使用Jupyter Notebook来书写和分享交互程序是非常方便的。它是一个Web应用程序,可以在交互环境下编写带有富文本、图片、函数等内容的Python代码。欲获取更多详情,请访问该项目的主页。Jupyter Notebook可以通过以下方式进行安装:

要确保已经安装了必要的软件。表1-1总结了本章和后续需要的一些基本工具。下一节中会有更详尽的安装说明。

表1-1 安装软件

$ pip install "ipython[notebook]"

工具

说明

Python 3.5

本书中的示例代码兼容Python 3.5.1版本。详见表1-2 可用的Python发行版本。支持代码包也提供兼容Python 2.7.9的文件

pip(Python包管理器)

官方版本的Python 3.5和2.7.9版本中默认包含pip

IPython

选择性安装。IPython是一个增强的Python解释器

集成开发环境(Integrated Development Environment,IDE)

使用Python编辑器或者任何你选择的IDE。本章后面的表格中会列出一些优秀的IDE

在接下来的章节中,我们需要安装一些其他的依赖软件。Python包管理器(pip)将会完成这项琐碎的工作。

 

你已经安装了Python的必要环境或者你已经知道如何安装了吗?如果是,那么请你跳过接下来的安装说明,直接进入本书的主题章节,开始真正的实践内容。

安装Python有两种方法。你可以使用官方发行版本的Python或者任何一种可免费获取的捆绑发行版本。

1.方法1——官方发行版本

对于Linux或Mac用户来说,你的系统中可能已经安装了Python。如果没有,你可以使用系统中的包管理器进行安装。Windows用户可以通过Python官方网站下载Install Python来安装Python 3.5,如图1-1所示。

图1-1

如上面的安装截屏所示,在安装过程中,请确保选中“Add Python 3.5 to PATH”的选项。你也可以访问Python的官方网站来下载特定平台的发行版本。

2.方法2——捆绑发行版本

有很多Python免费发行版本,这些版本捆绑了很多实用的包,包括pip和IPython。表1-2总结了一些最流行的Python发行版本,其中包括官方发行版本。

表1-2 Python发行版本

发行版本 支持平台 说明
Python官方发行版本 Windows、Linux、Mac • 免费获取
• 2.7.9版本和3.5版本默认包含pip
Anaconda Windows、Linux、Mac • 免费获取
• 包含pip、IPython和Spyder集成开发环境
• 捆绑包
• 主要关于科学、数学、工程、数据分析
Enthought Canopy Express Windows、Linux、Mac • 免费获取
• 包括pip和IPython
• 集成了Python代码编辑器和应用开发平台
Python (x, y) Windows • 免费获取
• 包括pip、IPython和Spyder集成开发环境

3.Python安装位置

让我们简单说说Python的安装位置,以及如何确保在你的终端窗口中python是一个可用的命令。当然,事情可能会因情况不同而各异,这取决于你的安装位置和你选择的Python发行版本。

 

Python的官方文档页面有丰富的关于在不同平台上安装Python的信息。一旦你有超出本书所涵盖内容的需求时,可以参考官方网站给你提供进一步的帮助。

类UNIX操作系统

在类UNIX操作系统如Linux中,典型的默认安装路径通常是/usr/bin/python 或者/usr/local/bin/python。

如果你使用系统中自带的包管理器安装Python,那么在终端窗口中是可以使用命令Python或者Python 3的。如果命令不可用,你需要更新系统环境变量PATH,使其包含Python的可执行文件路径。例如,你的系统中有Bash shell,在用户目录下的.bashrc文件中增加如下配置:

export PATH=$PATH:/usr/bin/

用你的Python实际安装路径来替换/usr/bin。

Windows操作系统

在Windows操作系统中,典型的Python默认安装路径如下: C:\Users\name\AppData\ Local\Programs\Python\Python35-32\ python.exe。用你的Windows用户名替换name。根据安装文件和操作系统的不同,Python的安装路径也可能是Python 35-64。正如前面提到的,在安装的时候,你需要选择“Add Python 3.5 to PATH”,以保证Python或python.exe自动被识别为命令。另外,你也可以重新运行安装文件并选中该选项。

4.验证Python安装

打开终端窗口(或者是Windows操作系统中的命令提示符)并输入下面的命令来验证Python的版本。只有安装了Python并且该命令在终端窗口中是可用命令时,那么该命令才会生效。否则,需要指定Python可执行文件的全路径。例如,在Linux上,如果Python安装在/usr/bin下,你可以这样指定/usr/bin/python:

$ python -V

 

注意,上面的命令行中的$符号属于终端窗口,它并不是命令本身的一部分。也就是说,实际的命令只是python -V。在Linux的终端窗口中,对普通用户来说$或%是一个提示符。对root用户(管理员)来说,提示符是#。类似的,在Windows系统中,对应的符号是>。你要在这个符号的后面输入实际的命令。

如果我们运行上述命令,下面是一个可能的输出示例:

[user@hostname ~]$ python -V
Python 3.5.1 :: Anaconda 4.0.0 (64-bit)

5.安装pip

pip是一个软件包管理器,它使得从官方的第三方软件库PyPI中安装Python包变得十分简单。pip在Python 2的2.7.9以上版本,以及Python 3的3.4以上版本中已经安装好了。如果你使用其他版本的Python,可以在https://pip.pypa.io/en/stable/installing中找到安装说明。

在Linux操作系统下,pip和Python可执行文件的默认位置是相同的。例如,如果Python安装在/usr/bin/python,那么pip的路径应该是/usr/bin/pip。在Windows操作系统下,pip.exe通常默认安装在如下位置:C:\Users\name\AppData\Local\Programs\Python\Python35-32\ Scripts\pip.exe。正如前文提到,用你的Windows用户名替换路径中的name。根据安装文件和操作系统的不同,Python的安装路径也有可能是Python 35-64。

6.安装IPython

这是一个可选的安装。IPython是一个增强版的Python解释器。如果你使用的发行版本没有捆绑它,那么你可以使用以下命令进行安装:

$ pip install ipython

安装完成后,在终端中输入ipython就可以启动IPython的交互shell。Anaconda Python 3.5发行版的IPython shell截图如下。

7.选择集成开发环境

选择IDE进行开发是个人的喜好问题。简单来说,IDE是一个旨在加快应用程序开发的工具。它通过对开发人员最常用的工具的集成,从而让他们能够来快速编写出高效的代码。Python安装时带有一个叫作IDLE的程序。这是一个基础的Python IDE,可供你入门时使用。对于高级的开发,你可以从众多免费的或商业的工具中选择一个。任何一款优秀的Python IDE至少都具有以下几个特点:

你可以尝试从一款免费的IDE开始。下面列出部分流行的IDE。如果你只对简单的源代码编辑器感兴趣,可以浏览https://wiki.python.org/moin/PythonEditors,表1-3提供了一些可供选择的编辑器。

表1-3 可选的IDE

Python IDE

说明

社区版的PyCharm

有免费的社区版本。非常好的开始学习Python的工具

Wing IDE 101

只对非商业用途免费。商业版的有更多的特性。又是一款优秀的Python IDE

Spyder

免费获取,开源;也可以通过Python的捆绑发行版本获取,例如Python(x,y)和Anaconda

Eclipse PyDev

免费获取,开源

Sublime Text 2或Sublime Text 3(beta版)

只对评估目的免费。高度可配置的IDE

你读过诸如《指环王》或托尔金的《霍比特人》等奇幻小说吗?或者说看过根据这些小说改编的电影吗?那么,好,这是一本高奇幻的、托尔金式的关于Python应用开发的书籍。

 

更多关于J.R.R.托尔金的作品,请访问相关网站。高奇幻一词通常被用来代表交替虚幻世界中的一些列奇幻主题。

这本书带你来到一个虚构的世界,在那里你将开发一个基于上述主题的文本游戏。是的,即使在这个想象的世界里,你仍然可以继续成为一个开发人员!在阅读本书的过程中,将会有很多许多虚构的人物陪伴着你。当你学习Python开发的不同方面时,这些人物会跟你对话,问问题,提出新的功能,甚至与敌人打架。

应该指出的是,本书不是关于游戏应用程序开发的。本书只是使用一个简单的基于文本的游戏作为一种媒介,来学习Python开发的各个方面。

 

说一句题外话,如果你有兴趣玩一个高奇幻主题的游戏,那么你有相当多的选择。在诸多开源的项目中,《韦诺之战》是评价最高的、免费的、高奇幻主题的回合制策略游戏。请访问该游戏官网查看更多详情。

让我们来认识一下本书各章节中那些会一直陪伴你的想象中的人物,参见表1-4。

表1-4 人物说明

Foo先生
Foo先生是一个人类骑士,一个被描绘成守护着南部平原的大骑士。他是本书中的主要人物,并会在本书中一直与我们进行交谈
兽人骑士
兽人是一个类似人类的想象中的动物。在这里,他被描绘成一个敌方的士兵。兽人的坐骑是一种类似于野猪的动物。在本章中,你就将遇到这个生物
精灵骑士
精灵是一种超自然的神灵。精灵通常骑在一匹小马上。他被描绘成一个友好的伙伴。你会在第6章中遇到精灵先生
仙子
仙子是拥有与生俱来的魔力而且十分聪明的仙女。她的魔法只在第7章(详见O(log n))中使用过一次,用来寻找她的魔法盒。你会在第6章中第一次遇到仙子
侏儒
侏儒是一种矮小的、类似人类的神话中的存在。他被描绘成为Foo山的“大侏儒”。他问了很多问题。从本书第6章开始的后半部分里,你会看到侏儒的身影

有了这个有趣的主题作为一个工具,让我们通过简单的命令行应用程序开始我们的Python之旅。这将是一个基于文本的游戏。在接下来几章中会增加一些复杂度,随之而来的一些有趣的问题会不断给你带来挑战。这本书将向你展示如何优雅地处理这种情况。

我们已经安装了必要的工具和开发环境,现在是时候来编写我们的第一个Python程序了。这是一个简单的靠碰运气取胜的游戏,同时也是一个命令行游戏。随着本书内容的不断推进,我们会给游戏增加更多的复杂度,通过学习到的新技术来开发更加高效的程序。所以,准备行动吧!

在人类和他们的敌人之间的战争中,兽人就是第一个到来的敌人。一支巨大的兽人军队正在向人类的聚集地进发。他们几乎摧毁了行进道路上的一切。人类不同种族的首领们联起手来击败他们最强大的敌人,来共同为他们的伟大时代而战斗。人们都被召集起来参加了军队。Foo先生,一个保卫南部平原的勇敢骑士,穿过一片未知的茂密森林,开始长途跋涉,向东部进发。两天两夜,他小心翼翼地穿过茂密的树林。在路上,他发现了一个小的孤立定居点。因为疲劳的原因,再加上要希望补充粮食储备,他决定绕道而行。当他走进村庄时,他看见了五个木屋,如图1-2所示。周围没有发现任何人。犹豫之后,他决定走进其中一间木屋……

图1-2

你正在设计一个简单的游戏,游戏中玩家需要为Foo先生选择一个木屋。这些木屋是随机被同伴或敌人占据的。同时,有些木屋仍有可能是空闲的。如果选择的是一个被敌人所占据的木屋,那么玩家就输掉比赛。如果是另外的两种场景,那么玩家就赢得比赛。

现在游戏的目标已经明确了,请打开你最喜欢的编辑器,并记录下主要的步骤。通常这被称作伪代码。

当用户希望一直玩这个游戏时:

正如你所注意到的,代码的关键部分就是让同伴或者敌人随机占据木屋,并且保持剩下的木屋是未被占据的空闲状态。我们要如何实现呢?让我们用Python解释器来快速地解决这个问题。如果你安装了IPython,启动IPython解释器;否则,就通过在终端窗口中输入python命令来使用默认的Python解释器。首先,我们需要一个Python list来保存所有木屋占有者的类型。接下来,我们将使用内置的random模块并调用random.choice来从列表中随机取得一个元素。代码如下面的屏幕截图所示:

现在,我们只需要编写其他的条件代码即可。接下来,让我们来查看一下代码。

 

对于Python 2.7.9来说,在上面的第一个示例代码中唯一需要修改的是将调用内置函数input的地方替换成raw_input:

从为本章提供的补充代码包中下载源代码文件ch01_ex01.py。文件扩展名.py表明它是一个Python文件。在Python编辑器或你选择的一款IDE中打开它。建议在阅读以下讨论时,请保持随时能查看该文件。通常能够浏览完整的代码会让你更容易且更好地理解它。观察下面的代码片段,它只是上文提到的文件中的if __name__ == '__main__'条件代码块中的一小部分。

 

如果你安装的是Python 2.7.9的话,支持代码包中有一个单独的Python 2.7.9兼容源代码文件。

# For Python 2.7
user_choice = raw_input(msg)

让我们来看一下上面截图的代码片段。

接下来的几行代码主要是在控制台中打印关于游戏的进一步信息:

让我们看一看上面截图中的代码:

现在,让我们来看看下面的while循环吧。

接下来,通过打印明细来表明占有者的信息。最后,根据与小木屋编号对应的列表项来确定赢家。需要注意的是,huts列表的索引是从0开始的。因此,根据选定的小木屋编号idx来取得相应的列表项时,我们需要使用idx-1作为列表的索引。

假设你的系统环境变量PATH中已经包含了Python(可能是Python或者Python 3)的话,那么可以通过以下方式在命令行运行该程序:

$ python ch01_ex01.py

仅此而已!请玩玩这个游戏,并尝试选择正确的木屋来解救Foo先生吧!下面的Linux终端窗口的快照显示了程序的运行情况:

在上节中,你写了一个快速的指令集并创建了一个非常好的命令行游戏。你可以让朋友们试试看,他们很有可能会喜欢这个游戏(也许他们只是想表现得很友好!)。于是你收到了该游戏的第一个功能需求,如图1-3所示。

“我认为这个游戏还有很大的改进空间。在游戏的下一个版本中,包含战斗功能如何?当Foo先生遇到敌人的时候,他不应该如此轻易地放弃。他要与敌人进行战斗!让战斗来决定胜负。”(你的朋友)

图1-3

你喜欢这个想法,并决定在下一个版本的代码中加入此项功能。此外,你也希望程序更加具有交互性。

你为第一个程序写的脚本还很小。然而,随着我们不断地增加新功能,代码维护会迅速成为一件令你头疼的事情。接下来,我们会将现有的代码包装到小的函数中,这样会使代码更容易管理。在函数式编程中,关注的焦点通常是在函数的排列和组合上面。例如,你可以通过重复使用一组简单的函数来构建复杂的逻辑。

在增加任何新功能之前,让我们回顾一下你写的之前版本的脚本(版本0.0.1)。我们将识别出可以包装在函数中的代码块。下面的两个代码片段中标记出来了这样的代码块:

我们将大部分被标记出来的代码包装到独立的函数中,如下所示:

1: show_theme_message
2: show_game_mission
3: occupy_huts
4: process_user_choice
5: reveal_occupants
6: enter_hut

除了这6个代码块之外,我们还可以创建一个顶层的包含所有这些逻辑的函数。在Python中,使用关键字def来定义函数,关键字后面紧跟着函数名,括号中是参数。例如, 函数reveal_occupants需要木屋列表的信息。如果不想在函数中重新创建dotted_line字符串,我们也可以选择将其作为传递的参数。因此,我们需要传递木屋的编号idx、木屋列表huts和字符串dotted_line作为函数的参数。这个函数可以写成如下的样子:

经过了一些初步处理,最原始的脚本会被重写成下面这样:

现在的代码阅读起来就更容易了。我们刚才所做的也被称为“重构”;后面几章中将会介绍更多关于重构的技术。这使得对个别方法的修改变得更加容易。例如,如果你想自定义任务说明或者场景描述,你不需要打开主程序run_application。类似的,可以对occupy_huts做进一步的扩展而不涉及对main代码的任何改动。

 

代码的初始重构版本并不是完美的,仍有很大的改进空间。你可以减少传递参数dotted_line的负担,或者可以想出其他方法来处理粗体文本的打印吗?

在上节中,我们将游戏的逻辑包装到单个的函数中。这不仅提高了代码的可读性,也使它更加容易维护。让我们继续并在游戏中增加新的attack()功能。下面的步骤显示了包含攻击功能的游戏的逻辑。

当用户希望一直玩这个游戏时:

最初,Foo先生和兽人都是完全健康的。为了量化健康,我们给这些角色都分配一些生命力(或者游戏点)。因此,当我们说这个角色是完全健康的,这就意味着它有全部的生命力。根据角色的不同,默认的生命力点数将会有所不同。图1-4显示了Foo先生和兽人默认的生命力点数,用健康标签表示。

图1-4

健康值上面的生命条代表一个健康表。本质上讲,它记录着角色的生命力。在随后的讨论中,我们将生命力和健康表作为同等的术语来使用。在战斗过程中,无论是玩家或者敌人都有可能受伤。此时,我们忽略掉两者都不受到伤害的第三种可能性。伤害会让受伤方的可用生命力的点数下降。在游戏中,我们假设在每个回合的攻击中只有一个角色会被击中。图1-5将会帮助你理解这样的攻击回合。

图1-5

这里,Foo先生的健康表示为满格,而兽人已经受伤了,如图1-6所示。

图1-6

嗯,兽人认为他是可以击败Foo先生的!这很有趣。让我们先来开发这个游戏,然后再看看谁会有更大的获胜机会!

有了以上对问题的理解,我们来看看实现这些功能的代码。

从本章的代码包中下载源代码文件ch01_ex02.py,然后浏览一下整个代码。最关键的逻辑都在attack()方法里了。我们还需要用一个数据结构来存储Foo先生和敌人的健康记录。让我们从介绍下面的处理一些打印职责的功能函数开始讲起:

现在请看看主函数run_application,以及功能函数reset_health_meter。除了引入字典health_meter,我们也将游戏的逻辑封装在函数play_game里了。

在新游戏开始之后,字典health_meter中的值可以通过调用函数reset_health_ meter来恢复初始设置:

接下来,让我们看一下play_game函数。如果木屋中有敌人,玩家将会被询问是否继续进行攻击(while循环的开始)。基于用户的输入,代码会调用attack函数或者是退出当前游戏:

通过接受用户的输入,可以使用交互的while循环对敌人发起接连不断的攻击。执行攻击函数可能会导致对Foo先生或者敌人的伤害,也可能同时对两者产生伤害,但也有可能对两者都没有造成伤害。为了简单起见,我们只考虑其中的两种可能性:每次进攻只会对敌人或者Foo先生中的一方造成伤害。在上节中,我们使用了内置的随机数生成器来随机地决定木屋的占有者,我们可以使用同样的方法来决定谁在战斗中受伤:

injured_unit = random.choice(['player', 'enemy'])

但是请稍等片刻。Foo先生有话要说:“我们应该考虑对玩家和敌人造成伤害的可能性大小。在接下来所示的攻击函数中,我们假设敌人有60%的概率会被击中,而Foo先生大概有40%的概率会成为敌人的攻击目标”,如图1-7所示。

图1-7

最简单的方法是创建一个包含10个元素的列表。列表中应该有6个“敌人”的入口和4个“玩家”的入口。然后,让我们用random.choice来随机从列表中选取一个元素。你可以给游戏引入一个难易程度并修改两者的概率比例:

一旦随机选取了injured_unit,伤害值就会从10到15之间随机取得,包括10和15在内。这里,我们使用了函数random.randint。最后也是最重要的一步是更新受伤方的health_meter字典,从他的生命力中减去相应的点值。

我们已经讨论了这个游戏中最重要的功能,现在我们从下载文件中看一下其他的支持函数。以下的截屏显示了程序的运行情况:

你在前面游戏中增加的攻击功能让它变得更加有趣味性。你可以看到一些朋友一次又一次地来玩这个游戏。新的功能需求又源源不断地涌来。

以下是部分功能需求的列表:

这是一个非常长的列表。你正在准备一项计划。为了实现上述的一些功能,下面的列表罗列出你需要向现存代码中增加的内容。

这已经是一个相当长的列表。虽然你仍然可以继续使用函数式编程方法,但在这种情况下,随着游戏的不断演进以及新功能的不断加入,这会变得非常困难。

值得庆幸的是,面向对象编程可以来拯救你。将Foo先生当作是Knight类的一个实例怎么样?这样的话,应当很容易对于Foo先生相关的参数进行管理。例如,可以用一个属性生命点来代替之前例子中的health_meter字典,用其来表示Foo先生的健康状况。类似地,可以用类中的其他属性来存储占领木屋时获得的黄金或者武器的数量(另外一个功能需求)。

还有比上述记录更多的功能需求。类中的各个方法应该是行为的具体实现,例如攻击、逃跑、治疗等。Foo先生的骑士同伴们也可以是Knight类的实例;或者你也可以为他们创建一个HorseRider类,该类可以接受来自Foo先生的命令。

对于这个新版本,让我们从前面的列表中选择几个功能需求。事实上,这应该是由Foo先生来决定的事情,如图1-8所示。

图1-8

Foo先生,正如你所期望的……我们只想在这个版本中增加治疗的功能。

现在是时候要明确地定义这个版本的目标了。你不仅仅要给你的应用增加新的功能,而且要对你的代码做一些基本的改变以适应将来更多的需求。

在这个版本中,任务是要占据全部的5个木屋。这里,你要实现一个可以恢复Foo先生全部攻击点的全新的治疗功能。你也需要实现一些策略控制,例如从战斗中逃走,在同伴的木屋中接受治疗,然后满血复活、击败敌人。

我们已经讨论了如何创建一个Knight类,该类有助于简化数据处理和其他与Foo先生相关的事情,例如攻击力或者他攻击敌人的方式。

其他的类应该如何定义呢?如何将敌人转变成一个对象呢?敌人可能会占据多个木屋。要记住我们需要打败所有的敌人。请想象一下如下场景:Foo先生打伤了在2号木屋的敌人,使其生命力降低了。然后他又来到了被另外一个敌人占据的木屋前。现在,我们需要为不同木屋中的敌人维护单独的生命力计数器。

在未来的版本中,你能想象到的是玩家会要求有不同攻击能力和治愈能力的多样类型的敌人,就像我们的Foo先生一样。因此,从这点来说,有这样一个单独的类,它的实例代表具体的一个敌人,这就显得很有意义。我们将这个类命名为OrcRider。它将和Knight类有着类似的属性。然而,为了简单起见,我们不会赋予敌人诸如治疗、更换木屋等能力。

Foo先生说,他很高兴地读到了敌人被拒绝赋予一些重要的能力(但是因为他带着头盔,你看不到他十分高兴的脸庞)。

还有另外一些我们需要考虑的事情。到目前为止,huts只是一个简单的以字符串形式来保存的关于占有者类型的Python列表。

请看一看需求特性的列表,我们也需要根据战斗的结果来记录木屋中的黄金和盔甲的数量,并对占有者进行更新。在未来的版本中,你也许会想展示一些统计数据,例如占有者的历史记录、黄金数量的变化情况等等。对于所有的这些以及其他的一些内容,我们将创建一个Hut类。

请拿出笔和纸来,写下到目前为止我们讨论的每一个类的重要属性。在这一点上,不要担心是将它划分为一个实例变量还是一个封装了一些指令来完成一个特定任务的类的方法,只是将你认为属于每个类的东西写下来即可。

图1-9显示了Knight类、Hut类以及OrcRider类的一系列可能的属性列表。用删除线标识出来的属性名表示这些可能的属性不会在这个示例中实现。但是,有这种超前的想法总是好的,请让这种想法在应用的设计阶段一直保持在你的脑海里。

这不是一个完整的规范,但是我们有了一个很好的出发点。当Foo先生进入一个敌人的木屋中,我们可以选择调用Knight类的进攻方法。正如以前一样,进攻方法会随机决定谁会受伤,以及减少哪个角色的生命力。在骑士类中,很方便就可以增加一个新属性,enemy代表了他的极为活跃的对手。在这个示例中,enemyOrcRider的一个实例。

图1-9

让我们来进一步实现这个设计。你注意到KnightOrcRider类有一些共同的东西了吗?我们会用继承原则来为这些类创建一个叫作GameUnit的超类。我们把共同的代码移入超类中,然后让子类重写它们各自想要的不同实现。在下节中,我们将会使用一种类似图表的统一建模语言(United Modeling Language,UML)来表示这些类。

1.伪UML表示

图1-10将有助于你建立对不同组件之间如何相互通信的基本理解。

图1-10

图1-10和UML表示很类似。它有助于建立一个软件设计的可视化表示。在本书中,我们将会大致遵循UML的表达方式。我们称这里使用的图表为伪UML图表(或类UML图表)。

2.了解伪UML图表

这里对本书使用的类UML图表的通用惯例进行讲解。在图表中,我们用圆角矩形来代表每一个类。它显示了类的名字以及类的属性。属性前面的加号(+)表明该属性是公有的。受保护和私有的方法通常是用减号(−)来表示。这个图表中显示的所有属性都是公有属性。所以,你也可以选择在每个属性的旁边加上一个加号。在后面的章节中,我们将遵循这个惯例。为了方便说明,我们只列举出几个相关的公有属性。请注意,我们在图表中使用不同类型的连接线。

现在,让我们讨论一下前面图表中的各个组件。

KnightOrcRider类继承自GameUnit类。在这种情况下,Knight类会继承一些默认的方法,例如attackhealrun_away等。OrcRider类则不会拥有这些覆盖方法,我们也不会赋予敌人这些能力。

Hut类会有一个占有者。占有者可能是Knight类或者OrcRider类的一个实例,当木屋为空闲时则为None类型。图表中的实心菱形连接线表示组合。

 

对象组合

这是一个重要的面向对象原则。它意味着一种has-a的关系。在这种情况下,Hut包含其他一些被用来执行特定任务的对象或者由这些对象组成。那就请大声说出来吧:Hut有一个Knight,Hut有一个OrcRider,等等。

除了已经讨论的4种类,我们还将引入另外一个类来对顶层代码进行包装。让我们把它叫作AttackOfTheOrcs类吧。因为有5个木屋,AttackOfTheOrcs类中的方法会创建相应数量的Hut实例。这就是对象聚合,在前面的图表中以空心菱形表示。

你注意到AttackOfTheOrcs类中还有另一个has-a关系吗?该类中的player属性是一个Knight类的实例,但是这在将来也有可能会发生变化。我们通过使用实心菱形的箭头将KnightAttackOfTheOrcs组合在一起来表示这种关系。

有了这个深层次的理解,现在我们开始开发代码。请下载源代码文件ch01_ex03.py。我们只会查看代码中几个重要的方法。想要获取完整的代码请参照源文件。

 

这个例子的代码全部集中在单一文件ch01_ex03.py中。这是很好的做法吗?当然不是!随着我们学习的不断深入,你会学习到关于最佳实践的内容。在本书后面的章节中,我们将会讨论一些应用开发的重要内容,即重构、编码标准和设计模式。作为一个练习,试着将代码拆分成两个更小的模块,并为其添加代码文档。

如下所示的是主要的执行代码,以及一些关于AttackOfTheOrcs类的细节。在__init__方法中,我们会对一些实例变量进行初始化,并稍后对它们的值进行更新。例如,当游戏开始时,self.player表示Knight类的实例:

 

简单复习一下,__init__方法有点类似于C++语言中的构造函数。但是,要注意它们之间还是存在一些差异的。例如,你不可以重载__init__,但是在别的语言中也许是可以的。相反,你可以通过使用可选参数或者classmethod修饰符很轻松地实现这个目的。在本书后面的章节中我们会提及某几个方面的内容。

让我们快速地查看一下play_occupy_huts方法。

self.playerKnight的一个实例。大部分高级的动作发生时,我们都会调用该实例的acquire_hut方法。此后,程序只是保存玩家和敌人的健康表数据。同时,它也询问Hut实例来确认它是否被占有。

接下来,在_occupy_hut方法中,Hut类的对象被创建出来并插入self.huts列表中。该方法如下所示:

 

Python中的公有、保护和私有

你可能会注意到AttackOfTheOrcs的一些方法名是以下划线开头的,例如_process_user_choice()。这实际上是一种用来表明这个方法不是被拿来公用的方式。它只会在类的内部使用。有的语言,例如C++,会使用访问控制符来定义类,即private、protected以及public。它们用来为类的属性访问控制增加限制。

在Python中不会有这种情况。Python中允许在外部对一个以下划线开头的属性进行访问,例如game._process_user_choice()。如果属性的名字是以两个下划线开头的,那么你就不可以直接调用它。例如,你不可以直接调用game.__process_user_choice()。也就是说,存在另外一种从外部访问这类属性的方法。但是现在我们暂且不讨论它。尽管Python允许你访问这类属性,但这不是一个好的做法。

请观察Knight类中的acquire_hut方法:

接下来,让我们讨论一下这个方法:

Hut.acquire()方法被调用时会发生什么呢?以下是Hut类的代码片段:

acquire方法仅仅使用方法中作为参数传递进来的对象来更新occupant属性。

现在是游戏时间!我们已经查看了这个新的类中最重要的方法了。你可以继续从ch01_ex03.py文件中查看剩余部分的代码,或者你可以尝试亲自来编写一下这些方法。像之前一样,从命令行来运行该应用。下面显示了游戏运行的情况。

在上节中,我们使用面向对象的方法重新设计了代码。我们也展示了继承的使用方法,定义了超类GameUnit,继承它并创建了子类KnightOrcRider。作为本章的最后一个话题,让我们来讨论一下在Python中如何使用抽象基类。

 

本章旨在让读者能够对Python中的抽象基类有一个基本的了解。这里进行的讨论还远远谈不上全面,但是对于在我们应用的代码中实现一个抽象基类来说已经足够了。要进一步阅读,请查看访问Python官网,查看Python的文档。

如果你熟悉面向对象的语言,例如Java或者C++,那么你可能已经了解了抽象基类的概念。

基类是一个其他类可以继承它的父类。类似的,你可以创建一个抽象基类,然后其他类可以继承这个类。那么两者的区别是什么呢?最大的一个区别是抽象基类不能被实例化。但这并不是两者唯一的区别。抽象基类强制要求派生类实现基类中定义的具体方法。这些关于抽象基类的知识对于我们在本书中的示例来说已经足够了。欲知更多详情,可以单击前文提到的Python文档链接。让我们一起来查看一个简单的示例,其中包含如何在Python中实现抽象基类,以及抽象基类与普通的基类有何差异。抽象基类模块提供了必要的基础框架。下面的代码片段对比了抽象基类和普通基类的实现:

左边的类AbstractGameUnit是一个抽象基类,然而右边的GameUnit类是一个普通的基类。如上图所示,抽象基类中的3个不同点已经用数字标注出来了。

 

这里展示的代码是Python3.5的代码。对于Python 2.7版本来说,语法会有些不同。欲得到同等的示例代码,请参照支持材料中Python2目录下的ch01_ex03_AbstractBaseClass.py文件。

在ch01_ex03.py文件中,你会看到很多注释。这些注释是特意加上的,为了让你能够更好地去改进这部分代码。这段代码还有很大的改进空间。你可以看看自己是不是能够对代码进行重写,从而让其更加健壮。如果你倾向一个具体的问题,那么请看下面的例子:

Knight类和OrcRider类继承了超类GameUnit。这个练习就是要将GameUnit转换成一个AbstractGameUnit,也就是一个抽象基类。这里已经给出答案了;下面的图片中已经展示了Python 3.5语法下的代码的大框了。

请参照ch01_ex03_AbstractBaseClass.py文件:

 

请注意,对于Python 2.7版本来说,会有一个独立版本的代码。请参照支持代码包中的src_ch1_Python2目录。

在本章中,我们接触了一些开发简单的基于命令行的应用所用到的一些基本概念。我们首先学会了如何搭建Python开发环境。

我们编写的第一个程序是一个简单的Python脚本。很快我们就发觉,如果加入更多的功能的话,这个简单的脚本将很难进行代码维护。下一步,我们做了一些代码重构并将代码包装在函数里。这增加了代码的可读性,也让代码更加容易管理。朋友们建议为应用加入更多功能,这让我们考虑对代码进行重新设计。我们学会了如何将代码转换成面向对象的设计,并实现了一些新的功能。

我们怎么能忘记Foo先生呢!他会在本书中一直陪伴我们。

我们开发的代码完全没有漏洞吗?你可能在玩游戏的过程中已经注意到一些问题的存在!在下一章中,我们将学习如果通过异常处理来让程序更加健壮。

你看到的本书中的示例代码其实都是图片文件或者代码截屏。

这些图片的渲染质量将取决于你的PDF阅读器的页面显示分辨率以及缩放比例。

如果你无法清晰地阅读代码,你可以对你的PDF或者电子书阅读器进行如下设置:

如果问题仍然存在,你可以尝试一些不同的分辨率。

如何设置分辨率呢?这将取决于你的电子书阅读器。例如,如果你使用Adobe Reader的话,单击“文件—偏好”,然后从左边的面板中选择“页面显示”,你会在右面的面板中看到分辨率选项。选择“96像素/英寸”或者相近的分辨率,然后看看这是否对更好地渲染图片有帮助。


相关图书

深度学习的数学——使用Python语言
深度学习的数学——使用Python语言
动手学自然语言处理
动手学自然语言处理
Web应用安全
Web应用安全
Python高性能编程(第2版)
Python高性能编程(第2版)
图像处理与计算机视觉实践——基于OpenCV和Python
图像处理与计算机视觉实践——基于OpenCV和Python
Python数据科学实战
Python数据科学实战

相关文章

相关课程