我们在开发过程中,经常遇到需要打开文件的状况,有的开发者比较粗心,会直接使用f = open("xxx.xxx")开启文件,这样写在正常情况下没有问题,但是程序遇到了意外状况可能会导致文件损坏,所以我自己没这样写过,而是使用with open("xxx.xxx") as f的上下文管理器形式替代。本文将介绍如何自己定义一个上下文管理器。
上下文管理器就是能够帮助你管理上下文,不管程序进行是否正常,都能够在执行前进行一些前置操作,执行完成或被迫中断后进行一步后置操作。假设现在有这样一个场景,你要模拟每天上班打卡,来到公司后和离开公司前都要打卡,不管今天是正常时间上班还是迟到了,或者上班到一半突然接到电话说媳妇要生了得赶紧去医院,都得打卡。
按照惯例,上代码先
import timeclass CheckIn: def __init__(self, name, office_location): self.name = name self.office_location = office_location self.file = None def __enter__(self): print(f"{self.name} 你好,你已经在{self.office_location} 完成签到打卡") now_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) file = open(f"work-{now_time_str}.txt", "w") self.file = file return file def __exit__(self, exc_type, exc_value, trace_back_info): print(f"{self.name} 你好,你已经在{self.office_location} 完成签退打卡") self.file.close()if __name__ == "__main__": with CheckIn("华哥", "开放办公区") as pub_office: pub_office.write("开放式办公区 签到") now_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) pub_office.write(now_time_str) pub_office.write("\n") time.sleep(1.5) with CheckIn("华哥", "封闭办公区") as pri_office: pri_office.write("封闭式办公区 签到") now_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) pri_office.write(now_time_str) pri_office.write("\n") time.sleep(1.5) pri_office.write("封闭式办公区 签退") now_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) pri_office.write(now_time_str) pub_office.write("开放式办公区 签退") now_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) pub_office.write(now_time_str)
这里我们定义一个类,类中有__enter__和__exit__两个特殊方法,分别实现程序前置和收尾两个操作。我在__enter__中添加了print一个字符串和开启一个文件的操作,这里直接使用open是因为可以保证这个文件开启后能正确关闭,否则像我开头说的那样,不要直接open。return的对象就是你with那行后面as的对象,在这里就是23和30行的pub_office和pri_office,当然你也可以不return东西。在__exit__函数中,我进行了print操作和关闭在__enter__中开启的文件。我们测试一下结果
运行之后,你可以看到出现了两个文件,里面也记录了签到和签退。这里我模拟了你作为公司核心技术人员,需要穿过开放办公区来到封闭办公区域办公,所以有两次打卡。上面的结果是正常运行的状况,但是我们定义上下文管理器是为了处理非常状况,假设有天你在工作,突然老妈给你打电话说媳妇快生了,这时候你把工作扔了赶紧过去,我们在34行下面加一行
raise Exception("媳妇快生了,得赶紧去医院")
看一下是什么效果
可以看到,四个print信息仍然运行正常,但是在work-xxx.txt中的信息则不完整,因为print信息是在__enter__和__exit__中定义的,无论如何都会运行;但是往文件中写入信息是在with块中定义的,只有运行正常时才会完整执行。
如果将
raise Exception("媳妇快生了,得赶紧去医院")
插到27行下面,则只print两行信息,此时你是在刚在开放区打卡完就接到老妈电话,还没来得及进封闭区,所以只有开放区打卡记录。
以上内容是Python比较高级的特性,需要多花点时间消化,如果你读到了这里,那么我将给你讲述一个更加简单的方法定义上下文管理器。以下内容是给像你这样有耐心和有求知精神的人看的。
from contextlib import contextmanager@contextmanagerdef check_in(name, office_location): now_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) file = open(f"work-{now_time_str}.txt", "w") try: print(f"{name} 你好,你已经在{office_location} 完成签到打卡") yield file finally: print(f"{name} 你好,你已经在{office_location} 完成签退打卡") file.close()
这是使用装饰器定义一个上下文管理器,功能和用法和上面的实现完全一致,只需将上面代码中的类名 CheckIn 改成 check_in 即可
if __name__ == "__main__": with check_in("华哥", "开放办公区") as pub_office: pub_office.write("开放式办公区 签到") now_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) pub_office.write(now_time_str) pub_office.write("\n") time.sleep(1.5) with check_in("华哥", "封闭办公区") as pri_office: pri_office.write("封闭式办公区 签到") now_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) pri_office.write(now_time_str) pri_office.write("\n") raise Exception("媳妇快生了,得赶紧去医院") time.sleep(1.5) pri_office.write("封闭式办公区 签退") now_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) pri_office.write(now_time_str) pub_office.write("开放式办公区 签退") now_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) pub_office.write(now_time_str)
装饰器形式的实现同类形式的实现不同在于,装饰器通过yield返回as后面的内容(pub_office和pri_office),同时内部使用的是try,finally结构。装饰器的实现,尽管看起来更加简单易用,也不要用这种写法完全替代类形式的实现,在工业环境中使用上面类形式的实现更加稳妥。