如何在不使用python 2.7中的类的情况下将上下文管理器作为装饰器?

时间:2016-11-08 00:24:20

标签: python decorator contextmanager

我有一些用以下样式编写的现有代码,它按预期工作,我可以使用chunkUndo作为上下文管理器。

from contextlib import contextmanager
from functools import wraps

@contextmanager
def chunkUndo(mode='simple'):
    print 'mode:',mode
    print 'undo chunk start'
    try:
        yield
    finally:
        print 'undo chunk end'

# this works
with chunkUndo():
    print 'do work here'

但是,由于我有很多已编写的上下文管理器代码,我不想将它们更改为class based contextDecorators。相反,我想为已装饰的chunkUndo添加一个装饰器,使其成为装饰器功能,如:

def makeContextDecorator(f):

    @wraps(f)
    def wrapper(*args, **kw):
        # some code here
    return wrapper

@makeContextDecorator
@contextmanager
def chunkUndo(mode='simple'):
    print 'mode:',mode
    print 'undo chunk start'
    try:
        yield
    finally:
        print 'undo chunk end'

@chunkUndo
def  do_work():
    print 'do work'

do_work()

最终结果是我可以将chunkUndo用作装饰器和上下文管理器,也可以将参数传递给它。我该怎么做?

1 个答案:

答案 0 :(得分:1)

如果您希望chunkUndo既可以作为上下文管理器,也可以作为装饰器将其自身应用于它所装饰的功能,那么您需要使makeContextDecorator功能更加复杂。这是您的两个示例中的第一次尝试:

from functools import wraps

def makeContextDecorator(cm):
    def wrapper(func=None):
        if func is not None:
            @wraps(func)
            def inner(*args, **kwargs):
                with cm():
                    return func(*args, **kwargs)
            return inner
        else:
            return cm()
    return wrapper

以下是它的实际行动:

@makeContextDecorator
@contextlib.contextmanager
def foo():
    print("start")
    try:
        yield
    finally:
        print("end")

with foo():
    print("with") # prints "start", "with", "end" on separate lines

@foo
def bar(x):
    print("bar", x)

bar(1) # prints "start", "bar 1", "end" on separate lines

此设计仅适用于不接受任何参数的上下文管理器。

您可以在with语句中使用参数(您只需要将wrapper更改为接受*args**kwargs样式参数),但是这会有点尴尬,因为你无法区分用单个可调用参数调用(例如with foo(lambda x: x*2):)和被调用为装饰器。

当您使用装饰器语法时,接受上下文管理器的参数会更加困难。这是因为调用带有参数的装饰器(例如@foo("xyz"))意味着装饰器实际上是装饰器工厂。它需要返回一些像装饰器(修改另一个函数的函数)一样的东西。但是返回的值也需要直接像上下文管理器一样工作。您需要编写一个可以同时执行这两个操作的类,这就是您要避免的内容。