从零开始学习PYTHON3讲义(十)

自己做一个“电子记事本”

《从零开始PYTHON3》第十讲

截至上一讲,我们已经完成了Python语言的基本部分。我们用了三讲来讨论Python语言的控制结构,用了两讲来介绍Python的基本数据类型。可以说仅就语法和语言关键字的部分来讲,当前所学已经足以完成大多数工作。
由本讲开始,我们开始讲述一些经典的Python语言应用场景。以案例的形式为引导,学习如何使用Python解决具体问题。

我们之前讲过的程序中,所有的操作,都是在内存中进行的。关机或者停电,都会造成内容的丢失。如果想不丢失,就需要把数据保存到硬盘文件中,专有名词称为“持久化”。
硬盘是一种历史遗留的习惯说法,当前的SSD/闪存/存储卡,实际起到的也是同样的作用。
我们编程一直保存的.py文件,就是文件的一种,这是程序的源码实现了持久化,关机、断电都不会丢失。


文件操作

使用Python进行文件操作并不难。但是文件这个概念还是很大的,围绕着文件,还有很多概念需要介绍。这些概念具有通用性,并非Python所有。

  • 文件名:文件必须有一个文件名,通常文件名包含主文件名和扩展文件名(扩展名因为在文件名的最后部分,所以也称为“后缀名”),文件名和扩展名之间使用英文小数点隔开。
    不同的操作系统,对于文件名的要求是不同的。通常来说文件名中可以使用字母、数字、下划线,不能使用其它的符号。扩展文件名一般包含特殊的含义,比如.py后缀的文件就代表Python语言程序文件。.doc后缀是Word文档。
    大多数系统都允许中文的文件名,但跟变量名的原因类似,作为程序和程序数据的时候,尽量不选用中文文件名。
  • 文件必须有一个存储位置,也就是文件夹,还被称为“目录”。文件夹的名字跟文件有同样的要求,事实上在很多操作系统中,文件夹就是一种特殊的文件。习惯上文件夹不使用扩展名。
  • 文件夹是可以包含其它文件和文件夹的。因此从任何一个确定的存储位置开始,可以有“文件夹\文件夹\文件”这样的形式,来精确的定位某一个特定的文件。这称为“PATH”,中文是“路径”的意思,很形象。(本例中路径所使用的格式是Windows的格式,Linux等类Unix系统使用相反的斜线“/”来间隔文件夹及文件名。) 在同一个文件夹中的文件或者另外的文件夹,必须具有唯一的名字。或者说,路径必须是唯一的,一个路径可以唯一的找到某一个特定的文件。
  • 文件路径如果没有指定文件夹部分,只有一个文件名,那代表文件就在“当前目录”。在Python中,当前目录指的是程序启动时所在的目录。

好了,以上是关于文件操作的基本知识。下面回到Python。
相对内存的操作来说,硬盘、SSD等,虽然速度已经很快,但依然算是计算机中的慢速设备。每次需要进行某个文件操作的时候,Python和操作系统,都需要分配系列的资源。包括驱动程序、内存、基础参数等,来支持操作的进行。这个过程叫“打开文件”。
在对文件的操作完成之后,为了让宝贵的系统资源能够被其它程序或者操作使用,需要释放这些资源,这个过程叫做“关闭文件” 在打开文件和关闭文件之间,是文件操作的工作。这样的程序结构在编程中非常常见,也被称为“三明治结构”。以后你还会见到很多这种三明治结构。

Python中的文件打开操作使用:

fd=open(文件路径,"文件操作类型") 

这是一个打开文件的函数,第一个参数表示要打开文件的文件路径。 第二个参数代表以何种方式操作文件,常用文件操作有:

  • r : 读取模式,只读取文件,不允许写数据到文件

  • w : 写入模式,可以读取和写入文件

  • a : 追加模式,从文件尾部追加数据。

函数打开文件完成后,会返回一个值,在上面代码中是赋值给fd变量。这个值也称为“文件句柄”,这个词算是外来词汇。可以把前面打开文件所申请的内存、驱动程序等资源理解为一口装满水的锅,而句柄则是这个锅的“把手”。有这个句柄在手,就可以通过句柄指挥这些资源对文件做各种操作。所以从这个角度说,句柄这个词翻译的挺传神的。
再提醒一下,文件必须先打开,才能进行其余的文件操作。

Python的文件关闭操作要简单的多,原因是不需要提供过多的参数:

fd.close()

文件关闭之后,不能对该文件进行其它操作。
另外你可能注意到了,打开文件的时候,使用的是通用内置函数open。而文件关闭的时候,使用的是“文件句柄”所包含的close()操作,这说明关闭操作,只对句柄这种特定的类型有效。
所以看起来close()的调用没有任何参数,但实际上默认是对fd本身进行了关闭操作。
接着是对文件的读取操作,Python有3种常用的读取形式:

#方法1
a = fd.readline()   #读一行

#方法2
a= fd.readlines()  #读所有行

#方法3
for line in fd:  #直接把文件当做一个序列来遍历:
    print(fd)

使用哪种方式更好,一般看你要做的操作是什么,以及程序的结构方便,功能其实都是差不多的。

最后是写入文件:

fd.write(要写入的内容)
#通常写入的内容或者是字符串类型,其它类型要转换成字符串

挑战

今天的挑战就是写一个“记事本”小程序。程序的功能分为三个部分:

  1. 把内容记录到文件。
  2. 显示记录的所有内容。
  3. 删除不再需要的内容。

请先思考一下,可以用伪代码或者流程图描述一下思路,再继续后面的内容。


正式的“记事本”程序实际上很复杂,在手机市场中搜索,能找到上千种app,对于用户体验等方面的设计和功能要求非常高,竞争激烈。我们在这里出于学习目的,并且主要集中在对于文件操作的学习,所以一切都比较简化。 在挑战的题目中,实际上已经把程序分了3部分功能,保存、显示和删除。这等于已经帮助我们进行了整体程序结构的设计。我们沿着这个思路,先使用“伪代码”的形式,把流程梳理清楚。

  1. 把内容记录到文件
    • 获取要记录的内容(笔记内容),这里有一个待解决的问题,就是如何获取?
    • 打开文件用于写出
    • 保存笔记内容
    • 关闭文件
  2. 显示文件内容
    • 打开文件读取
    • 逐行读取文件内容
    • 显示
    • 关闭文件
  3. 删除不需要的内容
    • 首先的问题,如何定位不需要的内容?
      • 在显示文件的过程中,对内容按照行进行编号
    • 打开文件用于读取
    • 全部读取
    • 关闭文件
    • 打开文件用于写出
    • 循环遍历所有行,跳过要删除的行,写出
    • 关闭文件
  4. 共性问题
    • 三个小程序,都应当读、写同一个文件,否则无法互相配合

逻辑写的并不复杂,我们在下面源码的部分再更细致的讲解。这个“伪代码”提纲的功能,是让你在开始编写程序的时候,不至于不知道如何下手。


既然第4个共性的问题涉及到三个小程序,我们先从这个问题开始解决。方法非常简单,短到只有一行代码:

filename="daily.txt"

这一行代码只是定义了一个字符串变量filename,表示我们使用的记事文件名称。重点在于这行代码如何使用。

程序库

我们的课程一开始就大肆鼓吹Python的程序库如何丰富,我们今天就来自己定义一个程序库。上面这个仅仅一行代码的程序,我们保存为common.py,文件名不要输入错,因为我们后面还要用到。
此时common.py就称为一个程序库,虽然看上去很简陋,但它就是程序库。我们在这里很大程度出于演示程序库应用的目的。因为这样简单的功能,并非必须用程序库的方法解决。

现在我们有了一个程序库,使用程序库的方法有三种,我们使用源代码来展示:

#第一种方法
#引用程序库只需要在import之后跟主文件名,不能写上.py后缀
import common
a=common.filename  #使用其中变量的方法

#第二种
import common as cm   #引用库,并改成一个较短的名字
a=cm.filename

#第三种
from common import *	#引用库中所有的内容,并同当前程序混合
#上面的*符号是所有的意思
a=filename

这三种方法,各有不同的应用场景,可以根据自己的喜好选择。

除了可以定义自己的程序库,Python语言已经随着语言本身附带了很多官方发行的程序库,比如用于数学计算的math库。还有很多不同的开发组织提供的,数量庞大的功能库,比如人工智能库tensorflow。后面几讲我们会介绍一些常用扩展库的使用。


今天的挑战至此我们还有一个问题还没有解决办法,就是保存笔记的小程序,如何获取用户输入的记事内容。我们已经学习过了让用户输入的input函数,但启动程序,等待用户输入内容,感觉上啰嗦并且不够友好。
在这里我们尝试一下让用户在执行程序的时候,同时输入一条信息,当做我们程序的参数,随后程序获取这个参数,并记录到记事本中。
这样在程序启动前给定的信息,叫做“命令行参数”,命令行的概念我们课程一开始就讲解过了。获取用户输入的命令行参数,就要用到一个标准的Python系统库sys。下面是示例代码:

#引入sys系统库,这个库里包含很多跟操作系统相关的功能
import sys
#显示命令行参数的数量和参数内容
print(len(sys.argv),sys.argv)

#sys.argv就是命令行的参数,是一个列表
#len(sys.argv)就是列表的长度,可以得到参数的个数

#如果在命令行使用如下命令:
python3 args.py 1 2 3
#会得到如下结果:
4 ['args.py', '1', '2', '3']

这里我们学到了第一个系统扩展库sys,第八讲中我们介绍了Python内置的帮助函数help。这个函数对于内置的各种库同样有效,比如help(sys)可以列出sys库的详细帮助。此外今天再介绍一个函数dir(),这个函数可以列出某个库中所有可以使用的函数、常量资源。比如你可以在Python交互模式中使用dir函数来试试:

>>> import sys    #首先要引用sys库,否则下面会报错,找不到sys库
>>> dir(sys) #下一行开始是sys库中可以使用的所有变量和函数,是列表形式
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__', '__stderr__', '__stdin__', '__stdout__', '_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework', '_getframe', '_git', '_home', '_xoptions', 'abiflags', 'api_version', 'argv', 'base_exec_prefix', 'base_prefix', 'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth', 'get_coroutine_wrapper', 'getallocatedblocks', 'getcheckinterval', 'getdefaultencoding', 'getdlopenflags', 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info', 'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'set_coroutine_wrapper', 'setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout', 'thread_info', 'version', 'version_info', 'warnoptions']
>>> 

继续说命令行参数的问题。通常我们都是在IDLE环境中,使用F5来运行一个程序。因此其实大多数情况,我们的文件保存成了什么名字,都经常不太关心。
如果想获取用户输入的命令行参数,我们当然只能在命令行执行程序,这样才可以输入参数。在命令行执行程序的方式,其实在第一讲演示运行游戏程序的时候已经用过了,那个时候我们没有重点讲解,所以我猜大多数学生应当没记住。
下面是在命令行执行Python程序的一般方法,首先要打开命令行程序,这在不同操作系统中方法不同,在Windows中是查找cmd命令行图标,点击就可以打开命令行,然后执行Python程序的方法:

python3 python程序名.py 参数1 参数2 参数3 ...

开始的python3是通过操作系统的命令行,执行python3解释程序。我们说Python是解释型的语言,就是因为我们写的,给电脑看的程序文本文件。需要python3解释程序来翻译,才能被计算机接受、运行。
python3之后是要执行的python程序名,也就是我们自己编写的程序、存盘之后的文件名。再随后是用户输入给程序使用的参数,可以有多个。所有这些各自独立的部分之间,都统一使用空格间隔开。

继续说我们上面的程序,用户输入的命令行参数,会成为一个列表数据。列表的第0个元素,是正在执行的python程序本身,我们的例子中是:args.py。接着就是用户输入的参数了,每个都是一个字符串元素,可以有多个,我们的例子中是3个,加上python程序本身,所以len(sys.argv)得到的是4个参数。如果我们使用for in加上range来遍历的话,刚好可以使用len函数的结果值当做for循环的结束条件。

现在已经可以动手写第一个小程序了:

import sys
from common import *

#判断是否有且仅有1个参数
if len(sys.argv) != 2:
    print("参数错误,程序退出!")
   exit(1)

#打开文件追加,第一次没有此文件则自动建立一个空的
fd=open(filename,"a")
#写出数据,我们把第1个参数当做记事内容写到文件中
fd.write(sys.argv[1]) 
#第四讲学过的转义符,\n是换行的意思,我们每写出一条记事独占一行
fd.write("\n")
#关闭文件
fd.close()

程序只要执行过之后,你会发现,在程序所在目录(文件夹)下,会多出来一个目录,名称为:__pycache__。这个目录是系统自动生成的,目的是加速扩展库的加载和执行,因为这一讲我们使用了自定义的扩展库,虽然很简单,但是也会出现这个文件夹。
我们引用的其它扩展库,也会出现这个文件夹,只是那些库不是我们管理,所以我们看不到。

接着看第二个小程序,显示记事文件内容:

import common

#标记一个行数
i = 0
#打开文件读取
fd=open(common.filename,"r")
#遍历所有行
for line in fd:
    #显示行号、内容,end=""表示不换行
    #通常end是\n换行符
    #不换行的原因是我们在写记事本的时候人为增加了换行\n
    print(i," ",line,end="")
    i += 1
fd.close()

最后是第三个小程序,删除记事文件中不要的行:

import sys
import common

if len(sys.argv) != 2:
    print("参数错误,程序退出!")
    exit(1)
lineToDelete = int(sys.argv[1])

i = j = 0

fd=open(filename,"r")
lines = fd.readlines()
fd.close

fd=open(filename,"w")
for line in lines:
    if i != lineToDelete:
        print(j," ",line,end="")
        fd.write(line)
        j += 1
    i += 1
fd.close()

我们来看看程序怎么使用的:

#下面代码块用来演示如何在命令行使用这3个小程序:

#首先记录一行记事
d:\dev> python3 dailyWrite.py 从零开始Python3
#再记录一行
d:\dev> python3 dailyWrite.py 寓乐湾教育

#显示记事本内容,注意显示的内容被编号了
d:\dev> python3 dailyRead.py 
0   从零开始Python3
1   寓乐湾教育

#再加入两行记事
d:\dev> python3 dailyWrite.py 一行垃圾文字
d:\dev> python3 dailyWrite.py Python3棒极了
#再显示一次,有4行内容了
d:\dev> python3 dailyRead.py 
0   从零开始Python3
1   寓乐湾教育
2   一行垃圾文字
3   Python3棒极了
#删除第2行内容,删除后会显示删除后的内容
d:\dev> python3 dailyDelete.py 2
0   从零开始Python3
1   寓乐湾教育
2   Python3棒极了

练习时间

  1. 前面删除记事内容的第3个小程序中,变量i/j的功能分别是什么?
  2. 请为删除记事内容的小程序增加详细注释,除空行外每行都要加注释。

本讲小结

  • 文件操作是一个软件的基本操作,用处非常多
  • 文件有多种多样的格式,比如音乐、视频、照片、文本
  • 程序文件是文本文件,也就是由文字、字符组成的文件,我们的样例“笔记本”程序所记录的文件也是文本文件。
  • 文件的操作要小心,以免破坏掉有用的文件
  • 扩展库(或:扩展程序库)是Python扩展功能的主要形式,Python有世界上各个公司、组织发布的海量扩展库资源,在所有的语言中是最多的,Python也因此被称为“胶水语言”,意思是把扩展库的功能粘合在一起
  • 编程,重要的是由思路。大项目拆成小项目,逐层细化。在这个过程中,我们原来介绍了用函数化来管理这些分拆的每一个部分。今天又学到了程序库,用不同的程序库来分类一组相应的函数或者变量

练习答案

变量i用于对应记事本文件中所有的行,包括将要删除的行。
变量j用于对应删除行之后的文件中所有行。

注释部分略。