数据结构 Python语言描述 第2版

978-7-115-55148-1
作者: [美]肯尼思· A.兰伯特(Kenneth A. Lambert )
译者: 肖鉴明
编辑: 吴晋瑜

图书目录:

详情

本书用 Python 语言来讲解数据结构及实现方法。全书首先概述 Python 编程的功能—这些功能是实际编程和解决问题时所必需的;其次介绍抽象数据类型的规范、实现和应用,多项集类型,以及接口和实现之间的重要差异;随后介绍线性多项集、栈、队列和列表;最后介绍树、图等内容。本书附有大量的复习题和编程项目,旨在帮助读者巩固所学知识。 本书不仅适合高等院校计算机专业师生阅读,也适合对 Python 感兴趣的读者和程序员阅读。

图书摘要

版权信息

书名:数据结构(Python语言描述)(第2版)

ISBN:978-7-115-55148-1

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

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

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

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


著    [美] 肯尼思•A.兰伯特(Kenneth A. Lambert)

译    肖鉴明

责任编辑 吴晋瑜

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

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

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

读者服务热线:(010)81055410

反盗版热线:(010)81055315


Fundamentals of Python: Data Structures 2nd Edition

Kenneth A.Lambert

Copyright © 2019 by Course Technology, a part of Cengage Learning

Original edition published by Cengage Learning. All Rights reserved.

本书原版由圣智学习出版公司出版。版权所有,盗印必究。

Posts & Telecom Press Co., Ltd is authorized by Cengage Learning to publish, distribute and sell exclusively this edition. This edition is authorized for sale in the People’s Republic of China only (excluding Hong Kong SAR, Macao SAR and Taiwan). No part of this publication may be reproduced or distributed by any means, or stored in a database or retrieval system, without the prior written permission of Cengage Learning.

本书中文简体字翻译版由圣智学习出版公司授权人民邮电出版社独家出版发行。此版本仅限在中华人民共和国境内(不包括香港、澳门特别行政区及台湾)销售。未经授权的本书出口将被视为违反版权法的行为。未经出版者预先书面许可,不得以任何方式复制或发行本书的任何部分。

Cengage Learning Asia Pte.Ltd.

151 Lorong Chuan, #02-08 New Tech Park, Singapore 556741

本书封面贴有Cengage Learning防伪标签,无标签者不得销售。


本书用Python语言来讲解数据结构及实现方法。全书首先概述Python编程的功能—这些功能是实际编程和解决问题时所必需的;其次介绍抽象数据类型的规范、实现和应用,多项集类型,以及接口和实现之间的重要差异;随后介绍线性多项集、栈、队列和列表;最后介绍树、图等内容。本书附有大量的复习题和编程项目,旨在帮助读者巩固所学知识。

本书不仅适合高等院校计算机专业师生阅读,也适合对Python感兴趣的读者和程序员阅读。


肯尼思•A. 兰伯特(Kenneth A. Lambert)是一名计算机科学教授,也是美国华盛顿与李大学(Washington and Lee University)计算机科学系的系主任。他教授“程序设计概论”课程已有30多年,并且一直是计算机科学教育领域的活跃研究者。Lambert自行撰写或与他人合著的书多达28本,包括一系列Python的入门图书、与Douglas Nance和Thomas Naps一起编写的一系列C++的入门图书、与Martin Osborne一起编写的一系列Java的入门图书,等等。


感谢我的朋友马丁•奥斯本(Martin Osborne)多年来对我多本图书给出的建议、友好的批评以及鼓励。

还要感谢华盛顿与李大学计算机科学系112班的同学们在几个学期里对本书进行的课堂测试。

最后,感谢项目总经理Kristin McNary、项目经理Chris Shortt、课件设计师Maria Garguilo和Kate Mason、资深策划编辑Magesh Rajagopalan、技术编辑Danielle Shaw,尤其是资深责任编辑Michelle Ruelos Cannistraci,他负责处理这一版出版过程中的内容细节。


欢迎阅读本书!本书涵盖本科阶段通常在计算机科学的第二门课程(CS2)里涉及的内容。尽管本书使用的是Python编程语言,但在开始学习之前,你只需要掌握使用高级编程语言进行编程的基础知识。

本书主要介绍计算机编程中如下4个主要方面的内容。

(1)编程基础——数据类型、控制结构、算法开发以及通过函数进行程序设计,是解决计算机问题所需要掌握的基本思想。本书用Python编程语言介绍这些核心主题,旨在帮助你通过理解这些主题解决更广泛的问题。

(2)面向对象编程——面向对象编程是用于开发大型软件系统的主要编程范式。本书介绍OOP的基本原理,旨在让你能够熟练地应用它们。和其他教科书不同,本书会引导你开发一个专业的多项集类的框架,以说明这些原理。

(3)数据结构——大多数程序会依赖数据结构解决问题。在最具体的层级,数据结构包含数组以及各种类型的链接结构。本书介绍如何使用这些数据结构来实现各种类型的多项集结构(如栈、队列、列表、树、包、集合、字典和图),还会介绍如何使用复杂度分析来评估这些多项集的不同,进而实现在时间与空间上的权衡。

(4)软件开发生命周期——本书不会设单独的一两章去介绍软件开发技术,而是通过大量的案例全面概述这方面的内容。本书还会强调,编写程序通常并不是解决问题或软件开发里最困难或最具挑战性的部分。

在过去的30年里,与计算机相关的技术和应用日渐复杂,计算机科学的相关课程(尤其是入门级的课程)更是如此。如今,人们期望学生在学了一点点编程和解决问题的相关知识之后,就能够很快开始学习诸如软件开发、复杂度分析以及数据结构这类课程——这些课程在30年前都属于高级课程的范畴。除此之外,面向对象编程兴起并成为主导范式,也让授课老师和教材的编写者可以把那些功能强大甚至能够直接应用于行业里的编程语言(如C++和Java)引到入门课程里。这就导致刚开始学习计算机知识的学生还没来得及体验用计算机解决问题的优势以及带来的兴奋感,就因为要去精通那些更高级的概念以及编程语言里的语法而变得不知所措。

本书使用Python编程语言,以使计算机科学的第二门课程对学生和授课老师来说更具吸引力且易于学习。

Python具有如下教学优势。

(1)Python的语法非常简单且标准。Python的语句和伪代码算法的语句非常接近,而且Python的表达式使用了代数里的常规符号。这样,你可以花更少的时间了解编程语言的语法,进而把较多的时间花在解决有趣的问题上。

(2)Python的语义是安全的。任何表达式或语句只要违反了语言所定义的语义,都会得到错误的消息。

(3)Python的扩展性很好。Python可以让初学者很容易地编写出简单的程序。Python也包含了现代编程语言的许多功能,例如,对数据结构的支持以及面向对象的软件开发这样的高级功能,使开发者能够在需要的时候(比如说在计算机科学的第二门课程里)使用这些功能。

(4)Python语言具有良好的可交互性。你可以在解释器的提示符窗口里输入表达式和语句,以验证代码,并且会立即收到反馈。你也可以编写较长的代码段,并把它们保存在脚本文件里,以作为模块或作为独立的应用程序加载。

(5)Python是通用的。在当今的语言环境下,这意味着该语言有可以用在现代应用程序中的相应资源——这些资源包括媒体计算和Web服务,等等。

(6)Python是免费的,并且在业内得到了越来越广泛的使用。你可以在各种设备上直接下载并运行Python。Python的用户群体也非常庞大,而你的简历里有Python编程方面的专业背景将是一个加分项。

综上所述,Python是一个既方便又灵活的工具,无论对于初学者还是专家来说,它都可以用来表达计算思想。如果你在第一年里很好地学习了这些想法,那么多半可以轻松过渡到之后课程会用到的其他编程语言。更为重要的是,你会花更少的时间来盯着计算机屏幕,而可以把更多的时间用于思考解决有趣的问题。

本书通过循序渐进的方式推进,并且只有在需要的时候才会引入新概念。

第1章回顾Python编程的相关功能,这是用Python学习计算机科学的第二门课程里的编程和解决问题必需的。如果你有丰富的Python编程经验,那么可以快速地浏览一遍这一章的内容;如果你是Python新手,那么可以通过这部分内容深入了解这门语言。

本书其余部分(第2~12章)涵盖通常会包含在计算机科学的第二门课程里的主要主题,特别是抽象数据类型的规范、实现及应用等内容,并且会把多项集类型作为学习的主要工具和重点。在这些内容里,你将全面了解面向对象的编程技术以及好软件的设计要素。本书还涉及计算机科学的第二门课程里其他一些重要的主题,例如数据的递归处理、搜索和排序算法以及软件开发[复杂度分析或用在设计文档里的图形符号(UML)]里会用到的工具。

第2章介绍抽象数据类型(Abstract Data Type,ADT)的概念,并且对各种多项集中的抽象数据类型进行概览。

第3章和第4章介绍实现大部分多项集的数据结构,并且介绍了一些用来进行复杂度分析的工具。第3章介绍使用大O表示法的复杂度分析。这部分内容包含了很多材料,用搜索和排序算法作为例子,对算法和数据结构的运行时以及内存使用情况进行了简单分析。第4章介绍使用数组和线性链接结构的相关细节,这些数据结构用于实现大部分多项集。你将了解支持数组和链接结构的计算机内存的底层模型,以及使用它们所需要面对的时间/空间的权衡等内容。

第5章和第6章把关注点转移到面向对象设计的原则上。这些原则在后续章节里用于构建多项集类的专家级框架。

第5章讨论接口和实现之间的重要差异。开发包多项集的一个接口和多个实现作为展现这些差异的第一个例子。本章会将重点放在接口包含的通用方法上,允许不同类型的多项集在应用程序里进行协作,比如用来创建迭代器的方法。这个通用方法所创建的迭代器能够通过简单的循环来遍历任何一个多项集。本章还会介绍多态以及信息隐藏,这些主题也会通过接口和实现之间的差异表现出来。

第6章介绍类的层次结构是如何减少面向对象软件系统里的冗余代码的,还会介绍继承、方法调用的动态绑定以及抽象类的相关概念。这些概念会在后续章节里被反复使用。

在掌握这些概念和原则之后,你就可以开始学习第7~12章里其他重要的多项集抽象数据类型了。

第7~9章介绍栈、队列以及列表。我们会先从用户的角度进行介绍,以便你能了解所选实现里提供的接口以及一系列性能特征。我们会通过一个或多个应用程序说明每个多项集的用法,然后开发出这个多项集的若干种实现,并分析它们在性能上的权衡。

第10~12章介绍更高级的数据结构和算法,以便帮助你过渡到计算机科学里更高阶的课程。第10章讨论各种树结构,如二叉查找树、堆和表达式树。第11章通过哈希策略研究无序集合、包、集合和字典的实现。第12章介绍图和图处理算法。

本书的特色在于呈现一个多项集类的专家级框架。你看到的不会是一系列毫不相关的多项集,而是每个多项集在整体框架里的相应位置。这种方法能够让你了解到多项集类型的共同点以及使用不同多项集类型的原因。同时,你会接触到继承和类的层次结构,而这正是面向对象的软件设计的主题,虽然这些主题是这个级别的课程很少讲解和体现的。

本书用大量常见的例子和图表来详细阐述和介绍各个概念,然后再把这些新的概念应用到完整的程序之中,以展示如何用它们来解决各种问题。本书很早就会强调并且持续不断地强化什么是良好的编程习惯以及如何编写简洁易读的文档。

本书还有如下几个重要特点。

(1)案例研究——这些案例研究都是完整的Python程序,既有简单的,也有复杂的。为了强调软件开发生命周期的重要性和实用性,案例研究部分会涵盖用户需求、案例分析、案例设计、案例实现和测试建议、在每个阶段明确定义的所要完成的任务等内容。有些案例研究会在各章末尾的“编程项目”里得到扩展。

(2)章节总结——除了第1章,其他各章都会以对各章重要概念的总结作为结尾。

(3)关键术语——引入的新术语将用黑体着重显示。

(4)复习题——除第1章之外的其他各章都配有复习题。这些复习题通过对本部分的基础知识进行提问来巩固阅读效果。从第2章开始,每一章的末尾都有复习题。

(5)编程项目——本书各章最后都会给出一些难度不同的编程项目。

各章开头会列出具体的学习目标,增加了更多用以阐释各种概念的例图,添加并修改了许多编程项目。第2章新增了有关迭代器和高阶函数的内容。第9章新增了有关类Lisp列表、递归列表处理和函数式编程的内容。

读者可以从www.epubit.com下载本书的配套文件。

选用本书作为教材的老师,可通过contact@epubit.com.cn申请教学PPT和习题解答。


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

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

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

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

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

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

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

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

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

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

异步社区

微信服务号


在完成本章的学习之后,你能够:

本章给出了Python编程的一个快速概览,旨在帮助Python初学者和不熟悉Python的人尽快上手,但并不打算全面介绍计算机科学或Python编程语言。如果你想更详细地了解如何使用Python语言进行编程,那么可以参阅我写的《Python基础:第一个程序》(Fundamentals of Python: First Programs)(Cengage Learning出版社,2019年出版)。要了解Python编程语言的相关文档,请访问Python官方网站。

如果你已经安装了Python,请在终端提示符窗口运行“python”或“python3”命令来查看Python的版本号(Linux和Mac用户需要先打开终端窗口,Windows用户需要先打开DOS窗口)。我们推荐使用Python的最新版本。请登录Python官方网站,下载和安装Python的最新版本。要运行本书中的程序,你需要使用Python 3.0或更高的版本。

和所有现代编程语言一样,Python也有大量的功能和结构,但它是少数几种基本程序要素相当简单的语言之一。本节将介绍使用Python编程的一些基础知识。

Python程序包含一个或多个模块。模块是指包含Python源代码的文件,其中可以包含语句、函数的定义以及类的定义。简短的Python程序也称为脚本(script),通常包含在一个模块之中;较长的或较为复杂的程序,通常包含一个主模块以及一个或多个支持模块。主模块会包含程序执行的起点,支持模块则会包含函数和类的定义。

我们来看一个完整的Python程序。这是一个和用户玩猜数字游戏的程序。一开始,计算机要求用户输入给定数值范围内所猜数字的最小值和最大值。随后,计算机“思考”出在这个范围之内的一个随机数,并反复要求用户猜测这个数,直到用户猜对为止。用户每次猜测之后,计算机都会给出一个提示,并且会在游戏结束时显示总的猜测次数。这个程序会用到本章稍后部分里将讨论的几种Python语句,例如输入语句、输出语句、赋值语句、循环语句和条件语句。这个程序还包含了一个函数的定义。

这个程序的代码如下(代码位于文件numberguess.py之中)。

"""
Author: Ken Lambert
Plays a game of guess the number with the user.
"""

import random

def main():
    """Inputs the bounds of the range of numbers
    and lets the user guess the computer’s number until
    the guess is correct."""
    smaller = int(input("Enter the smaller number: "))
    larger = int(input("Enter the larger number: "))
    myNumber = random.randint(smaller, larger)
    count = 0
    while True:
        count += 1
        userNumber = int(input("Enter your guess: "))
        if userNumber < myNumber:
            print("Too small")
        elif userNumber > myNumber:
            print("Too large")
        else:
            print("You’ve got it in", count, "tries!")
            break

if __name__ == "__main__":
    main()

用户和程序进行某次互动之后的输出如下。

Enter the smaller number: 1
Enter the larger number: 32
Enter your guess: 16
Too small
Enter your guess: 24
Too large
Enter your guess: 20
You’ve got it in 3 tries!

实际运行过程中,代码及其输出会显示为黑色、蓝色、橙色和绿色等不同的颜色。Python的IDLE会对代码加以着色,以便让程序员更方便地识别程序元素的各种类型。不同颜色的表示规则详见后续说明。

对于书里的大多数示例,你都可以通过在终端窗口中输入命令来运行它们的Python程序。例如,要运行文件numberguess.py里包含的程序,只需在大多数终端窗口中输入如下命令:

python3 numberguess.py

你可以通过Python的IDLE(Integrated DeveLopment Environment)创建或编辑一个Python模块。要启动IDLE,只需在终端提示符下输入idleidle3命令,抑或通过快捷方式启动它。你也可以通过双击Python源代码文件(任何扩展名为.py的文件)或者右击源代码文件并在弹出的菜单中选择“通过IDLE打开或编辑”来启动IDLE。若要通过双击文件的方式启动IDLE,则需要确保源代码文件在系统里默认的打开方式已被设置为通过IDLE打开(macOS上这是默认设置,但在Windows上不是默认设置)。

IDLE提供了一个Shell窗口,可以交互式地运行Python表达式和语句。通过IDLE,你可以在编辑器窗口和Shell窗口之间来回切换,以开发和运行完整的程序。IDLE还能格式化代码,并对代码进行着色处理。

当你用IDLE打开Python文件时,文件就会显示在编辑器窗口里,这时Shell窗口会作为单独的窗口弹出。要运行文件里的程序,你只需把光标移动到编辑器窗口中,然后按下F5键。Python会在编辑器窗口中编译代码,然后在Shell窗口中运行它。

如果遇到Python程序没有响应或者无法正常退出的情况,可以按Ctrl+C组合键或者关闭Shell窗口来终止程序运行。

程序注释是指那些会被Python编译器忽略的文本,但对于阅读程序的人来说是非常有价值的。Python中的单行注释以#符号开头,并且直到当前行的末尾结束。在IDLE里,注释会显示为红色(本书为黑白印刷,故无法显示)。注释的示例如下:

#This is an end-of-line comment.

多行注释是用3个单引号或3个双引号括起来的一个字符串。这样的注释又称为文档字符串(docstring),用于对程序的主要结构进行文档化描述。前面给出的numberguess程序就包含了两个文档字符串:第一个文档字符串位于程序文件的顶部,用于描述整个numberguess模块;第二个文档字符串位于主函数(main)之下,用于描述这个函数做什么事情。尽管文档字符串看上去很简短,但是它们能在Python的Shell窗口中为程序员提供至关重要的帮助。

语言中的词法元素是用于构造语句的一类单词和符号。和所有高级编程语言一样,Python的一些基本符号也是关键字,例如ifwhile以及def。这些关键字在IDLE里会显示为橙色。词法元素还包括标识符(名称),字面值(数字、字符串和其他内置的数据结构),以及运算符和分隔符(引号、逗号、圆括号、方括号和大括号)。内置的函数名称的标识符会显示为紫色。

Python的关键字和名称是区分大小写的。也就是说,while是Python的关键字,而While则是程序员定义的名称。Python的关键字均以小写字母的方式拼写,并且在IDLE窗口中显示为橙色。

Python内置的函数名称均显示为黑色,但将其作为函数、类或方法名引用时显示为蓝色。名称可以以字母或下划线(_)开头,其后可以接任意数量的字母、下划线或数字。

在本书中,模块、变量、函数和方法的名称都是以小写字母的形式拼写的。除了模块,当这些名称包含多个单词时,那些位于后面的单词的首字母以大写形式拼写。类名遵循相同的惯例,但其首字母都是大写的。另外,如果一个变量名是常量,那么所有字母将大写,并且通过下划线来分隔单词。表1-1给出了Python命名惯例的一些示例。

表1-1 Python命名惯例的一些示例

名称类型

例子

变量

salary、hoursWorked、isAbsent

常数

ABSOLUTE_ZERO、INTEREST_RATE

函数或方法

printResults、cubeRoot、input

BankAccount、SortedSet

要尽可能使用能够描述其在程序中所对应角色的名称。通常,变量名应该是名词或形容词(如果它们表示布尔值),而函数和方法名应该是动词(如果它们表示动作)、名词或形容词(如果它们表示返回的值)。

一种语言里的语法元素是由词法元素所组成的语句(表达式、语句、定义以及其他结构)的类型。和大多数高级语言不同,Python用空白符(white space character)(空格、制表符或换行符)来表示不同类型的语句的语法。这就是说,缩进和换行符在Python代码中是非常重要的。IDLE这样的智能编辑器有助于实现代码的正确缩进,这让程序员不需要操心用分号隔开句子,或者用花括号来标记语句块。本书的Python代码均以4个空格作为缩进宽度。

数字(整数或浮点数)的写法和其他编程语言里的一样。布尔值TrueFalse是关键字。其他诸如字符串、元组、列表和字典这样的数据结构,也有与之相对应的字面值,参见后续内容。

字符串字面值

你可以用单引号、双引号或者成对的3个双引号或3个单引号将字符串括起来。最后的这种表示方法对于包含多行文本的字符串来说是很有用的。字符值是指只包含一个字符的字符串。\字符用于将非图形化的字符(例如,换行符\n和制表符\t,或者\字符本身)进行转义。下面的代码段及其输出展示了各种可能性。

print("Using double quotes")
print('Using single quotes')
print("Mentioning the word ‘Python’ by quoting it")
print("Embedding a\nline break with \\n")
print("""Embedding a
line break with triple quotes""")

输出结果如下。

Using double quotes 
Using single quotes 
Mentioning the word 'Python' by quoting it 
Embedding a 
line break with \n 
Embedding a 
line break with triple quotes

Python里的算术表达式用的是标准运算符(+*/%)和中缀表示法。无论操作数是什么数字类型,/运算符都会生成一个浮点数,而//运算符会输出整数形式的商。当这些运算符和多项集[1](如字符串和列表)一起使用时,+运算符用于连接操作。此外,**运算符用于幂运算。

比较运算符有<<=>>===以及!=,用于比较数字或字符串。

==运算符用于比较数据结构里的内容,例如,可以对两个列表进行比较;而is运算符则用于比较两个对象的标识是否一致。比较运算符会返回TrueFalse

逻辑运算符andornot会把0、None、空字符串以及空列表这样的值视为False,而把大多数其他Python值视为True

下标运算符[]会和多项集对象一起使用,这是我们稍后会介绍的。

选择器运算符.用于引用一个模块、类或对象中一个具名的项。

Python运算符的优先级和其他语言是一样的(依次是选择运算符、函数调用运算符、下标运算符、算术运算符、比较运算符、逻辑运算符和赋值运算符)。括号也和其他语言一样,用于让子表达式更早地予以执行。

**=运算符满足右向结合律,而其他运算符满足左向结合律。

调用函数的方法和其他语言中的也是一样的,即函数名称后面跟着用括号括起来的参数列表。示例如下:

min(5, 2)     # Returns 2

Python提供了一些标准函数,例如absround。你也可以从模块里导入其他函数。

标准输出函数print会将其参数显示到控制台。这个函数支持使用可变数量的参数。Python会自动为每个参数运行str函数,以获取其字符串表示,并且在输出之前用空格把每个字符串隔开。默认情况下,print会以换行符作为结束。

标准输入函数input会一直等待用户通过键盘输入文本。当用户按下回车键时,这个函数将返回一个包含所有输入字符的字符串。这个函数接受一个可选的字符串作为其参数,并且会不换行地打印出这个字符串,以提示用户进行输入。

你可以把一些数据类型名称当作类型转换函数使用。例如,当用户在键盘上输入一个数字时,input函数返回这个数字的字符串形式,而不是这个数字本身。程序必须先把这个字符串转换为int类型或float类型,然后才能进行数字处理。下面这段代码先要求用户输入圆的半径,然后把这个字符串转换为float类型,最后计算并输出圆的面积。

radius = float(input("Radius: "))
print("The area is", 3.14 * radius ** 2)

和大多数其他编程语言一样,Python允许算术表达式中的操作数具有不同的数值类型。在这种情况下,返回的结果类型会和操作数里最通用的类型相同。例如,把int类型的操作数和float类型的操作数相加,会得到float类型的数。

函数支持可选参数,因此在调用函数时,可以使用关键字对参数进行命名。例如,print函数默认在其要显示的参数后输出换行符。为了不产生新行,你可以把可选参数end赋值为一个空字符串,如下所示。

print("The cursor will stay on this line, at the end", end = "")

必选参数是没有默认值的;可选参数有默认值,并且在通过关键字使用它们时,只要它们处于必选参数之后,就可以按照任何顺序进行传递。

例如,标准函数round预期会有一个必选参数——这个参数是一个需要被四舍五入的数字;除此之外,还可以用第二个可选参数来对精度进行确认。当省略第二个参数时,这个函数会返回最接近的整数(int类型);而当包含第二个参数时,这个函数会返回一个浮点数(float类型)。示例如下。

>>> round(3.15)
3
>>> round(3.15, 1)
3.2

通常,在调用函数时,传递给它的参数数量必须至少和必选参数的数量相同。

注意:标准函数和Python的库函数在调用时都会对传入的参数进行类型检查。由程序员定义的函数则可以接收任何类型的参数,包括函数和类型本身。

Python中的变量是通过赋值语句引入的,例如:

PI = 3.1416

上述代码把变量PI的值设为3.1416。简单赋值语句的语法如下:

<identifier> = <expression>

也可以像下面这样,在一个赋值语句里,同时引入多个变量:

minValue, maxValue = 1, 100

因此,如果要交换变量ab的值,就可以这样写:

a, b = b, a

赋值语句必须写在一行代码里,但是可以在逗号、圆括号、花括号或方括号之后换行。在没有这些分隔符号的情况下,还有一种实现语句换行的方法:用一个转义符\结束这一行代码。一般来说,采用这种方法时,这个符号会被放在表达式里的运算符之前或之后。如下示例在实际编写时并不需要换行:

minValue = min(100,
               200)
product = max(100, 200) \
          * 30

当你在逗号或转义符之后按回车键时,IDLE会自动缩进下一行代码。

在Python里,变量都可以被指定为任何类型的值。这些变量并不是像其他语言那样被声明为特定的类型,而只是被赋了一个值。

因此,在Python程序里,几乎永远都不会出现数据类型的名称。但是,值和对象都是有类型的,这些类型在表达式里作为操作数进行运算时,会在运行时进行类型检查,因此类型错误还是会被发现的。只是相对来说,程序员在编写代码时不用对数据类型进行太多的关注。

import语句使得另一个模块中的标识符可以被一个程序所见到。这些标识符可能是对象名、函数名或类名。有几种方式可以表示一条import语句。最简单的方式是,导入一个模块名称,例如:

import math

这使得在math模块中定义的任何名称,在当前的模块中都可以通过math.<name>形式的语法而变得可用。因此,math.sqrt(2)将返回2的平方根。

另一种导入的形式是,直接使用名称进入导入,这样一来,就可以不带模块名作为前缀而直接使用该名称了:

from math import sqrt
print(sqrt(2))

还可以通过列出几个单独的名称来导入它们:

from math import pi, sqrt
print(sqrt(2) * pi)

也可以使用*运算符导入一个模块中的所有名称,但是我们并不认为这是好的做法。

尽管Python的官方网站给出了关于Python语言的完整文档,但是在Python 的Shell窗口中也可以快速获取和语言大部分组成相关的帮助信息。要访问这样的帮助信息,只需在Shell提示符中输入名为help(<component>)的函数,其中的<component>是模块、数据类型、函数或方法的名称。例如,调用help(abs)(math.sqrt)分别会显示absmath.sqrt函数的文档。调用help(int)help(math)将分别显示int类型和math模块中所有操作的文档。

注意,如果一个模块并非在Shell窗口启动时Python将要加载的内置模块,程序员必须先导入模块,然后才能查找相关的帮助信息。例如,Shell窗口中的如下会话将显示本章前面所介绍的numberguess程序的文档:

>>> import numberguess
>>> help(numberguess)
Help on module numberguess: 

NAME
    numberguess 

DESCRIPTION 
    Author: Ken Lambert 
    Plays a game of guess the number with the user. 

FUNCTIONS 
    main() 
        Inputs the bounds of the range of numbers, 
        and lets the user guess the computer’s number until
        the guess is correct. 

FILE
    /Users/ken/Documents/CS2Python/Chapters/Chapter1/numberguess.py

Python包含了针对顺序、条件执行和迭代等情况的常见的各种控制语句。语句的序列是一组连续编写的语句。序列中的每一条语句必须以相同的缩进开始。本节将介绍条件执行和迭代的控制语句。

Python条件语句的结构和其他语言中的类似,即通过关键字ifelifelse以及冒号和缩进来实现。

单向if语句的语法如下:

if <Boolean expression>:
    <sequence of statements>

正如前面提到的,布尔表达式(boolean expression)可以是任意的Python值,其中的一些值被当作False,另一些值被当作True。如果布尔表达式为True,就运行该语句序列;否则,什么也不会发生。语句序列(一条或多条语句)必须至少按照一个空格或制表符(通常一个制表符是4个空格)来缩进或对齐。冒号是唯一的分隔符,如果序列中只有一条语句,它可以紧跟在同一行的冒号之后。

双向if语句的语法如下:

if <Boolean expression>:
    <sequence of statements>
else:
    <sequence of statements>

注意缩进和关键字else后面的冒号。在这种用法下,只有一个序列将被运行。如果布尔表达式为True,那么第一个序列会被运行;如果布尔表达式为False,那么第二个序列将被运行。

多向if语句的语法如下:

if <Boolean expression>:
    <sequence of statements>
elif <Boolean expression>:
    <sequence of statements>
...
else:
    <sequence of statements>

多向if语句也只会运行一个语句序列。多向if语句会包含一个或多个不同的布尔表达式,除了第一个布尔表达式,其他布尔表达式都会被写在关键字elif之后。在这种用法里,最后一个else:分支是可以省略的。

下面的示例是比较两个数的大小的问题,并输出正确的答案:

if x > y: 
    print("x is greater than y")
elif x < y: 
    print("x is less than y")
else: 
    print("x is equal to y")

前面讨论的numberguess程序包含了一个main函数的定义和如下的if语句:

if __name__ == "__main__":
    main()

上述if语句的作用是,允许程序员要么将模块当作一个独立的程序运行,要么从Shell窗口或另一个模块中导入它。其工作方式为:每个Python模块都包含一组内建的模块变量,当加载该模块时,Python虚拟机会自动为这些变量赋值。

如果该模块是作为一个独立的程序加载(要么从一个终端提示符运行它,要么从一个IDLE窗口加载它)的,该模块的_name_变量会设置为字符串"_main_";否则,这个变量会设置为模块的名称,在这个例子中,也就是"numberguess"。不管怎么样,该变量的赋值都会在模块中的任何代码加载之前完成。因此,当执行到模块末尾的if语句时,只有模块作为一个独立的程序启动,才会调用模块的main函数。

在开发独立的程序模块时,if _name_ == "_main_"这样的习惯用法很有用,因为这使得程序员只要把模块导入Shell窗口中就可以看到其相关帮助文档。同样,程序员在IDLE中进行模块开发时,可以在模块中用这一方法来编写和运行测试工具函数。

Python的while循环语句的结构和其他语言也是类似的。其语法如下。

while <Boolean expression>: 
    <sequence of statements>

下面的示例展示了如何计算从1到10的乘积并输出结果。

product = 1
value = 1
while value <= 10: 
    product *= value 
    value += 1
print(product)

注意,这里使用了扩展的赋值运算符*=product *= value这行代码等价于如下的代码。

product = product * value

Python中的for循环语句可以用于在值的序列上进行更简洁的迭代。这条语句的语法如下。

for <variable> in <iterable object>:
    <sequence of statements>

当运行这个循环时,可迭代对象(iterable object)中的每一个值都被赋给循环变量,并且把这个值应用在其后面的语句序列里。可迭代对象的示例是字符串和列表。下面的代码使用了Python的range函数,返回整数的一个可迭代的序列,可以计算前面示例中的乘积。

product = 1
for value in range(1, 11):
    product *= value
print(product)

Python程序员通常更喜欢用for循环来迭代确定范围的值或值的序列。如果继续循环的条件是某个布尔表达式,那么程序员会使用while循环。

和其他语言一样,Python中的字符串也是一个复合对象,它包含了其他对象,也就是字符。然而,Python中的每个字符本身也是包含单个字符的字符串,并且在字面上也采用了和字符串相似的方式来表示。Python的字符串类型名为str,其中包含了很多的运算,其中一些将在本节加以介绍。

使用比较运算符来比较字符串,是按照ASCII码的顺序比较两个字符串中每个位置的字符对。因此,'a'会小于'b',但'A'也会小于'a'。注意,在本书中,我们把单字符的字符串用单引号括起来,把多字符的字符串用双引号括起来。

+运算符将生成并返回一个包含两个操作数的新字符串。

在下标运算符最简单的形式中,它期待范围是从0到字符串的长度减去1的一个整数。该运算符返回在字符串中该位置的字符,因此会有:

"greater"[0]          # Returns 'g'

尽管字符串索引不能超过其长度减去1,但是允许出现负的索引。如果索引为负值,Python会把这个值和字符串的长度相加,以确定要返回的字符的位置。在这种情况下,所给出的负的索引值不得小于字符串长度的负值。

字符串是不可变的,也就是说,一旦创建了字符串,就不能更改其内部的内容。因此,不能使用下标运算符来替换字符串中一个给定位置的字符。

下标运算符有一种名为切片运算符(slice operator)的变体,可以用来获取字符串里的子字符串。切片运算符的语法如下:

<a string>[<lower>:<upper>]

如果给出了<lower>(低索引),这个值的范围应当是从0到字符串的长度减去1的整数;如果给出了<upper>(高索引),这个值的范围是从0到字符串的长度的整数。

如果省略这两个值,那么切片运算符会返回整个字符串。但是,如果省略第一个值,那么切片运算符将返回一个以当前字符串的第一个字符作为开头的子字符串;如果省略第二个值,那么切片运算符将返回一个以当前字符串的最后一个字符作为结尾的子字符串。对于其他情况,切片运算符都会返回这样一个子字符串:这个子字符串会从低索引处的字符开始,到高索引减1的位置作为结束。

下面是切片运算符的一些示例:

"greater"[:]     # Returns "greater" 
"greater"[2:]    # Returns "eater" 
"greater"[:2]    # Returns "gr" 
"greater"[2:5]   # Returns "eat"

你可以尝试在Python的Shell窗口中使用切片运算符,以进一步了解它的用法。

许多关于数据处理的应用程序需要有表格形式的输出。在这种格式下,数字和其他信息在列里可以左、右对齐。如果一列数据以最左边的字符进行垂直对齐,则为左对齐;如果一列数据以最右边的字符进行垂直对齐,则为右对齐。为了保持数据列之间的距离,在左对齐的情况下,需要在基准线的右侧添加空格,而在右对齐的情况下,需要在基准线的左侧添加空格。如果某列中数据两侧的空格数相等,这一列就是居中对齐。

格式化字符串里的数据字符以及满足给定基准线的附加空格的总数称为它的字段宽度(field width)

print函数会在遇到第一列时自动开始打印输出基准线。在下面的示例中,它展示了如何用print语句来生成两列的格式,即输出指数(7~10)及其对应的值(107~1010):

>>> for exponent in range(7, 11): 
        print(exponent, 10 ** exponent) 
7 10000000 
8 100000000 
9 1000000000 
10 10000000000

需要注意的是,如果指数为10,第二列的输出将后移一个空格,会显得有点参差不齐。如果让左边这一列数据左对齐,让右边这一列数据右对齐,那么输出的结果看上去会更整洁。当对浮点数进行格式化输出时,需要指定想要显示的精度的位数以及相应的字段宽度。这一点在显示需要精确到两位数的财务数据时显得尤其重要。

Python包含一种通用的格式化机制。这种机制能够让程序员为不同类型的数据指定相同的字段宽度。下面的示例展示了如何在字段宽度为6的情况下,对字符串"four"进行左对齐和右对齐:

>>> "%6s" % "four"       # Right justify
' four'
>>> "%-6s" % "four"      # Left justify
'four '

第一行代码通过在字符串的左侧填充两个空格对它进行右对齐;第二行代码则通过在字符串的右边填充两个空格进行左对齐。

这种运算最简单的形式如下所示:

<format string> % <datum>

这个版本包含了格式字符串、格式运算符%以及需要被格式化的单个数据值。格式化字符串可以包括字符串数据以及和数据格式相关的其他信息。要格式化后面的字符串数据值,可以在格式字符串里使用%<field width>s表示法。当字段宽度为正时,数据是右对齐的;当字段宽度为负时,数据是左对齐的。如果字段宽度小于或等于字符中的数据的打印长度,将不添加对齐信息。%运算符会用格式字符串里的信息来构建并返回一个格式化之后的字符串。

要格式化整数,使用字母d而不是s。然而要格式化一个序列的数据值,需要构建一个格式化字符串,其中包含了针对每个数据的一个格式化代码,并且把所有数据值放在%运算符之后的元组之中。于是,就有了这个操作的另一个版本的代码:

<format string> % (<datum-1>, …, <datum-n>)

通过格式化运算,10的幂指数的循环可以把数字显示在对齐的列里了。第一列在宽度为3的一个字段中左对齐,第二列在一个宽度为12的字段中右对齐。

>>> for exponent in range(7, 11): 
         print("%-3d%12d" % (exponent, 10 ** exponent)) 
7        10000000 
8       100000000 
9      1000000000 
10    10000000000

对于float类型的数据值来说,它的格式信息如下:

%<field width>.<precision>f

其中,.<precision>这一部分是可选的。下面这个交互式示例展示了浮点数在使用了格式字符串和没有使用格式字符串这两种情况下的输出:

>>> salary = 100.00
>>> print("Your salary is $" + str(salary))
Your salary is $100.0
>>> print("Your salary is $%0.2f" % salary)
Your salary is $100.00

下面是另一个使用格式字符串的小示例,它被用来以字段宽度为6、精度为3对浮点值3.14进行格式化:

>>> "%6.3f" % 3.14
' 3.140'

注意,Python给数字字符串添加了一个精度位数,并且其左侧填充空格,从而实现了字段宽度为6、精度为3的设置。这个宽度包含小数点后所占据的位置。

除了标准的运算符和函数,Python还包含了大量的可以操作对象的方法。和函数类似,方法也接收参数,执行任务并且返回相应的值。不同之处在于,方法的调用总是关联在对象上。调用方法的语法如下:

<object>.<method name>(<list of arguments>)

下面是一些对字符串进行方法调用的示例:

"greater".isupper()                   # Returns False 
"greater".upper()                     # Returns "GREATER" 
"greater".startswith("great")         # Returns True

如果运行一些对象无法识别的方法,Python会引发异常并且暂停当前程序。要想知道对象所支持的方法集,可以在Python的Shell窗口里,把这个对象的类型作为参数来运行Python的dir函数。例如,dir(str)会返回一个字符串对象所支持的方法名称,运行help(str.upper),则会输出和str.upper方法用法相关的文档。

对于有些方法名,例如__add____len__,当Python看到一个对象和某种运算符或函数一起使用的时候,就会运行这些方法,例如:

len("greater")      # Is equivalent to "greater".__len__() 
"great" + "er"      # Is equivalent to "great".__add__("er") 
"e" in "great"      # Is equivalent to "great".__contains__("e")

我们建议读者通过dirhelp函数来进一步研究str类型的各个方法。

现代编程语言都会包含若干种(如列表这样的)多项集类型。这些多项集能够让程序员对多个数据值进行组织并且进行统一的操作。本节将探讨Python内置的多项集。后续章节部分则探讨如何向语言里添加新的多项集类型。

列表(list)是零个或多个Python对象的一个序列,这些对象通常称为(item)。列表的表现形式是:用方括号括起整个列表,并用逗号分隔元素,如下所示。

[]                             # An empty list
["greater"]                    # A list of one string
["greater", "less"]            # A list of two strings
["greater", "less", 10]        # A list of two strings and an int
["greater", ["less", 10]]      # A list with a nested list

和字符串类似,列表也可以通过标准运算符执行切片以及连接操作。不同之处在于,在这种情况下,返回的结果也是列表。和字符串不同,列表是可变的,这意味着,可以替换、插入或删除列表中所包含的项。这一功能带来两个和字符串的不同:首先,切片和连接运算符所返回的列表是新的列表,而不是最初列表的一部分;其次,list类型包含了几个叫作变异器(mutator)的方法,用于修改列表的结构。可以在Python的Shell窗口里输入dir(list)来查看这些方法。

最常用的列表变异器方法是appendinsertpopremovesort。下面是使用这些方法的一些示例。

testList = []              # testList is [] 
testList.append(34)        # testList is [34] 
testList.append(22)        # testList is [34, 22] 
testList.sort()            # testList is [22, 34] 
testList.pop()             # Returns 22; testList is [34] 
testList.insert(0, 22)     # testList is [22, 34] 
testList.insert(1, 55)     # testList is [22, 55, 34] 
testList.pop(1)            # Returns 55; testList is [22, 34] 
testList.remove(22)        # testList is [34] 
testList.remove(55)        # raises ValueError

字符串的split方法会从字符串里分离出一个单词列表,而join方法会把单词列表连在一起从而形成字符串,如下所示:

"Python is cool".split()       # Returns ['Python', 'is', 'cool']
" ".join(["Python", "is", "cool"])       # Returns 'Python is cool'

我们鼓励你使用dirhelp函数来对列表的各个方法进行探索。

元组(tuple)是一个不可变的元素序列。其形式是用圆括号将各项括起来,并且必须至少包含两个项。元组实际上就像列表一样,只不过它没有变异器方法。但是,如果要使元组只包含一个元素,则必须在元组里包含逗号,如下所示:

>>> (34)
34

>>> (34,)
(34)

可以看到,Python把第一个表达式(34)当作用括号括起来的整数,而第二个表达式(34,)则被视为只有一个元素的新元组。要了解元组所支持的各个方法,请在Python的Shell窗口里运行dir(tuple)命令。

for循环可以用来遍历序列(如字符串、列表或元组)里的所有元素,例如,下面这段代码会把列表里的所有元素打印出来:

testList = [67, 100, 22]
for item in testList:
    print(item)

for循环的遍历和基于索引遍历列表是等效的,但会显得更简单。基于索引遍历列表的代码如下:

testList = [67, 100, 22]
for index in range(len(testList)):
    print(testList[index])

字典(dictionary)包含零个或多个条目。每个条目(entry)都有唯一的键和它所对应的值相关联。键通常是字符串或整数,而值是任意的Python对象。

字典的表现形式是把键值条目括在一组大括号里,如下所示:

{}                                      # An empty dictionary
{"name":"Ken"}                          # One entry
{"name":"Ken", "age":67}                # Two entries
{"hobbies":["reading", "running"]}      # One entry, value is a list

下标运算符可以用于访问一个给定键的值,给一个新键添加一个值,以及替换给定键的值。pop方法会删除一个条目并返回给定键所对应的值。keys方法会把所有键返回成一个可迭代对象;而values方法会把所有值返回成一个可迭代对象。和列表类似,字典本身也是一个可迭代对象,但是for循环会在字典的键上进行迭代。下面这段代码会打印出一个小字典里的所有键:

>>> for key in {"name":"Ken", "age":67}:
    print(key)
name
age

你可以在Python的Shell窗口里使用dirhelp函数研究字典的方法,并且对字典及其操作进行尝试。

程序员可以在字符串列表、元组或字典里通过in运算符来对值或多项集进行搜索。整个运算符会返回TrueFalse。对于字典来说,搜索的目标值应该是一个键。

如果已知给定值存在于序列(字符串、列表或元组)里,那么index方法将返回这个值所出现的第一个位置。

对于字典而言,方法getpop都有两个参数:键和默认值。当搜索失败时,这两个方法返回默认值;当搜索成功时,这两个方法返回键所对应的值。

下标运算符可以用来访问列表、元组和字典里的元素,但是通常来说,你可以通过模式匹配更方便地、一次性地访问多个元素。例如,颜色选择对话框返回的值就是一个包含两个元素的元组。当用户选择了颜色之后,元组的第一项是一个由3个数字组成的嵌套元组,第二项是一个字符串。因此,外层元组的形式就是((<r>, <g>, <b>), <string>)。为了能够进一步处理这些数据,你可以把3个数字分配给3个不同的变量,再把字符串分配给第四个变量。下面这段代码通过colorTuple的下标运算符完成了这些处理操作,并且为返回值都取了相应的名字:

rgbTuple = colorTuple[0]
hexString = colorTuple[1]
r = rgbTuple[0]
g = rgbTuple[1]
b = rgbTuple[2]

模式匹配可以把一个结构分配给形式完全相同的另一个结构。这里的目标结构所包含的变量从源结构里的相应位置处获得对应的值。接下来,你就可以使用这些变量进行后续的处理了。因此,使用模式匹配,你就可以在一行代码里完成上面代码完成的全部功能,如下所示:

((r, g, b), hexString) = colorTuple

尽管Python是一种面向对象的编程语言,但它也包含了很多内置函数,并且允许程序员创建新的函数。这些新函数可以使用递归,还可以把函数作为数据进行传递和返回。因此,Python允许程序员用一种完全函数式的编程样式来设计解决方案。本节将介绍一些相关的理念。

Python里定义函数的语法如下。

def <function name>(<list of parameters>):
    <sequence of statements>

命名函数名称和参数名称的规则与惯例与命名变量的是相同的。必选参数的列表可以为空,也可以包含用逗号隔开的名称。在这里,和其他编程语言不同的是,这些参数名称或函数名称本身并不会和数据类型进行关联。

下面是一个简单函数的定义,它可以计算并返回一个数的平方。

def square(n): 
    """Returns the square of n.""" 
    result = n ** 2 
    return result

可以看到,在函数的标题下有一行用三引号括起来的字符串,这是一个文档字符串(docstring)。这个字符串就像函数里的注释一样,当用户在Python的Shell窗口里输入help(square)时,也会显示这个字符串。你所定义的每一个函数里都应该有这样的文档字符串,以说明这个函数的功能,并提供相关的所有参数以及返回值的信息。

函数可以引入名为临时变量(temporary variable)的新变量。在函数square里,n是参数,而result就是临时变量。函数的参数和临时变量只会在函数调用的生存周期内存在,并且对其他函数及其外围程序都是不可见的。因此,就算几个不同的函数使用了相同的参数名和变量名,也不会发生冲突。

当一个函数不包含return语句时,它将在最后一条语句执行之后自动返回None值。

在模块中,你可以按照任意顺序定义函数,只要在编译它的定义之前没有执行这些函数即可。在下面这个示例里,模块的开头部分就有一次非法的函数调用。

first()                 # Raises a NameError (function undefined yet)

def first():
    print("Calling first.")
    second()    # Not an error, because not actually
                # called until after second is defined

def second():
    print("Calling second.")

first()         # Here is where the call of first should go

当Python运行第一行代码时,first函数还没有被定义,因此会引发异常。如果在这一行的开头放一个注释符#,然后再次运行这段代码,那么整个程序都会正常运行。在这种情况下,即使在定义second函数之前就调用了它,实际上也要等到first函数被定义了之后才会真正调用它。

你可以用<parameter name> = <default value>这样的语法把参数指定为有默认值的可选参数。在参数列表中,必选参数(没有默认值的参数)必须位于可选参数之前。

递归函数(recursive function)是指会调用自身的函数。为了防止函数无限地重复调用自身,代码中必须至少有一条选择语句。这条用来查验条件的语句被称为基本情况(base case),用于确定接下来要继续递归还是停止递归。

让我们看看怎样把一个迭代算法转换成一个递归函数。下面是displayRange函数的定义,它会打印出从下限到上限的所有数字:

def displayRange(lower, upper): 
    """Outputs the numbers from lower to upper.""" 
    while lower <= upper: 
        print(lower) 
        lower = lower + 1

如何将这个函数转换为递归函数呢?首先,需要注意如下两点重要的情况。

等价的递归函数可以执行类似的基本操作,区别在于:循环被替换成了if语句;赋值语句被替换成了函数的递归调用。修改后的代码如下:

def displayRange(lower, upper): 
    """Outputs the numbers from lower to upper.""" 
    if lower <= upper: 
        print(lower) 
        displayRange(lower + 1, upper)

尽管这两个函数的语法和设计是不一样的,但是它们执行的算法过程相同。递归函数的每次调用都像在迭代版本函数里的循环一样,每次都会访问整个序列里的下一个数。

通常来说,递归函数至少有一个参数。这个参数的值会被用来对递归过程的基本情况进行判定,从而决定是否要结束整个调用。在每次递归调用之前,这个值也会被进行某种方式的修改。每次对这个值的修改,都应该产生一个新数据值,可以让函数最终达到基本情况。在displayRange这个示例里,每次递归调用之前都会增加参数lower的值,从而让它最终能够超过参数upper的值。

接下来的示例是一个可以构建并且返回数值的递归函数。Python内置的sum函数会接收一个包含数字的多项集,并且返回它们的总和。本例中的这个函数会返回从下限到上限的数字之和。如果lower大于upper,那么ourSum递归函数将返回0(基本情况);否则,这个函数会把lower加到以lower+1upper为参数的ourSum函数的结果里,再返回这个最终结果。下面是这个函数的代码。

def ourSum(lower, upper):
    """Returns the sum of the numbers from lower thru upper."""
    if lower > upper:
        return 0
    else:
        return lower + ourSum(lower + 1, upper)

可以看到,ourSum函数的递归调用会把从lower + 1upper的数字进行相加,然后再把这个结果和lower相加并返回它。

要想更好地理解递归的工作流程,优选方法是跟踪递归调用。例如,为了对ourSum函数的递归版本进行跟踪,你可以添加一个代表缩进边距的参数并且添加一些print语句。这样在每次调用时,函数的第一条语句会计算缩进数量,然后在打印两个参数的值以及每次返回调用之前的返回值时都使用相同的缩进。这样就可以对两个参数的值以及每次调用的返回值进行跟踪了。下面是相应的代码以及它的交互式会话的结果。

def ourSum(lower, upper, margin = 0): 
    """Returns the sum of the numbers from lower to upper, 
    and outputs a trace of the arguments and return values 
    on each call.""" 
    blanks = " " * margin 
    print(blanks, lower, upper)         # Print the arguments
    if lower > upper: 
        print(blanks, 0)                # Print the returned value
        return 0
    else:
        result = lower + ourSum(lower + 1, upper, margin + 4)
        print(blanks, result)           # Print the returned value
        return result

交互式的结果如下。

>>> ourSum(1, 4) 
1 4 
   2 4 
      3 4 
         4 4 
            5 4 
            0 
         4 
      7 
   9 
10 
10

从结果可以看出,随着对ourSum调用的进行,参数会不断向右缩进。注意,每次调用时,lower的值都增加1,而upper的值始终保持不变。对ourSum的最后一次调用返回0。随着递归的返回,所返回的每个值都与其上面的值对齐,并且会增加上lower的当前值。这样的跟踪,对于递归函数来说,是非常有用的调试工具。

函数的定义是可以嵌套在一个函数的语句序列里的。下面这段代码是factorial(阶乘)递归函数的两个不同的定义。其中,第一个定义使用了嵌套的辅助函数来对所需要的参数进行递归;第二个定义则是为第二个参数提供了默认值,从而简化了设计。

# First definition
def factorial(n):
    """Returns the factorial of n."""

    def recurse(n, product):
        """Helper function to compute factorial."""
        if n == 1: return product
        else: return recurse(n - 1, n * product)

    return recurse(n, 1)

# Second definition
def factorial(n, product = 1):
    """Returns the factorial of n."""
    if n == 1: return product
    else: return factorial(n - 1, n * product)

在Python里,函数本身也是一种独特的数据对象。也就是说,你可以把它们赋给变量、存储在数据结构里、作为参数传递给其他函数以及作为其他函数的值返回。因此,我们这样来定义高阶函数(higher-order function):它接收另一个函数作为参数,并且以某种方式应用该函数。Python有两个内置的高阶函数,分别是mapfilter,它们可以用于处理可迭代对象。

如果你要把一个整数列表转换成另一个包含这些整数的字符串形式的列表,那么可以像下面这个示例这样,通过循环遍历每一个整数将其转换成字符串,再把这个字符串添加到新的列表里:

newList = []
for number in oldList: newList.append(str(number))

除了这种方法,你还可以使用map函数。这个函数会接收另一个函数和一个可迭代对象作为参数,然后返回另一个可迭代对象。这个函数会把作为参数传递的函数应用在可迭代对象里的每个元素上。简单来说,map函数会对可迭代对象里的每个元素进行转换。因此,下面的代码

map(str, oldList)

创建了包含字符串的可迭代对象,而代码

newList = list(map(str, oldList))

则会基于这个新创建出来的可迭代对象构建一个新列表。

假设你想从考试分数的一个列表中删除所有零分,只需使用下面这个循环语句:

newList = [] 
for number in oldList: 
    if number > 0: newList.append(number)

当然,你也可以使用filter函数完成这个操作。这个函数的参数是一个布尔函数以及一个可迭代对象。filter函数会返回这样一个可迭代对象,它的每一个元素都会被传递给布尔函数,如果这个函数返回True,那么这个元素将被保留在返回的可迭代对象里;否则,这个元素将被删除。简单来说,filter函数会把所有能够通过检验的元素保留在可迭代对象里。因此,如果程序员已经定义布尔函数isPositive(判断是否为正数),那么下面这段代码

filter(isPositive, oldList)

将创建一个不包含任何零分的可迭代对象,而代码

newList = list(filter(isPositive, oldList))

则会基于这个新创建出来的可迭代对象创建一个新列表。

程序员可以通过动态创建一个匿名函数来传递给map或是filter函数,从而避免定义只用一次的辅助函数(如isPositive)。要创建这些匿名函数,可以使用Python的lambda表达式来实现。lambda表达式的语法如下所示。

lambda <argument list> : <expression>

有一点需要注意的是,lambda表达式不能像其他Python函数那样包含一整个语句序列。下面这段代码

newList = list(filter(lambda number: number > 0, oldList))

就通过使用匿名的布尔函数来从成绩列表里剔除所有成绩为零的元素。

另一个高阶函数functools.reduce通过把接收两个参数的函数的结果以及迭代对象的下一个元素再次应用于这个接收两个参数的函数,来把可迭代对象计算成单一的值。因此,前面通过for循环来计算数字序列乘积的逻辑也可以写作:

import functools
product = functools.reduce(lambda x, y: x * y, range(1, 11))

如果Python虚拟机在程序执行期间遇到了语义错误,则会得到相应的错误消息,从而引发一个异常并且暂停程序。语义错误的一些简单示例包括未定义的变量名、除以0以及超出列表范围的索引等。这些错误对于程序员来说是一件好事,因为程序员可以对这些代码进行修正,从而得到更好的程序。但是,某些错误(例如,期望输入数字的时候输入了其他字符)是由用户引起的。对于在这些情况下产生的异常,程序不应该停止执行,而应该对这些异常进行捕获,并且允许用户修正错误。

Python提供了try-except语句,可以让程序捕获异常并执行相应的恢复操作。这个语句最简单形式的语法如下。

try:
    <statements>
except <exception type>:
    <statements>

运行上述语句时,try子句中的语句将先被执行。如果这些语句中的一条引发了异常,那么控制权会立即转移到except子句去。如果引发的异常类型和这个子句里的类型一致,那么会执行它里面的语句;否则,将转移到try-except语句的调用者,并基于调用链向上传递,直到这个异常被成功捕获,或者是程序因错误消息而停止执行。如果try子句里的语句没有引发任何异常,那么会跳过except子句并继续执行,直到try-except语句的末尾。

通常来说,如果你知道当前情况下可能会发生的异常类型,就应该在这个语句里包括它。如果不知道异常的类型,那么可以用更通用的Exception类型匹配可能会引发的任何异常。

在下面这个示例里,程序定义了一个名为safeIntegerInput的递归函数。这个函数会捕获ValueError异常。如果用户输入了错误的字符,这个异常就会被引发。这个函数会要求用户重新输入,直到用户输入格式正确的整数为止,然后它会把这个正确的整数返给调用者。

"""
Author: Ken Lambert
Demonstrates a function that traps number format errors during input.
"""

def safeIntegerInput(prompt): 
    """Prompts the user for an integer and returns the 
    integer if it is well-formed. Otherwise, prints an 
    error message and repeats this process.""" 
    inputString = input(prompt) 
    try: 
        number = int(inputString)
        return number
    except ValueError:
        print("Error in number format:", inputString)
        return safeIntegerInput(prompt)

if __name__ == "__main__": 
    age = safeIntegerInput("Enter your age: ") 
    print("Your age is", age)

上述程序的运行结果如下。

Enter your age: abc 
Error in number format: abc 
Enter your age: 6i 
Error in number format: 6i 
Enter your age: 61 
Your age is 61

Python为管理和处理若干种类型的文件提供了强大的支持。本节将探讨对文本文件以及对象文件的一些操作。

根据文本文件的格式和其中数据的用途,你可以把文本文件里的数据看作字符、单词、数字或者若干行文本。如果把这些数据当作整数或浮点数,就必须用空白字符(空格、制表符和换行符)将其分隔开。例如,含有6个浮点数的文本文件可能会像下面这样:

34.6 22.33 66.75 
77.12 21.44 99.01

用文本编辑器查看上述内容,可以看到文本中的各项元素是以空格和换行符作为分隔符的。

输出或输入到文本文件的所有数据必须是字符串形式的,因此在输出前,数字必须被转换为字符串,并且在输入后这些字符串必须被转换回数字。

你可以使用文件对象来把数据输出到文本文件。Python的open函数接收文件路径和用来指定打开模式的字符串作为参数,会打开一个与磁盘文件的连接并且返回相应的文件对象。对于输入到文件,模式字符串是'r';对于输出到文件,模式字符串是'w'。下面这段代码会为名叫myfile.txt的文件打开一个用来输出的文件对象。

>>> f = open("myfile.txt", 'w')

如果这个文件不存在,就会使用给定的路径名创建并打开这个文件;如果这个文件存在,Python会直接打开这个文件。当数据被输出到文件里并且关闭文件之后,文件里先前存在的数据都会被删除。

字符串数据通过write方法和文件对象写入(或输出)到文件里。write方法的参数是一个字符串。如果想要让输出的文本以换行符作为结尾,就需要在字符串里包含对应的转义符\n。下面这条语句会向文件里写入两行文本。

>>> f.write("First line.\nSecond line.\n")

在完成所有输出之后,你就可以使用close方法关闭文件了。

>>> f.close()

若没有关闭输出文件,则可能导致数据丢失。

文件的write方法接收一个字符串作为参数。因此,其他类型的数据(如整数或浮点数)在写入输出文件之前,都必须先被转换为字符串。在Python里,可以使用str函数把绝大多数的数据类型的值转换为字符串,然后以空格或换行符作为分隔符,将其写入文件里。

下面这段代码展示了如何把整数输出到文本文件。它会生成500个介于1和500之间的随机整数,并将这些整数写入名为integers.txt的文本文件。在这里,分隔符用的是换行符。

import random
f = open("integers.txt",’w’)
for count in range(500):
    number = random.randint(1, 500)
    f.write(str(number) + "\n") 
f.close()

打开文件进行输入的方法和打开文件进行输出是类似的。唯一需要做的是用不同的模式字符串。在打开文件进行输入的情况下,模式字符串应该用'r'。但是,如果从当前的工作目录没办法访问到路径,Python就会引发错误。用来打开myfile.txt文件进行输入的相应代码如下。

>>> f = open("myfile.txt", 'r')

从输入文件读取数据有若干种方法。最简单的是用文件的read方法把文件的全部内容输入单个字符串中。这时,如果文件含有多行文本,那么换行符会被嵌入这个字符串。下面这个与Shell窗口的交互展示了如何使用read方法。

>>> text = f.read()
>>> text
'First line.\nSecond line.\n'
>>> print(text)
First line.
Second line.

输入完成后,再执行read函数将得到一个空字符串,这表示已到达文件末尾。再要输入,就需要重新打开文件。在此过程中,并不需要关闭文件。

除了这种方式,应用程序还可以每次只读取和处理一行文本。这时需要用到for循环,它会把文件对象当作文本行的序列。在循环体里,循环变量每次都会被绑定到序列里的下一行文本。下面这段代码会重新打开示例文件,并访问其中的文本行。

>>> f = open("myfile.txt", 'r')
>>> for line in f:
        print(line)
First line.

Second line.

可以看到,print函数输出了一个额外的换行符,这是因为从文件输入的每一行文本都会保留其换行符。你可以用前面提到的print函数的可选参数从输出的文本里删除这个额外的换行符。

如果你想从文件里读取指定数量的文本行(如,只读第一行),那么可以使用文件的readline方法。readline方法会从输入的文本里只获取一行数据,并且返回这个包含换行符的字符串。如果readline遇到了文件末尾,那么会返回空字符串。下面这段代码使用循环while Truereadline方法输入所有文本行。

>>> f = open("myfile.txt", 'r')
>>> while True:
        line = f.readline()
        if line == "":
            break
        print(line)
First line.

Second line.

文件输入操作都会把数据作为字符串返回到程序。如果这些字符串是其他类型的数据(如整数或浮点数),那么程序员在对它们进行操作之前,必须先把它们转换为相应的类型。在Python里,可以分别使用intfloat函数把字符串形式的整数和浮点数转换为相应的数字。

从文件读取数据时,另一个重要的考虑因素是:文件里数据元素的格式。在前面的示例里,你看到了如何以换行符作为分隔把整数输出到文本文件。在输入时,这些数据可以通过for循环轻松读取,它的循环体会在每次遍历时访问一行文本。为了能够把这一行文本转换成相应的整数,程序员可以用字符串的strip方法来剔除换行符,再运行int函数来获得这个整数的值。

下面这段代码展示了这个流程。它会先打开先前写入了随机整数的文件,然后读取它们,最后打印出它们的总和。

f = open("integers.txt", 'r')
theSum = 0
for line in f:
    line = line.strip()
    number = int(line)
    theSum += number
print("The sum is", theSum)

从文本文件读取用空格分隔的数字相对麻烦一些。有一种方法是前面提到的:通过for循环读取一行数据。但是,每一行都有可能包含着若干个用空格分隔的整数。好在,你可以用字符串的split方法得到代表这些整数的字符串列表,然后用另一个for循环处理这个列表里的所有字符串。

下面这段代码对上一个示例进行了修改,能够处理用空格或换行符分隔的整数。

f = open("integers.txt", 'r')
theSum = 0
for line in f:
    wordlist = line.split()
    for word in wordlist:
        number = int(word)
        theSum += number
print("The sum is", theSum)

可以看到,我们并没有从数据行里剔除换行符,因为split方法会自动处理它。

通常来说,应该尽可能地简化代码。例如,在前面的两个示例里,我们通过循环来对整数序列进行求和。Python中有一个叫作sum的内置函数,这个函数可以用来执行相应操作。但是,在调用这个函数之前,我们必须先把输入文件里的单词序列转换成整数序列。你可以在不使用循环的情况下,通过下面4个步骤来完成所需要的操作。

下面是这个操作的简化版本,只有两行代码:

f = open("integers.txt", 'r')
print("The sum is", sum(map(int, f.read().split())))

因为split方法会把单词之间的空格或换行符都识别为分隔符,所以针对两种使用不同格式来对数据值进行分隔的文件,这段代码都可以正常处理。

你可以把任何对象转换为文本加以存储,但是把复杂的对象映射为文本再映射回来可能会很烦琐,并且维护起来也非常麻烦。好在Python有一个模块,能让程序员使用名为pickle(腌制)的过程来保存和加载对象。这个名称的原意是指把黄瓜进行腌制并保存到罐子里。但是,在对对象进行转换的情况下,你还会把腌黄瓜“反腌制”得到普通黄瓜。因此,在把任何对象保存到文件之前,你可以对它进行“腌制”,然后在把它从文件加载到程序中时,可以对它进行“反腌制”。在此过程中,Python会自动处理所有转换细节。

首先导入pickle模块。然后,通过标志"rb""wb"(用于字节流)打开文件,从而进行输入与输出操作,最后和之前一样关闭文件。要保存对象,可以使用pickle.dump函数。它的第一个参数是需要被“转储”——保存——到文件里的对象,其第二个参数是文件对象。

例如,你可以使用pickle模块把名为lyst的列表里的所有对象保存到名为items.dat的文件里。在此过程中,你不需要知道列表里有哪些类型的对象,也不需要知道有多少个对象。下面是相应的代码:

import pickle
lyst = [60, "A string object", 1977]
fileObj = open("items.dat", "wb")
for item in lyst:
    pickle.dump(item, fileObj)
fileObj.close()

在这个示例里,你可以一次性把整个列表写入文件,而不用逐一写入列表里的每个对象。但需要注意的是,对于本书里讨论的某些多项集类型(例如基于链接结构的多项集)来说,你是不能这么做的。在这些情况下,你只能把多项集里的每个元素逐一写入文件,然后根据文件的输入内容重新构建整个多项集。

要把“腌制”好了的对象从文件里再加载回到程序中,你可以使用pickle.load函数。如果已经处于文件末尾,那么这个函数会引发异常。由于没有一个明确的方法可以在引发异常之前检测到是否到达文件末尾,输入的过程变得复杂起来。不过好在有Python的try-except语句。这条语句可以捕获异常并让程序恢复运行。

现在,你可以构建出一个输入文件的循环了,整个循环将会持续不断地加载对象,直到文件末尾。当到达文件末尾时,程序会引发EOFError异常。随后,except子句将关闭文件并退出循环。如下代码用于把对象从items.dat文件加载回名为lyst的新列表里。

lyst = list()
fileObj = open("items.dat", "rb")
while True:
    try:
        item = pickle.load(fileObj)
        lyst.append(item)
    except EOFError:             # End of input detected here
        fileObj.close()
        break
print(lyst)

(class)用来描述与一组对象有关的数据和方法。它提供了用来创建对象的蓝图,以及在对象上调用方法时所需要执行的代码。Python里的数据类型都是类。

在Python中,类定义的语法如下。

def <class name>(<parent class name>)[2]:

    <class variable assignments>

    <instance method definitions>

类名按照惯例首字母应为大写样式,而定义类的代码通常会被存放在首字母小写的类名的模块文件里。相关的类也可能会出现在同一个模块里。

父类(parent class)的名称是可选的,在默认情况下,它会是object。所有Python类属于一个以object作为根节点的层次结构。在object里,Python定义了几种方法:__str____eq__,因此所有子类会自动继承这些方法。稍后你将看到,这些方法为任何新的类都提供了最基础的一些默认行为。

实例方法(instance method)是在类的对象上运行的。它们包含用来访问或修改实例变量的代码。实例变量(instance variable)是指由单个对象所拥有的存储信息。

类变量(class variable)是指由类的所有对象存储所有的信息。

为了说明这些概念,我们将探讨定义Counter(计数器)类的代码。顾名思义,计数器对象会跟踪一个整数的计数。计数器的值最初为0,也可以随时重置为0。你可以对计数器进行递增或者递减、获取其当前的整数值、获取其字符串表达式以及比较两个计数器是否相等。相应代码如下。

class Counter(object):
    ""Models a counter."""

    # Class variable
    instances = 0

    # Constructor
    def __init__(self):
        """Sets up the counter."""
        Counter.instances += 1
        self.reset()

    # Mutator methods
    def reset(self):
        """Sets the counter to 0."""
        self.value = 0

    def increment(self, amount = 1):
        """Adds amount to the counter."""
        self.value += amount

    def decrement(self, amount = 1):
        """Subtracts amount from the counter."""
        self.value -= amount

    # Accessor methods
    def getValue(self):
        """Returns the counter's value."""
        return self.value

    def __str__(self):
        """Returns the string representation of the counter."""
        return str(self._value) 

    def __eq__(self, other):
        """Returns True if self equals other
        or False otherwise."""
        if self is other: return True
        if type(self) != type(other): return False
        return self.value == other.value

在Python的Shell窗口里对计数器对象的操作结果如下。

>>> from counter import Counter
>>> c1 = Counter()
>>> print(c1)
0
>>> c1.getValue()
0
>>> str(c1)
'0'
>>> c1.increment()
>>> print(c1)
1
>>> c1.increment(5)
>>> print(c1)
6
>>> c1.reset()
>>> print(c1)
0
>>> c2 = Counter()
>>> Counter.instances
2
>>> c1 == c1
True
>>> c1 == 0
False
>>> c1 == c2
True
>>> c2.increment()
>>> c1 == c2
False

接下来,我们会对这些代码和结果进行一些观察。

Counter类是object的子类。类变量instances会跟踪已创建的计数器对象的数量。除了对它进行最初赋值,类变量必须以类名作为前缀进行访问。

定义实例方法的语法和定义函数的语法是类似的。但是实例方法会有一个名为self的额外的参数,并且这个参数总是出现在参数列表的开头。在方法定义的上下文里,self是指在运行时这个方法的对象本身。

创建Counter的实例之后,实例方法__init__(也称为构造函数)将自动运行。这个方法用来初始化实例变量,并且对类变量进行更新。可以看到,__init__通过语法self.reset()调用reset实例方法,从而对单个实例变量进行初始化。

其他实例方法可以分为两种:变异器(mutator)和访问器(accessor)。变异器会通过修改对象的实例变量对其内部状态进行修改或更改。访问器则只会查看或使用对象的实例变量的值,而不会去修改它们。

reset实例方法被首次调用时,它引入了实例变量self.value。之后,对这个方法的任何其他调用,都会将这个变量的值修改为0。

使用实例变量都会加上前缀self。和参数或临时变量不同的地方是,实例变量在类的任何方法里是可见的。

incrementdecrement方法都包含默认参数,从而为程序员提供了指定数目的可能性。

Counter类的__str__方法将覆盖object类里的这个方法。当把这个对象作为参数传递给str函数时,Python会调用对象的__str__方法。在运行对象上的方法时,Python会先在这个对象自己的类里查找相应方法的代码。如果找不到这个方法,那么Python将在它的父类里查找,以此类推。如果在最后(在查看object类之后)还是找不到这个方法的代码,Python就会引发异常。

当Python的print函数接收到一个参数时,这个参数的__str__方法将自动运行,从而得到它的字符串表达式,以便用来输出。我们鼓励程序员为每个新定义的类实现__str__方法,从而对调试提供帮助。

当看到==运算符时,Python将运行__eq__方法。在object类里,这个方法的默认定义是运行is运算符。这个运算符会对两个操作数的对象标识进行比较。在本例中,对于两个不同的计数器对象,只要它们具有相同的值,就应该被视为相等。==的第二个操作数可以是任意对象,因此__eq__方法会在访问实例变量之前先判断操作数的类型是否相同。注意,你可以通过对象上的点运算符访问它的实例变量。

要开发出自己的Python类,你还需要了解很多其他内容,而这就是本书后续章节将涵盖的内容。

1.编写一个程序,使之能够接收球体的半径(浮点数),并且可以输出球体的直径、周长、表面积以及体积。

2.员工的周工资等于小时工资乘以正常的总工作时间再加上加班工资。加班工资等于总加班时间乘以小时工资的1.5倍。编写一个程序,让用户可以输入小时工资、正常的总工作时间以及加班总时间,然后显示出员工的周工资。

3.有一个标准的科学实验:扔一个球,看看它能反弹多高。一旦确定了球的“反弹高度”,这个比值就给出了相应的反弹度指数。例如,如果从10ft(1ft=0.3048m)高处掉落的球可以反弹到6 ft高,那么相应的反弹度指数就是0.6;在一次反弹之后,球的总行进距离是16 ft。接下来,球继续弹跳,那么两次弹跳后的总距离应该是:10 ft + 6 ft + 6 ft + 3.6 ft = 25.6 ft。可以看到,每次连续弹跳所经过的距离是:球到地面的距离,加上这个距离乘以 0.6,这时球又弹回来了。编写一个程序,可以让用户输入球的初始高度和允许球弹跳的次数,并输出球所经过的总距离。

4.德国数学家Gottfried Leibniz发明了下面这个用来求π的近似值的方法:

π/4 = 1−1/3 + 1/5 − 1/7 + …

请编写一个程序,让用户可以指定这个近似值所使用的迭代次数,并且显示出结果。

5.TidBit计算机商店有购买计算机的信贷计划:首付10%,年利率为12%,每月所付款为购买价格减去首付之后的5%。编写一个以购买价格为输入的程序,可以输出一个有适当标题的表格,显示贷款期限内的付款计划。表的每一行都应包含下面各项:

一个月的利息等于余额 × 利率/12;一个月所欠的本金等于当月还款额减去所欠的利息。

6.财务部门在文本文件里保存了所有员工在每个工资周期里的信息列表。文件中每一行的格式如下:

<last name> <hourly wage> <hours worked>

请编写一个程序,让用户可以输入文件的名称,并在终端上打印出给定时间内支付给每个员工的工资报告。这个报告是一个有合适标题的表,其中每行都应该包含员工的姓名、工作时长以及给定时间内所支付的工资。

7.统计学家希望使用一组函数计算数字列表的中位数(median)和众数(mode)。中位数是指如果对列表进行排序将会出现在列表中点的数字,众数是指列表中最常出现的数字。把这些功能定义在名叫stats.py的模块中。除此之外,模块还应该包含一个名叫mean的函数,用来计算一组数字的平均值。每个函数都会接收一个数字列表作为参数,并返回一个数字。

8.编写程序,让用户可以浏览文件里的文本行。这个程序会提示用户输入文件名,然后把文本行都输入列表。接下来,这个程序会进入一个循环,在这个循环里打印出文件的总行数,并提示用户输入行号。这个行号的范围应当是1到文件的总行数。如果输入是0,那么程序退出;否则,程序将打印出行号所对应的文本行。

9.在本章讨论的numberguess程序里,计算机会“构思”一个数字,而用户则输入猜测的值,直到猜对为止。编写这样一个程序,使其可以调换这两个角色,也就是:用户去“构思”一个数字,然后计算机去计算并输出猜测的值。和前面那个游戏版本一样,当计算机猜错时,用户必须给出相应的提示,例如“<”和“>”(分别代表“我的数字更小”和“我的数字更大”)。当计算机猜对时,用户应该输入“=”。用户需要在程序启动的时候输入数字的下限和上限。计算机应该在最多[log2(high−low)+1]次猜测里找到正确的数字。程序应该能够跟踪猜测次数,如果猜测错误的次数到了允许猜测的最大值但还没有猜对,就输出消息“You're cheating!”。下面是和这个程序进行交互的示例:

Enter the smaller number: 1
Enter the larger number: 100
Your number is 50
Enter =, <, or >: >
Your number is 75
Enter =, <, or >: <
Your number is 62
Enter =, <, or >: <
Your number is 56
Enter =, <, or >: =
Hooray, I've got it in 4 tries!

10.有一个简单的课程管理系统,它通过使用名字和一组考试分数来模拟学生的信息。这个系统应该能够创建一个具有给定名字和分数(起初均为0)的学生对象。系统应该能够访问和替换指定位置处的分数(从0开始计数)、得到学生有多少次考试、得到的最高分、得到的平均分以及学生的姓名。除此之外,在打印学生对象的时候,应该像下面这样显示学生的姓名和分数:

Name: Ken Lambert
Score 1: 88
Score 2: 77
Score 3: 100

请定义一个支持这些功能和行为的Student类,并且编写一个创建Student对象并运行其方法的简短的测试函数。

[1] 这里的“多项集”即collection,取自Python官方文档的说法。collection在计算机领域意指所有能够包含元素的数据结构。——译者注

[2] 例子里的第一行代码应该是class <class name>(<parent class name>),原文有误。——译者注


相关图书

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

相关文章

相关课程