Python--文件和异常

目录

1、读取文件

1.1 读取文件的全部内容

1.2 相对路径和绝对路径

1.3 访问文件中的各行

1.4 使用文件中的内容

1.5 包含100万位的大型文件

1.6 圆周率中的生日

2、写入文件

2.1 写入一行

2.2 写入多行

3、异常

3.1 处理ZeroDivisionError 异常

3.2 使用try-except代码块

3.3 使用异常避免崩溃

3.4 else代码块

3.5 处理FileNotFoundError异常

3.6 分析文本

3.7 使用多个文件

3.8 静默失败

3.9 决定报告哪些错误

3.10 count()求特定单词

4、存储系统

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

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

4.3 重构


1、读取文件

要使⽤⽂本⽂件中的信息,⾸先需要将信息读取到内存中。既可以⼀次性 读取⽂件的全部内容,也可以逐⾏读取。

1.1 读取文件的全部内容

要读取⽂件,需要⼀个包含若⼲⾏⽂本的⽂件。下⾯来创建⼀个⽂件,它 包含精确到⼩数点后 30 位的圆周率值,且在⼩数点后每 10 位处换⾏:

3.1415926535 
	8979323846 
	2643383279

下⾯的程序打开并读取这个⽂件,再将其内容显⽰到屏幕上:

 from pathlib import Path
❶ path = Path('pi_digits.txt')
❷ contents = path.read_text()
 print(contents)

要使⽤⽂件的内容,需要将其路径告知 Python。路径(path)指的是⽂件或

⽂件夹在系统中的准确位置。Python 提供了 pathlib 模块,让你能够更轻

松地在各种操作系统中处理⽂件和⽬录。提供特定功能的模块通常称为库 (library)。这就是这个模块被命名为 pathlib 的原因所在。

这⾥⾸先从 pathlib 模块导⼊ Path 类。Path 对象指向⼀个⽂件,可⽤ 来做很多事情。例如,让你在使⽤⽂件前核实它是否存在,读取⽂件的内 容,以及将新数据写⼊⽂件。这⾥创建了⼀个表⽰⽂件 pi_digits.txt 的 Path 对象,并将其赋给了变量 path(⻅❶)。由于这个⽂件与当前编写 的 .py ⽂件位于同⼀个⽬录中,因此 Path 只需要知道其⽂件名就能访问 它。

创建表⽰⽂件 pi_digits.txt 的 Path 对象后,使⽤ read_text() ⽅法来读 取这个⽂件的全部内容(⻅❷)。read_text() 将该⽂件的全部内容作为 ⼀个字符串返回,⽽我们将这个字符串赋给了变量 contents。在打印 contents 的值时,将显⽰这个⽂本⽂件的全部内容:

3.1415926535 
  8979323846 
  2643383279 

相⽐于原始⽂件,该输出唯⼀不同的地⽅是末尾多了⼀个空⾏。为何会多 出这个空⾏呢?因为 read_text() 在到达⽂件末尾时会返回⼀个空字符 串,⽽这个空字符串会被显⽰为⼀个空⾏。 要删除这个多出来的空⾏,可对字符串变量 contents 调⽤ rstrip():

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

Python ⽅法 rstrip() 能删除字符串末尾的空⽩。现在,

输出与原始⽂件的内容完全⼀致了:

3.1415926535 
  8979323846 
  2643383279

要在读取⽂件内容时删除末尾的换⾏符,可在调⽤ read_text() 后直接

调⽤⽅法 rstrip():

contents = path.read_text().rstrip()

1.2 相对路径和绝对路径

当将类似于 pi_digits.txt 这样的简单⽂件名传递给 Path 时,Python 将在当 前执⾏的⽂件(即 .py 程序⽂件)所在的⽬录中查找。

根据你组织⽂件的⽅式,有时可能要打开不在程序⽂件所属⽬录中的⽂ 件。例如,你可能将程序⽂件存储在了⽂件夹 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 传递路径 'text_files/filename.txt' 是⾏不通的,因为 Python 只在⽂件夹 python_work 中查找该位置。为了明确地指出希望 Python 到哪⾥去查找,需要提供绝对路径。

绝对路径通常⽐相对路径⻓,因为它们以系统的根⽂件夹为起点:

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

使⽤绝对路径,可读取系统中任何地⽅的⽂件。就⽬前⽽⾔,最简单的做 法是,要么将数据⽂件存储在程序⽂件所在的⽬录中,要么将其存储在程 序⽂件所在⽬录下的⼀个⽂件夹(如 text_files)中。

注意:在显⽰⽂件路径时,Windows 系统使⽤反斜杠(\)⽽不是斜杠 (/)。但是你在代码中应该始终使⽤斜杠,即便在 Windows 系统中

也是如此。在与你或其他⽤户的系统交互时,pathlib 库会⾃动使⽤

正确的路径表⽰⽅法。

1.3 访问文件中的各行

你可以使⽤ splitlines() ⽅法将冗⻓的字符串转换为⼀系列⾏,再使⽤ for 循环以每次⼀⾏的⽅式检查⽂件中的各⾏:

from pathlib import Path
 path = Path('pi_digits.txt')
❶ contents = path.read_text()
❷ lines = contents.splitlines()
 for line in lines:
 print(line)

1.4 使用文件中的内容

将⽂件的内容读取到内存中后,就能以任意⽅式使⽤这些数据了。

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
 print(pi_string)
 print(len(pi_string))

变量 pi_string 存储的字符串包含原来位于每⾏左端的空格。要删除这 些空格,可对每⾏调⽤ lstrip():

--snip-- 
for line in lines: 
pi_string += line.lstrip() 
print(pi_string) 
print(len(pi_string))

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

1.5 包含100万位的大型文件

如果⼀个⽂本⽂件包含精确到⼩数点后 1 000 000 位⽽不是 30 位的圆周率值,也可以创建⼀个包含所有这些数字的字符串。⽆须对前⾯的程序做任何修改,只需将这个⽂件传递给它即可。在这⾥, 只打印到⼩数点后 50 位,以免终端花太多时间滚动显⽰全部的 1 000 000 位数字:

from pathlib import Path
path = Path('pi_million_digits.txt')
contents = path.read_text()
lines = contents.splitlines()
pi_string = ''
for line in lines:
 pi_string += line.lstrip()
print(f"{pi_string[:52]}...")
print(len(pi_string))

输出表明,创建的字符串确实包含精确到⼩数点后 1 000 000 位的圆周率

值:

3.14159265358979323846264338327950288419716939937510... 
1000002 

在可处理的数据量⽅⾯,Python 没有任何限制。只要系统的内存⾜够⼤, 你想处理多少数据就可以处理多少数据。

1.6 圆周率中的生日

我⼀直想知道⾃⼰的⽣⽇是否包含在圆周率值中。下⾯来扩展刚才编写的 程序,以确定某个⼈的⽣⽇是否包含在圆周率值的前 1 000 000 位中。。为此,可先将⽣⽇表⽰为⼀个由数字组成的字符串,再检查这个字符串是否 在 pi_string 中:

--snip-- 
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 million digits of 
pi.") 

⾸先提⽰⽤户输⼊其⽣⽇,再检查这个字符串是否在 pi_string 中。运 ⾏这个程序:

Enter your birthdate, in the form mmddyy: 120372 
Your birthday appears in the first million digits of pi! 

我的⽣⽇确实出现在了圆周率值中!读取⽂件的内 容后,就能以任意⽅式

对其进⾏分析了

2、写入文件

保存数据的最简单的⽅式之⼀是将其写⼊⽂件。

2.1 写入一行

定义⼀个⽂件的路径后,就可使⽤ write_text() 将数据写⼊该⽂件了。

from pathlib import Path
path = Path('programming.txt')
path.write_text("I love programming.")

write_text() ⽅法接受单个实参,即要写⼊⽂件的字符串。

注意:Python 只能将字符串写⼊⽂本⽂件。如果要将数值数据存储到

⽂本⽂件中,必须先使⽤函数 str() 将其转换为字符串格式。

2.2 写入多行

write_text() ⽅法会在幕后完成⼏项⼯作。⾸先,如果 path 变量对应 的路径指向的⽂件不存在,就创建它。其次,将字符串写⼊⽂件后,它会 确保⽂件得以妥善地关闭。

要将多⾏写⼊⽂件,需要先创建⼀个字符串(其中包含要写⼊⽂件的全部 内容),再调⽤ write_text() 并将这个字符串传递给它。

下⾯将多⾏内 容写⼊⽂件 programming.txt:

from pathlib import Path 
contents = "I love programming.\n" 
contents += "I love creating new games.\n" 
contents += "I also love working with data.\n" 
path = Path('programming.txt') 
path.write_text(contents)

⾸先定义变量 contents,⽤于存储要写⼊⽂件的所有内容。

注意:在对 path 对象调⽤ write_text() ⽅法时,务必谨慎。如果 指定的⽂件已存在, write_text() 将删除其内容,并将指定的内容写⼊其中。

3、异常

Python 使⽤称为异常(exception)的特殊对象来管理程序执⾏期间发⽣的 错误。每当发⽣让 Python 不知所措的错误时,它都会创建⼀个异常对象。

  • 如果你编写了处理该异常的代码,程序将继续运⾏;
  • 如果你未对异常进⾏ 理,程序将停⽌,并显⽰⼀个 traceback,其中包含有关异常的报告。

异常是使⽤ try-except 代码块处理的。

3.1 处理ZeroDivisionError 异常

下⾯来看⼀种导致 Python 引发异常的简单错误。

print(5/0)

Python ⽆法这样做,因此你将看到⼀个 traceback:


 Traceback (most recent call last):
 File "division_calculator.py", line 1, in 
 print(5/0)
 ~^~
❶ ZeroDivisionError: division by zero

在上述 traceback 中,错误 ZeroDivisionError 是个异常对象(⻅ ❶)。Python 在⽆法按你的要求做时,就会创建这种对象。在这种情况 下,Python 将停⽌运⾏程序,并指出引发了哪种异常,⽽我们可根据这些 信息对程序进⾏修改。

3.2 使用try-except代码块

当你认为可能发⽣错误时,可编写⼀个 try-except 代码块来处理可能引 发的异常。

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

3.3 使用异常避免崩溃

如果在错误发⽣时,程序还有⼯作没有完成,妥善地处理错误就显得尤其 重要。

 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)

这个 程序没有采取任何处理错误的措施,因此在执⾏除数为 0 的除法运算时,

它将崩溃:

Give me two numbers, and I'll divide them. 
Enter 'q' to quit. 
First number: 5 
Second number: 0 
Traceback (most recent call last): 
 File "division_calculator.py", line 11, in  
 answer = int(first_number) / int(second_number) 
 ~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~ 
ZeroDivisionError: division by zero

程序崩溃可不好,让⽤户看到 traceback 也不是个好主意。不懂技术的⽤户 会感到糊涂,怀有恶意的⽤户还能通过 traceback 获悉你不想让他们知道的 信息。

3.4 else代码块

通过将可能引发错误的代码放在 try-except 代码块中,可提⾼程序抵御 错误的能⼒。这个⽰例还包含⼀个 else 代码块,只有 try 代码块成功执⾏才需要继续执⾏的代码,都应放到 else 代码块中:

--snip-- 
 while True: 
--snip-- 
 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)

3.5 处理FileNotFoundError异常

在使⽤⽂件时,⼀种常⻅的问题是找不到⽂件:要查找的⽂件可能在其他 地⽅,⽂件名可能不正确,或者这个⽂件根本就不存在。

我们来尝试读取⼀个不存在的⽂件。下⾯的程序尝试读取⽂件 alice.txt 的内

容,但这个⽂件并没有被存储在 alice.py 所在的⽬录中:

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

请注意,这⾥使⽤ read_text() 的⽅式与前⾯稍有不同。如果系统的默 认编码与要读取的⽂件的编码不⼀致,参数 encoding 必不可少。如果要 读取的⽂件不是在你的系统中创建的,这种情况更容易发⽣。

Python ⽆法读取不存在的⽂件,因此引发了⼀个异常:

 Traceback (most recent call last): 
❶ File "alice.py", line 4, in  
❷ contents = path.read_text(encoding='utf-8') 
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
 File "/.../pathlib.py", line 1056, in read_text 
 with self.open(mode='r', encoding=encoding, errors=errors) as f: 
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
 File "/.../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 ⽐前⾯的那些都⻓,因此下⾯介绍如何看懂复杂的 traceback。

通常最好从 traceback 的末尾着⼿。

从最后⼀⾏可知,引发了异 常 FileNotFoundError(⻅❸)。这⼀点很重要,它让我们知道应该在 要编写的 except 代码块中使⽤哪种异常。

回头看看 traceback 开头附近(⻅❶),从这⾥可知,错误发⽣在⽂件 alice.py 的第四⾏。接下来的⼀⾏列出了导致错误的代码⾏(⻅❷)。 traceback 的其余部分列出了⼀些代码,它们来⾃打开和读取⽂件涉及的 库。通常,不需要详细阅读和理解 traceback 中的这些内容。

为了处理这个异常,应将 traceback 指出的存在问题的代码⾏放到 try 代码

块中。这⾥,存在问题的是包含 read_text() 的代码⾏:

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.")

这样,当找不 到⽂件时,Python 将运⾏ except 代码块中的代码,从⽽显⽰⼀条友好的

错误消息,⽽不是 traceback:

Sorry, the file alice.txt does not exist.

3.6 分析文本

你可以分析包含整本书的⽂本⽂件。

下⾯来提取童话 Alice in Wonderland(《爱丽丝漫游奇境记》)的⽂本,并 尝试计算它包含多少个单词。我们将使⽤ split() ⽅法,它默认以空⽩为 分隔符将字符串分拆成多个部分:

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.")

我将⽂件 alice.txt 移到了正确的⽬录中,让 try 代码块能够成功地执⾏。 对变量 contents(它现在是⼀个⻓⻓的字符串,包含童话 Alice in Wonderland 的全部⽂本)调⽤ split() ⽅法,⽣成⼀个列表,其中包含这 部童话中的所有单词(⻅❶)。

3.7 使用多个文件

下⾯多分析⼏本书。先将这个程序的⼤部分代码移到⼀个名为 count_words() 的函数中,这样对多本书进⾏分析会更容易:

 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.")
 path = Path('alice.txt')
 count_words(path)

这些代码⼤多与原来⼀样,只是被移到了函数 count_words() 中,并且 增加了缩进量。

为此,我们把要分析的⽂件的名称存储在⼀个列表中,然后对列表中 的每个⽂件都调⽤ count_words()。

 from pathlib import Path
 def count_words(filename):
 --snip--
 filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 
 'little_women.txt']
 for filename in filenames:
❶ path = Path(filename)
 count_words(path)

3.8 静默失败

我们告诉⽤户有⼀个⽂件找不到。但并⾮每次捕获异常 都需要告诉⽤户,你有时候希望程序在发⽣异常时保持静默,就像什么都 没有发⽣⼀样继续运⾏。要让程序静默失败,可像通常那样编写 try 代码 块,但在 except 代码块中明确地告诉 Python 什么都不要做。Python 有⼀ 个 pass 语句,可在代码块中使⽤它来让 Python 什么都不做:

def count_words(path):
 """计算⼀个⽂件⼤致包含多少个单词"""
 try:
 --snip--
 except FileNotFoundError:
 pass
 else:
 --snip--

现在,当出现 FileNotFoundError 异常时,虽然仍将执⾏except 代码块中的代码,但什么都不会发⽣。

3.9 决定报告哪些错误

如果⽤ 户知道要分析哪些⽂件,他们可能希望在有⽂件未被分析时出现⼀条消息

来告知原因。

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

3.10 count()求特定单词

可以使⽤⽅法 count() 来确定特定的单词或短语在字符串中出现了多

少次。例如,下⾯的代码计算 'row' 在⼀个字符串中出现了多少次:

>>> line = "Row, row, row your boat" 
>>> line.count('row') 
2 
>>> line.lower().count('row') 
3 

请注意,通过使⽤ lower() 将字符串转换为全⼩写的,可捕捉要查找 的单词的各种格式,⽽不管其⼤⼩写如何。

4、存储系统

很多程序要求⽤户输⼊某种信息,⽐如让⽤户存储游戏⾸选项或提供要可 视化的数据。当⽤户关闭程序时,⼏乎总是要保存他们提供的信 息。⼀种简单的⽅式是使⽤模块 json 来存储数据。

模块 json 让你能够将简单的 Python 数据结构转换为 JSON 格式的字符 串,并在程序再次运⾏时从⽂件中加载数据。你还可以使⽤ json 在 Python 程序之间共享数据。

注意:JSON(JavaScript Object Notation)格式最初是为 JavaScript 开发 的,但随后成了⼀种通⽤的格式,被包括 Python 在内的众多语⾔采 ⽤。

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

下⾯先编写⼀个存储⼀组数的简短程序,再编写⼀个将这些数读取到内存 中的程序。第⼀个程序将使⽤ json.dumps() 来存储这组数,⽽第⼆个程 序将使⽤ json.loads() 来读取它们。

json.dumps() 函数接受⼀个实参,即要转换为 JSON 格式的数据。这个 函数返回⼀个字符串,这样你就可将其写⼊数据⽂件了:

 from pathlib import Path
 import json
 numbers = [2, 3, 5, 7, 11, 13]
❶ path = Path('numbers.json')
❷ contents = json.dumps(numbers)
 path.write_text(contents)

下⾯再编写⼀个程序,使⽤ json.loads() 将这个列表读取到内存中:

 from pathlib import Path
 import json
❶ path = Path('numbers.json')
❷ contents = path.read_text()
❸ numbers = json.loads(contents)
 print(numbers)

在❶处,确保读取的是前⾯写⼊的⽂件。这个数据⽂件是使⽤特殊格式的 ⽂本⽂件,因此可使⽤ read_text() ⽅法来读取它(⻅❷)。然后将这 个⽂件的内容传递给 json.loads()(⻅❸)。这个函数将⼀个 JSON 格 式的字符串作为参数,并返回⼀个 Python 对象(这⾥是⼀个列表),⽽我 们将这个对象赋给了变量 numbers。最后,打印恢复的数值列表,看看是 否与 number_writer.py 中创建的数值列表相同:

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

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

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

使⽤ json 保存⽤户⽣成的数据很有必要,因为如果不以某种⽅式进⾏存 储,⽤户的信息就会在程序停⽌运⾏时丢失。

下⾯来看⼀个这样的例⼦:

提⽰⽤户在⾸次运⾏程序时输⼊⾃⼰的名字,并且在他再次运⾏程序时仍

然记得他。

先来存储⽤户的名字:

 from pathlib import Path 
 import json 
❶ username = input("What is your name? ") 
❷ path = Path('username.json') 
 contents = json.dumps(username) 
 path.write_text(contents) 
❸ print(f"We'll remember you when you come back, {username}!")

⾸先,提⽰⽤户输⼊名字(⻅❶)。接下来,将收集到的数据写⼊⽂件 username.json(⻅❷)。然后,打印⼀条消息,指出存储了⽤户输⼊的信息 (⻅❸):

What is your name? Eric 
We'll remember you when you come back, Eric! 

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

greet_user.py

from pathlib import Path 
 import json 
❶ path = Path('username.json') 
 contents = path.read_text() 
❷ username = json.loads(contents) 
print(f"Welcome back, {username}!")

我们读取数据⽂件的内容(⻅❶),并使⽤json.loads() 将恢复的数据 赋给变量 username(⻅❷)。有了已恢复的⽤户名,就可以使⽤个性化 的问候语欢迎⽤户回来了:

Welcome back, Eric!

需要将这两个程序合并到⼀个程序(remember_me.py)中。在这个程序运 ⾏时,将尝试从内存中获取⽤户的⽤户名。如果没有找到,就提⽰⽤户输 ⼊⽤户名,并将其存储到⽂件 username.json 中,以供下次使⽤。

 from pathlib import Path
 import json
 path = Path('username.json')
❶ if path.exists():
 contents = path.read_text()
 username = json.loads(contents)
 print(f"Welcome back, {username}!")
❷ else:
 username = input("What is your name? ")
 contents = json.dumps(username)
 path.write_text(contents)
 print(f"We'll remember you when you come back, {username}!")

4.3 重构

你经常会遇到这样的情况:虽然代码能够正确地运⾏,但还可以将其划分 为⼀系列完成具体⼯作的函数来进⾏改进。这样的过程称为重构。

要重构 remember_me.py,可将其⼤部分逻辑放到⼀个或多个函数中。

remember_me.py 的重点是问候⽤户,因此将其所有代码都放到⼀个名为

greet_user() 的函数中:

 from pathlib import Path 
 import json 
def greet_user(): 
❶"""问候⽤户,并指出其名字""" 
 path = Path('username.json') 
 if path.exists(): 
 contents = path.read_text() 
 username = json.loads(contents) 
 print(f"Welcome back, {username}!") 
 else: 
 username = input("What is your name? ") 
 contents = json.dumps(username) 
 path.write_text(contents) 
 print(f"We'll remember you when you come back, {username}!")
greet_user()

下⾯重构 greet_user(),不让它执⾏这么多任务。⾸先将获取已存储⽤

户名的代码移到另⼀个函数中:

from pathlib import Path 
import json

def get_stored_username(path): 
❶"""如果存储了⽤户名,就获取它""" 
 if path.exists(): 
 contents = path.read_text() 
 username = json.loads(contents) 
return username 
else: 
❷return None 
 def greet_user(): 
 """问候⽤户,并指出其名字""" 
 path = Path('username.json') 
username = get_stored_username(path) 
❸if username: 
 print(f"Welcome back, {username}!") 
 else: 
 username = input("What is your name? ") 
 contents = json.dumps(username) 
 path.write_text(contents) 
 print(f"We'll remember you when you come back, {username}!") 
 greet_user() 

新增的 get_stored_username() 函数⽬标明确,⽂档字符串(⻅❶) 指出了这⼀点。

还需要将 greet_user() 中的另⼀个代码块提取出来,将在没有存储⽤户名时提⽰⽤户输⼊的代码放在⼀个独⽴的函数中:

 from pathlib import Path
 import json
 def get_stored_username(path):
 """如果存储了⽤户名,就获取它"""
 --snip--
 def get_new_username(path):
 """提⽰⽤户输⼊⽤户名"""
 username = input("What is your name? ")
 contents = json.dumps(username)
 path.write_text(contents)
 return username
 def greet_user():
 """问候⽤户,并指出其名字"""
 path = Path('username.json')
❶ username = get_stored_username(path)
 if username:
 print(f"Welcome back, {username}!")
 else:
❷ username = get_new_username(path)
 print(f"We'll remember you when you come back, {username}!")
 greet_user()

在 remember_me.py 的这个最终版本中,每个函数都执⾏单⼀⽽清晰的任 务。我们调⽤ greet_user(),它打印⼀条合适的消息:要么欢迎⽼⽤户 回来,要么问候新⽤户。

你可能感兴趣的:(Python,python,程序人生,开发语言)