整理一下Python相关内容,方便查阅。
参考用书:《Python编程:从入门到实践》,作者:Eric Matthes
第8章 文件和异常
8.1 读取文件
这一小节中用到两个文件,一个文件是pi_digits.txt,一个文件是pi_million_digits.txt。它们均保存在与本小节编程文件相同的目录下。可点击下面两个链接下载这两个文件。
8.1.1 读取全部文件内容
下面的代码会打开并读取pi_digits.txt文件,并将其内容显示到屏幕上。
# file_reader.py
with open('pi_digits.txt') as file_object: # 打开文件
contents = file_object.read() # 读取文件
print(contents.rstrip()) # 打印文件内容
运行结果:
3.1415926535
8979323846
2643383279
分析程序代码:
-
open()函数:参数为要打开的文件的相对路径或绝对路径,返回一个表示文件的对象。使用as为这个对象指定别名file_object。
-
with关键字:它可以在不再需要访问文件后将其关闭。关闭时机由Python自动确定。
也可以不用with关键字,使用open()和close()函数配合来打开和关闭文件,但这样如果程序出现bug可能会导致close()函数得不到调用,从而造成数据丢失,因此不建议这样做。
-
read()函数:文件对象的一个方法,读取这个文件的内容,并将内容作为字符串返回。
-
空行处理:read()函数到达文件末尾时自动返回一个空字符串,打印时作为空行打印,这样输出的结果就比原文件多了一个空行。因此使用rstrip()方法删除字符串末尾的空白符。
当使用其它目录下的文件时,注意在windows系统中文件路径使用反斜线(\),在Linux和OS X中使用斜线(/)。
8.1.2 逐行读取文件内容
可对文件对象使用for循环来遍历文件的每一行:
# file_reader.py
filename = 'pi_digits.txt'
with open(filename) as file_object:
for line in file_object:
print(line.rstrip())
运行结果:
3.1415926535
8979323846
2643383279
这里要注意,逐行读取时依然有空行处理,处理的原理和方式与上面相同。
8.1.3 创建包含文件各行内容的列表
使用with关键字时,open()返回的文件对象只能在with代码块内使用,因此需要有适当的变量来存储文件内容。可使用函数readlines()将文件各行的内容存储在一个列表中:
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(pi_string[:52] + "...")
print(len(pi_string))
运行结果:
3.14159265358979323846264338327950288419716939937510...
100002
8.2 写入文件
下面的程序将创建名为programming.txt的文件,并在其中写入一句话I love programming.
:
filename = 'programming.txt'
with open(filename, 'w') as file_object:
file_object.write("I love programming.")
我们来看这段代码中的两个函数:
- open()函数:在open()函数中,第一个参数表示要打开的文件,如果文件不存在,则新建该文件。
第二个参数表示文件的打开模式,'r'
表示以读取模式打开,'r'
表示以写入模式打开,'a'
表示以附加模式打开。
值得注意的是,以写入模式打开文件后,文件对象中的内容会被清空,因此后面写入的内容会完全覆盖原文件的内容。如果想要保留原来的内容,承接在原内容之后写入内容,则应当使用附加模式打开。
- write()函数:
文件对象.write(str)
会在指定的文件对象中写入字符串str。需要注意两点:① str必须为字符串,如果是数值,请用str()
转换数据类型;② write()函数写入字符串后不会换行,想换行请手动添加'\n'
。
8.3 异常
8.3.1 使用try-except-else代码块处理异常
一个常见的错误是,除数为零。这时程序会抛出ZeroDivisionError,并终止运行。如果我们能在代码中提前告诉Python发生这种异常时应该怎么办,就有备无患了:
try:
print(5/0)
except ZeroDivisionError:
print("You can't divide by zero!")
运行结果:
You can't divide by zero!
这时try-except代码块,使用它处理异常的好处:首先,错误提示很友好,不是traceback;其次,如果后面还有代码,程序将继续运行。即,我们可以使用异常避免崩溃。
避免崩溃而出现traceback有两个好处:首先,不懂得技术的用户看到traceback会感到迷惑,而看到友好的错误提示信息;其次,懂得技术的程序员看到traceback中包含了许多与源代码相关的信息,这可以被用来展开攻击,隐藏traceback在避免攻击中显得尤为重要。
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
try:
answer = int(first_number) / int(second_number)
except ZeroDivisionError:
print("You can't divide by zero!")
else:
print(answer)
try-except-else大致的原理是,运行try中的代码块,如果出现了except后面提到的异常,执行except代码块中的内容,如果没有抛出异常,执行else代码块中的内容。这段完成后继续执行后面的程序。
如果try中的代码块抛出异常,但不是except后面跟的异常,就会出现traceback并崩溃。
8.3.2 处理FileNotFoundError异常
上面提到了ZeroDivisionError,还有一种异常是FileNotFoundError,它发生在尝试打开一个文件而该文件不存在时。例如,假设当前目录中不存在alice.txt文件,那么执行下面的代码:
filename = 'alice.txt'
with open(filename) as f_obj:
contents = f_obj.read()
会抛出一个含FileNotFoundError异常的trackback。为避免这一点,我们可以将with代码块放入try-except-else语句中。我们下面试图写一个程序,它能计算一个文本中含有多少个单词,为此先介绍split()函数:
split()函数可以将指定的字符串按空白符分隔成一个个单词,将这些单词保存到一个列表中并返回。例如,
title = "Alice's adventures in wonderland"
print(title.split())
运行结果:
["Alice's", 'adventures', 'in', 'wonderland']
我们要计算这几本书的单词数(点击链接可下载):
alice.txt (Alice’s Adventures in Wonderland)
moby_dick.txt (Moby Dick)
little_women.txt (Little Women)
还有一本并没有下载的书siddhartha.txt。
代码如下:
def count_words(filename):
"""计算一个文件大致包含多少个单词"""
try:
with open(filename) as f_obj:
contents = f_obj.read()
except FileNotFoundError:
print("Sorry, the file " + filename + " does not exist.")
else:
# 计算文件大致包含多少个单词
words = contents.split()
num_words = len(words)
print("The file " + filename + " has about " + str(num_words) + " words.")
filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:
count_words(filename)
运行结果:
The file alice.txt has about 26436 words.
Sorry, the file siddhartha.txt does not exist.
The file moby_dick.txt has about 214422 words.
The file little_women.txt has about 187000 words.
下面介绍pass语句。我们将except代码块修改为
...
except FileNotFoundError:
pass
...
这时遇到FileNotFoundError异常时程序便会“一声不吭”,继续执行,运行结果变为:
The file alice.txt has about 26436 words.
The file moby_dick.txt has about 214422 words.
The file little_women.txt has about 187000 words.
8.4 存储数据
我们使用标准库中的json模块来实现数据的存储。用到其中的两个方法,dump()和load(),前者将内容存储到文件中,后者加载文件内容。
例如,我们想存一串数字列表到文件numbers.json中,便于另外的进程调用,可以用dump()方法实现:
import json
numbers = [2, 3, 5, 7, 11, 13]
filename = 'numbers.json'
with open(filename, 'w') as f_obj:
json.dump(numbers, f_obj)
该段代码新建了一个名为numbers.json的文件,并将[2, 3, 5, 7, 11, 13]
存入其中。
现在我们想调用储存的数字列表,可以用load()方法实现:
import json
filename = 'numbers.json'
with open(filename) as f_obj:
numbers = json.load(f_obj)
print(numbers)
运行结果:
[2, 3, 5, 7, 11, 13]
第9章 测试代码
9.1 测试函数
假设我们有模块name_function.py:
def get_formatted_name(first, last, middle=''):
"""Generate a neatly formatted full name."""
if middle:
full_name = first + ' ' + middle + ' ' + last
else:
full_name = first + ' ' + last
return full_name.title()
用以下代码测试该模块中给出的函数的正确性:
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')
def test_first_middle_last_name(self):
"""能够正确地处理像Wolfgang Amadeus Mozart这样的姓名吗?"""
formatted_name = get_formatted_name('wolfgang', 'mozart', 'amadeus')
self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart')
-
导入unittest模块,它能提供代码测试工具。
-
创建一个类NamesTestCase,继承unittest.TestCase类。
-
所有以test_打头的方法都将自动运行。上例中每个test_方法都能用来测试函数get_formatted_name()的一个方面。
-
unittest类最有用的功能:断言方法。这里用到的是assertEqual()。它能比较传入的两个字符串是否相同。
9.2 测试类
有6个常用的断言方法:
方法 | 用途 |
---|---|
assertEqual(a, b) | 核实 a == b |
assertNotEqual(a, b) | 核实 a != b |
assertTrue(x) | 核实x为True |
assertFalse(x) | 核实x为False |
assertIn(item, list) | 核实item在list中 |
assertNotIn(item, list) | 核实item不在list中 |
测试类与测试函数基本一致,用到上述断言方法测试类的功能。
unittest.TestCase类还提供了一个setUp()方法,可以用它来提高效率。假设模块survey.py中有类AnonymousSurvey:
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('- ' + response)
我们可以用setUp()方法先创建一个问题和一份回答,供后面的测试方法使用。
# test_survey.py
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('English', 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)