装饰器1-笔记
本文最后更新于:2024年8月11日 晚上
楔
闲来无事打算重构屎山
以前的想法比较混乱,代码风格不佳,而且关注点太多,函数交错林立,这次打算尽可能把注意力放在核心功能的实现上
但是在开发过程中遇到了前所未有的问题,其中一个巨大的问题在于最核心的代码的执行上
在初代中我们使用了==线程投毒==的方式在程序退出后终止Word.Application
,虽然算不上优雅但是能保证程序都能退出,同时更加优化之前遇到的问题
之前遇到的主要问题如下:
- word程序无法退出:因为
win32com
拉起word程序是异步的, 因此在主程序完成任务后结束了,但word程序没有收到Quit()
命令,变成了僵尸进程 - word超时等待:有的文档或者因为本身有损坏,或者因为word调用触发了某些安全警告或者什么东西,比如
Microsoft Word宏安全警告
,会导致这个word程序有一个弹窗,显然程序是没法识别弹窗的,因此长时间挂起导致主程序也跟着卡死
围绕上述两个问题,有两个解决方案:
- 全异步架构:因为异步有超时机制,因此把核心代码、word退出代码异步化,再设置一个主程序进行调用,当发现
超时等待
时就直接调用协程WordSafeQuit
直接杀死word程序,并通知主程序异常 - 修饰器+异步:这是我实际采用的方式,因为我想进一步的把word安全退出机制分离出来,方便复用,这个想法是把超时等待的解决封装在异步协程中,在把主程序封装给装饰器,由装饰器管理word的实例化和退出
这其中遇到了很多艰难又复杂的问题。
最大的问题在于修饰器,请先看我们最初的实现:
1 |
|
首先的第一个问题就是Line6
的func
第一个参数是self
,直接使用传参会导致self
被word_
覆盖,修复的方案就是指定参数,很简,
L6
–>func(word=word_,*args,**kwargs)
接着我们遇到第二个问题,就是报错服务器未连接
,一开始我们不清楚原因,排查了很久,资料也搜不到,
如果我们放弃装饰器,直接在运行时实例化一个word对象传入、或者在函数内部实例化word对象使用,都可以正常运行
问题肯定在修饰器上,如果你删掉finally
后的word_.Quit()
,也能正常运行,问题范围再一次缩小了!
最后我们检查了id(word_)和id(word)
发现二者并不相同,但word对象是class
,不可能传入副本,问题出在哪?
经过不懈的查找资料,了解装饰器的运行原理——还是没发现原因
最后通过打印运行时序perf_counter
惊讶的发现,函数体内的word.Documents.Open
竟然在word_.Quit()
方法执行完后才执行,这™显然会出问题
问题已经缩小到为什么word
会被提前退出
但为什么?
我一开始以为是修饰器机制问题,但实际上不是。
如果你尝试使用上下文管理with
来保持word_
在调用完之前不会退出,这似乎是一个可行的方案,它会报错,但会提供关键的错误信息,最终让我们定位到了问题所在——
我们的方法是异步的,因此foo
在执行时不会等待func
返回,他直接运行到finally
之后func
才运行到Open
,因此上下文管理会报错,这个方法不可等待
这里,答案就呼之欲出了——使用异步的上下文管理器,最终虽然代码没有我想的那么简洁,但也不是不能用:
1 |
|