1. Python的文件及异常
1.1 文件操作
1.1.1 从文件中读取数据
许多情况下,我们的信息是存储在文本中的。例如对用户行为的分析,用户访问系统或者网站的访问信息会被存储于文本中,然后对文本内容进行分析,分析用户行为,找出其中有价值的信息。
要是用文本文件中信息,首先需要将信息读取到内存中。因此,我们可以根据文件大小,选择读取文件的方式。该方式有两种情况,一种是一次性读取文件的全部内容;一种是逐行读取,即一次读一行。
1.1.1.1 读取整个文件
要读取整个文件,需要一个包含几行内容的文本文件。
例如,我们创建一个文本,并将一首古诗词存于其中,然后读取整个文件内容。文本文件中的内容如下所示:
念奴娇·赤壁怀古
宋代:苏轼
大江东去,浪淘尽,千古风流人物。
故垒西边,人道是,三国周郎赤壁。
乱石穿空,惊涛拍岸,卷起千堆雪。
江山如画,一时多少豪杰。
遥想公瑾当年,小乔初嫁了,雄姿英发。
羽扇纶巾,谈笑间,樯橹灰飞烟灭。
故国神游,多情应笑我,早生华发。
人生如梦,一尊还酹江月。
代码:
1 file = open("test_file1",‘r‘,encoding="utf-8") 2 print(file.read())
3 file.close()
说明:
第1行,“test_file1.txt” 是需要打开的文件名称;‘r’ 是以读的方式打开文件;encoding 指定用什么字符集打开,open()函数是用来打开文件的声明。
第2行,使用整个文件读取的方法read()读完整个文件内容。
第3行,使用close()方法关闭文件。
运行结果:
1 念奴娇·赤壁怀古 2 宋代:苏轼 3 4 大江东去,浪淘尽,千古风流人物。 5 故垒西边,人道是,三国周郎赤壁。 6 乱石穿空,惊涛拍岸,卷起千堆雪。 7 江山如画,一时多少豪杰。 8 遥想公瑾当年,小乔初嫁了,雄姿英发。 9 羽扇纶巾,谈笑间,樯橹灰飞烟灭。 10 故国神游,多情应笑我,早生华发。 11 人生如梦,一尊还酹江月。
虽然以上方式也实现了我们的需求,但是使用open()和close()来打开和关闭文件时,如果程序存在bug,则会导致close()方法未执行,文件将不会被关闭。这看似微不足道,但为妥善的关闭文件,可能会导致数据丢失或受损。如果在程序中过早的使用close()方法关闭文件,那么在需要时以无法访问,这也会导致更多的错误。因此,以上代码可以使用另一种实现方式。
代码:
1 with open("test_file1",‘r‘,encoding="utf-8") as file_object: 2 print(file_object.read())
说明:
第1行,关键字with会在不再需要访问文件后将其关闭,因此,在整个代码中都没有出现close()方法的调用。
运行结果:
1 念奴娇·赤壁怀古 2 宋代:苏轼 3 4 大江东去,浪淘尽,千古风流人物。 5 故垒西边,人道是,三国周郎赤壁。 6 乱石穿空,惊涛拍岸,卷起千堆雪。 7 江山如画,一时多少豪杰。 8 遥想公瑾当年,小乔初嫁了,雄姿英发。 9 羽扇纶巾,谈笑间,樯橹灰飞烟灭。 10 故国神游,多情应笑我,早生华发。 11 人生如梦,一尊还酹江月。
以上运行结果跟之前的实现方式显示的结果完全一致,但是建议使用第二种方式读取文件。因为第二种方式,我们只管打开文件,并在需要的时候使用它,Python自会在合适的时候自动关闭打开的文件。
1.1.1.2 文件路劲
在Python中,当我们将类型 .txt 的简单的文件名传递给函数open()时,Python会先到当前执行的程序所在的目录中查找该文件。但有时我们为了方便管理文件,会将文件存于单独的目录下。
文件路劲有两种:一种是绝对路劲;一种是相对路劲。如果我们需要打开的文件所在的文件夹与执行程序位于同一目录下,那么可以使用相对路劲即可;如果我们需要打开的文件所在的文件夹位于执行程序目录之外的地方,那么只能使用绝对路劲。
在Windows中,文件路劲用的是反斜杠(\),而在Linux、Unix、OS X等等其他系统中,文件的路劲用的是斜杠(/)。
因为在Python中,反斜杠(\)被视为转义标记,所以,在Windows中为了确保万无一失,应该在文件的路劲的引号前加上r。
例如,分别以相对路径和绝对路劲的方式打开相应路径下的文件。
代码1:
1 with open(r"text_files\test_file",‘r‘,encoding="utf-8") as file_object: 2 print(file_object.read())
运行结果:
1 将进酒 2 作者:李白 3 4 君不见,黄河之水天上来,奔流到海不复回。 5 君不见,高堂明镜悲白发,朝如青丝暮成雪。 6 人生得意须尽欢,莫使金樽空对月。 7 天生我材必有用,千金散尽还复来。 8 烹羊宰牛且为乐,会须一饮三百杯。 9 岑夫子,丹丘生,将进酒,杯莫停。 10 与君歌一曲,请君为我倾耳听。 11 钟鼓馔玉不足贵,但愿长醉不复醒。 12 古来圣贤皆寂寞,惟有饮者留其名。 13 陈王昔时宴平乐,斗酒十千恣欢谑。 14 主人何为言少钱,径须沽取对君酌。 15 五花马,千金裘,呼儿将出换美酒, 16 与尔同销万古愁。
代码:
1 with open(r"F:\PyProject\s14\text_files\test_file",‘r‘,encoding="utf-8") as file_object: 2 print(file_object.read())
运行结果:
1 满江红·怒发冲冠 2 年代: 宋 作者: 岳飞 3 4 怒发冲冠,凭栏处潇潇雨歇。 5 抬望眼,仰天长啸,壮怀激烈。 6 三十功名尘与土, 7 八千里路云和月。 8 莫等闲白了少年头,空悲切。 9 10 靖康耻,犹未雪; 11 臣子恨,何时灭! 12 驾长车踏破贺兰山缺。 13 壮志饥餐胡虏肉, 14 笑谈渴饮匈奴血。 15 待从头收拾旧山河,朝天阙。
1.1.1.3 逐行读取文件
在Python中,有时我们需要检查文件中是否包含我们关注的信息,或者需要修改的信息,此时,可使用for循环来逐行读取文件。
例如,将一首存于文本文件中,然后逐行读取,并打印。
文本内容如下所示:
当你老了
叶芝(爱尔兰)
当你老了,头白了,睡思昏沉,
炉火旁打盹,请取下这部诗歌,
慢慢读,回想你过去眼神的柔和,
回想它们昔日浓重的阴影;
多少人爱你青春欢畅的时辰,
爱慕你的美丽,假意或者真心,
只有一个人爱你那朝圣者的灵魂,
爱你衰老了的脸上痛苦的皱纹;
垂下头来,在红光闪耀的炉子旁,
凄然地轻轻诉说那爱情的消逝,
在头顶的山上它缓缓踱着步子,
在一群星星中间隐藏着脸庞。
代码:
1 with open(r"text_files\test_file1",‘r‘,encoding="utf-8") as file_object: 2 for line in file_object: 3 print(line)
说明:
第2行,用for循环遍历整个文件,逐行读取文件的内容。
运行结果:
1 当你老了 2 3 叶芝(爱尔兰) 4 5 6 7 当你老了,头白了,睡思昏沉, 8 9 炉火旁打盹,请取下这部诗歌, 10 11 慢慢读,回想你过去眼神的柔和, 12 13 回想它们昔日浓重的阴影; 14 15 多少人爱你青春欢畅的时辰, 16 17 爱慕你的美丽,假意或者真心, 18 19 只有一个人爱你那朝圣者的灵魂, 20 21 爱你衰老了的脸上痛苦的皱纹; 22 23 垂下头来,在红光闪耀的炉子旁, 24 25 凄然地轻轻诉说那爱情的消逝, 26 27 在头顶的山上它缓缓踱着步子, 28 29 在一群星星中间隐藏着脸庞。
从以上的运行结果可知,每行打印后都会增加一个空白行,故与原文格式不完全一致。这是因为在这个文件中,每行的末尾都有一个看不见的换行符,而print语句也会加上一个换行符,因此,每行末尾都有两个换行符:一个来自文件本身,另一个来自print语句。
那么怎么才能使打印的内容跟原文保持一致呢?我们可以使用rstrip()函数消除多余的空白。
代码:
1 with open(r"text_files\test_file1",‘r‘,encoding="utf-8") as file_object: 2 for line in file_object: 3 print(line.rstrip())
说明:
第2行,使用函数rstrip()来消除多余的空白行。
运行结果:
1 当你老了 2 叶芝(爱尔兰) 3 4 当你老了,头白了,睡思昏沉, 5 炉火旁打盹,请取下这部诗歌, 6 慢慢读,回想你过去眼神的柔和, 7 回想它们昔日浓重的阴影; 8 多少人爱你青春欢畅的时辰, 9 爱慕你的美丽,假意或者真心, 10 只有一个人爱你那朝圣者的灵魂, 11 爱你衰老了的脸上痛苦的皱纹; 12 垂下头来,在红光闪耀的炉子旁, 13 凄然地轻轻诉说那爱情的消逝, 14 在头顶的山上它缓缓踱着步子, 15 在一群星星中间隐藏着脸庞。
从以上的运行结果可知,打印的内容跟文件中的完全一致。
1.1.1.4 将文件内容存入一个列表
在Python中,使用关键字with时,函数open()返回的文件对象只在with代码块内可以用。如果要在访问文件的内容,可在with代码块内将文件的各行存储在一个列表中,并在with代码块外使用列表。
例如,我们将一个文件的内容读取后存入一个列表。
代码:
1 with open(r"text_files\test_file2","r",encoding="utf-8") as file_object: 2 lines = file_object.readlines() 3 for line in lines: 4 print(line.rstrip())
说明:
第1行,用关键字with 即函数open(),以读的方式用utf-8打开文件test_file2.txt。
第2行,使用readlines()方法从文件中读取每一行内容,并将其存储在一个列表中,并赋值给变量lines。
第3行,开始用for循环遍历列表。
第4行,打印列表内容,并用函数rstrip()去掉多余的空格。
运行结果 :
1 世界上最遥远的距离——[印度]泰戈尔 2 3 世界上最遥远的距离 不是生与死 4 而是我就站在你面前你却不知道我爱你 5 6 世界上最遥远的距离 7 不是我就站在你面前你却不知道我爱你 8 而是明明知道彼此相爱却不能在一起 9 10 世界上最遥远的距离 11 不是明明知道彼此相爱却不能在一起 12 而是明明无法抵挡这股想念却还是故意装作丝毫没有把你放在心里 13 14 世界上最遥远的距离 15 不是明明无法抵挡这股想念却还是故意装作丝毫没有把你放在心里 16 而是用自己冷漠的心对爱你的人掘了一条无法跨越的沟渠
从以上的运行结果可知,由于列表lines的每一个元素都对应于文件中的一行,因此打印出来的内容与文件中完全一致。6
1.1.1.5 使用文件中内容
当我们将文件读到内存后,就可以以任何方式使用这些数据了。
例如,将文件中的一首诗读取到内存中,然后使用它,答应所有内容,并求其长度。文件内容如下:
《相思》
(唐代:王维)
红豆生南国,春来发几枝。
愿君多采撷,此物最相思。
代码:
1 with open(r"text_files\test_file3","r",encoding="utf-8") as file_object: 2 lines = file_object.readlines() 3 file_string = ‘‘ 4 for line in lines: 5 file_string += line.strip() 6 print(file_string) 7 print(len(file_string))
说明:
第3行,我们创建了一个变量,初始化为空。
第4~5行,用for循环将每行都加入变量file_string,并删除每行末尾的换行符。
第6行,打印该字符串file_string 。
第7行,用函数len()求出字符串的长度,并打印出来。
运行结果:
1 《相思》(唐代:王维)红豆生南国,春来发几枝。愿君多采撷,此物最相思。 2 35
1.1.2 写入文件
保存数据的最简单的方式之一就是讲其写入到文件中。通过将输出的内容写入文件,即便关闭程序输出的终端窗口,这些输出的内容也依然存在。这样,我们仍然还可以使用输出的内容。
在Python中,打开文件时,我们可以指定以下几种模式:
(1)r:读取模式。只能读取打开的文件内容。
(2)w:写入模式。只能往打开的文件写内容,并且会清空文件中的内容,然后再写入。
(3)a:附加模式。在原文后追加内容,不会删除原有文件的内容。
(4)r+:读写模式。对打开的文件既可读取,又可写入。
1.1.2.1 写入空文件
当我们想要将文本写入文件中时,我们需要在调用方法open()时给其提供另一个实参,告诉Python我们要写入打开的文件。
例如,我们创建一个空文件,然后往其中写入一些内容。
代码:
1 filename = r"text_files\test_file4" 2 with open(filename,‘w‘,encoding="utf-8") as file_object: 3 file_object.write("东边日出西边雨,道是无晴却有晴。")
说明:
第2行,调用open()方法提供了三个实参,第一个实参是要打开的文件的名称;第二个实参是(‘w’)告诉Python解释器,要以写入模式打开这个文件;第三个实参告诉Python解释器,使用字符集utf-8。
第3行,调用方法write()将“东边日出西边雨,道是无晴却有晴。”写入文件中。
以上代码没有终端输出,但是如果我们打开文件test_file4.txt时,将会看到里面有我们写入的诗句:东边日出西边雨,道是无晴却有晴。
执行代码后,文件内容如下所示:
东边日出西边雨,道是无晴却有晴。
1.1.2.2 写入多行内容
当我使用方法write()往文件中写入内容时,写入的文件的末尾不会自动添加换行符,因此,如果我们要往文件中写入多行内容时,需要在write()语句中自行写上换行符。
例如,往文件test_file5.txt中写入一首诗。
代码:
1 filename = r"text_files\test_file5" 2 with open(filename,‘w‘,encoding="utf-8") as file_object: 3 file_object.write("\t\t宿严陵钓台\n") 4 file_object.write("\t作者:神颖 朝代:唐代\n") 5 file_object.write("寒谷荒台七里洲,") 6 file_object.write("贤人永逐水东流。\n") 7 file_object.write("独猿叫断青天月,") 8 file_object.write("千古冥冥潭树秋。\n")
执行完以上代码后,会自动生成一个文件test_file5.txt,其中写入的具体内容如下所示:
宿严陵钓台
作者:神颖 朝代:唐代
寒谷荒台七里洲,贤人永逐水东流。
独猿叫断青天月,千古冥冥潭树秋。
从写入文件的内容可知,调用write()方法往文件中写内容时,如果不加换行符,写入的内容会挤在一起。除了使用换行符,还可以使用空格、制表符及空行等来设置输出的格式。
1.1.2.3 追加文件内容
在Python中,如果我们想要往文件中增加内容,但不覆盖文件中已有的内容时,那么可以使用附加模式(a)打开文件。
例如,在test_file4.txt文件中追加内容:金风玉露一相逢,便胜却人间无数。
代码:
1 filename = r"text_files\test_file4" 2 with open(filename,‘a‘,encoding="utf-8") as file_object: 3 file_object.write("\n-----追加以下诗句--------------") 4 file_object.write("\n金风玉露一相逢,便胜却人间无数。")
说明:
第2行,我们打开文件时,指定了实参‘a‘,以便将内容追加到文件中,而不是覆盖文件原内容。
第3~4行,我们写入了两行内容,他们将被追加到文件test_file4.txt的末尾。
执行后文件内容如下所示:
东边日出西边雨,道是无晴却有晴。
-----追加以下诗句--------------
金风玉露一相逢,便胜却人间无数。
1.1.2.4 文件内容的修改
在Python中,如果我们想修改文件中的某一内容,可以使用函数replace()替换需要修改的文件内容。
例如,我们创建一个文件test_file6.txt,然后将文件中的"莫愁前路无知己,天下谁人不识君。"修改为:"海内存知已,天涯若比邻。" 然后将修改后的内容写入文件test_file6_new.txt。修改前文件的内容如下:
东风不与周郎便,铜雀春深销二乔。
莫愁前路无知己,天下谁人不识君。
代码:
1 filename = r"text_files\test_file6" 2 filename_new = r"text_files\test_file6_new" 3 find_str = "莫愁前路无知己,天下谁人不识君。" 4 replcae_str = "海内存知已,天涯若比邻。" 5 with open(filename,‘r‘,encoding="utf-8") as file_object: 6 with open(filename_new, ‘w‘, encoding="utf-8") as file_object_new: 7 for line in file_object: 8 if find_str in line: 9 line = line.replace(find_str,replcae_str) 10 file_object_new.write(line)
代码运行后的新文件内容如下所示:
东风不与周郎便,铜雀春深销二乔。
海内存知已,天涯若比邻。
1.3 异常
在所有编程语言中,对异常的处理非常重要。Python程序执行期间发生错时,会通过一种特殊对象来管理程序,这种特殊的对象就被称为异常。每当发生让Python不知所措的错误时,它都会创建一个异常对象。如果我们编写了处理该异常的代码,那么程序将会继续运行;反之,程序将停止运行,并显示一个traceback,其中包含有关异常的报告。
在Python中,异常是使用try-except代码块处理的。try-except代码块让Python执行指定的操作,同时告诉Python发生异常时应如何处理。使用了try-except代码块时,即便出现异常,程序也将继续运行:显示我们编写的友好的错误消息,而不是令用户迷惑的traceback。
1.3.1 处理ZeroDivisionError异常
我们都知道,算术运算中,除数不能为0。当我们用一个数字除以0时,往往会提示除数不能为0。
例如,用10除以0。
代码:
1 y = 10 2 x = 0 3 z = y / x 4 print(z)
运行结果:
1 Traceback (most recent call last): 2 File "F:/PyProject/s14/day3/test_except.py", line 5, in <module> 3 z = y / x 4 ZeroDivisionError: division by zero
从以上运行结果可知,在traceback中,第4行指出的错误ZeroDivisionError是一个异常对象。Python无法按照我们的要求处理时,就会创建这种对象。在这种情况下,Python将停止运行程序,并指出引发了哪种异常,而我们可根据这些信息对程序进行修改。
1.3.2 try-except代码块的应用
在Python中,当我们认为可能会发生错误时,可编写一个try-except代码块来处理可能引发的异常。
例如,用try-except处理1.3.1 中的代码发生的异常。
代码:
1 try: 2 y = 10 3 x = 0 4 z = y / x 5 print(z) 6 except ZeroDivisionError: 7 print("You can‘t divide by zero!")
说明:
当我们将错误的代码print(z)放到了一个try代码中,如果try代码中的代码运行没有问题,则Python将跳过except代码块;反之,则执行except中的代码块。
运行结果:
1 You can‘t divide by zero!
从以上的运行结果可知,try中的代码引发了ZeroDivisionError异常,因此Python指出了该如何解决问题的except代码块,并运行其中的代码。
1.3.2 使用异常的好处
使用异常处理,避免程序出现崩溃现象。当程序发生错误时,如果程序还有代码没运行完,如何妥善处理错误就至关重要。
例如,编写一个程序,让用户根据提示输入两个数,然后求商。
代码:
1 while True: 2 first_number = input("请任意输入第一个数字:") 3 if first_number == "q": 4 break 5 second_number = input("请任意输入第二个数字:") 6 if second_number == "q": 7 break 8 answer = int(first_number) / int(second_number) 9 print("第一个数字除以第二数字等于:",answer)
运行结果:
1 请任意输入第一个数字:8 2 请任意输入第二个数字:9 3 第一个数字除以第二数字等于: 0.8888888888888888 4 请任意输入第一个数字:10 5 请任意输入第二个数字:0 6 Traceback (most recent call last): 7 File "F:/PyProject/s14/day3/test_except.py", line 10, in <module> 8 answer = int(first_number) / int(second_number) 9 ZeroDivisionError: division by zero
从以上的运行结果可知,当我们输入的第二个数为0时,程序就崩溃,无法继续运行。为此,我们引入了else 代码块。
例如,使用try-except代码块和else代码块处理异常。
代码:
1 while True: 2 first_number = input("请任意输入第一个数字:") 3 if first_number == "q": 4 break 5 second_number = input("请任意输入第二个数字:") 6 if second_number == "q": 7 break 8 try: 9 answer = int(first_number) / int(second_number) 10 except ZeroDivisionError: 11 print("除数不能为0,请重新输入!") 12 else: 13 print("第一个数字除以第二数字等于:",answer)
运行结果:
1 请任意输入第一个数字:18 2 请任意输入第二个数字:7 3 第一个数字除以第二数字等于: 2.5714285714285716 4 请任意输入第一个数字:23 5 请任意输入第二个数字:0 6 除数不能为0,请重新输入! 7 请任意输入第一个数字:q
从以上运行结果可知,使用异常处理后,当输入的第二个数为0时,程序仍然可以继续运行,除非输入q时退出程序。
1.3.3 FileNotFoundError异常
当我们使用文件时,一种最常见的异常问题就是找不到文件,为了避免出现异常,我们可以使用try-except代码块进行处理。
例如,打开一个不存的文件,看其运行结果。
代码:
1 filename = r"test_file" 2 with open(filename,‘r‘,encoding="utf-8") as file_object: 3 lines = file_object.read() 4 for line in lines: 5 print(line)
运行结果:
1 Traceback (most recent call last): 2 File "F:/PyProject/s14/day3/test_except.py", line 4, in <module> 3 with open(filename,‘r‘,encoding="utf-8") as file_object: 4 FileNotFoundError: [Errno 2] No such file or directory: ‘test_file‘
从以上运行结果可知,由于文件不存在,在traceback中的最后一行报告了FileNotFoundError异常,这是Python找不到打开的文件时创建的异常。该异常时由于函数open()导致的,因此要处理这个错误,必须将try语句放在包含open()的代码行之前。
例如,用try-except代码块处理上述代码。
优化后代码:
1 filename = r"test_file" 2 try: 3 with open(filename,‘r‘,encoding="utf-8") as file_object: 4 lines = file_object.read() 5 for line in lines: 6 print(line) 7 except FileNotFoundError: 8 print("Sorry,the file ",filename,"does not exist.")
运行结果:
1 Sorry,the file test_file does not exist.
从以上运行结果中可知,由于try代码块引发异常,故跳到except代码块运行。
1.3.4 文本文件分析
文本文件的分析包括分析单个文本和多个文本。
1.3.4.1 单文本文件分析
我们可以分析包含整本书的文本文件,计算其包含多少单词。此时,我们可以使用方法split(),它根据一个字符串创建一个单词列表。
例如,分析文本文件alice.txt中有多少个单词。
代码:
1 filename = r"text_files\alice.txt" 2 try: 3 with open(filename,‘r‘,encoding="utf-8") as file_object: 4 contents = file_object.read() 5 except FileNotFoundError: 6 print("Sorry, the file ",filename,"does not exist.") 7 else: 8 #计算文本大致包含多少个单词 9 words = contents.split() 10 num_words = len(words) 11 print("The file ",filename,"has about ",str(num_words),"words.")
说明:
第2~4行,属于try代码块,其中以读模式,用utf-8打开文件,并将内容读取后赋值给变量contents。
第5~6行,属于except 代码块,处理异常FileNotFoundError,并打印提示。
第7~11行,属于else代码块,其中第9行使用方法split()对变量contents进行处理,生成一个列表,兵赋值给变量words;第10行使用len()求生成的列表的长度,就可以知道原字符串中大致包含多少个单词。
运行结果:
1 The file text_files\alice.txt has about 28753 words.
1.3.4.2 多文本文件分析
在Python中,如果我们想分析多个文本文件,那么可以调用函数count_words()来实现。
例如,我们先将单文本文件分析的代码修改用count_words()来实现。
代码:
1 def count_words(filename): 2 ‘‘‘算一个英文的文本大致包含多少个单词‘‘‘ 3 try: 4 with open(filename,‘r‘,encoding="utf-8") as file_object: 5 contends = file_object.read() 6 except FileNotFoundError: 7 print("Sorry,the file ",filename,"does not exist.") 8 else: 9 # 计算英文文件大致包含多少个单词 10 words = contends.split() 11 nume_words = len(words) 12 print("The file ",filename,"has about ",str(nume_words)) 13 filename = r"text_files\alice.txt" 14 count_words(filename)
说明:
第1行,用def 申明一个函数count_words()。
第2、9行,都是注释的内容,这说明Python中注释可以使用三个单引号将内容引起来或者在每行内容前加#号,都可实现注释功能。
第14行,调用代码中申明的函数count_words(),并给其传递一个实参。
运行结果:
1 The file text_files\alice.txt has about 28753
从以上的运行结果可知,其结果跟1.3.4.1中的一致。这说明实现单个文件分析时有两种实现方式。
那么我们想实现分析多个文件时,该怎么办呢?
例如,分析多个英文文本文件。
代码:
1 def count_words(filename): 2 """计算一个英文的文本大致包含多少个单词""" 3 try: 4 with open(filename, ‘r‘, encoding="utf-8") as file_object: 5 contends = file_object.read() 6 except FileNotFoundError: 7 print("Sorry,the file ", filename, "does not exist.") 8 else: 9 # 计算英文文件大致包含多少个单词 10 words = contends.split() 11 nume_words = len(words) 12 print("The file ", filename, "has about ", str(nume_words)) 13 filenames = [r‘text_files\alice.txt‘,r‘text_files\little_warrior.txt‘,r‘text_files\moby_dick.txt‘,r‘text_files\test_file.txt‘,r"text_files\siddhartha.txt"]
14 for filename in filenames: 15 count_words(filename)
说明:
第13行,将要分析的文件的名称存到一个列表中,并在每个元素前加一个字母r。
第14行,用for语句循环列表中的文件名称。
第15行,调用函数count_words(),并将每次循环的结果传递给该函数。
运行结果:
1 The file text_files\alice.txt has about 28753 words .
2 The file text_files\little_warrior.txt has about 120399 words .
3 The file text_files\moby_dick.txt has about 214408 words .
4 Sorry,the file text_files\test_file.txt does not exist.
5 The file text_files\siddhartha.txt has about 37428 words .
从以上运行结果可知,test_file.txt文件不存在时,也不会影响呈现期后面的代码的运行,也不会出现完整的traceback,这样避免了用户看到。
因此,使用try-except代码块处理异常的好处是:避免用户看到traceback;让程序能够继续往后执行。
对于以上的运行结果,如果我们在出现异常时,什么也不做,也不想让用户知道,那么可使用pass语句。
例如,实现多文件分析,但程序异常时让什么也不做,同时让用户无感知。
代码:
1 def count_words(filename): 2 """计算一个英文的文本大致包含多少个单词""" 3 try: 4 with open(filename, ‘r‘, encoding="utf-8") as file_object: 5 contends = file_object.read() 6 except FileNotFoundError: 7 pass 8 else: 9 # 计算英文文件大致包含多少个单词 10 words = contends.split() 11 nume_words = len(words) 12 print("The file ", filename, "has about ", str(nume_words),"words .") 13 filenames = [r‘text_files\alice.txt‘,r‘text_files\little_warrior.txt‘,r‘text_files\moby_dick.txt‘,r‘text_files\test_file.txt‘,r‘text_files\siddhartha.txt‘] 14 for filename in filenames: 15 count_words(filename)
说明:
第7行,使用pass语句,什么也不做。
运行结果:
1 The file text_files\alice.txt has about 28753 words . 2 The file text_files\little_warrior.txt has about 120399 words . 3 The file text_files\moby_dick.txt has about 214408 words . 4 The file text_files\siddhartha.txt has about 37428 words .
从以上的运行结果可知,当文件test_file.txt没有找到时,什么也没做,也不输出任何提示,程序也能继续运行。
pass语句充当了占位符,它提醒我们在程序的某个位置什么都没做,并且以后也许要在该处做些什么。
例如,我们在分析多个文件时,如果文件找不到时,我们不给用户提示任何信息,但是我们需要将该文件写入一个记录文件名称的文件,便于对该类文件进行处理。
代码:
1 def count_words(filename): 2 """计算一个英文的文本大致包含多少个单词""" 3 try: 4 with open(filename, ‘r‘, encoding="utf-8") as file_object: 5 contends = file_object.read() 6 except FileNotFoundError: 7 # pass 8 with open(filename_missing,‘a‘,encoding="utf-8") as file_missing_object: 9 file_missing_object.write(filename) 10 else: 11 # 计算英文文件大致包含多少个单词 12 words = contends.split() 13 nume_words = len(words) 14 print("The file ", filename, "has about ", str(nume_words),"words .") 15 filenames = [r‘text_files\alice.txt‘,r‘text_files\little_warrior.txt‘,r‘text_files\moby_dick.txt‘,r‘text_files\test_file.txt‘,r‘text_files\siddhartha.txt‘] 16 filename_missing =r"file_missing.tex" 17 for filename in filenames: 18 count_words(filename)
说明:
第8行,以追加的模式,用utf-8字符集打开文件。
第9行,将出现异常,找不到的文件名称写入文件file_missing.txt中。
运行结果:
1 The file text_files\alice.txt has about 28753 words .
2 The file text_files\little_warrior.txt has about 120399 words .
3 The file text_files\moby_dick.txt has about 214408 words .
4 The file text_files\siddhartha.txt has about 37428 words .
从以上运行结果看,其结果跟使用pass语句时一致,但是,程序目录中多了一个file_missing.txt文件,并且其中的内容如下:
text_files\test_file.txt
以上文件就是分析文件的过程中没有找到的文件。
1.3.5 如何处理异常
在编程语言中,一个友好的异常处理,可以提高用户体验。当程序出现异常时,我们需要判断是否应该告知用户。一般情况下,一个进过详细测试过的完善的程序,很少出现内部错误,即由语法或逻辑引发的异常,但如果程序依赖外界因素,如用户输入、存在指定的文件、有网络连接,就可以出现异常。
由于Python的错误处理结构可以让我们能够细致的控制与用户分享错误信息的程度,因此,我们需要在可能出现异常的地方使用try-except代码块来处理异常,并根据实际需要决定是否应给用户提示相应的信息。
1.4 存储数据
很多情况下,我们要求用户输入某种信息,并且在程序运行结束时都需保持这些信息。那么用什么方式来保持这些信息呢?
此时,我们会想到一种简单的存储数据的简单的方式,即模块JSON(JavaScript Object Notation)。它可让我们简单的将Python数据结构转储到文件中,并在程序再次运行时加载该文件中的数据。
1.4.1 模块JSON的应用
模块JSON 中包括两个函数json.dump和json.load()。其中函数json.dump()需要接受两个实参,即存储的数据以及可用于存储数据的文件对象;函数json.load()只需接受需要读取其内容到内存中的文件对象。
1.4.1.1 函数json.dump()的应用
用函数json.dump()将数据存储到.json文件中。
例如,将数字0到9存到一个列表中,然后使用函数json.dump()来存储该数字列表。
代码:
1 import json 2 3 list_numbers = [ i for i in range(10)] 4 print(list_numbers) 5 6 filename = r‘text_files\list_numbers.json‘ 7 with open(filename,‘w‘) as file_object: 8 json.dump(list_numbers,file_object)
说明:
第1行,导入json模块;
第3行,创建一个0到9的数字列表,并赋值给变量list_numbers。
第4行,打印该列表,便于与文件list_number.json对比。
第8行,使用函数json.dump()将列表的数据存储到文件对象中。
运行结果即文件list_numbers.json中的内容都如下所示:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
由于打印的列表内容和存储到文件list_numbers.json中的内容都一致,说明数据存储成功。
1.4.1.2 函数json.load()的应用
用函数json.load()从.json文件中读取数据到内存中。
例如,将文件list_numbers.json中内容读取出来,并打印。
代码:
1 import json 2 3 filename = r"text_files\list_numbers.json" 4 with open(filename,‘r‘,encoding="utf-8") as file_object: 5 list_numbers = json.load(file_object) 6 print(list_numbers)
说明:
第5行,使用函数json.load()将文件对象中的内容读取到内存中,并赋值给变量list_numbers。
运行结果:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
从以上运行结果可知,我们如果想实现数据共享,可以使用json来实现。
1.4.2 保存和读取用户生成的数据
在Python中,对于用户生成的数据,我们可使用json保存,因为如果不以某种方式来存储这些数据,待程序运行结束后这些信息就会丢失。
1.4.2.1 保存用户生成的数据
例如,用户登录时,提示用户在界面输入用户名,然后用json将其保存起来。
代码:
1 import json 2 3 username = input("Please enter your username: ") 4 password = input("Please enter your password:") 5 6 filename = r"text_files\account.json" 7 with open(filename,‘w‘,encoding="utf-8") as file_object: 8 json.dump(username,file_object) 9 print("Welcome!")
说明:
第3~4行,分别提示用户输入用户名和密码。
第8~9行,使用函数json.dump()分别将用户名保存到文件account.json中。
运行结果:
1 Please enter your username: YunQtian 2 Welcome!
程序执行结束后,文件account.json中的内容如下所示:
"YunQtian"
1.4.2.2 读取用户生成的数据
例如,从1.4.2.1保存的文件accoun.json中读取用户的用户名,并打印一条信息。
代码:
1 import json 2 3 filename = r"text_files\account.json" 4 5 with open(filename,‘r‘,encoding="utf-8") as file_object: 6 username = json.load(file_object) 7 print("Welcome back,",username,"!")
说明:
第6行,使用函数json.load()从文件account.json中读取存储的用户名。
运行结果:
Welcome back, YunQtian !
保存和读取用户生成的数据,其中可以在一个程序中实现。
例如,将以上两个代码合并为一个,并做好异常处理。
代码:
1 import json 2 3 ‘‘‘ 4 如果文件以及存在该用户,则加载它;反之,提示用户输入,并保持它 5 ‘‘‘ 6 7 filename = r"text_files\account.json" 8 try: 9 with open(filename,‘r‘,encoding="utf-8") as file_object: 10 username =json.load(file_object) 11 except FileNotFoundError: 12 username = input("Please enter your username:") 13 with open(filename,‘w‘,encoding="utf-8") as file_object: 14 json.dump(username,file_object) 15 print("Welcome to ",username,"!") 16 else: 17 print("Welcome back,",username,"!")
说明:
第1行,导入json模块。
第3~5行,代码注释,即程序描述。
第8~10行,属于try代码块,如果文件存在,则从文件读取用户名。
第11~15行,属于except代码块,如果文件不存在,则提示用户输入其用户名,并以读模式和utf-8打开文件,再调用函数json.load()将输入的有用户名存储到json文件中,并打印欢迎的问候。
第16~17行,属于else代码块,打印欢迎回来的问候。
第一次执行,如果我呢间不存在,运行结果如下:
1 Please enter your username:YunQtian 2 Welcome to YunQtian !
第二次执行,已经存在该文件时,其运行结果如下:
1 Welcome back, YunQtian !
从以上结果可推知,从第二次完后,每次执行的结果都一致。因为文件以及存在,每次运行都从文件中读取。
1.4.3 重构
1.4.3 重构的定义
在Python中,重构就是将一个能正常运行的代码作进一步的改进,即将代码划分为一系列完成具体工作的行数的过程。
1.4.4 重构的作用
重构可以让代码更加清晰、更易理解、更易扩展。
例如,我们将1.4.2.2 中的代码进行重构,由于该代码的重点是问候用户,故将所有代码放到一个greet_user()的函数中。
代码:
1 import json 2 def greet_user(): 3 """问候用户,并指出其名字""" 4 filename = r"text_files\account.json" 5 try: 6 with open(filename,‘r‘,encoding="utf-8") as file_object: 7 username = json.load(file_object) 8 except FileNotFoundError: 9 username = input("Please enter your username:") 10 with open(filename,‘w‘,encoding="utf-8") as file_object: 11 json.dump(username,file_object) 12 print("Welcome to",username,"!") 13 else: 14 print("Welcom back ,",username,"!") 15 16 greet_user()
说明:
第2~14行,定义一个用户问候的函数greet_user(),并将之前的diam全写入该函数中。
第16行,调用函数greet_user()。
运行结果:
第一次执行,如果我呢间不存在,运行结果如下:
1 Please enter your username:YunQtian
2 Welcome to YunQtian !
第二次执行,已经存在该文件时,其运行结果如下:
1 Welcome back, YunQtian !
函数greet_user()所做的不仅仅是问候用户,还在存储了用户名时获取它。而在没有存储用户名时提示用户输入一个。现在我们开始重构greet_user(),让他不执行那么多任务。为此,我首先将获取存储的用户名的代码移到另一个函数中。
代码:
1 import json 2 3 def get_stored_username(): 4 """如果存储了用户,就获取它""" 5 filename = r"text_files\account.json" 6 try: 7 with open(filename,‘r‘,encoding="utf-8") as file_object: 8 username = json.load(file_object) 9 except FileNotFoundError: 10 return None 11 else: 12 return username 13 14 def greet_user(): 15 """问候用户,并指出其名字""" 16 username = get_stored_username() 17 if username: 18 print("Welcome back",username,"!") 19 else: 20 username = input("Please enter your username:") 21 filename = r"text_files\account.json" 22 with open(filename,‘w‘,encoding="utf-8") as file_object: 23 json.dump(username,file_object) 24 print("Welcome to",username,"!") 25 26 greet_user()
说明:
第3~12行,定于了一个函数get_stored_username(),并且在文档字符串中明确指出了该函数的作用。
第14~24行,第一一个函数greet_user(),如果成功的获取了用户名,就打印一条欢迎用户回来的问候语,反之,提示用户输入其用户名。
运行结果:
第一次执行,如果我呢间不存在,运行结果如下:
1 Please enter your username:YunQtian 2 Welcome to YunQtian !
第二次执行,已经存在该文件时,其运行结果如下:
1 Welcome back, YunQtian !
在上述重构的代码中,我们还可将函数greet_user()中的另一个代码提取出来,即将没有存储用户名时提示用户输入的代码放到一个独立的函数中。
代码:
1 import json 2 3 def get_stored_username(): 4 """如果存储了用户,就获取它""" 5 filename = r"text_files\account.json" 6 try: 7 with open(filename,‘r‘,encoding="utf-8") as file_object: 8 username = json.load(file_object) 9 except FileNotFoundError: 10 return None 11 else: 12 return username 13 14 def get_new_username(): 15 """提示用户输入用户名""" 16 username = input("Please enter your usernamr:") 17 filename = r"text_files\account.json" 18 with open(filename,‘w‘,encoding="utf-8") as file_object: 19 json.dump(username,file_object) 20 return username 21 22 def greet_user(): 23 """问候用户,并指出其用户名""" 24 username = get_stored_username() 25 if username: 26 print("Welcome back",username,"!") 27 else: 28 username = get_new_username() 29 print("Welcome to ",username,"!") 30 31 greet_user()
说明:
第3~12行,定义了一个获取已存在的用户的用户名的函数get_stored_username(),找到就返回该用户名,反之返回None。
第14~20行,定义了一个提示新用户输入用户名的函数get_new_username(),并使用json.dump()函数将其存入文件中,并返回该用户名。
第22~29行,定义一个greet_user()函数,问候每个用户,并指出其用户名。
第31行,用greet_user()打印一条适合的问候语。
运行结果:
第一次执行,如果我呢间不存在,运行结果如下:
1 Please enter your username:YunQtian 2 Welcome to YunQtian !
第二次执行,已经存在该文件时,其运行结果如下:
1 Welcome back, YunQtian !
从以上这个最终重构的代码可知,每个函数都执行单一而清晰的任务。我们调用函数greet_user()打印一条合适的问候语,即要么问候老用户,要么欢迎新用户。
因此,函数greet_user()首先调用函数get_stored_username(),若获取到返回的用户名,则打印问候老用户的问候语,反之,再调用函数get_new_username(),提示用户输入其用户名,并将其存入打开的文件中,再打印一条欢迎新用户的问候语。
因此,想要编写出清晰而易于维护和扩展的代码,这种划分工作必不可少,即重构在编程中很重要。