将参数传递给decontext装饰器

时间:2014-08-07 19:48:34

标签: python decorator contextmanager

我有一个帮助类Decontext,我用它来将上下文管理器变成装饰器(pyton 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类在*args函数执行时传递context_manager中的所有__call__

示例:

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 个答案:

答案 0 :(得分:2)

您遇到的问题是,您在_cm方法中设置的__init__属性实际上并不存储上下文管理器实例,而是上下文管理器的类型(或者可能是生成上下文管理器实例的工厂函数)。您需要稍后调用类型或工厂来获取实例。

尝试这个,它应该适用于两个上下文管理器实例(假设它们不可调用)和需要参数的上下文管理器类型:

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

为了简化这种情况下的代码,我总是假设您要在上下文管理器工厂中传递,而不是传递给上下文管理器实例。

答案 1 :(得分:1)

认为以下是您正在寻找的内容。主要的关键是你不能直接将你想要的上下文管理器参数传递给__call__类的Decontext方法,所以我们使用辅助函数来做到这一点。可能有一种方法可以简化这一点(但我会将其作为练习留给读者:))

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

答案 2 :(得分:0)

如果我理解正确,你应该这样做:

@my_deco
def func(arg1):
   print "blah"

装饰者必须装饰一些东西(比如一个函数)。但是,您的班级还有其他一些问题,但我不确定如何修复它们,因为它有点难以理解它应该做什么。