《Python从入门到实践》第十章 文件和异常

读取文件

读取文件的全部内容

from pathlib import Path

path = Path('pi_digits.txt')
contents = path.read_text()
print(contents)

要使用文件的内容,需要将其路径告诉python,路径指的是在文件或文件夹在系统中的准确位置。Python提供了pathlib模块,让你能够更轻松地在各种系统中处理文件和目录

上述代码先从pathlib模块中导入Path类,Path对象指向一个文件,可用来做很多事情,例如让你在使用文件前核实它是否存在,读取文件的内容,以及将新数据写入文件。

这里创建了一个表示文件pi_digits的Path对象,并将其赋给了变量path。由于这个文件与当前编写的.py文件在同一个目录中,因此Path只需要知道文件名就能访问它

注意:VS Code会在最近打开的文件夹中查找文件,因此如果你使用的是VS Code,请先打开本章程序所在的文件夹。

read_text()将该文件的全部内容作为一个字符串返回。

相比于原文件,该输出唯一不同的地方是末尾多了一个空行;因为read_text()在到达文件末尾时会返回一个空字符串,而这个空字符串会被显示为一个空行。

要删除这个多出来的空行,可以对字符串变脸调用rstrip()

from pathlib import Path

path = Path('pi_digits.txt')
contents = path.read_text().rstrip()
print(contents)

也可以采用方法链式调用

contents = path.read_text().rstrip()

相对文件路径和绝对文件路径

当类似于pi_digits.txt这样简单文件名传递给Path时,Python将在当前执行的文件所在的目录中去查找。

根据你组织文件的方式,有时候可能打不开在程序文件所属目录中的文件。

例如:你可能将程序文件存储在了文件夹python_work中,并且在文件夹python_work中创建了一个名为text_files的文件夹,用于存储程序文件要操作的文本文件。虽然文件夹text_files在文件夹python_work中,但仅想Path传递文件夹text_files中的文件名称也是不行的,因为Python旨在文件夹python_work中查找,而不会在其子文件text_files中查找。

要让Python打开不与程序文件位于同一个目录中的文件,需要提供正确的路径。

在编程中,指定路径的方法有两种,首先,相对文件路径让Python到相对于当前运行的程序所在目录的指定位置去查找,由于文件夹text_files位于文件夹python_work中,因此需要创建一个以text_files打头并以文件名结尾的路径,如下所示:

path = Path('text_files/filename.txt)

其次,可以将文件在计算机中的准确位置告诉Python,这样就不用管当前运行的程序存储在什么地方了,这称为绝对文件路径。加入text_files并不在文件夹python_work中,则上述的相对路径是行不通的。

绝对文件路径通常比相对路径长,因为它们以系统的根文件夹为起点:

path = Path('/home/eric/data_files/text_files/filename.txt)

使用绝对路径,可以读取系统中任何地方的文件。

在显示文件路径时,Windows系统使用反斜杠(\)而不是斜杠(/)。但是你在代码中应该始终使用斜杠。

访问文件中的各行

from pathlib import Path

path = Path('pi_digits.txt')
contents = path.read_text().rstrip()
lines = contents.splitlines()
for line in lines:
    print(line)

可以使用splitlines()方法将冗长的字符串转换为一系列行,再使用for循环以每次一行的方式检查文件中的各行。该方法把冗长字符串转换为一个列表,每一行的内容作为列表中的元素

使用文件的内容

from pathlib import Path

path = Path('pi_digits.txt')
contents = path.read_text()
lines = contents.splitlines()
pi_string = ''
for line in lines:
    pi_string += line.lstrip()
print(pi_string)
print(len(pi_string))

lstrip()删除掉原来位于每行左端的空格

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

包含100万位的大型文件

相关数据文件从网上下载

只打印到小数点后50位

from pathlib import Path

path = Path('pi_million_digits.txt')
contents = path.read_text()

lines = contents.splitlines()
pi_spring = ''
for line in lines:
    pi_spring += line.lstrip()
    
print(f"{pi_spring[:52]}...")
print(len(pi_spring))

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

from pathlib import Path

path = Path('pi_million_digits.txt')
contents = path.read_text()

lines = contents.splitlines()
pi_spring = ''
for line in lines:
    pi_spring += line.lstrip()

birthday = input("Enter your birthday, in the form mmddyy:")
if birthday in pi_spring:
    print("Your birthday appears in the first million digits of pi!")
else:
    print("Your birthday does not appear in the first million digits of pi.")
   

练习

练习10.1

from pathlib import Path

path = Path('learning_python.txt')
contents = path.read_text()
print(contents)

lines = contents.splitlines()
print(lines[0])
for line in lines:
    print(line)

练习10.2

可以使用replace()方法将字符串中的特定单词替换为另一个单词

from pathlib import Path

path = Path('learning_python.txt')
contents = path.read_text()
print(contents)

lines = contents.splitlines()
print(lines[0])
for line in lines:
    print(line.replace('Python', 'C'))
    print(line)

replace()方法并不会改变原本,而是创造一个副本

练习10.3

简化代码,尽量删减临时变量,如上述代码中的lines

写入文件

写入一行

定义一个文件的路径后,就可以使用write_text()将数据写入该文件里

from pathlib import Path

path = Path('learning_python.txt')
path.write_text('ssss')

注意,write_text()会将原有的内容全部覆盖

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

写入多行

write_text()在幕后完成的工作有:

①如果path变量对应的路径指向的文件不存在,就创建它

②将字符串写入文件后,它会确保文件得以妥善地关闭。如果没有妥善地关闭文件,可能会导致数据丢失或受损。

from pathlib import Path

path = Path('ceshi.txt')
contents = 'I love programming.\n'
contents += 'I love creating new games.\n'
contents += 'I also love working with data.\n'

path.write_text(contents)

该输入方式对字符串的长度没有任何限制

注意:当对path对象调用write_text()方法时,务必谨慎,如果指定的文件已存在,write_text()将删除其内容。

练习

练习10.4

from pathlib import Path

path = Path('tourist.txt')
name = input('请输入你的名字:\n')

path.write_text(name)

练习10.5

from pathlib import Path

path = Path('guest_book.txt')
book = ''
while True:
    name = input('请输入你的名字:\n')
    book += f"{name}\n"
    tip = input('是否继续输入信息:yes/no\n')
    if tip == 'no':
        break

path.write_text(book)

异常

处理ZzeroDivisionError异常

print(5/0)

Traceback (most recent call last):
  File "C:\Users\xjj\Desktop\python_work\main_idea.py", line 1, in <module>
    print(5/0)
          ~^~
ZeroDivisionError: division by zero

Python无法按照你的要求做的时候,就会创建这种对象

使用try-except代码块

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

如果try代码块中的代码导致错误,Python将查找与之匹配的except代码块并运行其中的代码,这样用户看到的是一条友好的错误消息,而不是traceback

如果try-except代码块后面还有其他代码,程序将继续运行,因为Python已经知道了如何处理错误。

使用异常避免崩溃

程序崩溃的话,不懂技术的用户会感到糊涂,怀有恶意的用户还能通过traceback获悉你不想让他们知道的信息,从而发起攻击。

else代码块

通过将可能引发错误的代码放在try-except代码块中,可提高程序低于错误的能力,下面的实例还包含一个else代码块,只有try代码成功执行才需要继续执行的代码,都应放到else代码块中:

print("Give me tow 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("\nSecond 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)

这样的话如果出现异常,程序还是会继续运行,而用户根本看不到traceback

只有可能引发异常的代码才需要放在try语句中,有时候,有一些仅在try代码成功执行时才需要运行的代码,这些代码应该放在else代码块中,except代码块告诉Python,如果在尝试运行try代码块中的代码时引发了指定的异常该怎么办。

通过预测可能发生错误的代码,可编写稳健的程序,它们即便面临无效数据或缺少资源,也能继续运行,不受无意的用户错误和恶意攻击的影响。

处理FileNotFound异常

在使用文件时,一种常见的问题是找不到文件:要查找的文件可能在其他地方,文件名可能不正确,或者这个文件根本的就不存在。对于所有这些情况,都可使用try-except代码块来处理。

我们来尝试读取一个不存在的文件

from pathlib import Path

path = Path('alice.txt')
contents = path.read_text(encoding='utf-8')

如果系统的默认编码与要读取的文件的编码不一致,参数encoding必不可少。如果要读取的文件不是在你的系统中创建的,这种情况更容易发生。

引发异常:

Traceback (most recent call last):
  File "C:\Users\xjj\Desktop\python_work\main_idea.py", line 4, in <module>
    contents = path.read_text(encoding='utf-8')
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\xjj\AppData\Local\Programs\Python\Python311\Lib\pathlib.py", line 1056, in read_text
    with self.open(mode='r', encoding=encoding, errors=errors) as f:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\xjj\AppData\Local\Programs\Python\Python311\Lib\pathlib.py", line 1042, in open
    return io.open(self, mode, buffering, encoding, errors, newline)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'

蓝色标记不封是我们获取信息的主要部分。

为了处理这个异常,我们可以将traceback指出的问题的代码行放到try代码块中

from pathlib import Path

path = Path('alice.txt')
try:
    contents = path.read_text(encoding='utf-8')
except FileNotFoundError:
    print(f"Sorry, the file {path} does not exist.")

分析文本

from pathlib import Path

path = Path('alice.txt')
try:
    contents = path.read_text(encoding='utf-8')
except FileNotFoundError:
    print(f"Sorry, the file {path} does not exist.")
else:
    # 计算文件大致包含多少个单词
    words = contents.split()
    num_words = len(words)
    print(f"The file {path} has about {num_words} words")

使用split()方法,它默认以空白为分隔符将字符串拆分为多个部分,生成一个列表。

使用多个文件

from pathlib import Path


def count_words(path):
    """计算一个文件大致包含多少个单词"""
    try:
        contents = path.read_text(encoding='utf-8')
    except FileNotFoundError:
        print(f"Sorry, the file {path} does not exist.")
    else:
        # 计算文件大致包含多少个单词
        words = contents.split()
        num_words = len(words)
        print(f"The file {path} has about {num_words} words")


filenames = ['alice.txt', 'siddartha.txt', 'moby_dick.txt', 
             'little_women.txt']
for filename in filenames:
    path = Path('filename')
    count_words(path)

在这里。try-except能够避免用户看到traceback,也能让程序继续可以分析找到其他文件。

静默失败

在上面的示例中,我们告诉用户有一个文件找不到,但并非每次捕获异常都需要告诉用户。

有时候希望程序在发生异常时保持静默,就像什么都没有发生一样继续运行。要让程序静默失败,可以像通常那样编写try代码块,但在except中明确告诉Python什么都不要做,Python有一个pas语句,可在代码块中使用它来让Python什么都不做:

try:

except FileNotFoundReeor:

        pass

else:

pass语句还充当了占位符,提醒你在程序的某个地方什么都没做,而且以后也许要在这里做些什么。

决定报告哪些错误

如果用户知道要分析哪些文件,他们可能希望在有文件违背分析时出现一条信息来告知原因。

如果用户只想看到结果,并不知道要分析哪些文件,可能就无须在有些文件不存在时告知他们。

向用户显示他们不想看到的信息可能会降低程序的可用性。

Python的错误处理结构让你能够细致地控制与用户共享错误信息的程度,要共享多少信息由你决定

练习

练习10.6

from pathlib import Path


print("请输入两个数,程序会将这两个数相加")
try:
    first_number = int(input("请输入第一个数\n"))
except ValueError:
    print("请输入整数!")
else:
    try:
        second_number = int(input("请输入第二个数\n"))
    except ValueError:
        print("请输入整数!")
    else:
        number = first_number+second_number
        print(number)

练习10.7

from pathlib import Path


print("请输入两个数,程序会将这两个数相加")
while True:
    try:
        first_number = int(input("请输入第一个数\n"))
    except ValueError:
        print("请输入整数!\n")
    else:
        try:
            second_number = int(input("请输入第二个数\n"))
        except ValueError:
            print("请输入整数!\n")
        else:
            number = first_number + second_number
            print(number)

练习10.8

from pathlib import Path

path1 = Path('cat.txt')
try:
    print(path1.read_text())
except FileNotFoundError:
    print("该文件不存在")    

path2 = Path('dog.txt')
try:
    print(path2.read_text())
except FileNotFoundError:
    print("该文件不存在")

练习10.9

from pathlib import Path

path1 = Path('cat.txt')
try:
    print(path1.read_text())
except FileNotFoundError:
    print("该文件不存在")

path2 = Path('dog.txt')
try:
    pass
except FileNotFoundError:
    print("该文件不存在")

练习10.10

from pathlib import Path

path = Path('alice.txt')
try:
    content = path.read_text('utf-8')
except FileNotFoundError:
    print("该文件不存在")
else:
    print(content.lower().count('the'))

存储数据

一种简单的方式是使用模块json来存储数据,json模块让你能够将简单的Ptython数据结构转换为JSON格式的字符串。JSON格式最初是为JavaScript开发的,但随后成了一种通用的格式,被包括Python在内的众多语言采用。

使用json.dumps()和json.loads()

from pathlib import Path
import json

numbers = [1, 6, 4, 3, 10, 19]
path = Path('numbers.json')
content = json.dumps(numbers)
path.write_text(content)

首先导入模块json,并创建一个数值列表,然后选择一个文件名,指定要将该数值列表存储到哪个文件,通常使用文件扩展名.json来指出文件存储的数据为JSON格式,接下来使用json.dumps()函数生成一个字符串,它包含我们要存储的数据的JSON表示形式,生成这个字符串后,使用write_text()方法将其写入文件。

使用json.loads()将这个列表读取到文件中。

from pathlib import Path
import json


path = Path('numbers.json')
content = path.read_text()
numbers = json.loads(content)
print(numbers)

这是一种在程序之间共享数据的简单方式。

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

使用json保存用户生成的数据很有必要,因为如果不以某种方式进行存储,用户的信息就会在程序停止运行时丢失

from pathlib import Path
import json

user_name = input('What is your name?')

path = Path('username.json')
contents = json.dumps(user_name)
path.write_text(contents)

print(f"we'll remember you when you are back, {user_name}")

现在再编写一个程序,向名字已被存储的用户发出问候

from pathlib import Path
import json


path = Path('username.json')
contents = path.read_text()
user_name = json.loads(contents)

print(f"welcome back, {user_name}")

接下来将两个程序合并到一个程序中,在这个程序运行时,将尝试从内存中获取用户的用户名,如果没有找到,就提示用户输入用户名,并将其存储到文件user.name.json中,以供下次使用,这里原本可以编写一个try-except代码块,以便在文件username.json不存在时采取合适的措施,但我们用了pathlib模块提供的一个便利方法——exists():

from pathlib import Path
import json

path = Path('username.json')
if path.exists():
    contents = path.read_text()
    user_name = json.loads(contents)
    print(f"welcome back, {user_name}")
else:
    user_name = input('What is your name?')
    contents = json.dumps(user_name)
    path.write_text(contents)
    print(f"we'll remember you when you are back, {user_name}")

如果指定的文件或文件夹存在,exists()方法返回True,否则返回False。

重构

虽然代码能够正确运行,但是还可以将其划分为一系列完成具体工作的函数来进行改进,这样的过程称为重构,重构让代码更清晰、更易于理解、更容易扩展。

from pathlib import Path
import json


def greet_user():
    """问候用户,并指出其名字"""
    path = Path('username.json')
    if path.exists():
        contents = path.read_text()
        user_name = json.loads(contents)
        print(f"welcome back, {user_name}")
    else:
        user_name = input('What is your name?')
        contents = json.dumps(user_name)
        path.write_text(contents)
        print(f"we'll remember you when you are back, {user_name}")


greet_user()

下面进行重构

from pathlib import Path
import json


def get_sorted_username(path):
    """如果存储了用户名,就获取它"""
    if path.exists():
        contents = path.read_text()
        user_name = json.loads(contents)
        return user_name
    else:
        return None
    
    
def greet_user():
    """问候用户,并指出其名字"""
    path = Path('username.json')
    username = get_sorted_username(path)
    if username:
        print(f"welcome back, {username}")
    else:
        user_name = input('What is your name?')
        contents = json.dumps(user_name)
        path.write_text(contents)
        print(f"we'll remember you when you are back, {user_name}")

还可以对对greet_user()进一步重构:

from pathlib import Path
import json


def get_sorted_username(path):
    """如果存储了用户名,就获取它"""
    if path.exists():
        contents = path.read_text()
        user_name = json.loads(contents)
        return user_name
    else:
        return None


def greet_new_username(path):
    """提示用户输入用户名"""
    user_name = input('What is your name?')
    contents = json.dumps(user_name)
    path.write_text(contents)
    return user_name


def greet_user():
    """问候用户,并指出其名字"""
    path = Path('username.json')
    username = get_sorted_username(path)
    if username:
        print(f"welcome back, {username}")
    else:
        username = greet_new_username(path)
        print(f"we'll remember you when you are back, {username}")


greet_user()

在这个最终版本中,每个函数都执行单一而清晰的任务。

练习

练习10.11

from pathlib import Path
import json


number = input('请输入你最喜欢的数字\n')
path = Path('favorite_number.json')
contents = json.dumps(number)
path.write_text(contents)
print(f"I've remember your favorite number is {number}")

from pathlib import Path
import json


path = Path('favorite_number.json')

read_number = path.read_text()
contents = json.loads(read_number)
print(f"your favorite number is {contents}")

练习10.12

from pathlib import Path
import json


path = Path('favorite_number.json')
if path.exists():
    read_number = path.read_text()
    contents = json.loads(read_number)
    print(f"your favorite number is {contents}")
else:
    number = input('请输入你最喜欢的数字\n')
    contents = json.dumps(number)
    path.write_text(contents)
    print(f"I've remember your favorite number is {number}")

练习10.13

from pathlib import Path
import json

favorite_info = {}
path = Path('favorite_info.json')
if path.exists():
    read_info = path.read_text()
    contents = json.loads(read_info)
    print(f"your favorite things include {contents}")
else:
    number = input('请输入你最喜欢的数字\n')
    fruit = input('请输入你最喜欢的水果\n')
    vegetable = input('请输入你最喜欢的蔬菜\n')
    favorite_info['number'] = number
    favorite_info['fruit'] = fruit
    favorite_info['vegetable'] = vegetable
    contents = json.dumps(favorite_info)
    path.write_text(contents)
    print(f"I've remember your favorite things include {favorite_info}")

练习10.14

from pathlib import Path
import json


def get_sorted_username(path):
    """如果存储了用户名,就获取它"""
    if path.exists():
        contents = path.read_text()
        user_name = json.loads(contents)
        return user_name
    else:
        return None


def get_new_username(path):
    """提示用户输入用户名"""
    user_name = input('What is your name?')
    contents = json.dumps(user_name)
    path.write_text(contents)
    return user_name


def greet_user():
    """问候用户,并指出其名字"""
    path = Path('username.json')
    username = get_sorted_username(path)
    if username:
        answer = input(f"你是{username}吗,yes/no")
        if answer == 'yes':
            print(f"welcome back, {username}")
        else:
            get_new_username(path)
    else:
        username = get_new_username(path)
        print(f"we'll remember you when you are back, {username}")


greet_user()

  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值