Python入门: (9)文件和异常

172 阅读10分钟

1. 从文件中读取数据

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

读取整个文件

with open('pi_digits.txt') as file_object:  # open('pi_digits.txt')返回一个表示文件pi_digits.txt的对象,Python将该对象赋给file_object供以后使用
    contents = file_object.read()
print(contents)

  • 要以任何方式使用文件,哪怕仅仅是打印其内容,都得先打开文件,才能访问它。函数open()接受一个参数:要打开的文件的名称。Python在当前执行的文件所在的目录中查找指定的文件。
  • 函数open()返回一个表示文件的对象
  • 关键字with在不再需要访问文件后将其关闭
  • 在这个程序中,注意到我们调用了open(),但没有调用close()。也可以调用open()close()来打开和关闭文件,但这样做时,如果程序存在bug导致方法close()未执行,文件将不会关闭。看似微不足道,但未妥善关闭文件可能导致数据丢失或受损。如果在程序中过早调用close(),你会发现需要使用文件时它已关闭(无法访问),这会导致更多的错误。
  • 并非在任何情况下都能轻松确定关闭文件的恰当时机,但通过使用前面所示的结构,可让Python去确定:你只管打开文件,并在需要时使用它,Python自会在合适的时候自动将其关闭
  • 有了表示pi_digits.txt的文件对象后,使用方法read()读取这个文件的全部内容,并将其作为一个长长的字符串赋给变量contents
  • 相比于原始文件,该输出唯一不同的地方是末尾多了一个空行。因为read()到达文件末尾时返回一个空字符串,而将这个空字符串显示出来时就是一个空行。要删除多出来的空行,可在函数调用print()中使用rstrip()来删除字符串末尾的空白

文件路径

  • 要让Python打开不与程序文件位于同一个目录中的文件,需要提供文件路径,让Python到系统的特定位置去查找
  • 可以使用 相对文件路径 来打开其中的文件,相对文件路径让Python到指定的位置去查找,而该位置是是相对于当前运行的程序所在目录的
with open('text_files/filename.txt') as file_object:
  • 还可以将文件在计算机中的准确位置告诉Python,这样就不用关心当前运行的程序存储在什么地方了。这称为 绝对文件路径 。通过使用绝对路径,可读取系统中任何地方的文件
  • 绝对路径通常比相对路径长,因此将其赋给一个变量,再将该变量传递给open()会有所帮助
file_path = '/home/ehmatthes/other_files/text_files/filename.txt'
with open(file_path) as file_object:

逐行读取

filename = 'pi_digits.txt'  # 变量filename表示的并非实际文件————它只是一个让Python知道到哪里去查找文件的字符串

with open(filename) as file_object:
    for line in file_object:
        print(line)

  • 打印每一行时,会出现两个空行。因为在这个文件中,每行的末尾都有一个看不见的换行符,而函数调用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())

使用文件的内容

  • 读取文本文件时,Python将其中的所有文本都解读为字符串。如果读取的是数,并要讲其作为数值使用,就必须使用函数int()将其转换为整数或使用函数float()将其转换为浮点数
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(f"len: {len(pi_string)}")

包含一百万位的大型文件

  • 对于可处理的数据量,Python没有任何限制。只要系统的内存足够多,想处理多少数据都可以
filename = 'random_digits.txt'

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

random_string = ''
for line in lines:
    random_string += line.strip()

birthday = input("Enter your birthday, in the form mmddyy: ")
if birthday in random_string:
    print("Your birthday appears in the random number!")
else:
    print("Your birthday does not appear in the random number!")

2. 写入文件

  • 保存数据最简单的方式之一是将其写入文件中。通过将输出写入文件,即便关闭包含程序输出的终端窗口,这些输出依然存在:可以在程序结束运行后查看这些输出,可以与别人分享输出文件,还可以编写程序来将这些输出读取到内存中并进行处理

写入空文件

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将在返回文件对象前清空该文件的内容

写入多行

  • 函数不会在写入的文本末尾添加换行符,要让每个字符串都单独占一行,需要在方法调用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")

附加到文件

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

3. 异常

  • Python使用称为异常的特殊对象来管理程序执行期间发生的错误。每当发生让Python不知所措的错误时,它都会创建一个异常对象。如果你编写了处理该异常的代码,程序将继续运行;如果未对异常进行处理,程序将停止并显示traceback,其中包含有关异常的报告
  • 异常是使用try-except代码处理的。try-except代码块让Python执行指定的操作,同时告诉Python发生异常时怎么办。使用try-except代码块时,即便出现异常,程序也将继续运行:显示你编写的友好的错误消息,而不是令用户迷惑的traceback
  • 通过将可能引发错误的代码放在try-except代码块中,可提高程序抵御错误的能力

处理ZeroDivisionError异常

print(5/0)

使用try-except代码块

try:
    print(5/0)
except ZeroDivisionError:
    print("You can't divide by zero!")

使用异常避免崩溃

  • 如果程序能够妥善处理无效输入,就能再提示用户提供有效输入,而不至于崩溃
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)

else代码块

  • 依赖try代码块成功执行的代码都应放到else代码块
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 0!")
    else:
        print(answer)

处理FileNotFoundError异常

filename = 'paper_1.txt'

with open(filename, encoding='utf-8') as f:  # 使用变量f表示文件对象
    contents = f.read()

filename = 'paper_1.txt'

try:
    with open(filename, encoding='utf-8') as f:  # 给参数encoding指定了值。在系统默认编码与要读取文件使用的编码不一致时,必须这样做
        contents = f.read()
except FileNotFoundError:
    print(f"Sorry, the file {filename} does not exist.")

分析文本

  • 方法split()以空格为分隔符分拆成多个部分,并将这些部分都存储到一个列表中
filename = 'paper_1.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.")

使用多个文件

  • 将原来的代码移到函数中,并更新注释
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 = ['paper_1.txt', 'paper_2.txt', 'paper_3.txt']
for filename in filenames:
    count_words(filename)

静默失败

  • Python提供了pass语句,可用于让Python在代码块中什么也不做
  • pass语句还充当了占位符,提醒你在程序某个地方什么都没有做,并且以后也许要在这里做些什么
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 = ['paper_1.txt', 'paper_2.txt', 'paper_3.txt']
for filename in filenames:
    count_words(filename)

决定报告哪些错误

  • 如果用户知道要分析哪些文件,他们可能希望在有文件却没有分析时出现一条消息来告知原因
  • 如果用户只想看到结果,并不知道要分析哪些文件,可能就无须在有些文件不存在时告知他们
  • Python的错误处理结构让你能够细致地控制与用户分享错误信息的程度,要分享多少信息由你决定

4. 存储数据

  • 模块json让你能够将简单的Python数据结构转储到文件中,并在程序再次运行时加载该文件中的数据,或在Python程序之间分享数据
  • JSON数据格式 并非Python专用,这使得以JSON格式存储的数据可以与其他编程语言互通分享
  • JSON格式最初是为JavaScript开发的,但随后成了一种常见格式,被包含Python在内的众多语言采用

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

  • 函数json.dump()接受两个实参:要存储的数据,以及可用于存储数据的文件对象
  • 函数json.load()加载存储在文件中的信息
import json

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

filename = 'numbers.json'
with open(filename, 'w') as f:
    json.dump(numbers, f)

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

print(numbers)

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

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 get_store_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_store_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()

文章中的所有代码经测试均可成功编译运行,可直接复制。具有显然结果或简单结论的代码不展示运行结果。如有问题欢迎随时交流~