我需要对从包(包是共享对象文件)导入的函数进行多次调用。但是,每次我从这个包中调用函数时,我都需要执行一些预处理/后处理步骤。像这样的东西:
import xyz
prepare()
xyz.foo(<args>)
done()
prepare()
xyz.bar(<args>)
done()
prepare()
xyz.foobar()
done()
有什么方法可以让 python 在从
prepare()
模块调用函数之前始终调用 xyz
。并在通话完成后调用done()
?
在我的整个代码中编写 prepare
和 done
看起来多余且混乱。感谢您的帮助!
这通常是通过上下文管理器完成的。
import contextlib
@contextlib.contextmanager
def preparation():
prepare()
yield
done()
with preparation():
xyz.foo(<args>)
with preparation():
xyz.bar(<args>)
with preparation():
xyz.foobar()
preparation
定义了一个返回上下文管理器的函数。 with
语句的工作原理是调用上下文管理器的 __enter__
方法,然后执行主体,然后确保在继续之前调用上下文管理器的 __exit__
方法(无论是由于引发异常还是主体正常完成) .
contextlib.contextmanager
提供了一种使用生成器函数定义上下文管理器的简单方法,而不是让您使用显式 __enter__
和 __exit__
方法定义类。
您提到特定模块中的每个功能都需要它。如果没有有关模块的确切详细信息,这可能不完全正确,但您也许可以在此基础上进行构建。
class XYZWrapper:
def __getattr__(self, name):
# Intentionally let an AttributeError propagate upwards
f = getattr(xyz, name)
def _(self, *args, **kwargs):
prepare()
return f(*args, **kwargs)
done()
setattr(XYZWrapper, name, _)
return _
prepared = XYZWrapper()
prepared.foo(<args>)
prepared.bar(<args>)
prepared.foobar()
简而言之,对
XYZWrapper
实例的任何属性访问都会尝试在 xyz
模块上查找相同的属性,如果成功,则定义一个包装器,根据需要调用 prepare()
和 done()
并修补 XYZWrapper
带有新包装的实例。
为了扩展 @chepner 的优秀答案,您可以定义自己的类并使用其
__getattr__
函数来创建一个函数,该函数将实际模块的函数与您的预处理和后处理函数包装起来:
import typing
import xyz
def XYZWrapper():
def __init__(self, pre, post):
self.pre = pre
self.post = post
def __getattr__(self, a):
func = getattr(xyz, a)
if isinstance(func, typing.Callable):
def wrapper(*args, **kwargs):
self.pre()
func(*args, **kwargs)
self.post()
return wrapper
raise TypeError(f"'{type(func)}' object is not callable")
要使用它,请执行以下操作
xyz = XYZWrapper(prepare, done)
xyz.foo(<args>)
...
请注意,如果您想覆盖
xyz
变量,则需要将包装类放在单独的文件中。