文章目录
- 第十章.文件和异常
- 10.1 从文件中读取数据
- 10.1.1 读取整个文件read()
- 10.1.2 相对路径和绝对路径
- 10.1.3 创建包含文件各行的列表readlines()
- 10.1.4 读取一行readline()
- 10.2 写入文件write()
- 10.3 异常 try-except
- 10.3.1 处理ZeroDivisionError异常
- 10.3.2 try-except-else代码块
- 10.3.3 try-except的三种格式
- 10.3.4 示例-分析文本的单词数
- 10.4 使用json格式保存和读取用户数据
- 10.5 重构—划分成具体函数
- 补充:打开多个文件
第十章.文件和异常
这一章很有用,以后做爬虫,做数据分析都要用到这章的知识
我们将学习处理文件,让程序能够快速地分析大量的数据
10.1 从文件中读取数据
读取文本文件时,Python将其中的所有文本都解读为字符串
10.1.1 读取整个文件read()
要以任何方式使用文件——哪怕仅仅是打印其内容,都得先打开文件,这样才能访问它
read(n) 方法用于从文件读取n个字节,如果未给定n或n为负则读取所有
如果文件最后有多个空行,也会以\n
的形式读取进来,直到读取完所有字符
# open('filename')返回一个文件对象(同目录下),并存到变量file_object中
with open('in.txt') as file_object:
# 读入全部内容,形成一个很长的字符串"1\n2\n3\n4"
contents = file_object.read()
print(contents)
in.txt
1
2
3
4
关键词with在不再需要访问文件后会自动将文件关闭
推荐用with的原因:一般可以调用open()和close()来打开和关闭文件,但如果程序存在bug,导致close语句没有执行,那文件就不会被关闭,而未妥善关闭文件可能导致数据丢失或受损。通过with结构可以让Python自己决定,你只管打开文件,并在需要的时候使用它,Python会自动在合适的时候自动将其关闭
不用with的结构(不推荐)
写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符’w’或者’wb’表示写文本文件或写二进制文件:
>>> f = open('/Users/michael/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()
你可以反复调用write()来写入文件,但是务必要调用f.close()来关闭文件。当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()的后果
是数据可能只写了一部分到磁盘
,剩下的丢失了。所以,还是用with语句来得保险
with open('/Users/michael/test.txt', 'w') as f:
f.write('Hello, world!')
10.1.2 相对路径和绝对路径
如果你要打开的文件不在程序文件所属的目录中,则可以使用相对路径或绝对路径来打开文件
相对路径
即相对于当前运行的程序所在目录
# 相对路径:从当前目录Program开始,继续匹配路径
with open('text_files/in.txt') as file_object:
contents = file_object.read()
print(contents)
绝对路径
- 当你精准的告诉Python文件的具体位置,那你就不用担心当前的运行程序存储在什么地方了。这称为绝对文件路径
- Linux或OS X路径用斜杠/
Windows路径用反斜杠\ - 通过使用绝对路径,可读取系统任何地方的文件
- 绝对路径通常比相对路径更长,建议先把它存储在一个变量中,再用open(变量)会有所帮助
# 绝对路径
file_path = '/Users/macos/Documents/Wilson79/LocalStore/综合文件/How to solve a problem.txt'
with open(file_path) as file_object:
contents = file_object.read()
print(contents)
- PyCharm获取绝对路径快速方法,右键Copy Path,或设置快捷键shift + cmd + p
10.1.3 创建包含文件各行的列表readlines()
使用关键词with时,open()返回的文件对象只在with代码块内可用,如果要在外面访问,可以把各行存在一个列表中
# 相对路径
file_path = 'text_files/in.txt'
with open(file_path) as file_object:
lines = file_object.readlines() # 返回一个列表
print(lines)
# 输出 ['1\n', '2\n', '3\n', '\n', '4\n', 'This is last line!']
for line in lines:
print(line.rstrip()) # 输出和in.txt一模一样
in.txt
1
2
3
4
This is last line!
对于你处理的数据量,Python没有任何限制,只要系统的内存足够多,你想处理多少数据都可以
比如一个包含一百万位的大型文件,也可以处理。当然打印时为了避免终端为显示全部1000000位而不断地翻滚
逐行读取还可以直接用for循环
with open(filename) as file_object:
for line in file_object:
print(line.rstrip())
注:
if 'ab' in 'helloabhere': # 判断字符串s1是否在字符串s2中
print("Yes")
10.1.4 读取一行readline()
with open('text_files/in.txt') as file_object:
line = file_object.readline()
print(line) # 第一行会有个\n,print也会有个\n
输出
1
10.2 写入文件write()
filename = 'text_files/in.txt'
# w写入模式 会覆盖原有文件内容
with open(filename, 'w') as file_object:
file_object.write("\tI love programming.\n")
file_object.write("\tI love creating new games.\n")
打开文件时,open()可以输入两个实参,第一个是文件名,第二个是以什么模式打开文件
open(filename, ‘a’)
- 只读模式
r
(缺省时默认是只读模式) - 写入模式
w
(覆盖原文件) - 附加模式
a
(给文件加内容,append) - 读写模式
r+
(读写文件,w不会覆盖原文件)
如果要写入的文件不存在,函数open()会自动创建它
可以使用空格、制表符和空行来设置写入内容的格式
python只能将字符串写入文件
注意:写入模式w会清空原文件,要特别小心使用
10.3 异常 try-except
目前(至少)有两种可区分的错误:语法错误 和 异常
语法错误又称解析错误,可能是你在学习Python 时最容易遇到的错误
即使语句或表达式在语法上是正确的,但在尝试执行时,它仍可能会引发错误。 在执行时检测到的错误被称为异常
Python使用被称为异常
的特殊对象来管理程序执行期间发生的错误
在执行时,Python会自动返回一个异常对象
如果你编写了处理该异常的代码,程序将继续运行;如果你未对异常进行处理,程序将停止,并显示一个traceback,其中包含有关异常的报告
异常是使用try-except代码块处理的。使用try-except代码块Python会执行指定的操作,同时告诉Python发生异常时该怎么办。通常会显示你自己编写的友好的错误消息,而不是令用户迷惑的traceback
程序崩溃时让用户看到traceback不太好
,因为如果用户怀着恶意,他会通过traceback获悉一些你不想让ta知道的信息,如被获取程序文件的名称,还将看到不能正确运行的代码,有时训练有素的攻击者就会根据这个信息对你的代码发起相应的攻击
File "/Users/macos/Documents/Wilson79/Python/Program/division.py", line 17
second_number = input(a"Second number: ")
^
SyntaxError: invalid syntax
10.3.1 处理ZeroDivisionError异常
print(5/0)
显然这样会出错,返回的错误ZeroDivisionError是一个异常对象
Python无法按照你要求的去做时就会创建一个异常对象
Traceback (most recent call last):
File "<input>", line 1, in <module>
ZeroDivisionError: division by zero
下次我们就可以告诉Python发生指定错误时该怎么办了
try:
print(5 / 0)
except ZeroDivisionError: # 告诉Python发生指定的异常该怎么做
print("Attention, you can't divide by zero!")
如果try代码块中的代码运行起来没有问题,Python将跳过except代码块;如果try中的代码出现错误,且except后指定的错误和引发的错误相同,则运行except中的代码
10.3.2 try-except-else代码块
有一些仅在try代码块成功执行时才需要运行的代码,这些代码应放在else代码块中
这样即使发生错误,用户也根本看不到traceback,程序还是会继续运行下去
# 2020年1月25日 星期六 14:58:27
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) # 5/2 = 2.5
except ZeroDivisionError:
print("You can't divide by 0!")
else: # 如果try正常执行,则执行else内容
print(answer)
输出
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: 3
Second number: q
这种结构让我们的程序即使面对无效数据或缺失数据,也能继续运行,从而能够抵御无意的用户错误
和恶意的攻击
10.3.3 try-except的三种格式
语句中的else是可有可无的
except语句中的as e也可以不加
- 一个 try 语句可能包含多个 except 子句
try:
print(7 / 0)
except ZeroDivisionError as e:
print('除数为0')
except ValueError:
pass # 什么也不做;还可以充当占位符,提醒你什么地方还没有做
else:
...
- 也可以合并except子句
try:
print(7 / 0)
except (ZeroDivisionError, ValueError):
print('程序异常')
- 无论遇到的是哪一种异常, 均做统一处理
try:
print(7 / 0)
except :
print('程序异常')
10.3.4 示例-分析文本的单词数
分析多个文本,统计每个文本的单词数(默认以空白字符分隔)
def count_word(filename):
try:
with open(filename) as f_obj:
content = ""
for line in f_obj:
content += line
except FileNotFoundError:
print("Sorry, the file " + filename + " does not exist.")
else:
wlist = content.split() # 返回一个单词列表
print("This file " + filename[10:] + " has " + str(len(wlist)) + " words.")
filenames = ['text_files/斗破苍穹.txt', 'text_files/in.txt', '牧神记.txt']
for filename in filenames:
count_word(filename) # 中文统计不准确,因为默认以空白字符分隔单词
决定报告哪些错误
编写得很好且经过详尽测试的代码不容易出现内部错误
,如语法或逻辑错误,但只要程序依赖于外部因素
,如用户输入、存在指定的文件、有网络链接,就可能发生异常。凭借经验可判断该在什么地方包含异常处理块,以及出现错误时该向用户提供多少相关的信息
10.4 使用json格式保存和读取用户数据
很多程序要求用户输入某种信息,而当用户下次启动时他们之前输入的信息还是存在的
模块json能够让你把简单的python数据结构存储到文件中,并在程序再次运行时加载该文件中的数据
JSON(JavaScript Object Notation)格式最初是JavaScript开发的,后来被各大语言广泛采用
两个常用的函数apijson.dump(data, file_object)
data = json.load(file_object)
import json
numbers = [1, 2, 3]
# 使用json.dump()存储数据
filename = 'numbers.json'
with open(filename, 'w') as file_object:
json.dump(numbers, file_object)
# 使用json.load()读取数据
with open(filename) as file_object:
numbers_ = json.load(file_object)
print(numbers_)
对于用户生成的数据,使用json保存它们大有裨益,因为如果不以某种方式进行存储,等程序停止运行时用户的信息将丢失
例:用户首次运行程序时被提示输入自己的名字,这样再次运行程序时就记住他了
import json
# 如果以前存储了用户名就加载它
# 否则,就提示用户输入用户名并存储它
filename = 'username.json'
try:
with open(filename) as file_object:
username = json.load(file_object)
except FileNotFoundError: # 此时说明用户首次运行程序
username = input("What is your name? ")
with open(filename, 'w') as file_object:
json.dump(username, file_object)
print("We'll remember you when you can back, " + username + "!")
else:
print("Welcome back, " + username)
10.5 重构—划分成具体函数
代码能够正常运行,但可做进一步的改进——将代码划分为一系列完成具体工作的函数。这个过程被称为重构
重构让代码更清晰、更易于理解、更容易扩展
上面一小节的代码,一个函数里包含了多个工作,下面我们来划分代码,让一个工作由一个函数来完成,这样易于维护和扩展
- get_stored_username()
- get_new_username()
- greet_user()
import json
def get_stored_username():
"""如果存储了用户名,就获取它"""
filename = 'username.json'
try:
with open(filename) as file_object:
username = json.load(file_object)
except FileNotFoundError:
return None
else:
return username
def get_new_username():
"""提示用户输入用户名,并保存到json"""
username = input("What is your name? ")
filename = 'username.json'
with open(filename, 'w') as f_obj:
json.dump(username, f_obj)
return username
def greet_user():
"""问候用户"""
username = get_stored_username()
if username:
print("Welcome back, " + username + "!")
else:
username = get_new_username()
print("We'll remember you when you come back, " + username + "!")
greet_user()
补充:打开多个文件
python 可以很容易对 csv 格式的文件进行处理
import csv
# 可以同时打开两个文件的
with open("words.csv") as file, open("words2.csv", 'a') as file2: # 若不存在则新建当前文件夹
reader = csv.DictReader(file)
spamwriter = csv.writer(file2)
for row in reader:
spamwriter.writerow([row["Words"], ' + ', row["单词"]])
# DictReader 和 writerow 的用法查Python官方文档就行了