保存和获取文件

本章节我们来学习如何保存和获取文件,为什么学习这个知识呢?回忆一下,我们之前的程序都是把内容存储在变量中,这时候一旦我们的程序运行结束,变量就消失了,我们存储的内容、计算的结果也就不见了!那怎么办呢?我们需要把有些有价值的东西存储起来,也就是存到电脑硬盘的某个地方。当然现在也可以考虑存储在云端,比如我们在Scratch中接触过的云变量。在本章中,我们会学习如何编写代码来存储数据以及从文件中获取数据。首先让我们从一段趣味代码:大海捞针开始!

大海捞针

这段趣味代码是为了解决这样一个假想的问题:我们有一个包含1000个文件的文件夹,文件名为0.txt到999.txt,其中只有一个文件的内容中包含单词needle(针)。如何写代码来找到具体是哪个文件呢?代码其实很简单:

for i in range(1000):
    filename = str(i) + '.txt'
    file = open('ch9/needle/'+filename,'r')  # 注意路径
    #file = open('needle/'+filename,'r')  # 注意路径
    text = file.read()
    if 'needle' in text:
        print('找到needle了,它在',str(i),'.txt文件里')
        file.close()
        break
    file.close()
print('扫描完毕')

读取文件

我们一段一段拆分开来理解,首先是如何打开文件:

file = open('ch9/needle/0.txt','r')
text = file.read()
print(text)

可以顺利的打开文件并读取里面的内容,但是注意最后最好是加上file.close(),这样可以把python代码中打开的文件对象关闭掉。为什么要这样做呢?因为打开文件会占用电脑操作系统的资源,特别是运行时间很长的程序,如果让不再使用的文件一直打开,最终会导致代码崩溃。所以,如果你要打开一个文件,一定还要记得关闭这个文件。

相对路径与绝对路径

默认情况下程序会从编辑器打开的当前目录来寻找文件,如果你想要打开的文件在其他位置上该怎么办呢,如何打开这样一个存放在其他地方的文件呢?为此,我们需要文件名增加一个路径,也就是告诉open函数具体的查找这个文件的地方。这时候就有两种方法:相对路径或者绝对路径。相对路径指的是相对于当前目录对文件位置的一个描述,比如needle/0.txt,当前目录有两种解释:一般情况下Python源代码文件所在的文件夹就是当前目录,但如果是用VSCode这类编辑器打开的文件夹,那么这个文件夹就是当前目录。如果需要从当前目录上一层文件夹找文件的话,可以使用"../"的写法来实现。 绝对路径是从文件系统根开始的一个路径,对于windows系统来说,就是从某个盘开始的完整路径。这么看来绝对路径能告诉我们文件在文件系统中的具体位置,应该是一种更有确定性的方法,确实是这样。但有时候这种写法就不够灵活了,比如我们需要把程序转移到另一台电脑上的时候,大概率就需要修改一下代码中的绝对路径了。具体使用相对路径还是绝对路径,不一而论,根据实际情况灵活选择。

循环读取

读取到内容之后就可以来判断一下文件中的内容是否包含我们想要的"needle"单词了,加上一个判断语句即可。这样第1个文件就校验完毕了,为了校验所有的1000个文件,就需要用循环来做了,并且还可以加上一些提示语。

for i in range(1000):
    filename = str(i) + '.txt'
    file = open('ch9/needle/'+filename,'r')  # 注意路径
    #file = open('needle/'+filename,'r')  # 注意路径
    text = file.read()
    if 'needle' in text:
        print('找到needle了,它在',str(i),'.txt文件里')
        file.close()
        break
    file.close()
print('扫描完毕')

疯狂填字游戏

接下来我们来实现另一个跟文件相关的趣味程序:疯狂填字游戏。它的玩法是这样的:我们有一个模板文件,里面有一些单词是缺失的,我们的程序会读取这个文件,并且引导用户来填写缺少的单词,从而创造出一个有趣的故事并且存到一个新的文件中。 之前我们已经学习了通过read()函数可以读取文件中的内容,不过这个方法是一次性读取文件中的全部内容,这就有一个缺点了:对于很大的文件,这会耗费大量的资源,想象一下如果一个文件包含数百万行文本,那么如果一下子把这些内容读入内存的话,很有可能会出现内存爆掉导致的错误。所以我们有另外一种常用的方法来读取文件:一次读取一行的内容。

readline方法

my_file = open('lib.txt','r')
line1 = my_file.readline()
print(line1)
line2 = my_file.readline()
print(line2)

在程序运行的结果中能看到虽然输出了每一行的内容,但是每行之后好像都多了一个换行的操作。这个是因为在文件中存储内容换行的时候其实都用了一个特殊字符"\n",这个就是换行的意思,一般情况下我们是看不到这个符号的,但编辑器看到这个符号后就会做出换行的处理。虽然看不到,但是我们在程序中是可以直接使用的,比如:

print("尝试一下换行:\n,效果还不错。\n")

读取每一行直到最后

my_file = open('lib.txt','r')
while True:
    line = my_file.readline()
    if line!='':
        print(line)
    else:
        break
my_file.close()

这样写代码确实是可以解决这个问题的,不过对于这种读取每行内容的情况,Python中还有一种更好、更简洁的方法:for语句。之前我们在迭代列表、迭代字典的时候都用过for语句,用在这里其实就是迭代处理文件中的每一行内容了。具体写法如下:

my_file = open('lib.txt','r')
for line in my_file:
    print(line)
my_file.close()

代码框架

有了上面的基础,我们就可以写出我们疯狂填字游戏的代码框架了:

def make_crazy_lib(filename):
    file = open(filename, 'r')
    text = ''
    for line in file:
        text = text + process_line(line)
    file.close()
    return text
def process_line(line):
    return line
def main():
    lib = make_crazy_lib('lib.txt')
    print(lib)
if __name__ == '__main__':
    main()

文本处理

接下来我们就按照这个代码框架来补充上需要处理的具体逻辑,我们需要把读取到的模板文件中每一行的内容依次处理,处理方式就是把这一行中的每个单词都找出来,然后看一下是否是占位符再进行引导用户进行输入。

行内文本处理为单词

首先是把每行内容中的每个单词都找出来,这个简单,用我们之前学过的split函数就可以啦:

words = line.split()
    for word in words:

单词识别并替换

对于每个单词都需要判定一下是不是占位符,如果是的话就引导用户输入具体的单词:

placeholders = ['NOUN', 'ADJECTIVE', 'VERB_ING', 'VERB']
def process_line(line):
    global placeholders
    processed_line = ''
    words = line.split()
    for word in words:
        if word in placeholders:
            answer = input('Enter a ' + word + ":")
            processed_line = processed_line + answer + ' '
        else:
            processed_line = processed_line + word + ' '
    return processed_line + '\n'

运行一下我们的程序,确实达到了我们想要的想过,但是仔细观察会发现,其中一些占位符并没有被替换,并且能看出来这些占位符都是NOUN名词,这个有什么特殊的地方吗?为什么会导致这个错误呢?你能分析出来吗?试试看,我们后续再揭晓。

名词Bug修复

strip函数

前面的代码之所以出问题,是因为NOUN名词后面有标点符号,导致跟占位符NOUN不能完全匹配,所以没有做替换的动作。我们通过strip函数可以很好的解决这个问题,首先我们来看下strip函数是如何工作的:

hello = "!?are you there?!"
goodbye = "?fine be that !way!?!!"
hello = hello.strip("!?")
goodbye = goodbye.strip("!?")
print(hello)
print(goodbye)

具体修复bug

def process_line(line):
    global placeholders
    processed_line = ''
    words = line.split()
    for word in words:
        stripped = word.strip('.,;?!')
        if stripped in placeholders:
            answer = input('Enter a ' + stripped + ":")
            processed_line = processed_line + answer
            if word[-1] in '.,;?!':
                processed_line = processed_line + word[-1] + ' '
            else:
                processed_line = processed_line + ' '
        else:
            processed_line = processed_line + word + ' '
    return processed_line + '\n'

虽然正常情况下我们的程序没有问题了,但是如果我们把程序内的文件名修改掉或者程序不变但是我们的文件名被改或者干脆文件被删掉了,那么我们的程序是不是也就无法正常运行了呢?这种情况可以避免吗?在讲解具体方法之前,我们先在终端尝试几个之前经常碰到的问题:

list = [1,2,3,4]
item = list[5]
print(item)
filename = 'document' + 1 + '.txt'、
int('1')
int('2')
int('E')
int('4')
int('5')
int('6')
msg = "hello"
def hi():
    print(msg)
    msg = "hi"
hi()
firstname = 'Mike'
print("First name:" + name)

这些错误我们之前经常会碰到,一旦碰到我们的程序就会中断,那有没有办法让我们的程序不中断呢?当然是有的!

异常处理

try语句

try:
    filename = '1.txt'
    file = open(filename,'r')
except:
    print("对不起,打开文件时报错,文件名:",filename)
else:
    print('太好了,顺利打开文件!')
    file.close()

显示处理异常

try:
    filename = '1.txt'
    file = open(filename,'r')
except FileNotFoundError:
        print("Sorry, couldn't find", filename + '.')
except IsADirectoryError:
    print("Sorry", filename, 'is a directory.')
except:
    print("对不起,打开文件时报错,文件名:",filename)
else:
    print('太好了,顺利打开文件!')
    file.close()
finally:
     print("不管怎样,程序顺利结束了!")

其他异常

try:
    num = input('Got a number? ')
    result = 42 / int(num)
except ZeroDivisionError:
    print("You can't divide by zero!")
except ValueError:
    print("Excuse me, we asked for a number.")
else:
    print('Your answer is', result)
finally:
    print('Thanks for stopping by.')

更新游戏异常处理

def make_crazy_lib(filename):
    try:
        file = open(filename, 'r')
        text = ''
        for line in file:
            text = text + process_line(line)
        file.close()
        return text
    except FileNotFoundError:
        print("Sorry, couldn't find", filename + '.')
    except IsADirectoryError:
        print("Sorry", filename, 'is a directory.')
    except:
        print("Sorry, could not read", filename)

保存文本

write函数

def save_crazy_lib(filename, text):
    try:
        file = open(filename, 'w')
        file.write(text)
        file.close()
    except:
        print("Sorry, couldn’t write file.", filename)

更新其他代码

def main():
    filename = 'lib.txt'
    lib = make_crazy_lib(filename)
    if (lib != None):
        save_crazy_lib('crazy_' + filename, lib)

文件名参数化

def main():
    if len(sys.argv) != 2:
        print("crazy.py <filename>")
    else:
        filename = sys.argv[1]
        lib = make_crazy_lib(filename)
        if (lib != None):
            save_crazy_lib('crazy_' + filename, lib)
Last Updated:
Contributors: houlinsen, 爱博卡鲁