将参数传递给 decontext 装饰器

Posted

技术标签:

【中文标题】将参数传递给 decontext 装饰器【英文标题】:Passing arguments to decontext decorator 【发布时间】:2014-10-01 04:40:01 【问题描述】:

我有一个帮助器类 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 类在__call__ 函数执行时传递context_manager 中的所有*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 

【问题讨论】:

你能举例说明你想如何使用它吗?我不清楚你的班级应该做什么。 听起来你只是想要这个:docs.python.org/2/library/contextlib.html#module-contextlib @Gerrat - 如果我定义了我的上下文管理器并尝试将其用作装饰器而不转换为装饰器,我会收到错误 object is not callable 也许你可以展示你的示例函数(例如你的some_func),以及你如何调用它以及你想要什么输出。真的不清楚你到底希望什么。 @Gerrat,不要听起来太混乱,而且我对装饰器和上下文管理器的理解很差,但我正在寻找一种方法来定义一个带参数的上下文管理器,我知道我可以通过with 声明,但如果可能,希望将其改为装饰器,保留传递参数的能力 【参考方案1】:

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

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

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

【讨论】:

如果可能的话,我想通过装饰器将参数传递给上下文管理器sample_t @Warz:您是否看过 this question 等关于如何编写带参数的装饰器?【参考方案2】:

认为您正在寻找以下内容。主要的关键是您不能将所需的上下文管理器参数直接传递给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

【讨论】:

【参考方案3】:

您遇到的问题是您在 __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

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

【讨论】:

就像第二个例子一样,我认为这正是他所要求的(听起来他的助手的唯一目的是将他的参数获取上下文管理器变成一个装饰器)。并不是说这是代码高尔夫,而是他们首先实例化上下文管理器然后使用它的特殊原因,而不是:with cm_factory(*cm_args, **cm_kwargs):? @Gerrat:嗯,我想没有真正的需要提前创建上下文管理器。但是这样做的一个好处是,任何异常(可能是由于无效的cm_args)都会在调用工厂的早期出现,而不仅仅是在稍后调用包装函数时。

以上是关于将参数传递给 decontext 装饰器的主要内容,如果未能解决你的问题,请参考以下文章

将命令行参数传递给调用带有装饰器参数的装饰函数的函数

Django - 将参数传递给 CBV 装饰器的正确方法?

将默认参数传递给python中的装饰器

如何将非硬编码参数传递给 Python 装饰器?

将附加参数传递给角度组件内的方法装饰器

在 Python 中,如何获取带参数传递给装饰器的函数名称?