Python 文件和异常|Python 主题月

本文正在参加「Python主题月」,详情查看 活动链接

学习处理文件,能够让程序快速地分析大量数据;学习错误处理,可以避免程序在面对意外情形时崩溃;学习异常,它们是 Python 创建的特殊对象,用于管理程序运行时出现的错误;学习模块 json,它能够让你保存用户数据,以免在程序停止运行后丢失。

从文件中读取数据

  • 要使用文本文件中的信息,首先需要将信息读取到内存中。为此,你可以一次性读取文件的全部内容,也可以以每次一行的方式逐步读取。

读取整个文件

  • 要读取文件,需要一个包含几行文本的文件。
  • 先创建一个文件:
# pi_digits.txt
3.1415926535
  8979323846
  2643383279

复制代码
  • 下面的程序打开并读取这个文件,再将其内容显示到屏幕上:
with open('pi_digits.txt') as file_object:
    contents = file_object.read()

print(contents)
复制代码
  • 要以任何方式使用文件,那怕仅仅是打印其内容,都得先打开文件,才能访问它。
  • 函数 open() 接受一个参数:要打开的文件的名称。Python 在当前执行的文件所在的目录中查找指定的文件。
  • 在这里,open('pi_digits.txt') 返回一个表示文件 pi_digits.txt的对象,Python 将该对象赋给 file_object 供以后使用。
  • 关键字 with 不再需要访问文件后将其关闭。
  • 相比于原始文件,该输出唯一不同的地方是末尾多了一个空行。为何会多出这个空行呢?因为 read() 到达文件末尾时返回一个空字符串,而将这个空字符串显示出来时就是一个空行。要删除多出来的空行,可在函数调用 print() 中使用 rstrip()
with open('pi_digits.txt') as file_object:
    contents = file_object.read()

print(contents.rstrip())
复制代码

文件路径

with open('text_file/filename.txt') as file_object:
复制代码
  • 通过相对路径,这行代码让 Python 到文件夹 python_work 下的文件夹 text_file 中去查找指定的 .txt 文件。
  • 还可以给出具体的绝对路径,但绝对路径通常比相对路径长,因此将其赋给一个变量,再将该变量传递给 open() 会有所帮助。

逐行读取

  • 要以每次一行的方式检查文件,可对文件对象使用 for 循环:
with open('pi_digits.txt') as file_object:
    for line in file_object:
        print(line)
复制代码
  • 这里也使用了关键字 with,让 Python 负责妥善地打开和关闭文件。为查看文件的内容,通过对文件对象执行循环来遍历文件中的每一行。
  • 打印每一行时,空行却更多了。。。
  • 那么要消除这些空行,还是要在 print() 函数中调用 rstrip()
with open('pi_digits.txt') as file_object:
    for line in file_object:
        print(line.strip())
复制代码

创建一个包含文件各行内容的列表

  • 使用关键字 with 时,open() 返回的文件对象只在 with 代码块内可用。
  • 如果要在 with 代码块外访问文件的内容,可在 with 代码块内将文件的各行存储在一个列表中,并在 with 代码块外使用该列表。
with open('pi_digits.txt') as file_object:
    lines = file_object.readlines()

for line in lines:
    print(line.rstrip())
复制代码
  • 方法 readlines() 从文件中读取每一行,并将其存储在一个列表中。

使用文件的内容

  • 将文件读取到内存中后,就能以任何方式使用这些数据了。
file_name = 'pi_digits.txt'

with open(file_name) as file_object:
    lines = file_object.readlines()

pi_string = ''

for line in lines:
    pi_string += line.rstrip()

print(pi_string)
print(len(pi_string))
复制代码
  • 变量 pi_string 指向的字符串包含原来位于每行左边的空格,为删除这些空格,可使用 strip() 而非 rstrip()
file_name = 'pi_digits.txt'

with open(file_name) as file_object:
    lines = file_object.readlines()

pi_string = ''

for line in lines:
    pi_string += line.strip()

print(pi_string)
print(len(pi_string))
复制代码

读取文本文件时,Python将其中的所有文本都解读为字符串。如果读取的是数,并要将其作为数值使用,就必须使用函数 int() 将其转换为整数或使用函数 float() 将其转换为浮点数。

包含一百万位的大型文件

  • 如果我们有一个文本文件,其中包含精确到小数点后 1000000 位而不是 30 位的圆周率值,也可创建一个包含所有这些数字的字符串。
  • 为此,无须对前面的程序做任何修改,只要将这个文件传递给它即可。

圆周率值中包含你的生日吗

  • 判断圆周率中是否包含你的生日:
file_name = 'pi_million_digits.txt'

with open(file_name) as file_object:
    lines = file_object.readlines()

pi_string = ''

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 millions digits of pi.")
复制代码

读取文件

  • 保存数据的最简单的方式之一是将其写入文件中。

写入空文件

  • 要将文本写入文件,你在调用 open() 时需要提供另一个实参,告诉 Python 你要写入打开的文件。
write_filename = 'programming.txt'

with open(write_filename, 'w') as file_object:
    file_object.write("I lvoe programming.")
复制代码
  • open() 函数第二个参数 w 告诉 Python,要以写入模式打开这个文件。
  • 打开文件时,可以指定读取模式(‘r’)写入模式(‘w’)附加模式(‘a’)读写模式(‘r+’)
  • 如果要写入的文件不存在,函数 open() 将自动创建它。

写入模式(‘w’) 打开文件时千万要小心,因为如果指定的文件已经存在,Python 将在返回文件对象前清空该文件的内容。

Python 只能将字符串写入文本文件。要将数值数据存储到文本文件中,必须先使用函数 str() 将其转换为字符串格式。

写入多行

  • 函数 write() 不会在写入的文本末尾添加换行符,因此如果写入多行时不会指定换行符。
filename = 'programming.txt'

with open(filename, 'w') as file_object:
    file_object.write("I lvoe programming.")
    file_object.write("I love creating new games.")
复制代码
  • 如果你打开 programming.txt,将发现两行内容挤在一起。
  • 要让每个字符串都单独占一行,需要在方法调用 write() 中包含换行符:
filename = 'programming.txt'

with open(filename, 'w') as file_object:
    file_object.write("I lvoe programming.\n")
    file_object.write("I love creating new games.\n")
复制代码

附加到文件

  • 如果要给文件添加内容,而不是覆盖原有的内容,可以以附加模式打开文件。
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")
复制代码

异常

  • Python 使用称为异常的特殊对象来管理程序执行期间发生的错误。
  • 每当发生让 Python 不知所措的错误时,它都会创建一个异常对象。
  • 如果你编写了处理该异常的代码,程序将继续运行;如果未对异常进行处理,程序将停止并显示 traceback,其中包含有关异常的报告。

处理 ZeroDivisionError 异常

  • 很简单的除零错误示例:
>>> print(5/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
复制代码
  • Python 无法执行除零错误,抛出一个 Traceback

使用 try-except 代码块

  • 你认为可能会发生错误时,可编写一个 try-except 代码块来处理可能引发的异常。
try:
    print(5/0)
except ZeroDivisionError:
    print("You can't divide by zero!")
复制代码
  • 将导致错误的代码行 print(5/0) 放在一个 try 代码块中。如果 try 代码块中的代码运行起来没有问题,Python 将跳过 except 代码块;如果 try 代码块中的代码导致了错误,Python 将查找与之匹配的 except 代码块并运行其中的代码。

使用异常避免崩溃

  • 发生错误时,如果程序还有工作尚未完成,妥善地处理错误就尤其重要。
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")

while True:
    first_number = input("First 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)
复制代码

else 代码块

  • 通过将可能引发错误的代码放在try-except代码块中,可提高程序抵御错误的能力。
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")

while True:
    first_number = input("First 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)
复制代码

处理 FileNotFoundError 异常

  • 使用文件时,一种常见的问题是找不到文件:查找的文件可能在其他地方,文件名可能不正确,或者这个文件根本就不存在。对于所有这些情形,都可使用 try-except 代码块以直观的方式处理。
filename = 'alice.txt'

with open(filename, encoding='utf-8') as f:
    contents = f.read()
复制代码
  • alice.txt 不在该程序所在的目录,抛出了 Traceback
Traceback (most recent call last):
  File "alice.py", line 3, in <module>
    with open(filename, encoding='utf-8') as f:
FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'
复制代码
  • 上述 traceback 的最后一行报告了 FileNotFoundError 异常,这是 Python 找不到要打开的文件时创建的异常。
  • open() 函数放在 try 函数里,处理该异常:
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 exit.")
复制代码

分析文本

  • 你可以分析包含整本书的文本文件。
  • 下面是对只包含童话名 "Alicein 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 exit.")
else:
    # 计算该文件大致包含多少个单词。
    words = contents.split()
    num_words = len(words)
    print(f"The file {filename} has about {num_words} words.")
复制代码

使用多个文件

  • 下面多分析几本书。这此之前,先将这个程序的大部分代码移到一个名为 count_words() 的函数中。这样,对多本书进行分析时将更容易:
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 exit.")
    else:
        words = contents.split()
        num_words = len(words)
        print(f"The file {filename} has about {num_words} words.")

filename = 'alice.txt'
count_words(filename)
复制代码
  • 现在可以编写一个简单的循环,计算要分析的任何文本包含多少个单词了。
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 exit.")
    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 不存在,但这丝毫不影响该程序处理其他文件:
The file alice.txt has about 29465 words.
Sorry, the file siddhartha.txt does not exit.
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:
        with open(filename, encoding='utf-8') as f:
            contents = f.read()
    except FileNotFoundError:
        pass
    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)
复制代码
  • 现在,出现 FileNot-FoundError 异常时,将执行 except 代码块中的代码,但什么都不会发生。

决定报告哪些错误

  • 编写得很好且经过详尽测试的代码不容易出现内部错误,如语法或逻辑错误,但只要程序依赖于外部因素,如用户输入、存在指定的文件、有网络链接,就有可能出现异常。凭借经验可判断该在程序的什么地方包含异常处理块,以及出现错误时该向用户提供多少相关的信息。

存储数据

  • 很多程序都要求用户输入某种信息,如让用户存储游戏首选项或提供要可视化的数据。不管关注点是什么,程序都把用户提供的信息存储在列表和字典等数据结构中。
  • 用户关闭程序时,几乎总是要保存他们提供的信息。一种简单的方式是使用模块 json 来存储数据。
  • 模块 json 让你能够将简单的 Python 数据结构转储到文件中,并在程序再次运行时加载该文件中的数据。
  • 你还可以使用 json 在 Python 程序之间分享数据。更重要的是,JSON数据格式 并非 Python 专用的,这让你能够将以 JSON格式 存储的数据与使用其他编程语言的人分享。

JSON(JavaScript Object Notation)格式最初是为 JavaScript 开发的,但随后成了一种常见格式,被包括 Python 在内的众多语言采用。

使用 json.dump()json.load()

  • 我们来编写一个存储一组数的简短程序,再编写一个将这些数读取到内存中的程序。
  • 第一个程序将使用 json.dump() 来存储这组数,而第二个程序将使用 json.load()
import json

numbers = [2, 3, 5, 7, 11, 13]

filename = 'number.json'

with open(filename, 'w') as f:
    json.dump(numbers, f)
复制代码
  • 通常使用文件扩展名 .json 来指出文件存储的数据为 JSON格式
  • 下面再编写一个程序,使用 json.load() 将列表读取到内存中:
import json

filename = 'number.json'

with open(filename) as f:
    numbers = json.load(f)

print(numbers)
复制代码
  • json 是一种在程序之间共享数据的简单方式。

保存和读取用户生成的数据

  • 使用 json 保存用户生成的数据大有裨益,因为如果不以某种方式存储,用户的信息会在程序停止运行时丢失。
  • 下面来看一个这样的例子:提示用户首次运行程序时输入自己的名字,并在再次运行程序时记住他。
  • 先保存用户名到 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}!")
复制代码
  • 再从 json 文件中读取用户名:
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}!")
复制代码

重构

  • 你经常会遇到这样的情况:代码能够正确地运行,但通过将其划分为一系列完成具体工作的函数,还可以改进。这样的过程称为重构
  • 重构让代码更清晰、更易于理解、更容易扩展。
  • 现在,重构上面合并的代码:
import json

def greet_user():
    """问候用户,并指出其名字"""

    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}!")

greet_user()
复制代码
  • 接着重构 greet_user(),增加了 get_stored_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 greet_user():
    """问候用户,并指出其名字。"""
    username = get_stored_username()

    if username:
        print(f"Welcome back, {username}!")
    else:
        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}!")

greet_user()
复制代码
  • 还需要重构 greet_user() 中的另一个代码块,将没有存储用户名时提示用户输入的代码放在一个独立的函数中:
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_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()
复制代码
  • 以上就是 greet_user() 的最终版本,每个函数都执行单一而清晰的任务。
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享