将参数传递给decontext装饰器
我有一个辅助类 Decontext
,我用它来把上下文管理器变成一个装饰器(Python 2.6)。
class Decontext(object):
"""
makes a context manager also act as decorator
"""
def __init__(self, context_manager):
self._cm = context_manager
def __enter__(self):
return self._cm.__enter__()
def __exit__(self, *args, **kwds):
return self._cm.__exit__(*args, **kwds)
def __call__(self, func):
def wrapper(*args, **kwds):
with self:
return func(*args, **kwds)
return wrapper
我的 contextmanager
需要一个参数,我正在想办法在使用这个装饰器的时候怎么传递这个参数?
@contextmanager
def sample_t(arg1):
print "<%s>" % arg1
yield
这是我使用它的方式,但失败了:
my_deco = Decontext(sample_t)
@my_deco(arg1='example')
def some_func()
print 'works'
编辑:
我希望 Decontext
类在 __call__
函数执行时能把所有的 *args
传递给上下文管理器。
举个例子:
decorator_example = Decontext(sample_t) // I don't want to pass in the argument here but when the decorator is created. How can I modify my class to make this enhancement
编辑 2:
这是我预期的结果
my_deco = Decontext(sample_t)
@my_deco(arg1='example')
def some_func()
print 'works'
预期输出:
'example' // running and passing argument to context_manager
'works' // after yield executing some_func
3 个回答
如果我理解得没错,你应该这样做:
@my_deco
def func(arg1):
print "blah"
装饰器需要装饰一些东西(比如一个函数)。不过,你的类还有其他一些问题,我不太确定该怎么修复,因为有点难理解它到底想要实现什么。
我觉得下面的内容是你想要的。关键在于,你不能直接把上下文管理器的参数传给你Decontext
类里的__call__
方法,所以我们用一个辅助函数来处理这个问题。可能还有更简单的方法(不过我就留给你自己去探索了 :))
from contextlib import contextmanager
from functools import partial
class _Decontext(object):
"""
makes a context manager also act as decorator
"""
def __init__(self, cm, *args, **kwargs):
self._cm = cm
self.args = args
self.kwargs = kwargs
def __call__(self, func):
def wrapper(*args, **kwds):
with self._cm(*self.args, **self.kwargs):
func(*args, **kwds)
return wrapper
# helper for our helper :)
def decontext(cm=None):
def wrapper(cm, *args, **kwargs):
return _Decontext(cm, *args, **kwargs)
return partial(wrapper, cm)
@contextmanager
def sample_t(arg1):
print "<%s>" % arg1
yield
my_deco = decontext(sample_t)
@my_deco(arg1='example')
def some_func():
print 'works'
if __name__ == '__main__':
some_func()
这个输出结果是:
<example>
works
你遇到的问题是,你在 __init__
方法中设置的 _cm
属性,实际上并不是存储一个上下文管理器的实例,而是存储了上下文管理器的类型(或者可能是一个能生成上下文管理器实例的工厂函数)。你需要在后面调用这个类型或工厂,才能得到一个实例。
试试这个方法,它应该能同时适用于上下文管理器实例(假设它们不是可调用的)和需要参数的上下文管理器类型:
class Decontext(object):
"""
makes a context manager also act as decorator
"""
def __init__(self, context_manager):
self._cm = context_manager # this may be a cm factory or type, but that's OK
def __enter__(self):
return self._cm.__enter__()
def __exit__(self, *args, **kwds):
return self._cm.__exit__(*args, **kwds)
def __call__(self, *cm_args, **cm_kwargs):
try:
self._cm = self._cm(*cm_args, **cm_kwargs) # try calling the cm like a type
except TypeError:
pass
def decorator(func):
def wrapper(*args, **kwds):
with self:
return func(*args, **kwds)
return wrapper
return decorator
这里的嵌套层次有点复杂,但这是你想要调用的方式所需要的。下面是一个实际应用的例子:
from contextlib import contextmanager
@contextmanager
def foo(arg):
print("entered foo({!r})".format(arg))
yield
print("exited foo({!r})".format(arg))
foo_deco_factory = Decontext(foo)
@foo_deco_factory("bar")
def baz(arg):
print("called baz({!r})".format(arg))
baz("quux")
它会输出:
entered foo("bar")
called baz("quux")
exited foo("bar")
注意,尝试将 foo_deco_factory
当作上下文管理器使用是行不通的(就像使用 with foo
也不行)。只有当 Decontext
实例是用上下文管理器实例初始化的(而不是类型或工厂),或者它已经作为装饰器被调用并传入了适当的参数时,使用上下文管理器协议才会有效。
如果你不需要装饰器本身也能作为上下文管理器使用,你可以很简单地把整个类改成一个函数(这样 __call__
就变成了一个额外的闭包层,而不是一个方法):
def decontext(cm_factory):
def factory(*cm_args, **cm_kwargs):
cm = cm_factory(*cm_args, **cm_kwargs)
def decorator(func):
def wrapper(*args, **kwargs):
with cm:
return func(*args, **kwargs)
return wrapper
return decorator
return factory
为了简化代码,我总是假设你传入的是一个上下文管理器工厂,而不是上下文管理器实例。