Contents
9.3. 使用with语句读写文件¶
9.3.1. 1.with语句介绍¶
with语句为上下文管理器,enter 和 __exit__两个方法实现,使用with操作文件会自动关闭文件句柄,无需额外进行file.close()
9.3.2. 2.with工作原理¶
紧跟with后面的语句被求值后,返回对象的“__enter__()”方法被调用,这个方法的返回值将被赋值给as后面的变量;
当with后面的代码块全部被执行完之后,将调用前面返回对象的“__exit__()”方法。
代码演示¶
class Sample:
def __enter__(self):
print("in __enter__")
return "Foo"
def __exit__(self, exc_type, exc_val, exc_tb):
print("in __exit__")
def get_sample():
return Sample()
with get_sample() as sample:
print("Sample: ", sample)
整个运行过程如下:¶
__enter__()方法被执行;
__enter__()方法返回的值,在这个例子中是”Foo”,赋值给变量sample;
执行代码块,打印sample变量的值为”Foo”;
__exit__()方法被调用;
9.3.3. 3.用于替代finally语句清理资源¶
在编写try语句时,finally关键字经常用来做一些资源清理类工作,比如关闭已创建的网络连接:
可以用上下文管理器来替代finally语句。做起来很简单,只要在__exit__里增加需要的回收语句即可:
conn = create_conn(host, port, timeout=None)
try:
conn.send_text('Hello, world!')
except Exception as e:
print(f'Unable to use connection: {e}')
finally:
conn.close()
class create_conn_obj:
"""创建连接对象,并在退出上下文时自动关闭"""
def __init__(self, host, port, timeout=None):
self.conn = create_conn(host, port, timeout=timeout)
def __enter__(self):
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
# 退出管理器时关闭连接
self.conn.close()
return False
with create_conn_obj(host, port, timeout=None) as conn:
try:
conn.send_text('Hello, world!')
except Exception as e:
print(f'Unable to use connection: {e}')
9.3.4. 4.上下文管理器与with语句¶
4.1 使用contextmanager装饰器¶
这个类对现有列表进行一系列的修改,但这些修改只有在没发生异常时才会生效,发生了异常,原始列表将保持不变。
class ListTransaction(object):
def __init__(self, thelist):
self.thelist = thelist
def __enter__(self):
self.workingcopy = self.thelist
return self.workingcopy
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
self.thelist[:] = self.workingcopy
return False
items = [1, 2, 3]
with ListTransaction(items) as working:
working.append(4)
working.append(5)
print(items)
try:
with ListTransaction(items) as working:
working.append(6)
working.append(7)
raise RuntimeError("We're hosed!")
except RuntimeError:
pass
print(items)
通过包装生成器函数,contextlib模块,可以更加容易实现自定义上下文管理器。例如:
#!/usr/bin/env python
from contextlib import contextmanager
@contextmanager
def ListTransaction(thelist):
print("before..........")
workingcopy = list(thelist)
yield workingcopy
# 仅在没有出现错误时才会修改原始列表
thelist[:] = workingcopy
print(workingcopy)
print("after...........")
mylist = [1, 2, 3]
with ListTransaction(mylist) as working:
working.append(4)
working.append(5)
working.append(6)
@contextmanager位于内置模块contextlib下,它可以把任何一个生成器函数直接转换为一个上下文管理器。
举个例子,我在前面实现的自动关闭连接的create_conn_obj上下文管理器,假如用函数来改写,可以简化成下面这样:
from contextlib import contextmanager
@contextmanager
def create_conn_obj(host, port, timeout=None):
"""创建连接对象,并在退出上下文时自动关闭"""
conn = create_conn(host, port, timeout=timeout)
try:
yield conn
finally:
conn.close()
4.2 使用with处理文件打开¶
##不推荐
f = open("some_file.txt")
try:
data = f.read()
# 其他文件操作..
finally:
f.close()
##推荐
with open("some_file.txt") as f:
data = f.read()
# 其他文件操作..
4.3 使用with忽视异常(仅限Python 3)¶
##不推荐
try:
os.remove("somefile.txt")
except OSError:
pass
##推荐
from contextlib import ignored # Python 3 only
with ignored(OSError):
os.remove("somefile.txt")
class ignore_closed:
"""忽略已经关闭的连接"""
def __enter__(self):
pass
def __exit__(self, exc_type, exc_value, traceback):
if exc_type == AlreadyClosedError:
return True
return False
with ignore_closed():
close_conn(conn)
4.4 同时打开多个文件¶
with open('log1') as obj1, open('log2') as obj2:
pass
4.5 使用with读取文件¶
#!/usr/bin/env python
# -*- coding:utf8 -*-
# auther; 18793
# Date:2019/6/28 12:51
# filename: 处理文件中数据.py
def file_hdl(name="test_num.txt"):
res = 0 # 累加计数器
i = 0 # 行数计数器
with open(name,encoding="utf8") as f:
# with打开文件后会自动关闭,上下文管理器
for line in f:
i += 1
print("第{}行的数据为:{}".format(i, line.strip()))
res += int(line)
print("{}文件中数的和为{}".format(name, res))
file_hdl()
输出信息
第1行的数据为:1
第2行的数据为:2
第3行的数据为:3
第4行的数据为:4
第5行的数据为:5
第6行的数据为:6
第7行的数据为:7
第8行的数据为:8
第9行的数据为:99
第10行的数据为:88
第11行的数据为:77
第12行的数据为:66
第13行的数据为:55
第14行的数据为:44
test_num.txt文件中数的和为465
4.6 同时开启2个文件修改后改名¶
代码示例
#!/usr/bin/env python
#-*- coding:utf8 -*-
import os
src_txt = "a.txt"
dst_txt = "a_bak.txt"
with open(src_txt,"w") as f:
f.write("花儿呀。\n"
"花儿呀")
with open(src_txt) as fr,open(dst_txt,"w") as fw:
for line in fr:
lines = line.replace("花","flower")
fw.write(lines)
os.remove(src_txt)
os.rename(dst_txt,src_txt)
4.7 模拟copy文件操作¶
#!/usr/bin/env python
# -*- coding:utf8 -*-
# auther; 18793
# Date:2019/5/21 18:01
# filename: 同时对文件进行读写.py
# 模拟一个复制文件的操作
with open("foo_bak.txt", "r", encoding="utf-8") as file_read:
lines = file_read.readlines()
print(lines)
copy_file = "foo_bak_01.txt"
with open(copy_file, "w", encoding="utf-8") as file_write:
file_write.writelines(lines)
print("文件复制成功")
4.8 模拟copy二进制(图片、音影)¶
#!/usr/bin/env python
# -*- coding:utf8 -*-
# auther; 18793
# Date:2019/5/21 18:01
# filename: 同时对文件进行读写.py
f_name = "a.jpg"
# 模拟一个复制文件的操作
with open(f_name, "rb", encoding="utf-8") as file_read:
lines = file_read.readlines()
print(lines)
copy_file = "copy.jpg"
with open(copy_file, "wb", encoding="utf-8") as file_write:
file_write.writelines(lines)
print("文件复制成功")
4.9 定义一个类似open函数的上下文管理器¶
代码示例
#!/usr/bin/env python
#-*- coding:utf8 -*-
import os
import shutil
file_info = "hujianli.py"
write_info='''#!/usr/bin/env python
#-*- coding:utf8 -*-
print("test")
print()
'''
def create_file(file):
if not os.path.exists(file):
with open(file,"w") as f:
f.write(write_info)
else:
number = 1
Flag = True
while Flag:
file_info_name = file.split('.')
file_name = file_info_name[0] + "_bak" + str(number) +"."+file_info_name[1]
if not os.path.exists(file_name):
with open(file) as f1,open(file_name,"w") as f2:
f2.write(f1.read())
Flag = False
number +=1
class FileMgr:
'自定义一个打开文件,后自动关闭文件的上下文管理器'
def __init__(self,filename):
self.filename = filename
self.f = None
def __enter__(self):
self.f = open(self.filename,encoding='utf-8')
return self.f
def __exit__(self, exc_type, exc_val, exc_tb):
if self.f:
self.f.close()
if __name__ == "__main__":
create_file(file_info)
with FileMgr(file_info) as f:
for line in f.readlines():
print(line,end='')
9.3.5. 5.文件操作,读写实现用户注册登录¶
#!/usr/bin/env python
# -*- coding:utf8 -*-
# auther; 18793
# Date:2019/11/10 10:26
# filename: 用户注册登录.py
class UserInfo:
def __init__(self):
self.__username = None
self.__password = None
def __str__(self):
return self.__username
def login(self):
"""
用户登录
:return:
"""
print("--------------------欢迎来到登录界面---------------------")
self.__username = input("请输入登录的用户名:")
self.__password = input("请输入登录的密码:")
with open("user.txt", encoding="utf-8") as rs:
info = rs.readlines()
for i in info:
user = i.replace("\n", "")
u_p = user.split()
if self.__username == u_p[0] and self.__password == u_p[1]:
print("用户登录成功")
return False
else:
print("用户登录失败")
return True
def regiested(self):
"""
用户注册
:return:
"""
print("---------------------欢迎来到用户注册------------------------")
self.__username = input("请输入注册的用户名:")
self.__password = input("请输入注册的密码:")
if self.__username and self.__password:
with open("user.txt", "a", encoding="utf-8") as ws:
ws.write(self.__username + "\t" + self.__password + "\n")
print("注册用户成功!")
else:
print("用户密码不能为空")
def main():
Flag = True
hu = UserInfo()
hu.regiested()
while Flag:
Flag = hu.login()
if __name__ == '__main__':
main()
9.3.6. 6.总结¶
实际上,在with后面的代码块抛出异常时,exit()方法被执行。开发库时,清理资源,关闭文件等操作,都可以放在exit()方法中。
总之,with-as表达式极大的简化了每次写finally的工作,这对代码的优雅性是有极大帮助的。 如果有多项,可以这样写:
with open('1.txt') as f1, open('2.txt') as f2:
#do something