【美】埃里克·马瑟斯(著) 袁国忠(译)
适用python3.6及以上版本
文章目录
- Python编程:从入门到实践(第二版)
- 第 2 章 变量和简单数据类型
- 前言
- 2.1 变量
- 1.定义
- 2.命名和使用
- 2.2字符串
- 1.定义
- 2.修改字符串的方法
- 2.3 数
- 1.整数
- 2.浮点数
- 3.计算结果
- 4.数中的下划线
- 5.同时给多个变量赋值
- 6.常量
- 2.4 注释
- 总结自测
- 第3章 列表简介
- 前言
- 3.1 定义
- 3.2 访问元素
- 3.3 元素调用方法
- 3.4 元素索引
- 3.5 像变量一样使用元素
- 3.6 修改、添加和删除元素
- 1、 修改
- 2、添加
- 3、删除
- 3.7 组织列表
- 1. 使用方法sort() 对列表永久排序
- 2. 使用函数sorted() 对列表临时排序
- 3. 倒着打印列表
- 4. 确定列表的长度
- 3.8 使用列表时避免索引(index)错误
- 总结自测
- 第 4 章 操作列表
- 前言
- 4.1 遍历整个列表
- 1. 理解for循环
- 2. 在for 循环中执行更多操作
- 4.2 避免缩进错误
- 4.3 创建数值列表
- 1. 使用函数range()
- 2. 使用range() 创建数字列表
- 3. 对数字列表执行简单的统计计算
- 4. 列表解析
- 4.4 使用列表的一部分
- 1. 切片
- 2.遍历切片
- 3. 复制列表
- 4.5 元组(tuple)
- 1. 定义
- 2. 遍历元组中的所有值
- 3. 修改元组变量
- 4.6 设置代码格式
- 总结自测
- 第 5 章 if 语句
- 前言
- 5.1 一个简单示例
- 5.2 条件测试
- 1. 检查是否相等
- 2.检查不相等
- 3.数值比较
- 4.检查多个条件
- 5.检查特定值是否包含在列表中
- 6.布尔表达式
- 5.3 if-else 语句
- 1. 简单的if 语句
- 2. if-else 语句
- 3. if-elif-else 结构
- 4.省略else 代码块
- 5.多个if
- 5.4 使用if 语句处理列表
- 1.确定列表不是空的
- 5.5 设置if 语句的格式
- 总结自测
- 第 6 章 字典
- 前言
- 6.1 创建字典
- 6.2 使用字典
- 1. 访问字典中的值
- 2. 添加键值对
- 3. 动态创建字典
- 4. 修改字典中的值
- 5. 删除键值对
- 6. 由类似对象组成的字典
- 7.使用get() 来访问值
- 6.3 遍历字典
- 1. 遍历所有键值对
- 2. 遍历字典中的所有键
- 3.按特定顺序遍历字典中的所有键
- 4. 遍历字典中的所有值
- 6.4 嵌套
- 1. 在列表中存储字典
- 2. 在字典中存储列表
- 3. 在字典中存储字典
- 总结自测
- 第 7 章 用户输入和while 循环
- 前言
- 7.1 函数input() 的工作原理
- 1. 编写清晰的程序
- 2 使用int() 来获取数值输入
- 3 求模运算符
- 7.2 while 循环简介
- 1 使用while 循环
- 2 让用户选择何时退出
- 3 使用标志
- 4 使用break 退出循环
- 5 在循环中使用continue
- 6 避免无限循环
- 7.3 使用while 循环处理列表和字典
- 1 在列表之间移动元素
- 2 删除为特定值的所有列表元素
- 3 使用用户输入来填充字典
- 总结自测
- 第 8 章 函数
- 前言
- 8.1 定义函数
- 1 向函数传递信息
- 2 实参和形参
- 8.2 传递实参
- 1 位置实参
- 2 关键字实参
- 3 默认值
- 4 等效的函数调用
- 5 避免实参错误
- 8.3 返回值
- 1 返回简单值
- 2 让实参变成可选的
- 3 返回字典
- 8.4 传递列表
- 1 在函数中修改列表
- 2 禁止函数修改列表
- 8.5 传递任意数量的实参
- 1 结合使用位置实参和任意数量实参
- 2 使用任意数量的关键字实参
- 8.6 将函数存储在模块中
- 1 导入整个模块
- 2 导入特定的函数
- 3 使用as 给模块指定别名
- 4 使用as 给函数指定别名
- 5 导入模块中的所有函数
- 8.7 函数编写指南
- 第 9 章 类
- 前言
- 9.1 创建和使用类
- 1 创建Dog 类
- 2 根据类创建实例
- a. 访问属性
- b. 调用方法
- c. 创建多个实例
- 9.2 使用类和实例
- 1 Car 类
- 2 给属性指定默认值
- 3 修改属性的值
- a. 直接修改属性的值
- b. 通过方法修改属性的值
- c. 通过方法对属性的值进行递增
- 9.3 继承
- 1 子类的方法__init__()
- 2 给子类定义属性和方法
- 3 重写父类的方法
- 4 将实例用作属性
- 9.4 导入类
- 1 导入单个类
- 2 在一个模块中存储多个类
- 3 从一个模块中导入多个类
- 4 导入整个模块
- 5 导入模块中的所有类
- 6 在一个模块中导入另一个模块
- 7 使用别名
- 8 自定义工作流程
- 9.5 Python标准库
- 9.6 类编码风格
- 第 10 章 文件和异常
- 前言
- 10.1 从文件中读取数据
- 1 读取整个文件
- 2 文件路径
- 3 逐行读取
- 4 创建一个包含文件各行内容的列表
- 5 使用文件的内容
- 6 包含一百万位的大型文件
- 7 圆周率值中包含你的生日吗
- 10.2 写入文件
- 1 写入空文件
- 2 写入多行
- 3 附加到文件
- 10.3 异常
- 1 处理ZeroDivisionError 异常
- 2 使用try-except 代码块
- 3 使用异常避免崩溃
- 4 else 代码块
- 5 处理FileNotFoundError 异常
- 6 分析文本
- 7 使用多个文件
- 8 静默失败
- 9 决定报告哪些错误
- 10.4 存储数据
- 1 使用json.dump() 和json.load()
- 2 保存和读取用户生成的数据
- 3 重构
- 第 11 章 测试代码
- 前言
- 11.1 测试函数
- 1 单元测试和测试用例
- 3 未通过的测试
- 4 测试未通过时怎么办
- 5 添加新测试
- 11.2 测试类
- 1 各种断言方法
- 2 一个要测试的类
- 3 测试AnonymousSurvey 类
- 4 方法setUp()
第 2 章 变量和简单数据类型 前言
主要介绍了变量、字符串、数的使用以及其相关函数可以实现的功能,最后讲解了注释的语法及其运用。
2.1 变量 1.定义
变量是可以赋值的标签,也可以理解为变量指向特定的值。
2.命名和使用- 变量名只能包含:字母、数字、下划线
- 变量名只能由:字母和下划线 打头,不能由数字打头。
- 变量名不能是 python关键字 和 函数名。例如:False、print
- 变量名应简短且富有描述性,用下划线_ 间隔单词。例如:teacher_name
- 变量名要谨慎使用**字母 l 和 o **,以免和数字1与 0混淆。
使用变量错误时,解释器会指出错误:NameError
2.2字符串 1.定义-
字符串就是一系列字符。用引号:单引号或者双引号 括起。例如:‘This is a string ! ’ "This is a
string ! " -
可以包含单、双引号
代码如下(示例):
‘This is a string "Hello World!" ! ’ #用单引号括字符串,那么此字符串只能够包含双引号。 "This is a string 'Hello World!' " #用双引号括字符串,那么此字符串只能够包含单引号。2.修改字符串的方法
- 修改字符串大小写
title() 将单词的首字母修改为大写
代码如下:
name = 'eric aloben' print(name.title())
解释结果:
Eric Aloben
全部大写
upper()
代码如下:
name = 'eric aloben' print(name.upper())
解释结果:
ERIC ALOBEN
全部小写
lower()
代码如下:
name = 'ERIC ALObEN' print(name.lower())
解释结果:
eric aloben
- 在字符串中使用变量
这种字符串名为f字符串。f是format(设置格式)的简写,因为Python通过把花括号内的变量替换为其值来设置字符串的格式。
例如:f"{变量名1} {变量名2}"
可以用来显示一条格式良好的信息。
first_name = "ada"
last_name = "lovelace"
full_name = f"{first_name} {last_name}"
print(full_name)
上述代码的输出如下:
ada lovelace
- 使用制表符或换行符添加空白
字符串添加制表符,可使用字符组合t
命令行敲的代码 (>>>后面的是代码,下一行黑色字体是输出结果)
>>> print("Python")
Python
>>> print("tPython")
Python
字符串中添加换行符,可使用字符组合n
>>> print("Languages:nPythonnCnJavaScript")
Languages:
Python
C
JavaScript
- 删除空白
删除字符串尾空白
rstrip( )
这种删除只是暂时的,接下来再次询问favorite_language 的值时,你会发现这
个字符串与输入时一样,依然包含多余的空白。
>>> favorite_language = 'python ' >>> favorite_language 'python ' >>> favorite_language.rstrip() 'python' >>> favorite_language 'python '
要永久删除这个字符串中的空白,必须将删除操作的结果关联到变量:
>>> favorite_language = 'python ' >>> favorite_language = favorite_language.rstrip() >>> favorite_language 'python'
删除字符串头空白
lstrip( )
删除字符串头尾空白
strip( )
2.3 数 1.整数
- 在Python中,可对整数执行加(+ )减(- )乘(* )除(/ )运算。
- 两个乘号 ** 表示乘方运算
例如:3 ** 2 输出结果为9 - 使用圆括号来修改运算次序
Python将所有带小数点的数称为浮点数 。大多数编程语言使用了这个术语,它指出了这
样一个事实:**小数点可出现在数的任何位置。**每种编程语言都必须细心设计,以妥善地
处理浮点数,确保不管小数点出现在什么位置,数的行为都是正常的。
但需要注意的是,结果包含的小数位数可能是不确定的:(所有语言都存在这种问题,没有什么可担心的。Python会尽力找到一种精确表示结果的方法,但鉴于计算机内部表示数的方式,这在有些情况下很难。就现在而言,暂时忽略多余的小数位数即可。在以后的项目中,你将在需要时学习处理多余小数位的方式。)
>>> 0.2 + 0.1 0.30000000000000004 >>> 3 * 0.1 0.300000000000000043.计算结果
将任意两个数相除时,结果总是浮点数,即便这两个数都是整数且能整除:
>>> 4/2 2.0
在其他任何运算中,如果一个操作数是整数,另一个操作数是浮点数,结果也总是浮点数。
>>> 1 + 2.0 3.0 >>> 2 * 3.0 6.0 >>> 3.0 ** 2 9.04.数中的下划线
书写很大的数时,可使用下划线将其中的数字分组,使其更清晰易读,打印这种使用下划线定义的数时,Python不会打印其中的下划线,表示法适用于整数和浮点数:
>>> universe_age = 14_000_000_000 >>>> print(universe_age) 140000000005.同时给多个变量赋值
需要用逗号将变量名分开;对于要赋给变量的值,也需同样处理。Python将按顺序将每个值赋给对应的变量。只要变量和值的个数相同,Python就能正确地将它们关联起来。
>>> x, y, z = 0, 0, 06.常量
类似于变量,但其值在程序的整个生命周期内保持不变。Python没有内置的常量类型,但Python程序员会使用全大写来指出应将某个变量视为常量,其值应始终不变:
MAX_CONNECTIONS = 50002.4 注释
在Python中,注释用 井号(# ) 标识。井号后面的内容都会被Python解释器忽略,如下所示:
# 向大家问好。
print("Hello Python people!")
选中代码段,Ctrl + / ,整体变为注释
总结自测自测(是否了解以下格式或方法所实现的功能)
- 1upper_命名变量是否正确?
- f"{}{}"
- title()
- upper()
- lower()
- strip()
- lstrip()
- 4/2输出结果
- 4.0/2输出结果
- 2 ** 2输出结果
- x,y,z = 1,2,3
本章主要学习了列表的定义和使用。列表是Python最为强大的功能之一,融合了众多的编程概念。
3.1 定义- 有一系列按特定顺序排列的元素组成。其中的元素可以没有任何逻辑关系。
- 因为通常包含多个元素,所以给列表指定一个复数的名字。例如:names
- 在Python中,用方括号([] )表示列表,并用逗号分隔其中的元素。
bicycles = ['trek', 'cannondale', 'redline', 'specialized'] print(bicycles)
3.2 访问元素运行结果:如果让Python将列表打印出来,Python将打印列表的内部表示,包括方括号。
要访问列表元素,可指出列表的名称,再指出元素的索引,并将后者放在方括号内。
bicycles = ['trek', 'cannondale', 'redline', 'specialized'] print(bicycles[0])
3.3 元素调用方法运行结果:当你请求获取列表元素时,Python只返回该元素,而不
包括方括号
如果列表元素是字符串的话,可以调用第2章介绍的字符串方法。其他类型同样。
bicycles = ['trek', 'cannondale', 'redline', 'specialized'] print(bicycles[0].title())3.4 元素索引
- 从 0 开始
- 通过 ** -1 访问最后一个列表元素** (仅当列表为空时,这种访问最后一个元素的方式会导致错误), -2 访问倒数第二个,-3 访问倒数第三个,以此类推。
bicycles = ['trek', 'cannondale', 'redline', 'specialized'] print(bicycles[-1]
3.5 像变量一样使用元素输出结果
specialized
bicycles = ['trek', 'cannondale', 'redline', 'specialized']
message = f"My first bicycle was a {bicycles[0].title()}."
print(message)
3.6 修改、添加和删除元素 1、 修改输出
指定列表名和要修改的元素的索引,再指定该元素的新值。
motorcycles = ['honda', 'yamaha', 'suzuki'] print(motorcycles) motorcycles[0] = 'ducati' print(motorcycles)
2、添加输出
[‘honda’, ‘yamaha’, ‘suzuki’]
[‘ducati’, ‘yamaha’, ‘suzuki’]
- 在列表末尾添加元素: append()
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)
motorcycles.append('ducati')
print(motorcycles)
输出
[‘honda’, ‘yamaha’, ‘suzuki’]
[‘honda’, ‘yamaha’, ‘suzuki’, ‘ducati’]
动态地创建列表
先创建一个空列表,再使用一系列函数调
用 append() 来添加元素。
motorcycles = []
motorcycles.append('honda')
motorcycles.append('yamaha')
motorcycles.append('suzuki')
print(motorcycles)
输出
[‘honda’, ‘yamaha’, ‘suzuki’]
- 在列表中插入元素
insert() 可在列表的任何位置添加新元素。为此,你需要指定新元素的索引和值。这种操作将插入位置以后的每个元素都右移一个位置。
motorcycles = ['honda', 'yamaha', 'suzuki'] motorcycles.insert(0, 'ducati') print(motorcycles)
3、删除输出:
[‘ducati’, ‘honda’, ‘yamaha’, ‘suzuki’]
- 使用del 语句删除元素
知道索引,可以从列表中删除一个元素。
motorcycles = ['honda', 'yamaha', 'suzuki'] print(motorcycles) del motorcycles[1] print(motorcycles)
输出:
[‘honda’, ‘yamaha’, ‘suzuki’]
[‘honda’, ‘suzuki’]
使用del 语句将值从列表中删除后,你就无法再访问它了。
- 使用方法pop() 删除元素
有时候要将元素从列表中删除,并接着使用它的值。
方法pop() 删除列表末尾的元素,并让你能够接着使用它。
motorcycles = ['honda', 'yamaha', 'suzuki'] print(motorcycles) popped_motorcycle = motorcycles.pop() print(motorcycles) print(popped_motorcycle)
输出:
[‘honda’, ‘yamaha’, ‘suzuki’]
[‘honda’, ‘yamaha’]
suzuki
输出表明,列表末尾的值’suzuki’ 已删除,它现在被赋给了变量
popped_motorcycle
使用pop() 来删除列表中任意位置的元素,只需在圆括号中指定要删除元素的索引即可。
motorcycles = ['honda', 'yamaha', 'suzuki']
first_owned = motorcycles.pop(0)
print(f"The first motorcycle I owned was a {first_owned.title()}.")
输出:
The first motorcycle I owned was a Honda.
- remove() 删除元素
不知道要从列表中删除的值所处的位置,只知道要删除的元素的值,可使用方法remove() 。
方法remove() 只删除第一个指定的值。 如果要删除的值可能在列表中出现多次,就需要使用循环来确保将每个值都删除。
motorcycles = ['honda', 'yamaha', 'suzuki', 'ducati']
print(motorcycles)
motorcycles.remove('ducati')
print(motorcycles)
3.7 组织列表 1. 使用方法sort() 对列表永久排序输出:
[‘honda’, ‘yamaha’, ‘suzuki’, ‘ducati’]
[‘honda’, ‘yamaha’, ‘suzuki’]
如果列表中的字符串元素都是小写字母的话,可以将列表中的元素按照首字母的大小升序排序。(ps:这个函数里面的参数较多,根据参数设置的不同会有不同的用法,后面会讲到。)
cars = ['bmw', 'audi', 'toyota', 'subaru'] cars.sort() print(cars)
输出:
[‘audi’, ‘bmw’, ‘subaru’, ‘toyota’]
对列表元素排列顺序的修改是永久性的
降序
将reverse参数从默认的False修改为True
cars = ['bmw', 'audi', 'toyota', 'subaru'] cars.sort(reverse=True) print(cars)
2. 使用函数sorted() 对列表临时排序输出:
[‘toyota’, ‘subaru’, ‘bmw’, ‘audi’]
函数sorted() 让你能够按特定顺序显示列表元素,同时不影响它们在列表中的原始排列顺序。
升序
cars = ['bmw', 'audi', 'toyota', 'subaru']
print("Here is the original list:")
print(cars)
print("nHere is the sorted list:")
print(sorted(cars))
print("nHere is the original list again:")
print(cars)
输出:
Here is the original list:
[‘bmw’, ‘audi’, ‘toyota’, ‘subaru’]
Here is the sorted list:
[‘audi’, ‘bmw’, ‘subaru’, ‘toyota’]
Here is the original list again:
[‘bmw’, ‘audi’, ‘toyota’, ‘subaru’]
降序
可向函数sorted() 传递参数reverse=True
要反转列表元素的排列顺序,可使用方法reverse() 。假设汽车列表是按购买时间排列的,可轻松地按相反的顺序排列其中的汽车:
方法reverse() 永久性地修改列表元素的排列顺序,但可随时恢复到原来的排列顺序,只需对列表再次调用reverse() 即可。
cars = ['bmw', 'audi', 'toyota', 'subaru'] print(cars) cars.reverse() print(cars)
4. 确定列表的长度输出:
[‘bmw’, ‘audi’, ‘toyota’, ‘subaru’]
[‘subaru’, ‘toyota’, ‘audi’, ‘bmw’]
使用函数len() 可快速获悉列表的长度。
命令行运行:
>>> cars = ['bmw', 'audi', 'toyota', 'subaru'] >>> print(len(cars)) 43.8 使用列表时避免索引(index)错误
刚开始使用列表时,经常会遇到一种错误。假设你有一个包含三个元素的列表,却要求获取第四个元素:
motorcycles = ['honda', 'yamaha', 'suzuki'] print(motorcycles[3])
总结自测这将导致索引错误(IndexError) :
Traceback (most recent call last):
File “motorcycles.py”, line 2, in
print(motorcycles[3])
IndexError: list index out of range
- names = [a,b,c]
print(names[-2]
以上代码的功能是什么? - append() 与 insert()各自的功能以及有什么不同?
- del语句,pop(), remove()各自的功能以及有什么不同?
- sort(), sorted(), reverse(), len()各自的功能是什么?
在本章介绍了学习如何遍历整个列表。
4.1 遍历整个列表 1. 理解for循环❶ magicians = ['alice', 'david', 'carolina'] ❷ for magician in magicians: ❸ print(magician)
首先,像第3章那样定义一个列表(见❶)。接下来,定义一个for 循环(见❷)。这行代码让Python从列表magicians 中取出一个名字,并将其与变量magician相关联。最后,让Python打印前面赋给变量magician 的名字(见❸)。这样,对于列表中的每个名字,Python都将重复执行❷处和❸处的代码行。你可以这样解读这些代码:对于列表magicians 中的每位魔术师,都将其名字打印出来。输出很简单,就是列表中所有的名字:
2. 在for 循环中执行更多操作输出:
alice
david
carolina
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
❶print(f"{magician.title()}, that was a great trick!")
相比于前一个示例,唯一的不同是为每位魔术师打印了一条以其名字为抬头的消息(见❶)。这个循环第一次迭代时,变量magician 的值为’alice’ ,因此Python打印的第一条消息的抬头为’Alice’ ;第二次迭代时,消息的抬头为’David’ ;第三次迭代时,抬头为’Carolina’ 。
输出
Alice, that was a great trick!
David, that was a great trick!
Carolina, that was a great trick!
在for 循环中,想包含多少行代码都可以。这些代码必须缩进。
4.2 避免缩进错误看一些较为常见的缩进(以及其他)错误
- 忘记缩进
magicians = ['alice', 'david', 'carolina'] for magician in magicians: print(magician)
输出:
File “magicians.py”, line 3
print(magician)
^
IndentationError: expected an indented block
- 忘记缩进额外的代码行
属于逻辑错误。代码不报错,但结果出乎意料。 - 不必要的缩进
message = "Hello Python world!" print(message)
输出:
File “hello_world.py”, line 2
print(message)
^
IndentationError: unexpected indent
- 循环后不必要的缩进
属于逻辑错误。代码不报错,但结果出乎意料。 - 遗漏了冒号
magicians = ['alice', 'david', 'carolina'] for magician in magicians print(magician)
极其容易出现冒号错误,要多加注意!!!
4.3 创建数值列表 1. 使用函数range()函数range() 让Python从指定的第一个值开始数,并在到达你指定的第二个值前一个数时停止。
for value in range(1, 5): print(value)
2. 使用range() 创建数字列表输出:
1
2
3
4
numbers = list(range(1, 6)) print(numbers)
输出:
[1, 2, 3, 4, 5]
使用函数range() 时,还可指定步长。为此,可给这个函数指定第三个参数,Python将根据这个步长来生成数。
例如,下面的代码打印1~10的偶数:
even_numbers = list(range(2, 11, 2)) print(even_numbers)
下面的代码演示了如何将前10个整数的平方加入一个列表中:
❶ squares = [] ❷ for value in range(1, 11): ❸ square = value ** 2 ❹ squares.append(square) ❺ print(squares)
首先,创建一个名为squares 的空列表(见❶)。接下来,使用函数range() 让Python遍历1~10的值(见❷)。在循环中,计算当前值的平方,并将结果赋给变量square (见❸)。然后,将新计算得到的平方值附加到列表squares 末尾(见❹)。最后,循环结束后,打印列表squares (见❺):
输出:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
也可以这样写:
squares = [] for value in range(1,11): ❶ squares.append(value**2) print(squares)3. 对数字列表执行简单的统计计算
数字列表的最大值、最小值和总和:
>>> digits = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] >>> min(digits) 0 >>> max(digits) 9 >>> sum(digits) 454. 列表解析
列表解析 将for 循环和创建新元素的代码合并成一
行,并自动附加新元素。
squares = [value**2 for value in range(1, 11)] print(squares)
4.4 使用列表的一部分输出:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
你一直在学习如何处理列表的所有元素。你还可以处理列表的部分元素,Python称之为切片 。
1. 切片要创建切片,可指定要使用的第一个元素和最后一个元素的索引。与函数range()一样,Python在到达第二个索引之前的元素后停止。要输出列表中的前三个元素,需要指定索引0和3,这将返回索引为0、1和2的元素。
players = ['charles', 'martina', 'michael', 'florence', 'eli'] print(players[0:3])
输出:
[‘charles’, ‘martina’, ‘michael’]
如果没有指定第一个索引,Python将自动从列表开头开始:
players = ['charles', 'martina', 'michael', 'florence', 'eli'] print(players[:4])
如果没有指定末尾索引,Python将自动到列表末尾结束:
players = ['charles', 'martina', 'michael', 'florence', 'eli'] print(players[2:])
输出:
[‘michael’, ‘florence’, ‘eli’]
负数索引返回离列表末尾相应距离的元素,因此你可以输出列表末尾的任意切片。例如,如果要输出名单上的最后三名队员,可使用切片players[-3:] :
players = ['charles', 'martina', 'michael', 'florence', 'eli'] print(players[-3:])2.遍历切片
在for 循环中使用切片。
players = ['charles', 'martina', 'michael', 'florence', 'eli']
print("Here are the first three players on my team:")
for player in players[:3]:
print(player.title())
3. 复制列表
要复制列表,可创建一个包含整个列表的切片,方法是同时省略起始索引和终止索引([:] )。这让Python创建一个始于第一个元素、终止于最后一个元素的切片,即整个列表的副本。
my_foods = ['pizza', 'falafel', 'carrot cake']
friend_foods = my_foods[:]
print("My favorite foods are:")
print(my_foods)
print("nMy friend's favorite foods are:")
print(friend_foods)
输出:
My favorite foods are:
[‘pizza’, ‘falafel’, ‘carrot cake’]
My friend’s favorite foods are:
[‘pizza’, ‘falafel’, ‘carrot cake’]
确实有两个列表。不是两个变量共同指向一个列表。
my_foods = ['pizza', 'falafel', 'carrot cake']
friend_foods = my_foods[:]
my_foods.append('cannoli')
friend_foods.append('ice cream')
print("My favorite foods are:")
print(my_foods)
print("nMy friend's favorite foods are:")
print(friend_foods)
输出:
My favorite foods are:
[‘pizza’, ‘falafel’, ‘carrot cake’, ‘cannoli’]
My friend’s favorite foods are:
[‘pizza’, ‘falafel’, ‘carrot cake’, ‘ice cream’]
直接将列表付给另一个变量。相当于两个变量共同指向一个列表。
my_foods = ['pizza', 'falafel', 'carrot cake']
# 这行不通:
friend_foods = my_foods
my_foods.append('cannoli')
friend_foods.append('ice cream')
print("My favorite foods are:")
print(my_foods)
print("nMy friend's favorite foods are:")
print(friend_foods)
4.5 元组(tuple)输出:
My favorite foods are:
[‘pizza’, ‘falafel’, ‘carrot cake’, ‘cannoli’, ‘ice cream’]
My friend’s favorite foods are:
[‘pizza’, ‘falafel’, ‘carrot cake’, ‘cannoli’, ‘ice cream’]
列表非常适合用于存储在程序运行期间可能变化的数据集。列表是可以修改的,这对处理网站的用户列表或游戏中的角色列表至关重要。然而,有时候你需要创建一系列不可修改的元素,元组可以满足这种需求。Python将不能修改的值称为不可变的 ,而不可变的列表被称为元组 。
1. 定义元组看起来很像列表,但使用圆括号而非中括号来标识。而且重要的是逗号分隔。
访问:
dimensions = (200, 50) print(dimensions[0]) print(dimensions[1]
修改
dimensions = (200, 50) dimensions[0] = 250
输出:
Traceback (most recent call last):
File “dimensions.py”, line 2,
dimensions[0] = 250
TypeError: ‘tuple’ object does not support item assignment
严格地说,元组是由逗号标识的,圆括号只是让元组看起来更整洁、更清晰。 如果你要定义只包含一个元素的元组,必须在这个元素后面加上逗号:
my_t = (3,)2. 遍历元组中的所有值
dimensions = (200, 50) for dimension in dimensions: print(dimension)3. 修改元组变量
虽然不能修改元组的元素,但可以给存储元组的变量赋值。因此,如果要修改前述矩形的尺寸,可重新定义整个元组:
dimensions = (200, 50)
print("Original dimensions:")
for dimension in dimensions:
print(dimension)
dimensions = (400, 100)
print("nModified dimensions:")
for dimension in dimensions:
print(dimension)
4.6 设置代码格式输出:
Original dimensions:
200
50
Modified dimensions:
400
100
PEP8 指南
缩进
行长、注释行长
空行
- 编写代码实现编列列表中的每一个元素打印:
hello, [元素]!
Welcome to Beijing!
注意不要犯缩进以及冒号的错误! - range(2,11,2)实现的功能是什么?
- 如何创建一个由10以内的偶数的平方的元素组成的列表?
- 如何求数字列表的最大值、最小值和总和?(使用的方法是什么?)
- players[0:3]
players[3:]
players[:3]
players[:-3] - for player in players[:3]:
- my_foods = [‘pizza’, ‘falafel’, ‘carrot cake’]
friend_foods = my_foods[:]
和
friend_foods = my_foods
有什么不同? - dimensions = (200, 50)
dimensions[0] = 250
对不对? - 如何创建只有一个元素的元组?
- 如何修改元组变量?
- 编写代码是否要遵循规范?
下面的代码遍历这个列表,并以首字母大写的方式打印其中的汽车名,不过对于’bmw’ ,则以全大写的方式打印:
cars = ['audi', 'bmw', 'subaru', 'toyota'] for car in cars: if car == 'bmw': print(car.upper()) else: print(car.title())
5.2 条件测试输出:
Audi
BMW
Subaru
Toyota
每条if 语句的核心都是一个值为True 或False 的表达式,这种表达式称为条件测试 。
Python根据条件测试的值为True 还是False 来决定是否执行if 语句中的代码。如果条件测试的值为True ,Python就执行紧跟在if 语句后面的代码;如果为False ,Python就忽略这些代码。
>>> car = 'bmw' >>> car == 'bmw' True
检查是否相等时区分大小写
>>> car = 'Audi' >>> car == 'audi' False
如果大小写无关紧要,只想检查变量的值,可将变量的值转换为小写,再进行比较:
>>> car = 'Audi' >>> car.lower() == 'audi' True >>> car 'Audi'
函数lower() 不会修改最初赋给变量car 的值,因此进行这样的比较时不会影响原来的变量
2.检查不相等requested_topping = 'mushrooms'
if requested_topping != 'anchovies':
print("Hold the anchovies!")
3.数值比较
大于>、大于等于 >= 、小于 <、小于等于 <=
>>> age = 19 >>> age < 21 True >>> age <= 21 True >>> age > 21 False >>> age >= 21 False4.检查多个条件
关键字and
多个条件全True,这个条件测试才为True。多个条件有一个为False,这个条件测试就是False。
>>> age_0 = 22 >>> age_1 = 18 >>> age_0 >= 21 and age_1 >= 21 False >>> age_1 = 22 >>> age_0 >= 21 and age_1 >= 21 True
为改善可读性,可将每个测试分别放在一对圆括号内,但并非必须这样做。
(age_0 >= 21) and (age_1 >= 21)
关键字or
只要至少一个条件满足,就能通过整个测试。仅当所有测试都没有通过时,使用or 的表达式才为False 。
>>> age_0 = 22 >>> age_1 = 18 >>> (age_0 >= 21) or( age_1 >= 21) True >>> age_0 = 18 >>> (age_0 >= 21) or( age_1 >= 21) False5.检查特定值是否包含在列表中
关键字in
要判断特定的值是否已包含在列表中,可使用关键字in 。
下面来看看你可能为比萨店编写的一些代码。这些代码首先创建一个列表,其中包含用户点的比萨配料,然后检查特定的配料是否包含在该列表中。
>>> requested_toppings = ['mushrooms', 'onions', 'pineapple'] >>> 'mushrooms' in requested_toppings True >>> 'pepperoni' in requested_toppings False
not in
还有些时候,确定特定的值未包含在列表中很重要。在这种情况下,可使用关键字not in 。
banned_users = ['andrew', 'carolina', 'david']
user = 'marie'
if user not in banned_users:
print(f"{user.title()}, you can post a response if you wish.")
6.布尔表达式输出:
Marie, you can post a response if you wish.
布尔表达式 ,是条件测试的别名。 与条件表达式一样,布尔表达式的结果要么为True ,要么为False 。
布尔值通常用于记录条件,如游戏是否正在运行,或者用户是否可以编辑网站的特定内容:
game_active = True can_edit = False5.3 if-else 语句 1. 简单的if 语句
注意缩进和冒号
age = 19
if age >= 18:
print("You are old enough to vote!")
print("Have you registered to vote yet?")
2. if-else 语句
其中的else 语句让你能够指定条件测试未通过时要执行的操作。
age = 17
if age >= 18:
print("You are old enough to vote!")
print("Have you registered to vote yet?")
else:
print("Sorry, you are too young to vote.")
print("Please register to vote as soon as you turn 18!")
3. if-elif-else 结构输出:
Sorry, you are too young to vote.
Please register to vote as soon as you turn 18!
我们经常需要检查超过两个的情形,为此可使用Python提供的if-elif-else 结构。Python只执行if-elif-else 结构中的一个代码块。它依次检查每个条件测试,直到遇到通过了的条件测试。测试通过后,Python将执行紧跟在它后面的代码,并跳过余下的测试。
age = 12
if age < 4:
print("Your admission cost is $0.")
elif age < 16:
print("Your admission cost is $25.")
elif age < 18:
print("Your admission cost is $30.")
else:
print("Your admission cost is $40.")
4.省略else 代码块输出:
Your admission cost is $25.
age = 12
if age < 4:
price = 0
elif age < 18:
price = 25
elif age < 65:
price = 40
elif age >= 65:
price = 20
print(f"Your admission cost is ${price}.")
5.多个if
requested_toppings = ['mushrooms', 'extra cheese']
if 'mushrooms' in requested_toppings:
print("Adding mushrooms.")
if 'pepperoni' in requested_toppings:
print("Adding pepperoni.")
if 'extra cheese' in requested_toppings:
print("Adding extra cheese.")
print("nFinished making your pizza!")
5.4 使用if 语句处理列表 1.确定列表不是空的输出:
Adding mushrooms.
Adding extra cheese.
Finished making your pizza!
在if 语句中将列表名用作条件表达式时,Python将在列表至少包含一个元素时返回True ,并在列表为空时返回False 。
requested_toppings = []
if requested_toppings:
for requested_topping in requested_toppings:
print(f"Adding {requested_topping}.")
print("nFinished making your pizza!")
else:
print("Are you sure you want a plain pizza?")
5.5 设置if 语句的格式输出:
Are you sure you want a plain pizza?
在条件测试的格式设置方面,PEP 8提供的唯一建议是,在诸如== 、>= 和<= 等比较运算符两边各添加一个空格。例如:
if age < 4:总结自测
- in 、not in 关键字
if user not in banned_users:
print(f"{user.title()}, you can post a response if you wish.")
以上代码的功能?
- and 、or关键字的特点是什么?
- if-elif-else结构有一个通过其他的语句那就不执行。
- if-if-if结构满足条件就执行
- 在if 语句中将列表名用作条件表达式时,Python将在列表至少包含一个元素时返回(),并在列表为空时返回() 。
如何创建字典,如何访问和修改字典中的信息。如何遍历字典中的数据。学习存储字典的列表、存储列表的字典和存储字典的字典。
6.1 创建字典列表是[],元组是(),字典是{}。
来看一个包含外星人的游戏,这些外星人的颜色和分数各不相同。下面是一个简单的字典,存储了有关特定外星人的信息:
alien_0 = {'color': 'green', 'points': 5}
print(alien_0['color'])
print(alien_0['points'])
6.2 使用字典输出:
green
5
在Python中,字典 是一系列键值对 。每个键 都与一个值相关联,你可使用键来访问相关联的值。与键相关联的值可以是数、字符串、列表乃至字典。事实上,可将任何Python对象用作字典中的值。
在Python中,字典用放在花括号({} )中的一系列键值对表示,如前面的示例所示,键值对用“ ,”分隔:
alien_0 = {'color': 'green', 'points': 5}
键值对 是两个相关联的值。指定键时,Python将返回与之相关联的值。键和值之间用冒号分隔,而键值对之间用逗号分隔。在字典中,想存储多少个键值对都可以。
alien_0 = {'color': 'green'}
1. 访问字典中的值
要获取与键相关联的值,可依次指定字典名和放在方括号内的键,如下所示:
alien_0 = {'color': 'green'}
print(alien_0['color'])
2. 添加键值对
字典是一种动态结构,可随时在其中添加键值对。要添加键值对,可依次指定字典名、用方括号括起的键和相关联的值。
alien_0 = {'color': 'green', 'points': 5}
print(alien_0)
alien_0['x_position'] = 0
alien_0['y_position'] = 25
print(alien_0)
输出:
{‘color’: ‘green’, ‘points’: 5}
{‘color’: ‘green’, ‘points’: 5, ‘x_position’: 0, ‘y_position’: 25}
注意 在Python 3.7中,字典中元素的排列顺序与定义时相同。如果将字典打印出来或遍历其元素,将发现元素的排列顺序与添加顺序相同。
3. 动态创建字典alien_0 = {}
alien_0['color'] = 'green'
alien_0['points'] = 5
print(alien_0)
4. 修改字典中的值输出:
{‘color’: ‘green’, ‘points’: 5}
alien_0 = {'color': 'green'}
print(f"The alien is {alien_0['color']}.")
alien_0['color'] = 'yellow'
print(f"The alien is now {alien_0['color']}.")
alien_0 = {'x_position': 0, 'y_position': 25, 'speed': 'medium'}
print(f"Original position: {alien_0['x_position']}")
# 向右移动外星人。
# 根据当前速度确定将外星人向右移动多远。
❶ if alien_0['speed'] == 'slow':
x_increment = 1
elif alien_0['speed'] == 'medium':
x_increment = 2
else:
# 这个外星人的移动速度肯定很快。
x_increment = 3
# 新位置为旧位置加上移动距离。
❷ alien_0['x_position'] = alien_0['x_position'] + x_increment
print(f"New position: {alien_0['x_position']}")
首先定义一个外星人,其中包含初始 坐标和 坐标,还有速度’medium’ 。出于简化考虑,省略了颜色和分数,但即便包含这些键值对,本例的工作原理也不会有任何变化。我们还打印了x_position 的初始值,旨在让用户知道这个外星人向右移动了多远。❶处使用一个if-elif-else 结构来确定外星人应向右移动多远,并将这个值赋给变x_increment 。如果外星人的速度为’slow’ ,它将向右移动一个单位;如果速度为’medium’ ,将向右移动两个单位;如果为’fast’ ,将向右移动三个单位。确定移动距离后,将其与x_position 的当前值相加(见❷),再将结果关联到字典中的键x_position 。因为这是一个速度中等的外星人,所以其位置将向右移两个单位:
5. 删除键值对输出:
Original x-position: 0
New x-position: 2
del 语句删除的键值对会永远消失。
alien_0 = {'color': 'green', 'points': 5}
print(alien_0)
❶ del alien_0['points']
print(alien_0)
6. 由类似对象组成的字典输出:
{‘color’: ‘green’, ‘points’: 5}
{‘color’: ‘green’}
在前面的示例中,字典存储的是一个对象(游戏中的一个外星人)的多种信息,但你也可以使用字典来存储众多对象的同一种信息。例如,假设你要调查很多人,询问他们最喜欢的编程语言,可使用一个字典来存储这种简单调查的结果,如下所示:
favorite_languages = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}
确定需要使用多行来定义字典时,要在输入左花括号后按回车键。在下一行缩进四个空格,指定第一个键值对,并在它后面加上一个逗号。此后再按回车键时,文本编辑器将自动缩进后续键值对,且缩进量与第一个键值对相同。
在最后一个键值对的下一行添加一个右花括号,并缩进四个空格,使其与字典中的键对齐。一种不错的做法是,在最后一个键值对后面也加上逗号,为以后在下一行添加键值对做好准备。
使用方法get() 在指定的键不存在时返回一个默认值,从而避免这样的错误。
方法get() 的第一个参数用于指定键,是必不可少的;第二个参数为指定的键不存在时要返回的值,是可选的:
alien_0 = {'color': 'green', 'speed': 'slow'}
point_value = alien_0.get('points', 'No point value assigned.')
print(point_value)
6.3 遍历字典 1. 遍历所有键值对输出:
No point value assigned.
user_0 = {
'username': 'efermi',
'first': 'enrico',
'last': 'fermi',
}
❶ for key, value in user_0.items():
❷ print(f"nKey: {key}")
❸ print(f"Value: {value}")
如❶所示,要编写遍历字典的for 循环,可声明两个变量,用于存储键值对中的键和值。这两个变量可以使用任意名称。下面的代码使用了简单的变量名,这完全可行:
for k, v in user_0.items()
for 语句的第二部分包含字典名和方法items() (见❶),它返回一个键值对列表。接下来,for 循环依次将每个键值对赋给指定的两个变量。在本例中,使用这
两个变量来打印每个键(见❷)及其相关联的值(见❸)。第一个函数调用print()
中的"n" 确保在输出每个键值对前都插入一个空行:
方法keys()
for name in favorite_languages.keys(): print(name.title())
遍历字典时,会默认遍历所有的键。因此,如果将上述代码替换为:
for name in favorite_languages:
方法keys() 并非只能用于遍历:实际上,它返回一个列表,其中包含字典中的所有键。
favorite_languages = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}
❶ if 'erin' not in favorite_languages.keys():
print("Erin, please take our poll!")
3.按特定顺序遍历字典中的所有键
sorted()方法
favorite_languages = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}
for name in sorted(favorite_languages.keys()):
print(f"{name.title()}, thank you for taking the poll.")
4. 遍历字典中的所有值输出:
Edward, thank you for taking the poll.
Jen, thank you for taking the poll.
Phil, thank you for taking the poll.
Sarah, thank you for taking the poll.
如果主要对字典包含的值感兴趣,可使用方法values() 来返回一个值列表,不包含任何键。
为剔除重复项,可使用集合(set)。集合 中的每个元素都必须是独一无二的:
favorite_languages = {
--snip--#这里代表省略的字典内容
}
print("The following languages have been mentioned:")
for language in set(favorite_languages.values()):
print(language.title())
可使用一对花括号直接创建集合,并在其中用逗号分隔元素:
>>> languages = {'python', 'ruby', 'python', 'c'}
>>> languages
{'ruby', 'python', 'c'}
集合和字典很容易混淆,因为它们都是用一对花括号定义的。当花括号内没有键值对时,定义的很可能是集合。不同于列表和字典,集合不会以特定的顺序存储元素。
6.4 嵌套有时候,需要将一系列字典存储在列表中,或将列表作为值存储在字典中,这称为嵌套 。你可以在列表中嵌套字典、在字典中嵌套列表甚至在字典中嵌套字典。
1. 在列表中存储字典列表嵌套字典
alien_0 = {'color': 'green', 'points': 5}
alien_1 = {'color': 'yellow', 'points': 10}
alien_2 = {'color': 'red', 'points': 15}
aliens = [alien_0, alien_1, alien_2]
for alien in aliens:
print(alien)
range()方法
返回一个可迭代对象(理解为可以用在for循环的一个对象),不是列表。
语法:
range(stop)
range(start, stop)
range(start, stop, step)
start: 从整数数字几开始,(默认是0)
stop:到数字几之前那个数结束。
step: 步长,默认为1。
>>>range(5) range(0, 5) >>> for i in range(5): ... print(i) ... 0 1 2 3 4 >>> list(range(5)) [0, 1, 2, 3, 4] >>> list(range(0)) [] >>>
# 创建一个用于存储外星人的空列表。
aliens = []
# 创建30个绿色的外星人。
for alien_number in range(30):
#range(30)唯一的用途是告诉Python要重复这个循环多少次。
new_alien = {'color': 'green', 'points': 5, 'speed': 'slow'}
aliens.append(new_alien)
# 显示前5个外星人。
for alien in aliens[:5]:
print(alien)
print("...")
# 显示创建了多少个外星人。
print(f"Total number of aliens: {len(aliens)}")
2. 在字典中存储列表
每当需要在字典中将一个键关联到多个值时,都可以在字典中嵌套一个列表。
favorite_languages = {
'jen': ['python', 'ruby'],
'sarah': ['c'],
'edward': ['ruby', 'go'],
'phil': ['python', 'haskell'],
}
for name, languages in favorite_languages.items():
print(f"n{name.title()}'s favorite languages are:")
for language in languages:
print(f"t{language.title()}")
3. 在字典中存储字典
users = {
'aeinstein': {
'first': 'albert',
'last': 'einstein',
'location': 'princeton',
},
'mcurie': {
'first': 'marie',
'last': 'curie',
'location': 'paris',
},
}
for username, user_info in users.items():
print(f"nUsername: {username}")
full_name = f"{user_info['first']}{user_info['last']}"
location = user_info['location']
print(f"tFull name: {full_name.title()}")
print(f"tLocation: {location.title()}")
总结自测
- 字典用什么括起来,键值对之间用什么分隔?
- 一个键能对应几个值?字典中的键可不可以重复?值呢?
- 如何删除键值对?
- 字典和列表的嵌套,字典嵌套字典。
输入方法:input()
while 循环让程序不断运行,直到指定的条件不满足为止。
使用这两者完成交互程序。
函数input() 让程序暂停运行,等待用户输入一些文本。获取用户输入后,Python将其赋给一个变量,以方便你使用。使用函数input() 时,Python将用户输入解读为字符串。
message = input("Tell me something, and I will repeat it back to you: ")
print(message)
函数input() 接受一个参数——要向用户显示的提示 (prompt)或说明,让用户
知道该如何做。在本例中,Python运行第一行代码时,用户将看到提示Tell me something, and I will repeat it back to you: 。程序等待用户输入,并在用户按回车键后继续运行。输入被赋给变量message ,接下来的print(message) 将输入呈现给用户:
1. 编写清晰的程序输出:
Tell me something, and I will repeat it back to you: Hello everyone!
Hello everyone!
有时候,提示可能超过一行。例如,你可能需要指出获取特定输入的原因。在这种情况下,可将提示赋给一个变量,再将该变量传递给函数input() 。这样,即便提示超过一行,input() 语句也会非常清晰。
prompt = "If you tell us who you are, we can personalize the messages you see."
prompt += "nWhat is your first name? "
name = input(prompt)
print(f"nHello, {name}!")
2 使用int() 来获取数值输入输出:
If you tell us who you are, we can personalize the messages you see.
What is your first name? Eric
Hello, Eric!
函数int()将数的字符串表示转换为数值表示
>>> age = input("How old are you? ")
How old are you? 21
>>> age = int(age)
>>> age >= 18
True
3 求模运算符
处理数值信息时,求模运算符 (%)是个很有用的工具,它将两个数相除并返回余数:
>>> 4 % 3 1 >>> 5 % 3 2 >>> 6 % 3 0 >>> 7 % 3 17.2 while 循环简介
for 循环用于针对集合中的每个元素都执行一个代码块,而while 循环则不断运行,直到指定的条件不满足为止。
1 使用while 循环current_number = 1 while current_number <= 5: print(current_number) current_number += 12 让用户选择何时退出
我们在其中定义了一个退出值 ,只要用户输入的不是这个值,程序就将接着运行:
prompt = "nTell me something, and I will repeat it back to you:" prompt += "nEnter 'quit' to end the program. " message = "" while message != 'quit': message = input(prompt) print(message)3 使用标志
在要求很多条件都满足才继续运行的程序中,可定义一个变量,用于判断整个程序是否处于活动状态。这个变量称为标志 (flag),充当程序的交通信号灯。可以让
程序在标志为True 时继续运行,并在任何事件导致标志的值为False 时让程序停止运行。
prompt = "nTell me something, and I will repeat it back to you:" prompt += "nEnter 'quit' to end the program. " active = True while active: message = input(prompt) if message == 'quit': active = False else: print(message)4 使用break 退出循环
prompt = "nPlease enter the name of a city you have visited:"
prompt += "n(Enter 'quit' when you are finished.) "
while True:
city = input(prompt)
if city == 'quit':
break
else:
print(f"I'd love to go to {city.title()}!")
5 在循环中使用continue
要返回循环开头,并根据条件测试结果决定是否继续执行循环,可使用continue语句,它不像break 语句那样不再执行余下的代码并退出整个循环。例如,来看一个从1数到10但只打印其中奇数的循环:
current_number = 0 while current_number < 10: current_number += 1 if current_number % 2 == 0: continue print(current_number)6 避免无限循环
# 这个循环将没完没了地运行! x = 1 while x <= 5: print(x)
如果程序陷入无限循环,可按Ctrl + C,也可关闭显示程序输出的终端窗口。
7.3 使用while 循环处理列表和字典for 循环是一种遍历列表的有效方式,但不应在for 循环中修改列表,否则将导致Python难以跟踪其中的元素。要在遍历列表的同时对其进行修改,可使用while 循环。通过将while 循环同列表和字典结合起来使用,可收集、存储并组织大量输入,供以后查看和显示。
1 在列表之间移动元素# 首先,创建一个待验证用户列表
# 和一个用于存储已验证用户的空列表。
unconfirmed_users = ['alice', 'brian', 'candace']
confirmed_users = []
# 验证每个用户,直到没有未验证用户为止。
# 将每个经过验证的用户都移到已验证用户列表中。
while unconfirmed_users:
current_user = unconfirmed_users.pop()
print(f"Verifying user: {current_user.title()}")
confirmed_users.append(current_user)
# 显示所有已验证的用户。
print("nThe following users have been confirmed:")
for confirmed_user in confirmed_users:
print(confirmed_user.title())
2 删除为特定值的所有列表元素
在第3章中,我们使用函数remove() 来删除列表中的特定值。这之所以可行,是因为要删除的值只在列表中出现一次。如果要删除列表中所有为特定值的元素,该怎么办呢?
pets = ['dog', 'cat', 'dog', 'goldfish', 'cat', 'rabbit', 'cat']
print(pets)
while 'cat' in pets:
pets.remove('cat')
print(pets)
3 使用用户输入来填充字典
responses = {}
# 设置一个标志,指出调查是否继续。
polling_active = True
while polling_active:
# 提示输入被调查者的名字和回答。
name = input("nWhat is your name? ")
response = input("Which mountain would you like to climb someday? ")
# 将回答存储在字典中。
responses[name] = response
# 看看是否还有人要参与调查。
repeat = input("Would you like to let another person respond? (yes/ no) ")
if repeat == 'no':
polling_active = False
# 调查结束,显示结果。
print("n--- Poll Results ---")
for name, response in responses.items():
print(f"{name} would like to climb {response}.")
总结自测
- input()方法将用户输入的文本接收为什么类型的值赋给变量?
- 要想将用户输入的数值文本接收为数字类型,使用什么方法?
- break, continue 的用法
函数是带名字的代码块,用于完成具体的工作。要执行函数定义的特定任务,可调用该函数。你还将学习向函数传递信息的方式;学习如何编写主要任务是显示信息的函
数,以及旨在处理数据并返回一个或一组值的函数;最后,学习如何将函数存储在称为模块 的独立文件中,让主程序文件的组织更为有序。
下面是一个打印问候语的简单函数,名为greet_user() :
❶ def greet_user():
❷ """显示简单的问候语。"""
❸ print("Hello!")
❹ greet_user()
本例演示了最简单的函数结构。❶处的代码行使用关键字def 来告诉Python,你要定义一个函数。这是函数定义 ,向Python指出了函数名,还可能在圆括号内指出函数为完成任务需要什么样的信息。在这里,函数名为greet_user() ,它不需要任何信息就能完成工作,因此括号是空的(即便如此,括号也必不可少)。最后,定义以冒号结尾。
紧跟在def greet_user(): 后面的所有缩进行构成了函数体。❷处的文本是称为文档字符串 (docstring)的注释,描述了函数是做什么的。文档字符串用三引号括起,Python使用它们来生成有关程序中函数的文档。代码行print(“Hello!”) (见❸)是函数体内的唯一一行代码,因此greet_user() 只做一项工作:打印Hello! 。
要使用这个函数,可调用它。函数调用 让Python执行函数的代码。要调用函数,**可依次指定函数名以及用圆括号括起的必要信息,**如❹处所示。由于这个函数不需要
任何信息,调用它时只需输入greet_user() 即可。和预期一样,它打印Hello!
:
1 向函数传递信息输出:
Hello!
def greet_user(username):
"""显示简单的问候语。"""
print(f"Hello, {username.title()}!")
greet_user('jesse')
2 实参和形参
在函数greet_user() 的定义中,变量username 是一个形参 (parameter),即函数完成工作所需的信息。在代码greet_user(‘jesse’) 中,值’jesse’ 是一个实参(argument),即调用函数时传递给函数的信息。调用函数时,将要让函数使用的信息放在圆括号内。在greet_user(‘jesse’) 中,将实参’jesse’ 传递给了函数greet_user() ,这个值被赋给了形参username 。
8.2 传递实参 1 位置实参调用函数时,Python必须将函数调用中的每个实参都关联到函数定义中的一个形参。为此,最简单的关联方式是基于实参的顺序。这种关联方式称为位置实参 。
❶ def describe_pet(animal_type, pet_name):
"""显示宠物的信息。"""
print(f"nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
❷ describe_pet('hamster', 'harry'
多次调用函数
可以根据需要调用函数任意次。要再描述一个宠物,只需再次调用describe_pet() 即可:
def describe_pet(animal_type, pet_name):
"""显示宠物的信息。"""
print(f"nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet('hamster', 'harry')
describe_pet('dog', 'willie')
位置实参的顺序很重要
2 关键字实参关键字实参 是传递给函数的名称值对。因为直接在实参中将名称和值关联起来,所以向函数传递实参时不会混淆(不会得到名为Hamster的harry这样的结果)。关键字实参让你无须考虑函数调用中的实参顺序,还清楚地指出了函数调用中各个值的用途。
def describe_pet(animal_type, pet_name):
"""显示宠物的信息。"""
print(f"nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet(animal_type='hamster', pet_name='harry')
使用关键字实参时,务必准确指定函数定义中的形参名。
3 默认值编写函数时,可给每个形参指定默认值 。在调用函数中给形参提供了实参时,Python将使用指定的实参值;否则,将使用形参的默认值。因此,给形参指定默认值后,可在函数调用中省略相应的实参。使用默认值可简化函数调用,还可清楚地指出函数的典型用法。
例如,如果你发现调用describe_pet() 时,描述的大多是小狗,就可将形参
animal_type 的默认值设置为’dog’ 。这样,调用describe_pet() 来描述小狗时,就可不提供这种信息:
def describe_pet(pet_name, animal_type='dog'):
"""显示宠物的信息。"""
print(f"nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet(pet_name='willie')
请注意,在这个函数的定义中,修改了形参的排列顺序。因为给animal_type 指定了默认值,无须通过实参来指定动物类型,所以在函数调用中只包含一个实参——宠物的名字。然而,Python依然将这个实参视为位置实参,因此如果函数调用中只包含宠物的名字,这个实参将关联到函数定义中的第一个形参。这就是需要将pet_name 放在形参列表开头的原因。
4 等效的函数调用鉴于可混合使用位置实参、关键字实参和默认值,通常有多种等效的函数调用方式。请看下面对函数describe_pet() 的定义,其中给一个形参提供了默认值:
def describe_pet(pet_name, animal_type='dog'):
基于这种定义,**在任何情况下都必须给pet_name 提供实参。**指定该实参时可采用位置方式,也可采用关键字方式。如果要描述的动物不是小狗,还必须在函数调用中给animal_type 提供实参。同样,指定该实参时可以采用位置方式,也可采用关键字方式。
# 一条名为Willie的小狗。
describe_pet('willie')
describe_pet(pet_name='willie')
# 一只名为Harry的仓鼠。
describe_pet('harry', 'hamster')
describe_pet(pet_name='harry', animal_type='hamster')
describe_pet(animal_type='hamster', pet_name='harry')
5 避免实参错误
等你开始使用函数后,如果遇到实参不匹配错误,不要大惊小怪。**你提供的实参多于或少于函数完成工作所需的信息时,将出现实参不匹配错误。**例如,如果调用函数describe_pet() 时没有指定任何实参,结果将如何呢?
def describe_pet(animal_type, pet_name):
"""显示宠物的信息。"""
print(f"nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet()
Python发现该函数调用缺少必要的信息,traceback指出了这一点:
8.3 返回值输出:
Traceback (most recent call last):
File “pets.py”, line 6, in < module >
describe_pet()
TypeError: describe_pet() missing 2 required positional arguments: ‘animal_type’ and ‘pet_name’
函数并非总是直接显示输出,它还可以处理一些数据,并返回一个或一组值。函数返回的值称为返回值 。在函数中,可使用return 语句将值返回到调用函数的代码行。返回值让你能够将程序的大部分繁重工作移到函数中去完成,从而简化主程序。
1 返回简单值def get_formatted_name(first_name, last_name):
"""返回整洁的姓名。"""
full_name = f"{first_name} {last_name}"
return full_name.title()
musician = get_formatted_name('jimi', 'hendrix')
print(musician)
2 让实参变成可选的
并非所有的人都有中间名,但如果调用这个函数时只提供了名和姓,它将不能正确运行。为了让中间名变成可选的,可给形参middle_name 指定一个空的默认值,
并在用户没有提供中间名时不使用这个形参。为让get_formatted_name() 在没有提供中间名时依然可行,可将形参middle_name 的默认值设置为空字符串,并
将其移到形参列表的末尾:
def get_formatted_name(first_name, last_name, middle_name=''):
"""返回整洁的姓名。"""
if middle_name:
full_name = f"{first_name} {middle_name} {last_name}"
else:
full_name = f"{first_name} {last_name}"
return full_name.title()
musician = get_formatted_name('jimi', 'hendrix')
print(musician)
musician = get_formatted_name('john', 'hooker', 'lee')
print(musician)
3 返回字典
def build_person(first_name, last_name):
"""返回一个字典,其中包含有关一个人的信息。"""
person = {'first': first_name, 'last': last_name}
return person
musician = build_person('jimi', 'hendrix')
print(musician)
8.4 传递列表
def greet_users(names):
"""向列表中的每位用户发出简单的问候。"""
for name in names:
msg = f"Hello, {name.title()}!"
print(msg)
us ernames = ['hannah', 'ty', 'margot']
greet_users(usernames)
1 在函数中修改列表
在函数中对这个列表所做的任何修改都是永久性的,这让你能够高效地处理大量数据。
# 首先创建一个列表,其中包含一些要打印的设计。
unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []
# 模拟打印每个设计,直到没有未打印的设计为止。
# 打印每个设计后,都将其移到列表completed_models中。
while unprinted_designs:
current_design = unprinted_designs.pop()
print(f"Printing model: {current_design}")
completed_models.append(current_design)
# 显示打印好的所有模型。
print("nThe following models have been printed:")
for completed_model in completed_models:
print(completed_model)
为重新组织这些代码,可编写两个函数,每个都做一件具体的工作。
def print_models(unprinted_designs, completed_models):
"""
模拟打印每个设计,直到没有未打印的设计为止。
打印每个设计后,都将其移到列表completed_models中。
"""
while unprinted_designs:
current_design = unprinted_designs.pop()
print(f"Printing model: {current_design}")
completed_models.append(current_design)
def show_completed_models(completed_models):
"""显示打印好的所有模型。"""
print("nThe following models have been printed:")
for completed_model in completed_models:
print(completed_model)
unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []
print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)
2 禁止函数修改列表
function_name(list_name_[:])
切片表示法[:] 创建列表的副本。
8.5 传递任意数量的实参下面的函数只有一个形参*toppings ,但不管调用语句提供了多少实参,这个形参会将它们统统收入囊中:
def make_pizza(*toppings):
"""打印顾客点的所有配料。"""
print(toppings)
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')
1 结合使用位置实参和任意数量实参
如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后。Python先匹配位置实参和关键字实参,再将余下的实参都收集到最后一个形参中。
def make_pizza(size, *toppings):
"""概述要制作的比萨。"""
print(f"nMaking a {size}-inch pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
注意 你经常会看到通用形参名
*args
它也收集任意数量的位置实参。
def build_profile(first, last, **user_info):
"""创建一个字典,其中包含我们知道的有关用户的一切。"""
user_info['first_name'] = first
user_info['last_name'] = last
return user_info
user_profile = build_profile('albert', 'einstein',
location='princeton',
field='physics')
print(user_profile)
函数build_profile() 的定义要求提供名和姓,同时允许根据需要提供任意数量的名称值对。形参**user_info 中的两个星号让Python创建一个名为user_info的空字典,并将收到的所有名称值对都放到这个字典中。在这个函数中,可以像访问其他字典那样访问user_info 中的名称值对。
8.6 将函数存储在模块中使用函数的优点之一是可将代码块与主程序分离。通过给函数指定描述性名称,可让主程序容易理解得多。你还可以更进一步,将函数存储在称为模块 的独立文件中,再将模块导入 到主程序中。import 语句允许在当前运行的程序文件中使用模块中的代码。
1 导入整个模块要让函数是可导入的,得先创建模块。模块 是扩展名为.py的文件,包含要导入到程序中的代码。下面来创建一个包含函数make_pizza() 的模块。为此,将文件pizza.py中除函数make_pizza() 之外的其他代码删除:
def make_pizza(size, *toppings):
"""概述要制作的比萨。"""
print(f"nMaking a {size}-inch pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")
接下来,在pizza.py所在的目录中创建一个名为making_pizzas.py的文件。这个文件导入刚创建的模块,再调用make_pizza() 两次:
import pizza pizza.make_pizza(16, 'pepperoni') pizza.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
Python读取这个文件时,代码行import pizza 让Python打开文件pizza.py,并将其中的所有函数都复制到这个程序中。你看不到复制的代码,因为在这个程序即将运行时,Python在幕后复制了这些代码。你只需知道,在making_pizzas.py中,可使用pizza.py中定义的所有函数。
2 导入特定的函数from module_name import function_name
通过用逗号分隔函数名,可根据需要从模块中导入任意数量的函数:
from module_name import function_0, function_1, function_2
使用这种语法时,调用函数时无须使用句点。调用时只需指定其名称即可。
3 使用as 给模块指定别名还可以给模块指定别名。通过给模块指定简短的别名(如给模块pizza 指定别名p)
import pizza as p p.make_pizza(16, 'pepperoni') p.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')4 使用as 给函数指定别名
如果要导入函数的名称可能与程序中现有的名称冲突,或者函数的名称太长,可指定简短而独一无二的别名.
from pizza import make_pizza as mp mp(16, 'pepperoni') mp(12, 'mushrooms', 'green peppers', 'extra cheese')5 导入模块中的所有函数
使用并非自己编写的大型模块时,最好不要采用这种导入方法。这是因为如果模块中有函数的名称与当前项目中使用的名称相同,可能导致意想不到的结果:Python可能遇到多个名称相同的函数或变量,进而覆盖函数,而不是分别导入所有的函数。
最佳的做法是,要么只导入需要使用的函数,要么导入整个模块并使用句点表示法。
from pizza import *8.7 函数编写指南
- 给函数指定描述性名称(模块同样):只在其中使用小写字母和下划线。
- 阐述其功能的注释:简要、紧跟在函数定义后面,并采用文档字符串格式。
- 给形参指定默认值时,等号两边不要有空格;
对于函数调用中的关键字实参,也应遵循这种约定
def function_name(parameter_0, parameter_1='default value') function_name(value_0, parameter_1='value')
PEP 8建议代码行的长度不要超过79字符,这样只要编辑器窗口适中,就能看到整行代码。如果形参很多,导致函数定义的长度超过了79字符,可在函数定义中输入左括号后按回车键,并在下一行按两次Tab键,从而将形参列表和只缩进一层的函数体区分开来。大多数编辑器会自动对齐后续参数列表行,使其缩进程度与你给第一个参数列表行指定的缩进程度相同:
def function_name( parameter_0, parameter_1, parameter_2, parameter_3, parameter_4, parameter_5): function body...
如果程序或模块包含多个函数,可使用两个空行将相邻的函数分开,这样将更容易知道前一个函数在什么地方结束,下一个函数从什么地方开始。
所有import 语句都应放在文件开头。唯一例外的情形是,在文件开头使用了注释来描述整个程序。
第 9 章 类 前言面向对象编程 是最有效的软件编写方法之一。在面向对象编程中,你编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。编写类时,你定义一大类对象都有的通用行为。基于类创建对象 时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。使用面向对象编程可模拟现实情景,其逼真程度达到了令人惊讶的地步。
根据类来创建对象称为实例化。
根据Dog 类创建的每个实例都将存储名字和年龄,我们赋予了每条小狗蹲下(sit() )和打滚(roll_over() )的能力:
❶ class Dog:
❷ """一次模拟小狗的简单尝试。"""
❸ def __init__(self, name, age):
"""初始化属性name和age。"""
❹ self.name = name
self.age = age
❺ def sit(self):
"""模拟小狗收到命令时蹲下。"""
print(f"{self.name} is now sitting.")
def roll_over(self):
"""模拟小狗收到命令时打滚。"""
print(f"{self.name} rolled over!")
❶处定义了一个名为Dog 的类。根据约定,在Python中,首字母大写的名称指的是类。这个类定义中没有圆括号,因为要从空白创建这个类。❷处编写了一个文档字符串,对这个类的功能做了描述。
方法__init__()
类中的函数称为方法 。(之前的笔记里可能对函数和方法的名称有些混淆)与函数为一点的差别是调用方法的方式。❸处的方法__init__() 是一个特殊方法,每当你根据Dog 类创建新实例时,Python都会自动运行它。在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。务必确保__init__() 的两边都有两个下划线,否则当你使用类来创建实例时,将不会自动调用这个方法,进而引发难以发现的错误。
我们将方法__init__() 定义成包含三个形参:self 、name 和age 。在这个方法的定义中,**形参self 必不可少,而且必须位于其他形参的前面。**为何必须在方法定义中包含形参self 呢?因为Python调用这个方法来创建Dog 实例时,将自动传入实参self 。每个与实例相关联的方法调用都自动传递实参self ,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。创建Dog 实例时,Python将调用Dog 类的方法__init__() 。我们将通过实参向Dog() 传递名字和年龄,self 会自动传递,因此不需要传递它。每当根据Dog 类创建实例时,都只需给最后两个形参(name 和age )提供值。
❹处定义的两个变量都有前缀self 。以self 为前缀的变量可供类中的所有方法使用,可以通过类的任何实例来访问。self.name = name 获取与形参name 相关联的值,并将其赋给变量name ,然后该变量被关联到当前创建的实例。self.age= age 的作用与此类似。像这样可通过实例访问的变量称为属性 。
Dog 类还定义了另外两个方法:sit() 和roll_over() (见❺)。这些方法执行时不需要额外的信息,因此它们只有一个形参self 。我们随后将创建的实例能够访问这些方法,换句话说,它们都会蹲下和打滚。当前,sit() 和roll_over()所做的有限,只是打印一条消息,指出小狗正在蹲下或打滚。但可以扩展这些方法以模拟实际情况:如果这个类包含在一个计算机游戏中,这些方法将包含创建小狗蹲下和打滚动画效果的代码;如果这个类是用于控制机器狗的,这些方法将让机器狗做出蹲下和打滚的动作。
2 根据类创建实例可将类视为有关如何创建实例的说明。Dog 类是一系列说明,让Python知道如何创建表示特定小狗的实例。
下面来创建一个表示特定小狗的实例:
class Dog:
--snip--
❶ my_dog = Dog('Willie', 6)
❷ print(f"My dog's name is {my_dog.name}.")
❸ print(f"My dog is {my_dog.age} years old.")
在这里,命名约定很有用:通常可认为首字母大写的名称(如Dog )指的是类,而小写的名称(如my_dog )指的是根据类创建的实例。
a. 访问属性要访问实例的属性,可使用句点表示法。
my_dog.nameb. 调用方法
句点表示法
my_dog.sit()c. 创建多个实例
my_dog = Dog('Willie', 6)
your_dog = Dog('Lucy', 3)
即使给第二条小狗指定同样的名字和年龄,Python依然会根据Dog 类创建另一个实例。你可按需求根据一个类创建任意数量的实例,条件是将每个实例都存储在不同的变量中,或者占用列表或字典的不同位置。
9.2 使用类和实例 1 Car 类class Car:
"""一次模拟汽车的简单尝试。"""
❶ def __init__(self, make, model, year):
"""初始化描述汽车的属性。"""
self.make = make
self.model = model
self.year = year
❷ def get_descriptive_name(self):
"""返回整洁的描述性信息。"""
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()
❸ my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
2 给属性指定默认值
创建实例时,有些属性无须通过形参来定义,可在方法__init__() 中为其指定默认值。
class Car:
def __init__(self, make, model, year):
"""初始化描述汽车的属性。"""
self.make = make
self.model = model
self.year = year
❶ self.odometer_reading = 0
def get_descriptive_name(self):
--snip--
❷ def read_odometer(self):
"""打印一条指出汽车里程的消息。"""
print(f"This car has {self.odometer_reading} miles on it.")
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()
3 修改属性的值
a. 直接修改属性的值
class Car:
--snip--
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
❶ my_new_car.odometer_reading = 23
my_new_car.read_odometer()
b. 通过方法修改属性的值
class Car:
--snip--
❶ def update_odometer(self, mileage):
"""将里程表读数设置为指定的值。"""
self.odometer_reading = mileage
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
❷ my_new_car.update_odometer(23)
my_new_car.read_odometer()
c. 通过方法对属性的值进行递增
class Car:
--snip--
def update_odometer(self, mileage):
--snip--
❶ def increment_odometer(self, miles):
"""将里程表读数增加指定的量。"""
self.odometer_reading += miles
❷ my_used_car = Car('subaru', 'outback', 2015)
print(my_used_car.get_descriptive_name())
❸ my_used_car.update_odometer(23_500)
my_used_car.read_odometer()
❹ my_used_car.increment_odometer(100)
my_used_car.read_odometer()
9.3 继承
编写类时,并非总是要从空白开始。如果要编写的类是另一个现成类的特殊版本,可使用继承 。一个类继承 另一个类时,将自动获得另一个类的所有属性和方法。原有的类称为父类 ,而新类称为子类 。子类继承了父类的所有属性和方法,同时还可以定义自己的属性和方法。
1 子类的方法__init__()在既有类的基础上编写新类时,通常要调用父类的方法__init__() 。这将初始化在父类__init__() 方法中定义的所有属性,从而让子类包含这些属性。
例如,下面来模拟电动汽车。电动汽车是一种特殊的汽车,因此可在前面创建的Car类的基础上创建ElectricCar。这样就只需为电动汽车特有的属性和行为编写代码。
下面来创建ElectricCar 类的一个简单版本,它具备Car 类的所有功能:
❶ class Car:
"""一次模拟汽车的简单尝试。"""
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()
def read_odometer(self):
print(f"This car has {self.odometer_reading} miles on it.")
def update_odometer(self, mileage):
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self, miles):
self.odometer_reading += miles
❷ class ElectricCar(Car):
"""电动汽车的独特之处。"""
❸ def __init__(self, make, model, year):
"""初始化父类的属性。"""
❹ super().__init__(make, model, year)
❺ my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
首先是Car 类的代码(见❶)。创建子类时,**父类必须包含在当前文件中,且位于子类前面。**在❷处,定义了子类ElectricCar 。定义子类时,**必须在圆括号内指定父类的名称。**方法__init__() 接受创建Car 实例所需的信息(见❸)。
❹处的super() 是一个特殊函数,让你能够调用父类的方法。这行代码让Python调用Car 类的方法__init__() ,让ElectricCar 实例包含这个方法中定义的所有属性。父类也称为超类 (superclass),名称super 由此而来。
为测试继承能够正确地发挥作用,我们尝试创建一辆电动汽车,但提供的信息与创建普通汽车时相同。在❺处,创建ElectricCar 类的一个实例,并将其赋给变量my_tesla 。这行代码调用ElectricCar 类中定义的方法__init__() ,后者让Python调用父类Car 中定义的方法__init__() 。我们提供了实参’tesla’、‘model s’ 和2019 。
除方法__init___() 外,电动汽车没有其他特有的属性和方法。当前,我们只想确认电动汽车具备普通汽车的行为:
输出:
2019 Tesla Model S
ElectricCar 实例的行为与Car 实例一样。
2 给子类定义属性和方法class Car:
--snip--
class ElectricCar(Car):
"""电动汽车的独特之处。"""
def __init__(self, make, model, year):
"""
初始化父类的属性。
再初始化电动汽车特有的属性。
"""
super().__init__(make, model, year)
❶ self.battery_size = 75
❷ def describe_battery(self):
"""打印一条描述电瓶容量的消息。"""
print(f"This car has a {self.battery_size}-kWh battery.")
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.describe_battery()
对于ElectricCar 类的特殊程度没有任何限制。模拟电动汽车时,可根据所需的准确程度添加任意数量的属性和方法。如果一个属性或方法是任何汽车都有的,而不是电动汽车特有的,就应将其加入到Car 类而非ElectricCar 类中。这样,使用Car 类的人将获得相应的功能,而ElectricCar 类只包含处理电动汽车特有属性和行为的代码。
3 重写父类的方法对于父类的方法,只要它不符合子类模拟的实物的行为,都可以进行重写。为此,可在子类中定义一个与要重写的父类方法同名的方法。这样,Python将不会考虑这个父类方法,而只关注你在子类中定义的相应方法。
class ElectricCar(Car):
--snip--
def fill_gas_tank(self):
"""电动汽车没有油箱。"""
print("This car doesn't need a gas tank!")
4 将实例用作属性
将大型类拆分成多个协同工作的小类。
例如,不断给ElectricCar 类添加细节时,我们可能发现其中包含很多专门针对汽车电瓶的属性和方法。在这种情况下,可将这些属性和方法提取出来,放到一个名为Battery 的类中,并将一个Battery 实例作为ElectricCar 类的属性:
class Car:
--snip--
❶ class Battery:
"""一次模拟电动汽车电瓶的简单尝试。"""
❷ def __init__(self, battery_size=75):
"""初始化电瓶的属性。"""
self.battery_size = battery_size
❸ def describe_battery(self):
"""打印一条描述电瓶容量的消息。"""
print(f"This car has a {self.battery_size}-kWh battery.")
class ElectricCar(Car):
"""电动汽车的独特之处。"""
def __init__(self, make, model, year):
"""
初始化父类的属性。
再初始化电动汽车特有的属性。
"""
super().__init__(make, model, year)
❹ self.battery = Battery()
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
9.4 导入类
将类存储在模块中,然后在主程序中导入所需的模块。
1 导入单个类模块car.py,其中只包含Car 类的代码。
创建另一个文件my_car.py,在其中导入Car 类并创建其实例:
from car import Car
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 23
my_new_car.read_odometer()
2 在一个模块中存储多个类
虽然同一个模块中的类之间应存在某种相关性,但可根据需要在一个模块中存储任意数量的类。Battery 类和ElectricCar 类都可帮助模拟汽车,下面将它们都加入模块car.py中:
"""一组用于表示燃油汽车和电动汽车的类。"""
class Car:
--snip--
class Battery:
"""一次模拟电动汽车电瓶的简单尝试。"""
def __init__(self, battery_size=75):
"""初始化电瓶的属性。"""
self.battery_size = battery_size
def describe_battery(self):
"""打印一条描述电瓶容量的消息。"""
print(f"This car has a {self.battery_size}-kWh battery.")
def get_range(self):
"""打印一条描述电瓶续航里程的消息。"""
if self.battery_size == 75:
range = 260
elif self.battery_size == 100:
range = 315
print(f"This car can go about {range} miles on a full charge.")
class ElectricCar(Car):
"""模拟电动汽车的独特之处。"""
def __init__(self, make, model, year):
"""
初始化父类的属性。
再初始化电动汽车特有的属性。
"""
super().__init__(make, model, year)
self.battery = Battery()
3 从一个模块中导入多个类
from car import Car, ElectricCar4 导入整个模块
还可以导入整个模块,再使用句点表示法访问需要的类。
❶ import car
❷ my_beetle = car.Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())
❸ my_tesla = car.ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())
5 导入模块中的所有类
不推荐使用这种导入方式
from module_name import *6 在一个模块中导入另一个模块
将类存储在多个模块中时,你可能会发现一个模块中的类依赖于另一个模块中的类。在这种情况下,可在前一个模块中导入必要的类。
"""一组可用于表示电动汽车的类。""" ❶ from car import Car class Battery: --snip-- class ElectricCar(Car): --snip--7 使用别名
要在程序中创建大量电动汽车实例,需要反复输入ElectricCar ,非常烦琐。为避免这种烦恼,可在import 语句中给ElectricCar 指定一个别名:
from electric_car import ElectricCar as EC8 自定义工作流程
如你所见,在组织大型项目的代码方面,Python提供了很多选项。熟悉所有这些选项很重要,这样你才能确定哪种项目组织方式是最佳的,并能理解别人开发的项
目。
一开始应让代码结构尽可能简单。先尽可能在一个文件中完成所有的工作,确定一切都能正确运行后,再将类移到独立的模块中。如果你喜欢模块和文件的交互方式,可在项目开始时就尝试将类存储到模块中。先找出让你能够编写出可行代码的方式,再尝试改进代码。
9.5 Python标准库Python标准库 是一组模块,我们安装的Python都包含它。你现在对函数和类的工作原理已有大致的了解,可以开始使用其他程序员编写好的模块了。可以使用标准库中的任何函数和类,只需在程序开头包含一条简单的import 语句即可。下面来看看模块random ,它在你模拟很多现实情况时很有用。
在这个模块中,一个有趣的函数是randint() 。它将两个整数作为参数,并随机返回一个位于这两个整数之间(含)的整数。下面演示了如何生成一个位于1和6之间的随机整数:
>>> from random import randint >>> randint(1, 6) 3
在模块random 中,另一个有用的函数是choice() 。它将一个列表或元组作为参数,并随机返回其中的一个元素:
>>> from random import choice >>> players = ['charles', 'martina', 'michael', 'florence', 'eli'] >>> first_up = choice(players) >>> first_up 'florence'
创建与安全相关的应用程序时,请不要使用模块random ,但该模块可以很好地用于创建众多有趣的项目。
9.6 类编码风格类名应采用驼峰命名法 ,即将类名中的每个单词的首字母都大写,而不使用下划线。实例名和模块名都采用小写格式,并在单词之间加上下划线。
对于每个类,都应紧跟在类定义后面包含一个文档字符串。这种文档字符串简要地描述类的功能,并遵循编写函数的文档字符串时采用的格式约定。每个模块也都应包含一个文档字符串,对其中的类可用于做什么进行描述。
可使用空行来组织代码,但不要滥用。在类中,可使用一个空行来分隔方法;而在模块中,可使用两个空行来分隔类。
需要同时导入标准库中的模块和你编写的模块时,先编写导入标准库模块的import语句,再添加一个空行,然后编写导入你自己编写的模块的import 语句。在包含多条import 语句的程序中,这种做法让人更容易明白程序使用的各个模块都来自何处。
第 10 章 文件和异常 前言处理文件,让程序能够快速地分析大量数据;
错误处理,程序在面对意外情形时崩溃;
异常,是Python创建的特殊对象,用于管理程序运行时出现的错误;
学习模块json ,它让你能够保存用户数据,以免在程序停止运行后丢失。
10.1 从文件中读取数据 1 读取整个文件要读取文件,需要一个包含几行文本的文件。下面首先创建一个文件,它包含精确到小数点后30位的圆周率值,且在小数点后每10位处换行:
下面的程序打开并读取这个文件,再将其内容显示到屏幕上:
file_reader.py
with open('pi_digits.txt') as file_object:
contents = file_object.read()
print(contents)
在这个程序中,第一行代码做了大量的工作。我们先来看看函数open() 。要以任何方式使用文件,那怕仅仅是打印其内容,都得先打开 文件,才能访问它。函数open() 接受一个参数:要打开的文件的名称。
Python在当前执行的文件所在的目录中查找指定的文件。 在本例中,当前运行的是file_reader.py,因此Python在file_reader.py所在的目录中查找pi_digits.txt。函数open() 返回一个表示文件的对象。在这里,open(‘pi_digits.txt’) 返回一个表示文件pi_digits.txt的对象,Python将该对象赋给file_object 供以后使用。
**关键字with 在不再需要访问文件后将其关闭。**在这个程序中,注意到我们调用了open() ,但没有调用close() 。也可以调用open() 和close() 来打开和关闭文件,但这样做时,如果程序存在bug导致方法close() 未执行,文件将不会关闭。这看似微不足道,但未妥善关闭文件可能导致数据丢失或受损。如果在程序中过早调用close() ,你会发现需要使用文件时它已关闭 (无法访问),这会导致更多的错误。并非在任何情况下都能轻松确定关闭文件的恰当时机,但通过使用前面所示的结构,可让Python去确定:你只管打开文件,并在需要时使用它,Python自会在合适的时候自动将其关闭。
有了表示pi_digits.txt的文件对象后,使用方法read() (前述程序的第二行)读取这个文件的全部内容,并将其作为一个长长的字符串赋给变量contents 。 这样,通过打印contents 的值,就可将这个文本文件的全部内容显示出来:
相比于原始文件,该输出唯一不同的地方是末尾多了一个空行。为何会多出这个空行呢?因为read() 到达文件末尾时返回一个空字符串,而将这个空字符串显示出来时就是一个空行。要删除多出来的空行,可在函数调用print() 中使用rstrip() :
本书前面说过,Python方法rstrip() 删除字符串末尾的空白。现在,输出与原始文件的内容完全相同。
2 文件路径要让Python打开不与程序文件位于同一个目录中的文件,需要提供文件路径,让Python到系统的特定位置去查找。
由于文件夹text_files位于文件夹python_work中,可以使用相对文件路径来打开其中的文件。相对文件路径让Python到指定的位置去查找,而该位置是相对于当前运行的程序所在目录的。例如,可这样编写代码:
with open('text_files/filename.txt') as file_object:
这行代码让Python到文件夹python_work下的文件夹text_files中去查找指定的.txt文件。
注意 显示文件路径时,Windows系统使用反斜杠( )而不是斜杠(/ ),但在代码中依然可以使用斜杠。
还可以将文件在计算机中的准确位置告诉Python,这样就不用关心当前运行的程序存储在什么地方了。这称为绝对文件路径 。
绝对路径通常比相对路径长,因此将其赋给一个变量,再将该变量传递给open()会有所帮助:
file_path = '/home/ehmatthes/other_files/text_files/_filename_.txt' with open(file_path) as file_object:
注意 如果在文件路径中直接使用反斜杠,将引发错误,因为反斜杠用于对字符串中的字符进行转义。例如,对于路径"C:pathtofile.txt" ,其中的t 将被解读为制表符。如果一定要使用反斜杠,可对路径中的每个反斜杠都进行转义,如"C:\path\to\file.txt" 。
3 逐行读取读取文件时,常常需要检查其中的每一行:可能要在文件中查找特定的信息,或者要以某种方式修改文件中的文本。例如,你可能要遍历一个包含天气数据的文件,并使用天气描述中包含sunny字样的行。在新闻报道中,你可能会查找包含标签 的行,并按特定的格式设置它。
要以每次一行的方式检查文件,可对文件对象使用for 循环:
file_reader.py
❶ filename = 'pi_digits.txt' ❷ with open(filename) as file_object: ❸ for line in file_object: print(line)
打印每一行时,发现空白行更多了:
为何会出现这些空白行呢?因为在这个文件中,每行的末尾都有一个看不见的换行符,而函数调用print() 也会加上一个换行符,因此每行末尾都有两个换行符:一
个来自文件,另一个来自函数调用print() 。要消除这些多余的空白行,可在函数调用print() 中使用rstrip() :
使用关键字with 时,open() 返回的文件对象只在with 代码块内可用。如果要在with 代码块外访问文件的内容,可在with 代码块内将文件的各行存储在一个列表中,并在with 代码块外使用该列表:可以立即处理文件的各个部分,也可以推迟到程序后面再处理。
filename = 'pi_digits.txt' with open(filename) as file_object: ❶ lines = file_object.readlines() ❷ for line in lines: print(line.rstrip())
❶处的方法readlines() 从文件中读取每一行,并将其存储在一个列表中。接下来,该列表被赋给变量lines 。在with 代码块外,依然可使用这个变量。在❷处,使用一个简单的for 循环来打印lines 中的各行。因为列表lines 的每个元
素都对应于文件中的一行,所以输出与文件内容完全一致。
将文件读取到内存中后,就能以任何方式使用这些数据了。下面以简单的方式使用圆周率的值。首先,创建一个字符串,它包含文件中存储的所有数字,且没有任何
空格:
filename = 'pi_digits.txt' with open(filename) as file_object: lines = file_object.readlines() pi_string = '' for line in lines: pi_string += line.rstrip() print(pi_string) print(len(pi_string))
注意 读取文本文件时,Python将其中的所有文本都解读为字符串。如果读取的是数,并要将其作为数值使用,就必须使用函数int() 将其转换为整数或使用函数float() 将其转换为浮点数。
6 包含一百万位的大型文件在这里,只打印到小数点后50位,以免终端为显示全部1 000 000位而不断滚动:
filename = 'pi_million_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
print(f"{pi_string[:52]}...")
print(len(pi_string))
7 圆周率值中包含你的生日吗输出:
3.14159265358979323846264338327950288419716939937510…
1000002
我一直想知道自己的生日是否包含在圆周率值中。下面来扩展刚才编写的程序,以确定某个人的生日是否包含在圆周率值的前1 000 000位中。为此,可将生日表示为一个由数字组成的字符串,再检查这个字符串是否包含在pi_string 中:
--snip--
for line in lines:
pi_string += line.strip()
birthday = input("Enter your birthday, in the form mmddyy: ")
if birthday in pi_string:
print("Your birthday appears in the first million digits of pi!")
else:
print("Your birthday does not appear in the first million digits of pi.")
10.2 写入文件
1 写入空文件
filename = 'programming.txt'
with open(filename, 'w') as file_object:
file_object.write("I love programming.")
在本例中,调用open() 时提供了两个实参(见❶)。第一个实参也是要打开的文件的名称。第二个实参(‘w’ )告诉Python,要以写入模式 打开这个文件。**打开文件时,可指定读取模式 (‘r’ )、写入模式 (‘w’ )、附加模式 (‘a’ )或读写模式 (‘r+’ )。**如果省略了模式实参,Python将以默认的只读模式打开文件。
如果要写入的文件不存在,函数open() 将自动创建它。然而,以写入模式(‘w’)打开文件时千万要小心,因为如果指定的文件已经存在,Python将在返回文件对象前清空该文件的内容。
注意 Python只能将字符串写入文本文件。要将数值数据存储到文本文件中,必须先使用函数str() 将其转换为字符串格式。
2 写入多行函数write() 不会在写入的文本末尾添加换行符
filename = 'programming.txt'
with open(filename, 'w') as file_object:
file_object.write("I love programming.")
file_object.write("I love creating new games.")
输出:
I love programming.I love creating new games.
要让每个字符串都单独占一行,需要在方法调用write() 中包含换行符:
filename = 'programming.txt'
with open(filename, 'w') as file_object:
file_object.write("I love programming.n")
file_object.write("I love creating new games.n")
3 附加到文件输出:
I love programming.
I love creating new games.
如果要给文件添加内容,而不是覆盖原有的内容,可以以附加模式 打开文件。以附加模式打开文件时,Python不会在返回文件对象前清空文件的内容,而是将写入文件的行添加到文件末尾。
如果指定的文件不存在,Python将为你创建一个空文件。
filename = 'programming.txt'
with open(filename, 'a') as file_object:
file_object.write("I also love finding meaning in large datasets.n")
file_object.write("I love creating apps that can run in a browser.n")
最终的结果是,文件原来的内容还在,后面则是刚添加的内容。
10.3 异常输出:
I love programming.
I love creating new games.
I also love finding meaning in large datasets.
I love creating apps that can run in a browser.
**Python使用称为异常 的特殊对象来管理程序执行期间发生的错误。**每当发生让Python不知所措的错误时,它都会创建一个异常对象。如果你编写了处理该异常的代码,程序将继续运行;如果未对异常进行处理,程序将停止并显示traceback,其中包含有关异常的报告。
1 处理ZeroDivisionError 异常下面来看一种导致Python引发异常的简单错误。你可能知道,不能用数除以0,但还是让Python这样做:
print(5/0)
显然,Python无法这样做,因此你将看到一个traceback:
在上述traceback中,❶处指出的错误ZeroDivisionError 是个异常对象。Python无法按你的要求做时,就会创建这种对象。在这种情况下,Python将停止运行程序,并指出引发了哪种异常,而我们可根据这些信息对程序进行修改。下面来告诉Python,发生这种错误时怎么办。这样,如果再次发生此类错误,我们就有备无患了。
当你认为可能会发生错误时,可编写一个try-except 代码块来处理可能引发的异常。你让Python尝试运行一些代码,并告诉它如果这些代码引发了指定的异常该怎么办。
处理ZeroDivisionError 异常的try-except 代码块类似于下面这样:
try:
print(5/0)
except ZeroDivisionError:
print("You can't divide by zero!")
如果try-except 代码块后面还有其他代码,程序将接着运行,因为已经告诉了Python如何处理这种错误。下面来看一个捕获错误后程序继续运行的示例。
3 使用异常避免崩溃print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")
while True:
❶ first_number = input("nFirst number: ")
if first_number == 'q':
break
❷ second_number = input("Second number: ")
if second_number == 'q':
break
❸ answer = int(first_number) / int(second_number)
print(answer)
在执行除数为0的除法运算时,它将崩溃,所以要使用以上异常来避免崩溃。
4 else 代码块--snip--
while True:
--snip--
if second_number == 'q':
break
❶ try:
answer = int(first_number) / int(second_number)
❷ except ZeroDivisionError:
print("You can't divide by 0!")
❸ else:
print(answer)
让Python尝试执行try 代码块中的除法运算(见❶),这个代码块只包含可能导致错误的代码。依赖try 代码块成功执行的代码都放在else 代码块中。在本例中,如果除法运算成功,就使用else 代码块来打印结果(见❸)。
except 代码块告诉Python,出现ZeroDivisionError 异常时该如何办(见❷)。如果try 代码块因除零错误而失败,就打印一条友好的消息,告诉用户如何避免这种错误。程序继续运行,用户根本看不到traceback:
5 处理FileNotFoundError 异常输出:
Give me two numbers, and I’ll divide them.
Enter ‘q’ to quit.
First number: 5
Second number: 0
You can’t divide by 0!
First number: 5
Second number: 2
2.5
First number: q
使用文件时,一种常见的问题是找不到文件:查找的文件可能在其他地方,文件名可能不正确,或者这个文件根本就不存在。对于所有这些情形,都可使用try-except 代码块以直观的方式处理。
filename = 'alice.txt' with open(filename, encoding='utf-8') as f: contents = f.read()
相比于本章前面的文件打开方式,这里有两个不同之处。一是使用变量f 来表示文件对象,这是一种常见的做法。二是给参数encoding 指定了值,在系统的默认编码与要读取文件使用的编码不一致时,必须这样做。
输出:
Traceback (most recent call last):
File “alice.py”, line 3, in
with open(filename, encoding=‘utf-8’) as f:
FileNotFoundError: [Errno 2] No such file or directory: ‘alice.txt’
上述traceback的最后一行报告了FileNotFoundError 异常,这是Python找不到要打开的文件时创建的异常。在本例中,这个错误是函数open() 导致的。因此,要处理这个错误,必须将try 语句放在包含open() 的代码行之前:
filename = 'alice.txt'
try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
print(f"Sorry, the file {filename} does not exist.")
6 分析文本
你可以分析包含整本书的文本文件。很多经典文学作品都是简单以文本文件的形式提供的,因为它们不受版权限制。本节使用的文本来自古登堡计划,该计划提供了一系列不受版权限制的文学作品。如果你要在编程项目中使用文学文本,这是一个很不错的资源。
下面来提取童话《爱丽丝漫游奇境记》(Alice in Wonderland )的文本,并尝试计算它包含多少个单词。**我们将使用方法split() ,它能根据一个字符串创建一个单词列表。**下面是对只包含童话名"Alice in Wonderland" 的字符串调用方法split() 的结果:
>>> title = "Alice in Wonderland" >>> title.split() ['Alice', 'in', 'Wonderland']
方法split() 以空格为分隔符将字符串分拆成多个部分,并将这些部分都存储到一个列表中。结果是一个包含字符串中所有单词的列表,虽然有些单词可能包含标点。为计算《爱丽丝漫游奇境记》包含多少个单词,我们将对整篇小说调用split() ,再计算得到的列表包含多少个元素,从而确定整篇童话大致包含多少个单词:
filename = 'alice.txt'
try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
print(f"Sorry, the file {filename} does not exist.")
else:
# 计算该文件大致包含多少个单词。
❶ words = contents.split()
❷ num_words = len(words)
❸ print(f"The file {filename} has about {num_words} words.")
7 使用多个文件
def count_words(filename):
"""计算一个文件大致包含多少个单词。"""
try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
print(f"Sorry, the file {filename} does not exist.")
else:
words = contents.split()
num_words = len(words)
print(f"The file {filename} has about {num_words} words.")
filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:
count_words(filename)
文件siddhartha.txt不存在,但这丝毫不影响该程序处理其他文件:
8 静默失败输出:
The file alice.txt has about 29465 words.
Sorry, the file siddhartha.txt does not exist.
The file moby_dick.txt has about 215830 words.
The file little_women.txt has about 189079 words.
在前一个示例中,我们告诉用户有一个文件找不到。但并非每次捕获到异常都需要告诉用户,有时候你希望程序在发生异常时保持静默,就像什么都没有发生一样继续运行。要让程序静默失败,可像通常那样编写try 代码块,但在except 代码块中明确地告诉Python什么都不要做。Python有一个pass 语句,可用于让Python在代码块中什么都不要做:
def count_words(filename): """计算一个文件大致包含多少个单词。""" try: --snip-- except FileNotFoundError: ❶ pass else: --snip-- filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt'] for filename in filenames: count_words(filename)
pass 语句还充当了占位符,提醒你在程序的某个地方什么都没有做,并且以后也许要在这里做些什么。
9 决定报告哪些错误该在什么情况下向用户报告错误?又该在什么情况下静默失败呢?如果用户知道要分析哪些文件,他们可能希望在有文件却没有分析时出现一条消息来告知原因。如果用户只想看到结果,并不知道要分析哪些文件,可能就无须在有些文件不存在时
告知他们。向用户显示他不想看到的信息可能会降低程序的可用性。Python的错误处理结构让你能够细致地控制与用户分享错误信息的程度,要分享多少信息由你决定。
编写得很好且经过详尽测试的代码不容易出现内部错误,如语法或逻辑错误,但只要程序依赖于外部因素,如用户输入、存在指定的文件、有网络链接,就有可能出现异常。凭借经验可判断该在程序的什么地方包含异常处理块,以及出现错误时该向用户提供多少相关的信息。
10.4 存储数据很多程序都要求用户输入某种信息,如让用户存储游戏首选项或提供要可视化的数据。不管关注点是什么,程序都把用户提供的信息存储在列表和字典等数据结构中。用户关闭程序时,几乎总是要保存他们提供的信息。一种简单的方式是使用模块json 来存储数据。
**模块json 让你能够将简单的Python数据结构转储到文件中,并在程序再次运行时加载该文件中的数据。**你还可以使用json 在Python程序之间分享数据。更重要的是,JSON数据格式并非Python专用的,这让你能够将以JSON格式存储的数据与使用其他编程语言的人分享。这是一种轻便而有用的格式,也易于学习。
1 使用json.dump() 和json.load()我们来编写一个存储一组数的简短程序,再编写一个将这些数读取到内存中的程序。第一个程序将使用json.dump() 来存储这组数,而第二个程序将使用
json.load() 。
函数json.dump() 接受两个实参:要存储的数据,以及可用于存储数据的文件对象。下面演示了如何使用json.dump() 来存储数字列表:
import json numbers = [2, 3, 5, 7, 11, 13] ❶ filename = 'numbers.json' ❷ with open(filename, 'w') as f: ❸ json.dump(numbers, f)
这个程序没有输出,但可以打开文件numbers.json来看看内容。数据的存储格式与
Python中一样:
下面再编写一个程序,使用json.load() 将列表读取到内存中:
import json ❶ filename = 'numbers.json' ❷ with open(filename) as f: ❸ numbers = json.load(f) print(numbers)2 保存和读取用户生成的数据
使用json 保存用户生成的数据大有裨益,因为如果不以某种方式存储,用户的信息会在程序停止运行时丢失。下面来看一个这样的例子:提示用户首次运行程序时输入自己的名字,并在再次运行程序时记住他。
import json
❶ username = input("What is your name? ")
filename = 'username.json'
with open(filename, 'w') as f:
❷ json.dump(username, f)
❸ print(f"We'll remember you when you come back, {username}!")
现在再编写一个程序,向已存储了名字的用户发出问候:
import json
filename = 'username.json'
with open(filename) as f:
❶ username = json.load(f)
❷ print(f"Welcome back, {username}!")
import json
# 如果以前存储了用户名,就加载它。
# 否则,提示用户输入用户名并存储它。
filename = 'username.json'
try:
❶ with open(filename) as f:
❷ username = json.load(f)
❸ except FileNotFoundError:
❹ username = input("What is your name? ")
❺ with open(filename, 'w') as f:
json.dump(username, f)
print(f"We'll remember you when you come back, {username}!")
else:
print(f"Welcome back, {username}!")
3 重构
你经常会遇到这样的情况:代码能够正确地运行,但通过将其划分为一系列完成具体工作的函数,还可以改进。这样的过程称为重构 。重构让代码更清晰、更易于理解、更容易扩展。
import json
def get_stored_username():
"""如果存储了用户名,就获取它。"""
--snip--
def get_new_username():
"""提示用户输入用户名。"""
username = input("What is your name? ")
filename = 'username.json'
with open(filename, 'w') as f:
json.dump(username, f)
return username
def greet_user():
"""问候用户,并指出其名字。"""
username = get_stored_username()
if username:
print(f"Welcome back, {username}!")
else:
username = get_new_username()
print(f"We'll remember you when you come back, {username}!")
greet_user()
第 11 章 测试代码
前言
编写函数或类时,还可为其编写测试。通过测试,可确定代码面对各种输入都能够按要求的那样工作。测试让你深信,即便有更多人使用你的程序,它也能正确地工作。在程序中添加新代码时,也可以对其进行测试,确认不会破坏程序既有的行为。程序员都会犯错,因此每个程序员都必须经常测试其代码,在用户发现问题前找出它们。
在本章中,你将学习如何使用Python模块unittest 中的工具来测试代码,还将学习编写测试用例,核实一系列输入都将得到预期的输出。你将看到测试通过了是什么样子,测试未通过又是什么样子,还将知道测试未通过如何有助于改进代码。你将学习如何测试函数和类,并将知道该为项目编写多少个测试。
要学习测试,必须有要测试的代码。下面是一个简单的函数,它接受名和姓并返回整洁的姓名:
def get_formatted_name(first, last):
"""生成整洁的姓名。"""
full_name = f"{first} {last}"
return full_name.title()
为核实get_formatted_name() 像期望的那样工作,我们来编写一个使用该函数的程序。程序names.py让用户输入名和姓,并显示整洁的姓名:
from name_function import get_formatted_name
print("Enter 'q' at any time to quit.")
while True:
first = input("nPlease give me a first name: ")
if first == 'q':
break
last = input("Please give me a last name: ")
if last == 'q':
break
formatted_name = get_formatted_name(first, last)
print(f"tNeatly formatted name: {formatted_name}.")
输出:
Enter ‘q’ at any time to quit.
Please give me a first name: janis
Please give me a last name: joplin
Neatly formatted name: Janis Joplin.
Please give me a first name: bob
Please give me a last name: dylan
Neatly formatted name: Bob Dylan.
Please give me a first name: q
从上述输出可知,合并得到的姓名正确无误。现在假设要修改get_formatted_name() ,使其还能够处理中间名。这样做时,要确保不破坏这个函数处理只含有名和姓的姓名的方式。为此,可在每次修改
get_formatted_name() 后都进行测试:运行程序names.py,并输入像Janis Joplin这样的姓名。不过这太烦琐了。所幸Python提供了一种自动测试函数输出的高效方式。倘若对get_formatted_name() 进行自动测试,就能始终确信当提供测试过的姓名时,该函数都能正确工作。
Python标准库中的模块unittest 提供了代码测试工具。单元测试 用于核实函数的某个方面没有问题。测试用例 是一组单元测试,它们一道核实函数在各种情形下的行为都符合要求。良好的测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情形的测试。全覆盖 的测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。对于大型项目,要进行全覆盖测试可能很难。通常,最初只要针对代码的重要行为编写测试即可,等项目被广泛使用时再考虑全覆盖。
你需要一段时间才能习惯创建测试用例的语法,但创建测试用例之后,再添加针对函数的单元测试就很简单了。要为函数编写测试用例,可先导入模块unittest 和要测试的函数,再创建一个继承unittest.TestCase 的类,并编写一系列方法对
函数行为的不同方面进行测试。
下面的测试用例只包含一个方法,它检查函数get_formatted_name() 在给定名和姓时能否正确工作:
import unittest
from name_function import get_formatted_name
❶ class NamesTestCase(unittest.TestCase):
"""测试name_function.py。"""
def test_first_last_name(self):
"""能够正确地处理像Janis Joplin这样的姓名吗?"""
❷ formatted_name = get_formatted_name('janis', 'joplin')
❸ self.assertEqual(formatted_name, 'Janis Joplin')
❹ if __name__ == '__main__':
unittest.main()
首先,导入了模块unittest 和要测试的函数get_formatted_name() 。在❶处,创建了一个名为NamesTestCase 的类,用于包含一系列针对get_formatted_name() 的单元测试。这个类可以随意命名,但最好让它看起来与要测试的函数相关并包含Test 字样。这个类必须继承unittest.TestCase类,这样Python才知道如何运行你编写的测试。
NamesTestCase 只包含一个方法,用于测试get_formatted_name() 的一个方面。将该方法命名为test_first_last_name() ,因为要核实的是只有名和姓的姓名能否被正确格式化。运行test_name_function.py时,所有以test_ 打头的方
法都将自动运行。在这个方法中,调用了要测试的函数。在本例中,使用实参’janis’ 和’joplin’ 调用get_formatted_name() ,并将结果赋给变量
formatted_name (见❷)。
在❸处,使用了unittest 类最有用的功能之一:**断言 方法。**断言方法核实得到的结果是否与期望的结果一致。在这里,我们知get_formatted_name() 应返回名和姓首字母大写且之间有一个空格的姓名,因此期望formatted_name 的值为Janis Joplin 。为检查是否确实如此,我们调用unittest 的方法assertEqual() ,并向它传递formatted_name 和’Janis Joplin’ 。代码行
self.assertEqual(formatted_name, 'Janis Joplin')
的意思是:“将formatted_name 的值与字符串’Janis Joplin’ 比较。如果它们相等,那么万事大吉;如果它们不相等,就告诉我一声!”
我们将直接运行这个文件,但需要指出的是,很多测试框架都会先导入测试文件再运行。导入文件时,解释器将在导入的同时执行它。❹处的if 代码块检查特殊变量__name__ ,这个变量是在程序执行时设置的。如果这个文件作为主程序执行,变量__name__ 将被设置为’main’ 。在这里,调用unittest.main() 来运行测试用例。如果这个文件被测试框架导入,变量__name__ 的值将不是’main’ ,因此不会调用unittest.main() 。
得到的输出如下:
第一行的句点表明有一个测试通过了。接下来的一行指出Python运行了一个测试,消耗的时间不到0.001秒。最后的OK 表明该测试用例中的所有单元测试都通过了。
上述输出表明,给定包含名和姓的姓名时,函数get_formatted_name() 总是能正确地处理。修改get_formatted_name() 后,可再次运行这个测试用例。如果它通过了,就表明给定Janis Joplin这样的姓名时,该函数依然能够正确地处理。
3 未通过的测试测试未通过时结果是什么样的呢?我们来修改get_formatted_name() ,使其能够处理中间名,但同时故意让该函数无法正确处理像Janis Joplin这样只有名和姓的姓名。
下面是函数get_formatted_name() 的新版本,它要求通过一个实参指定中间名:
def get_formatted_name(first, middle, last):
"""生成整洁的姓名。"""
full_name = f"{first} {middle} {last}"
return full_name.title()
这个版本应该能够正确处理包含中间名的姓名,但对其进行测试时,我们发现它不再能正确处理只有名和姓的姓名。这次运行程test_name_function.py时,输出如下:
里面包含很多信息,因为测试未通过时,需要让你知道的事情可能有很多。第一行输出只有一个字母E (见❶),指出测试用例中有一个单元测试导致了错误。接下来,我们看到NamesTestCase 中的test_first_last_name() 导致了错误(见❷)。测试用例包含众多单元测试时,知道哪个测试未通过至关重要。在❸处,我们看到了一个标准的traceback,指出函数调用get_formatted_name
(‘janis’, ‘joplin’) 有问题,因为缺少一个必不可少的位置实参。我们还看到运行了一个单元测试(见❹)。最后是一条消息,指出整个测试用例未通
过,因为运行该测试用例时发生了一个错误(见❺)。这条消息位于输出末尾,让你一眼就能看到。你可不希望为获悉有多少测试未通过而翻阅长长的输出。
测试未通过时,不要修改测试,而应修复导致测试不能通过的代码:检查刚刚对函数所做的修改,找出导致函数行为不符合预期的修改。
5 添加新测试--snip-- class NamesTestCase(unittest.TestCase): """测试name_function.py。""" def test_first_last_name(self): --snip-- def test_first_last_middle_name(self): """能够正确地处理像Wolfgang Amadeus Mozart这样的姓名吗?""" ❶ formatted_name = get_formatted_name( 'wolfgang', 'mozart', 'amadeus') self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart') if __name__ == '__main__': unittest.main()11.2 测试类
在本章前半部分,你编写了针对单个函数的测试,下面来编写针对类的测试。很多程序中都会用到类,因此证明你的类能够正确工作大有裨益。如果针对类的测试通过了,你就能确信对类所做的改进没有意外地破坏其原有的行为。
1 各种断言方法表11-1描述了6个常用的断言方法。使用这些方法可核实返回的值等于或不等于预期的值,返回的值为True 或False ,以及返回的值在列表中或不在列表中。只能在继承unittest.TestCase 的类中使用这些方法,随后来看看如何在测试类时使用中之一。
类的测试与函数的测试相似,你所做的大部分工作是测试类中方法的行为。不过还是存在一些不同之处,下面编写一个要测试的类。来看一个帮助管理匿名调查的类:
class AnonymousSurvey:
"""收集匿名调查问卷的答案。"""
❶ def __init__(self, question):
"""存储一个问题,并为存储答案做准备。"""
self.question = question
self.responses = []
❷ def show_question(self):
"""显示调查问卷。"""
print(self.question)
❸ def store_response(self, new_response):
"""存储单份调查答卷。"""
self.responses.append(new_response)
❹ def show_results(self):
"""显示收集到的所有答卷。"""
print("Survey results:")
for response in self.responses:
print(f"- {response}")
为证明AnonymousSurvey 类能够正确工作,编写一个使用它的程序:
from survey import AnonymousSurvey
# 定义一个问题,并创建一个调查。
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)
# 显示问题并存储答案。
my_survey.show_question()
print("Enter 'q' at any time to quit.n")
while True:
response = input("Language: ")
if response == 'q':
break
my_survey.store_response(response)
# 显示调查结果。
print("nThank you to everyone who participated in the survey!")
my_survey.show_results()
AnonymousSurvey 类可用于进行简单的匿名调查。假设我们将它放在了模块survey 中,并想进行改进:让每位用户都可输入多个答案;编写一个方法,只列出不同的答案并指出每个答案出现了多少次;再编写一个类,用于管理非匿名调查。
进行上述修改存在风险,可能影AnonymousSurvey 类的当前行为。例如,允许每位用户输入多个答案时,可能会不小心修改处理单个答案的方式。要确认在开发这个模块时没有破坏既有行为,可以编写针对这个类的测试。
下面来编写一个测试,对AnonymousSurvey 类的行为的一个方面进行验证:如果用户面对调查问题只提供一个答案,这个答案也能被妥善地存储。为此,我们将在这个答案被存储后,使用方法assertIn() 来核实它确实在答案列表中:
import unittest
from survey import AnonymousSurvey
❶ class TestAnonymousSurvey(unittest.TestCase):
"""针对AnonymousSurvey类的测试。"""
❷ def test_store_single_response(self):
"""测试单个答案会被妥善地存储。"""
question = "What language did you first learn to speak?"
❸ my_survey = AnonymousSurvey(question)
my_survey.store_response('English')
❹ self.assertIn('English', my_survey.responses)
if __name__ == '__main__':
unittest.main()
要测试类的行为,需要创建其实例。在❸处,使用问题"What language did you first learn to speak?" 创建一个名为my_survey 的实例,然后使用方法store_response() 存储单个答案English 。接下来,检查English 是否包含在列表my_survey.responses 中,以核实这个答案是否被妥善地存储了(见❹)。
4 方法setUp()在前面的test_survey.py中,我们在每个测试方法中都创建了一个AnonymousSurvey 实例,并在每个方法中都创建了答案。unittest.TestCase类包含的方法setUp() 让我们只需创建这些对象一次,就能在每个测试方法中使用。如果在TestCase 类中包含了方法setUp() ,Python将先运行它,再运行各个以test_ 打头的方法。这样,在你编写的每个测试方法中,都可使用在方法setUp() 中创建的对象
import unittest from survey import AnonymousSurvey class TestAnonymousSurvey(unittest.TestCase): """针对AnonymousSurvey类的测试。""" def setUp(self): """ 创建一个调查对象和一组答案,供使用的测试方法使用。 """ question = "What language did you first learn to speak?" ❶ self.my_survey = AnonymousSurvey(question) ❷ self.responses = ['English', 'Spanish', 'Mandarin'] def test_store_single_response(self): """测试单个答案会被妥善地存储。""" self.my_survey.store_response(self.responses[0]) self.assertIn(self.responses[0], self.my_survey.responses) def test_store_three_responses(self): """测试三个答案会被妥善地存储。""" for response in self.responses: self.my_survey.store_response(response) for response in self.responses: self.assertIn(response, self.my_survey.responses) if __name__ == '__main__': unittest.main()
测试自己编写的类时,方法setUp() 让测试方法编写起来更容易:可在setUp()方法中创建一系列实例并设置其属性,再在测试方法中直接使用这些实例。相比于在每个测试方法中都创建实例并设置其属性,这要容易得多。
运行测试用例时,每完成一个单元测试,Python都打印一个字符:**测试通过时打印一个句点,测试引发错误时打印一个E ,而测试导致断言失败时则
打印一个F 。**这就是你运行测试用例时,在输出的第一行中看到的句点和字符数量各不相同的原因。如果测试用例包含很多单元测试,需要运行很长时间,就可通过观察这些结果来获悉有多少个测试通过了。



