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

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

嗯,还不错。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容