9.文件和异常
1)从文件中读取数据
with open('pi_digits.txt') as file_object:
contents = file_object.read()
要以任何方式使用文件,哪怕只是打印内容,都需要打开文件才能访问。
函数open()接一个参数,即要打开的文件的名称。Python将在当前执行的文件所在的目录中查找指定的文件。
open()打开文件后不需要关闭,系统会自动在合适时机将其关闭。自行调用close可能会在存在bug时导致close()未执行而没有关闭文件。未妥善关闭文件可能导致数据丢失或受损。
使用语句print(contents)后输出结果的末尾多了一个空行(如果鼠标指针在新启一行)。这是因为read()达到文件末尾时放回一个空字符串。要删除空行可以调用print(contents.rstrip())
open()参数中显示文件路径时一般使用斜杆(虽然Windows系统使用反斜杠),因为反斜杠会被识别为转义字符,当然也可以对每个反斜杠进行转义。如'text_files/filename.txt'或'text_files\\filename.txt'(此二者为相对路径)
绝对路径:'C:\\path\\to\\file.txt'
"""逐行读取""" filename = 'pi_digits.txt' with open(filename) as file_object: for line in file_object: print(line.rstrip())
读取文本文件时,Python将其中的所有文本都解读为字符串。如果要将其作为数值使用,就必须使用函数int()转化为整数或使用float()转化为浮点数。
""""判断生日是否在Π中""" filename = 'pi_digits.txt' pi_string = '' with open(filename) as file_object: lines = file_object.readlines() 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 digits.") else: print("your birthday does not appear in the digits.")
2)写入文件
filename = 'pi_digits1.txt' with open(filename,'w') as file_object: file_object.write("Hello World!")
open()第二个实参为模式实参:
‘w’:以写入模式打开;
‘r’:以读取模式打开;
‘a’:以附加模式打开;(给文件添加内容而不是覆盖原有的内容)
‘r+’:以读写模式打开;
如果省略了模式实参,Python将以默认的只读模式打开文件。
如果写入的文件不存在将自动创建,如果指定的文件已经存在,将清空该文件的内容。
Python只能将字符串写入文本文件,要将数值数据存储到文本文件中,必须先使用函数str()使其转化为字符串格式。
函数write()不会在写入的文本末尾添加换行符,因此如果写入多行时没有指定换行符会导致两行连接在一起。
‘a’会将内容添加到文件末尾,同样不会有换行符。
3)异常
每当发生让Python不知所措的错误是,他都会创建一个一异常对象。如果你编写了处理该异常的代码,程序将继续运行,如果未对异常进行处理,程序将停止并显示traceback,其中包含有关异常的报告。
"""处理ZeroDivisionError异常""" try: print(5/0) except ZeroDivisionError: print("You can't divide by zero!") else: print(answer)
将导致错误的代码行放在一个try代码块中。如果try代码块中的代码运行起来没有问题,Python将跳过except代码块;如果导致了错误将查找与之匹配的except代码块并运行其中的代码。如果try-except代码块后面还有其他代码,程序会接着运行。
有时候一些代码尽在try代码块成功执行时才需要运行,这些代码应放在else代码块中。(尝试某些代码,出现某些指定异常则执行对应except代码块,没有异常则运行else代码块)
FileNotFoundError异常:Python找不到要打开的文件时创建的异常。这个错误是函数open()导致的,因此要处理这个错误,必须将tr语句放在包含open()的代码行之前。
title = "Alice in Wonderland in" print(title.split())
输出结果为['Alice', 'in', 'Wonderland', 'in']
def count_word(name): """计算一个文件大致包含多少个单词""" try: with open(name, encoding='utf-8') as f: contents = f.read() except FileNotFoundError: print(f"Sorry,the file {name} does not exist.") else: words = contents.split()#split可以根据一个字符串创建一个单词列表。 num_words = len(words) print(f"The file {name} has about {num_words} words.") filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt'] for filename in filenames: count_word(filename)
静默失败:捕获到异常后像什么都没有发生一样继续运行。只需要把except代码块的代码换成pass:
except FileNotFoundError:
pass
4)存储数据
用户关闭程序时。总是要保存他们提供的信息,一种简单的方是使用模块json(JavaScript Object Notation)来存储数据。模块json让你能够将简单的Python数据结构转储到文件中,并在程序再次运行时加载该文件中的数据。
import json numbers = [2, 3, 5, 7, 11, 13] filename = 'numbers.json' with open(filename, 'w') as f: json.dump(numbers,f)
json.dump()接受两个实参:要存储的数据,以及可用于存储数据的文件对象。数据的存储格式与Python中一样。
import json filename = 'numbers.json' with open(filename) as f: numbers = json.load(f) print(numbers)
json.load()加载存储在其实参中的信息。
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 will remember you when you come back,{username}!") else: print(f"Welcome back,{username}!")
上述代码可进行重构,而得到更清晰且易于维护和扩展的代码,如下:
import json def get_stored_username(): """如果存储了用户名,就获取它""" filename = 'username.json' try: with open(filename) as f: username = json.load(f) except FileNotFoundError: return None else: return username 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_new_username() if username: print(f"Welcome back,{username}!") else: username = get_new_username() print(f"We will remember you when you come back,{username}!") greet_user()
10.测试代码
1)测试函数
Python提供了了一种自动测试函数输出的高效方式(不再需要每次修改代码后自行输入数据测试),由标准库中的模块unittest提供。
单元测试用于核实函数的某个方面没有问题。测试用例是一组单元测试,它们一道核实在各种情形下的行为全部符合要求。良好的测试用例考虑了函数可能收到的各种输入,包含针对所有这些情形的测试。
全覆盖的测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。
"""name_function""" def get_formatted_name(first, last, middle=""): """生成姓名""" if middle: full_name = f"{first} {middle} {last}" else: full_name = f"{first} {last}" return full_name.title()
import unittest from name_function import get_formatted_name class NameTestCase(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_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()
运行该程序时,所有以test_打头的方法都将自行运行。
创建一个类,用于包含一系列针对要测试函数的单元测试。
这个类可以随便命名(最好以Test起始),但必须继承unittest.TextCase类,这样Python才知道如何运行你编写的测试。
self.assertEqual(formatted_name, 'Janis Joplin')是unittest类最有用的功能之一:断言方法。
断言方法核实得到的结果是否与期望的结果一致。意思是将formatted_name的值与字符串’Janis Joplin’进行比较。
很多测试框架都会先导入测试文件再运行,导入文件时,解释器将在导入的同时执行它。
if代码块检查特殊变量__name__,这个变量时在程序执行时设置的。如果这个文件为主程序执行时,该变量将被设置为__main__。在这里,调用unittest.main()来运行测试用例,如果这个文件被测试框架导入,变量__name__的值将不是__main__,因此不会调用unittest.main()
若输出结果为OK则表示该测试用例中的单元测试都通过了,未通过测试则返回FAILED。
2)测试类
六个常用的断言方法(只能在继承unittest.TestCase的类中使用这些方法)
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中
"""survey""" 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 responses: print(f"-{response}")
import unittest from survey import AnonymousSurvey class TestAnonymousSurvey(unittest.TestCase): 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) def test_store_three_response(self): question = "What language did you first learn to speak?" my_survey = AnonymousSurvey(question) responses = ['English', 'Spanish', 'Mandarin'] for response in responses: my_survey.store_response(response) for response in responses: self.assertIn(response, my_survey.responses) if __name__ == '__main__': unittest.main()
在该测试中,每个测试方法都创建了一个AnonymousSurvey的实例,并在每个方法中都创建了答案。
然而调用unittest.TestCase中的方法setUp()可以让我们只需要创建这些对象一次,就可以在每个测试方法中使用。
如果在测试方法中包含了方法setUpI(),Python将先运行它,再运行每个以test_打头的方法,这样在每个测试方法中都可以使用在方法setUp()中创建的对象。如下:
import unittest from survey import AnonymousSurvey class TestAnonymousSurvey(unittest.TestCase): 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_response(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()创建了一个调查对象以及一个答案列表,存储这两样东西的变量名包含了前缀self(即存储在属性中),因此可在这个类的任何地方使用。
的实例,并在每个方法中都创建了答案。
然而调用unittest.TestCase中的方法setUp()可以让我们只需要创建这些对象一次,就可以在每个测试方法中使用。
如果在测试方法中包含了方法setUpI(),Python将先运行它,再运行每个以test_打头的方法,这样在每个测试方法中都可以使用在方法setUp()中创建的对象。如下:
import unittest from survey import AnonymousSurvey class TestAnonymousSurvey(unittest.TestCase): 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_response(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()创建了一个调查对象以及一个答案列表,存储这两样东西的变量名包含了前缀self(即存储在属性中),因此可在这个类的任何地方使用。
测试自己编写的类时,方法setUp()让测试方法编写起来更容易:可在setUp()方法中创建一系列实例并设置其属性,再在测试方法中直接使用这些实例。相比与在每个测试方法中都创建实例并设置其属性要容易得多。
-----------------------------------整理自《Python编程 从入门到实践(第二版)》