装饰器1-笔记

本文最后更新于:2024年8月11日 晚上

闲来无事打算重构屎山

以前的想法比较混乱,代码风格不佳,而且关注点太多,函数交错林立,这次打算尽可能把注意力放在核心功能的实现上

但是在开发过程中遇到了前所未有的问题,其中一个巨大的问题在于最核心的代码的执行上

在初代中我们使用了==线程投毒==的方式在程序退出后终止Word.Application,虽然算不上优雅但是能保证程序都能退出,同时更加优化之前遇到的问题

之前遇到的主要问题如下:

  • word程序无法退出:因为win32com拉起word程序是异步的, 因此在主程序完成任务后结束了,但word程序没有收到Quit()命令,变成了僵尸进程
  • word超时等待:有的文档或者因为本身有损坏,或者因为word调用触发了某些安全警告或者什么东西,比如Microsoft Word宏安全警告,会导致这个word程序有一个弹窗,显然程序是没法识别弹窗的,因此长时间挂起导致主程序也跟着卡死

围绕上述两个问题,有两个解决方案:

  • 全异步架构:因为异步有超时机制,因此把核心代码、word退出代码异步化,再设置一个主程序进行调用,当发现超时等待时就直接调用协程WordSafeQuit直接杀死word程序,并通知主程序异常
  • 修饰器+异步:这是我实际采用的方式,因为我想进一步的把word安全退出机制分离出来,方便复用,这个想法是把超时等待的解决封装在异步协程中,在把主程序封装给装饰器,由装饰器管理word的实例化和退出

这其中遇到了很多艰难又复杂的问题。

最大的问题在于修饰器,请先看我们最初的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def WordSafeQuit(func):
def foo(*args,**kwargs):
pythoncom.CoInitialize()
word_ = DispatchEx('Word.Application')
try:
result = func(word_,*args,**args)
finally:
word_.Quit()
pythoncom.CoUninitialize()
return result
return foo

class DocCounter():
def __init__(self):
self.doc_lst = ['example.doc']

@WordSafeQuit
async def Main_Process(self,word)
doc = word.Documents.Open(...)
try:
await wait_for(self.CoProcess(doc),timeout=20)
except TimeoutError:
doc.Close()
del doc
async def Coprocess(doc):
return doc.ComputerStatistics(Statistic=0)

首先的第一个问题就是Line6func第一个参数是self,直接使用传参会导致selfword_覆盖,修复的方案就是指定参数,很简,

  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Class_DocSafeQuit(object):
async def __aenter__(self):
pythoncom.CoInitialize()
self.word = DispatchEx("Word.Application")
return self.word

async def __aexit__(self, exc_type, exc_val, exc_tb):
self.word.Quit()
pythoncom.CoUninitialize()


def DocSafeQuit(func):
async def foo(*args, **kwargs):
async with Class_DocSafeQuit() as word_:
result = await func(word=word_, *args, **kwargs)
return result
return foo

装饰器1-笔记
https://qlozin.top/2023/03/30/Python 重构屎山时遇到的装饰器/
作者
QLozin
发布于
2023年3月31日
更新于
2024年8月11日
许可协议