• 从零开始学习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]) 
    #第四讲学过的转义符,
    是换行的意思,我们每写出一条记事独占一行
    fd.write("
    ")
    #关闭文件
    fd.close()
    

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

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

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

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

    import sys
    
    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用于对应删除行之后的文件中所有行。

    注释部分略。

  • 相关阅读:
    【BZOJ1294】围豆豆(SCOI2009)-状压+BFS
    【BZOJ3244】树的计数(NOI2013)-概率期望+数学证明
    【BZOJ4826】影魔(AHOI&HNOI2017)-线段树+离线
    【BZOJ3832】Rally(POI2014)-拓扑排序+最长路+堆
    【BZOJ4556】字符串(TJOI&HEOI2016)-后缀数组+二分+RMQ+主席树
    【BZOJ1029】建筑抢修(JSOI2007)-贪心+堆
    【BZOJ1057】棋盘制作(ZJOI2007)-DP+悬线法
    【BZOJ1025】游戏(SCOI2009)-数论+背包DP
    java日期操作
    list类型for遍历
  • 原文地址:https://www.cnblogs.com/andrewwang/p/10183538.html
Copyright © 2020-2023  润新知