gpt4 book ai didi

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

转载 作者:太空宇宙 更新时间:2023-11-03 15:59:31 25 4
gpt4 key购买 nike

我有一些用以下风格编写的现有代码,它按预期工作,我可以使用 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作为装饰器和上下文管理器,也可能向它传递参数。我该怎么做?

最佳答案

如果您希望 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"))意味着装饰器实际上是一个装饰器工厂。它需要返回像装饰器(修改另一个函数的函数)一样工作的东西。但返回值也需要像上下文管理器一样直接工作。您需要编写一个可以同时执行这两项操作的类,这就是您所说的要避免的情况。

关于python - 如何在不使用 python 2.7 中的类的情况下使上下文管理器也作为装饰器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40476983/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com