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或无参数,会报错如下:
not writable,说明不可写。
上面也说明了,若使用print显示文件流,会是什么样子,它会显示文件的属性,而不是文件的内容。
read函数会一次性输出文件的所有内容。它要求open的参数为r或无参数,否则报错如下:
not readable,说明不可读。
read调用后的效果如下:
可以看出,输出了所有内容。
readline调用后的效果如下:
可以看出,仅输出了第一行。
readlines调用后的效果如下:
乍看上去,和read的效果差不多,但是readlines是返回一个列表,列表元素是字符串,字符串内容是每一行的文本;而read则直接返回一个长字符串,每行之间的文本用换行符隔开。简而言之,就是返回类型不同。
除了这三个之外,我们还可以用print来显示内容,但不能直接输出文件流,而是要利用循环,如下:
对文件流进行循环输出,每次输出一行。注意print默认换行,文件中每行末尾也有换行符,因此会出现两行之间隔一行的情况。为了消除这个换行符,可以在print中加入第二个参数end,效果如下:
默认end为'\n',现在将end改为空,就不会输出多余的换行了。
③、文件的关闭
使用close函数。close函数是文件流这个类特有的函数。
一定注意,在对文件修改处理之后使用close函数关闭文件。每一个open一定有一个close对应。不使用close函数很可能导致文件内容的丢失。
④、with语句
由以上可知,每个open必须对应一个close,太麻烦了,可以使用with语句简化。
with的主要作用有两个:第一是代替open给出一个文件流,第二个是在with下面的代码段运行完毕之后执行close函数。
也就是说,with与for、if等类似,也需要在后面加一个冒号:,也有代码段。
with的应用举例如下:
因此,使用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网页,其显示效果如下:
得了,显示的信息还没之前多,只显示了输出,输入怎么不显示呢?
看来我们的推断一定程度上时正确的——由于直接打开日志文件导致显示出现了不同,但是对的不多,因为使用浏览器打开的效果更差了。为什么呢?去看网页接受的原始数据吧。也就是右键——查看源代码。效果如下:
看起来和源文件中的一样,只是尖括号里面的内容被高亮成了红色。这说明并不是我们的打开方式有问题,而是这内容本身有问题。问题出在哪呢?
首先,这高亮的红色看起来就不善。应该是有错。注意,在源代码中,浏览器会将<>中的内容当做一个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'}
可以看到,<>中的内容正确显示出来,但还是没什么用,这不是我们要的输入啊。
源代码如下:
<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'}
可以看到,所有的<>{}都被转义了,说明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。
其效果如下:
嗯,还不错。