我有一些用以下样式编写的现有代码,它按预期工作,我可以使用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
用作装饰器和上下文管理器,也可以将参数传递给它。我该怎么做?
答案 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")
)意味着装饰器实际上是装饰器工厂。它需要返回一些像装饰器(修改另一个函数的函数)一样的东西。但是返回的值也需要直接像上下文管理器一样工作。您需要编写一个可以同时执行这两个操作的类,这就是您要避免的内容。