将参数传递给decontext装饰器

1 投票
3 回答
1818 浏览
提问于 2025-04-18 16:27

我有一个辅助类 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 个回答

0

如果我理解得没错,你应该这样做:

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

装饰器需要装饰一些东西(比如一个函数)。不过,你的类还有其他一些问题,我不太确定该怎么修复,因为有点难理解它到底想要实现什么。

1

觉得下面的内容是你想要的。关键在于,你不能直接把上下文管理器的参数传给你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
2

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

为了简化代码,我总是假设你传入的是一个上下文管理器工厂,而不是上下文管理器实例。

撰写回答