美文网首页Head First Python
《Head First Python》Ch6:存储和管理数据

《Head First Python》Ch6:存储和管理数据

作者: 老A不加V | 来源:发表于2021-02-22 20:56 被阅读0次

    1、对文件的基本操作

    Python提供了内置支持来实现文本文件的打开、处理和关闭。

    ①、文件的打开

    利用函数open,该函数有多种模式,但是主要功能是打开一个文件,并返回一个“流”。流可以认为是文件的一个别名,流的内容就是文件的内容。open函数是python的内置函数。

    open函数有两个参数,第一个参数是要打开的文件名,第二个参数是可选的。如果不选择使用第二参数,那么第二参数默认为'r'。

    第二参数有以下几个值:

    r:读数据,假设文件已经存在;

    w:写数据,如果文件已经含有数据,那就全删了再写,注意open函数本身不实现写这一功能,它只是告诉计算机我要对该文件实现写这一操作;

    a:追加数据,保留文件原有内容,向末尾加入新数据;

    x:打开一个新文件来写数据,若文件已存在则失败。

    ②、文件的处理

    主要是文件的写入和读取。写入使用print函数,读取使用read、readline、readlines函数。read类函数是文件流这个类特有的函数。

    print函数在写入文件时,需要两个参数:第一个是写入内容,第二个是要写入的文件名,以file=name形式输入。

    注意,只有在open的参数为w、a或x的情况下才可以写数据,若参数为r或无参数,会报错如下:

    image

    not writable,说明不可写。

    上面也说明了,若使用print显示文件流,会是什么样子,它会显示文件的属性,而不是文件的内容。

    read函数会一次性输出文件的所有内容。它要求open的参数为r或无参数,否则报错如下:

    image

    not readable,说明不可读。

    read调用后的效果如下:

    image

    可以看出,输出了所有内容。

    readline调用后的效果如下:

    image

    可以看出,仅输出了第一行。

    readlines调用后的效果如下:

    image

    乍看上去,和read的效果差不多,但是readlines是返回一个列表,列表元素是字符串,字符串内容是每一行的文本;而read则直接返回一个长字符串,每行之间的文本用换行符隔开。简而言之,就是返回类型不同。

    除了这三个之外,我们还可以用print来显示内容,但不能直接输出文件流,而是要利用循环,如下:

    image

    对文件流进行循环输出,每次输出一行。注意print默认换行,文件中每行末尾也有换行符,因此会出现两行之间隔一行的情况。为了消除这个换行符,可以在print中加入第二个参数end,效果如下:

    image

    默认end为'\n',现在将end改为空,就不会输出多余的换行了。

    ③、文件的关闭

    使用close函数。close函数是文件流这个类特有的函数。

    一定注意,在对文件修改处理之后使用close函数关闭文件。每一个open一定有一个close对应。不使用close函数很可能导致文件内容的丢失。

    ④、with语句

    由以上可知,每个open必须对应一个close,太麻烦了,可以使用with语句简化。

    with的主要作用有两个:第一是代替open给出一个文件流,第二个是在with下面的代码段运行完毕之后执行close函数。

    也就是说,with与for、if等类似,也需要在后面加一个冒号:,也有代码段。

    with的应用举例如下:

    image

    因此,使用with就不用去管close了。with符合python内置的一个编码约定,即“上下文管理协议”。

    2、练习

    为之前的web应用增加一个功能:记录日志。

    简而言之,就是实现以下功能:记录所有该网页的输入与输出并保存。

    考虑一下,输入是保存在flask返回的一个对象中的,输出就是search4letters的结果,那么代码应该如下:

    def log_request(req:'flask_request',res:str)->None:     
       with open('vsearch.log','a') as log: 
           print(req,res,file=log)
    

    好,定义了一个函数log_request,输入有两个,一个是req,一个是res,按注解来看,req的类型是“flask的返回值”,res的类型是字符串,而输出为none。

    这样一来,就把网页的输入和输出记录下来的。

    下面来看实际效果。

    在网页中输入三个测试用例后,后台生成了名为vsearch.log的文件,该文件位于py文件的同一文件夹。内容如下:

    <Request 'http://127.0.0.1:5000/search4' [POST]> {'i', 'e'}<Request 'http://127.0.0.1:5000/search4' [POST]> {'i', 'a', 'u', 'e'}<Request 'http://127.0.0.1:5000/search4' [POST]> {'x', 'y'}
    

    好像并不十分理想。是不是因为我们是直接打开该文件导致的呢?

    为解决这一问题,要写一个读取函数实现在浏览器上读取日志文件的功能。

    回忆一下,使用读取功能,要用open,参数应该是r或默认,使用read函数读取内容。

    好,那么读取函数就可以写出来了。为其分配一个url为viewlog。如下:

    @app.route('/viewlog')
    def view_the_log()->str:   
      with open('vsearch.log') as log:#with 不具有循环功能         
         contents=log.read()    
      return contents
    

    然后进入viewlog网页,其显示效果如下:

    image

    得了,显示的信息还没之前多,只显示了输出,输入怎么不显示呢?

    看来我们的推断一定程度上时正确的——由于直接打开日志文件导致显示出现了不同,但是对的不多,因为使用浏览器打开的效果更差了。为什么呢?去看网页接受的原始数据吧。也就是右键——查看源代码。效果如下:

    image

    看起来和源文件中的一样,只是尖括号里面的内容被高亮成了红色。这说明并不是我们的打开方式有问题,而是这内容本身有问题。问题出在哪呢?

    首先,这高亮的红色看起来就不善。应该是有错。注意,在源代码中,浏览器会将<>中的内容当做一个html标签,回忆一下之前写的网页,的确是这样。然而,这一长串Request http什么的,它就不是一个标签,浏览器不认识,于是高亮显示。实际上我们也很无辜,它默认是用<>来标识返回值的,要和浏览器消除这个误会。也就是说,告诉浏览器,这个<>里面不是标签,而是我们要的内容。Flask有函数可以做到这一点。这被称作转义,我们都接触过,在最开始写c的时候,在print中输出%,不能只打一个%,而是要打两个%%,否则会出错,这就是转义。

    转义可以看做是一种脱敏过程。使用的函数是escape,其效果是把敏感的字符全换成另外一种字符。我们在得到contents之后,对其使用escape,效果如下:

    <Request 'http://127.0.0.1:5000/search4' [POST]> {'i', 'e'} <Request 'http://127.0.0.1:5000/search4' [POST]> {'i', 'a', 'u', 'e'} <Request 'http://127.0.0.1:5000/search4' [POST]> {'x', 'y'}
    

    可以看到,<>中的内容正确显示出来,但还是没什么用,这不是我们要的输入啊。

    源代码如下:

    &lt;Request &#39;http://127.0.0.1:5000/search4&#39; [POST]&gt; {&#39;i&#39;, &#39;e&#39;}&lt;Request &#39;http://127.0.0.1:5000/search4&#39; [POST]&gt; {&#39;i&#39;, &#39;a&#39;, &#39;u&#39;, &#39;e&#39;}&lt;Request &#39;http://127.0.0.1:5000/search4&#39; [POST]&gt; {&#39;x&#39;, &#39;y&#39;}
    

    可以看到,所有的<>{}都被转义了,说明escape正常工作。那么问题就出在内容里了:我们记录的输入有问题。

    回顾上文,我们记录的输入是Flask的返回值(应该是返回的对象),实际上,这就是“在对象层次上记录web请求”,而并没有深入到请求内部,只是把这个请求的名字记录下来了,但这没什么用。要想让它有用,要调用另外一个函数,dir。

    dir函数的参数是一个对象,会返回这个对象的属性、方法列表。也就是把这个对象的内容显示出来,试一试在日志文件中写入返回对象的dir吧。注意,dir返回的不是一个字符串,要用str函数转成字符串,代码如下:

    def log_request(req:'flask_request',res:str)->None:
        with open('vsearch.log','a') as log:        
          print(str(dir(req)),res,file=log)
    

    其显示效果如下(注意重启app并重新进行三次输入):

    <Request 'http://127.0.0.1:5000/search4' [POST]> {'i', 'e'} <Request 'http://127.0.0.1:5000/search4' [POST]> {'i', 'a', 'u', 'e'} <Request 'http://127.0.0.1:5000/search4' [POST]> {'x', 'y'} ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_cached_json', '_get_data_for_json', '_get_file_stream', '_get_stream_for_parsing', '_load_form_data', '_parse_content_type', 'accept_charsets', 'accept_encodings', 'accept_languages', 'accept_mimetypes', 'access_route', 'application', 'args', 'authorization', 'base_url', 'blueprint', 'cache_control', 'charset', 'close', 'content_encoding', 'content_length', 'content_md5', 'content_type', 'cookies', 'data', 'date', 'dict_storage_class', 'disable_data_descriptor', 'encoding_errors', 'endpoint', 'environ', 'files', 'form', 'form_data_parser_class', 'from_values', 'full_path', 'get_data', 'get_json', 'headers', 'host', 'host_url', 'if_match', 'if_modified_since', 'if_none_match', 'if_range', 'if_unmodified_since', 'input_stream', 'is_json', 'is_multiprocess', 'is_multithread', 'is_run_once', 'is_secure', 'is_xhr', 'json', 'list_storage_class', 'make_form_data_parser', 'max_content_length', 'max_form_memory_size', 'max_forwards', 'method', 'mimetype', 'mimetype_params', 'on_json_loading_failed', 'parameter_storage_class', 'path', 'pragma', 'query_string', 'range', 'referrer', 'remote_addr', 'remote_user', 'routing_exception', 'scheme', 'script_root', 'shallow', 'stream', 'trusted_hosts', 'url', 'url_charset', 'url_root', 'url_rule', 'user_agent', 'values', 'view_args', 'want_form_data_parsed'] {'i', 'e'} ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_cached_json', '_get_data_for_json', '_get_file_stream', '_get_stream_for_parsing', '_load_form_data', '_parse_content_type', 'accept_charsets', 'accept_encodings', 'accept_languages', 'accept_mimetypes', 'access_route', 'application', 'args', 'authorization', 'base_url', 'blueprint', 'cache_control', 'charset', 'close', 'content_encoding', 'content_length', 'content_md5', 'content_type', 'cookies', 'data', 'date', 'dict_storage_class', 'disable_data_descriptor', 'encoding_errors', 'endpoint', 'environ', 'files', 'form', 'form_data_parser_class', 'from_values', 'full_path', 'get_data', 'get_json', 'headers', 'host', 'host_url', 'if_match', 'if_modified_since', 'if_none_match', 'if_range', 'if_unmodified_since', 'input_stream', 'is_json', 'is_multiprocess', 'is_multithread', 'is_run_once', 'is_secure', 'is_xhr', 'json', 'list_storage_class', 'make_form_data_parser', 'max_content_length', 'max_form_memory_size', 'max_forwards', 'method', 'mimetype', 'mimetype_params', 'on_json_loading_failed', 'parameter_storage_class', 'path', 'pragma', 'query_string', 'range', 'referrer', 'remote_addr', 'remote_user', 'routing_exception', 'scheme', 'script_root', 'shallow', 'stream', 'trusted_hosts', 'url', 'url_charset', 'url_root', 'url_rule', 'user_agent', 'values', 'view_args', 'want_form_data_parsed'] {'u', 'i', 'a', 'e'} ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_cached_json', '_get_data_for_json', '_get_file_stream', '_get_stream_for_parsing', '_load_form_data', '_parse_content_type', 'accept_charsets', 'accept_encodings', 'accept_languages', 'accept_mimetypes', 'access_route', 'application', 'args', 'authorization', 'base_url', 'blueprint', 'cache_control', 'charset', 'close', 'content_encoding', 'content_length', 'content_md5', 'content_type', 'cookies', 'data', 'date', 'dict_storage_class', 'disable_data_descriptor', 'encoding_errors', 'endpoint', 'environ', 'files', 'form', 'form_data_parser_class', 'from_values', 'full_path', 'get_data', 'get_json', 'headers', 'host', 'host_url', 'if_match', 'if_modified_since', 'if_none_match', 'if_range', 'if_unmodified_since', 'input_stream', 'is_json', 'is_multiprocess', 'is_multithread', 'is_run_once', 'is_secure', 'is_xhr', 'json', 'list_storage_class', 'make_form_data_parser', 'max_content_length', 'max_form_memory_size', 'max_forwards', 'method', 'mimetype', 'mimetype_params', 'on_json_loading_failed', 'parameter_storage_class', 'path', 'pragma', 'query_string', 'range', 'referrer', 'remote_addr', 'remote_user', 'routing_exception', 'scheme', 'script_root', 'shallow', 'stream', 'trusted_hosts', 'url', 'url_charset', 'url_root', 'url_rule', 'user_agent', 'values', 'view_args', 'want_form_data_parsed'] {'y', 'x'}
    

    额,效果看上去不怎么好。。。

    这是因为dir会把对象的所有属性和方法都列出来,但实际上,我们仅需要其中某一个属性,不需要所有的。

    通过筛选,我们选出了三个需要的属性:

    req.form,req.remote_addr,req.user_agent;

    分别对应从web应用的html表单提交的数据,也就是输入;运行webapp的浏览器的IP地址;以及所使用的浏览器的标识。为了一次性写入这三项数据,使用print如下:

    def log_request(req:'flask_request',res:str)->None: 
       with open('vsearch.log','a') as log: 
           print(req.form,req.remote_addr,req.user_agent,res,file=log,sep='|')
    

    注意最后的sep,这是分隔符,即使用|分隔各项数据,效果如下:

    ImmutableMultiDict([('phrase', 'hitch-hiker'), ('letters', 'aeiou')])|127.0.0.1|Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36|{'i', 'e'} ImmutableMultiDict([('phrase', 'life,the universe,and everything'), ('letters', 'aeiou')])|127.0.0.1|Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36|{'i', 'u', 'e', 'a'} ImmutableMultiDict([('phrase', 'galaxy'), ('letters', 'xyz')])|127.0.0.1|Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36|{'x', 'y'}
    

    有点乱,尽管使用了|作为分隔符,但还是难以看出各部分的内容。如果能够将其以一个表的形式显示就好了。

    现在来看一下,我们有什么?有一个字符串,其中保存了各项数据,各项数据之间用|分隔开。如果我们能有一种方法,把两个|中间的内容当做一个元素,建立一个二维数组保存这些元素就好了。

    有这样一个函数,split,它是字符串这一对象的一个方法。它的输入是一个字符,即所谓的分隔符,而输出则是一个列表,将字符串按分隔符分成多份,保存在一个字符串列表中返回。

    有了这个函数就很简单了,对于日志中的每一行,调用split,返回列表,然后将这个列表保存为一个二维列表的一项即可。

    代码如下:

    @app.route('/viewlog')
    def view_the_log()->str:    
       contents=[]    
       with open('vsearch.log') as log:#with 不具有循环功能         
           for line in log:            
               contents.append([])            
               for item in line.split('|'):                 
                  contents[-1].append(escape(item))   
       return str(contents)
    

    观察这个函数,在调用该函数时,首先建立一个空列表contents,然后打开日志文件,按行读取,对于每一行的内容line,在contents后新建一个子列表,然后对line调用split,将line的内容按|分隔为一个个元素,返回一个列表,循环遍历该列表,将列表的每一项脱敏,利用append加到contents的最后一项上。

    为了看清这个二维列表是如何建立的,要注意append的两次调用。第一次是在二维列表中添加一行空行,第二次是在这个空行后添加一项元素。

    其效果如下:

    [[Markup('ImmutableMultiDict([('phrase', 'galaxy'), ('letters', 'xyz')])'), Markup('127.0.0.1'), Markup('Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'), Markup('{'y', 'x'}\n')], [Markup('ImmutableMultiDict([('phrase', 'life,the universe,and everything'), ('letters', 'aeiou')])'), Markup('127.0.0.1'), Markup('Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'), Markup('{'e', 'a', 'u', 'i'}\n')], [Markup('ImmutableMultiDict([('phrase', 'hitch-hiker'), ('letters', 'aeiou')])'), Markup('127.0.0.1'), Markup('Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'), Markup('{'e', 'i'}\n')]]
    

    可以看出,比原来多了几个小括号。这说明它已经成了一个二维列表了。但是看起来还不是很容易。

    为了提高可读性,我们要写一个网页来正确显示这个二维列表。html提供了一组标记来定义我们这样的二维表格:

    <table>:一个表格、<th>:一个表格的列标题、<td>:一个表格数据、<tr>:一行表格数据。

    我们的重点并不是学习html,因此直接给出成品代码:

    {% extends 'base.html' %}
    {% block body %} 
    <h2>{{ the_title }}</h2>
    <table> 
        <tr>        
           {% for row_title in the_row_titles %}                    
               <th>{{row_title}}</th>       
           {% endfor %} 
        </tr>       
        {% for log_row in the_data %}   
           <tr>             
                 {% for item in log_row %}
              <td>{{item}}</td>             
                 {% endfor %}           
           </tr>        
       {% endfor %}
    </table> 
    {% endblock %}
    

    但是可以解释一下上述代码。

    首先,我们要把表格元素放在<table>标记中。

    然后,初始化第一行。使用<tr>标记表明这是一行数据。使用for开始一个循环。使用<th>标记表明这行数据都是列标题。

    接着,循环读取日志文件的每一行。在循环体内,使用<tr>标记表明这是一行数据。继续一个循环,读取每一行中的每一项,使用<td>标记表明这是一个数据项。

    最后结束各循环并完成表格。

    观察上述代码,有以下几个参数:

    the_title、the_row_titles、the_data,分别对应表的标题、每列的标题、每项的名字。

    因此在app的代码中我们要给该模板传入这些参数。代码如下:

    @app.route('/viewlog')def view_the_log()->str:    
          contents=[]    
          with open('vsearch.log') as log:#with 不具有循环功能        
               for line in log:            
                    contents.append([])            
                    for item in line.split('|'): 
                         contents[-1].append(escape(item))   #return  str(contents)   
          titles=('Form Data','Remote_addr','User_agent','Results')                     
          return render_template('viewlog.html',                           
                                 the_title='View Log',   
                                 the_row_titles=titles, 
                                 the_data=contents,)
    

    可以看出,这三个参数已经传入,有两个是直接赋值的,the_data则是contents。

    其效果如下:

    image

    嗯,还不错。

    相关文章

      网友评论

        本文标题:《Head First Python》Ch6:存储和管理数据

        本文链接:https://www.haomeiwen.com/subject/jrokfltx.html