我需要对从包(包是一个共享对象文件(导入的函数进行多次调用。然而,每次调用这个包中的函数时,我都需要执行一些预处理/后处理步骤。类似这样的东西:
import xyz
prepare()
xyz.foo(<args>)
done()
prepare()
xyz.bar(<args>)
done()
prepare()
xyz.foobar()
done()
在从xyz
模块调用函数之前,有没有办法让python始终调用prepare()
。并且还要在调用完成后调用done()
?在我的整个代码中编写prepare
和done
似乎是多余和混乱的。感谢你的帮助!
这通常是通过上下文管理器完成的。
import contextlib
@contextlib.contextmanager
def with_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
变量,则需要将包装器类放在一个单独的文件中。